feat(bot): unified topic processor replacing router+interpreter stack

Replace 3-layer architecture (gpt-5-nano router + gpt-4o-mini interpreter) with
single unified topic processor (gpt-4o-mini) for simplified message handling.

New components:
- HouseholdContextCache: TTL-based caching (5 min) for household config data
- TopicProcessor: Unified classification + parsing with structured JSON output

Key changes:
- Renamed ASSISTANT_ROUTER_MODEL → TOPIC_PROCESSOR_MODEL
- Added TOPIC_PROCESSOR_TIMEOUT_MS (default 10s)
- Refactored save() → saveWithInterpretation() for pre-parsed interpretations
- Removed deprecated createOpenAiTopicMessageRouter and ~300 lines legacy code
- Fixed typing indicator to only start when needed (purchase routes)
- Fixed amount formatting: convert minor units to major for rawText

Routes: silent, chat_reply, purchase, purchase_clarification, payment,
payment_clarification, topic_helper, dismiss_workflow

All 212 bot tests pass. Typecheck, lint, format, build clean.
This commit is contained in:
2026-03-14 13:33:57 +04:00
parent 9c3bb100e3
commit f38ee499ae
14 changed files with 1554 additions and 854 deletions

View File

@@ -36,7 +36,8 @@ import { getBotRuntimeConfig } from './config'
import { registerHouseholdSetupCommands } from './household-setup'
import { createOpenAiChatAssistant } from './openai-chat-assistant'
import { createOpenAiPurchaseInterpreter } from './openai-purchase-interpreter'
import { createOpenAiTopicMessageRouter } from './topic-message-router'
import { createTopicProcessor } from './topic-processor'
import { HouseholdContextCache } from './household-context-cache'
import {
createPurchaseMessageRepository,
registerConfiguredPurchaseTopicIngestion
@@ -153,11 +154,12 @@ const conversationalAssistant = createOpenAiChatAssistant(
runtime.assistantModel,
runtime.assistantTimeoutMs
)
const topicMessageRouter = createOpenAiTopicMessageRouter(
const topicProcessor = createTopicProcessor(
runtime.openaiApiKey,
runtime.assistantRouterModel,
Math.min(runtime.assistantTimeoutMs, 5_000)
runtime.topicProcessorModel,
runtime.topicProcessorTimeoutMs
)
const householdContextCache = new HouseholdContextCache()
const anonymousFeedbackRepositoryClients = new Map<
string,
ReturnType<typeof createDbAnonymousFeedbackRepository>
@@ -254,9 +256,10 @@ if (purchaseRepositoryClient && householdConfigurationRepositoryClient) {
householdConfigurationRepositoryClient.repository,
purchaseRepositoryClient.repository,
{
...(topicMessageRouter
...(topicProcessor
? {
router: topicMessageRouter,
topicProcessor,
contextCache: householdContextCache,
memoryStore: assistantMemoryStore,
...(topicMessageHistoryRepositoryClient
? {
@@ -281,9 +284,10 @@ if (purchaseRepositoryClient && householdConfigurationRepositoryClient) {
financeServiceForHousehold,
paymentConfirmationServiceForHousehold,
{
...(topicMessageRouter
...(topicProcessor
? {
router: topicMessageRouter,
topicProcessor,
contextCache: householdContextCache,
memoryStore: assistantMemoryStore,
...(topicMessageHistoryRepositoryClient
? {
@@ -476,11 +480,6 @@ if (
assistant: conversationalAssistant
}
: {}),
...(topicMessageRouter
? {
topicRouter: topicMessageRouter
}
: {}),
logger: getLogger('dm-assistant')
})
} else {
@@ -512,11 +511,6 @@ if (
assistant: conversationalAssistant
}
: {}),
...(topicMessageRouter
? {
topicRouter: topicMessageRouter
}
: {}),
logger: getLogger('dm-assistant')
})
}