const DEFAULT_LIVING_CHAT_TEMPERATURE = 0.35; const DEFAULT_LIVING_CHAT_MAX_OUTPUT_TOKENS = 420; const LIVING_CHAT_MAX_TOKENS_MIN = 120; const LIVING_CHAT_MAX_TOKENS_MAX = 900; const LIVING_CHAT_SYSTEM_PROMPT_PARTS = [ 'РўС‹ Р¶РёРІРѕР№ русскоязычный ассистент для чтения Рё анализа данных 1РЎ.', 'Работай честно: РЅРµ заявляй действия, которые недоступны РІ этом рантайме.', 'Разрешено: анализ Рё объяснение данных, формулировка запросов, подсказки РїРѕ следующему шагу.', 'Запрещено: обещать настройку 1РЎ, админ-действия, создание/проведение документов или любые изменения РІ базе.', 'Если пользователь спрашивает РїСЂРѕ возможности, отвечай только РїРѕ этому контракту.' ]; const LIVING_CHAT_DEVELOPER_PROMPT = 'Формат: коротко Рё РїРѕ сути, без JSON Рё без служебных блоков. Пиши человеко-понятно.'; const LIVING_CHAT_FALLBACK_REPLY = 'РџРѕРЅСЏР». Сформулируйте, что именно РЅСѓР¶РЅРѕ РїРѕ данным 1РЎ, Рё СЏ РїРѕРјРѕРіСѓ РїРѕ шагам.'; export interface RunAssistantLivingChatLlmRuntimeInput { userMessage: string; sessionItems: unknown[]; payload: { llmProvider?: unknown; apiKey?: unknown; model?: unknown; baseUrl?: unknown; temperature?: number; maxOutputTokens?: number; }; chatClient: { chat: ( config: { llmProvider: unknown; apiKey: string; model: string; baseUrl: unknown; temperature: number; maxOutputTokens: number; }, prompt: { systemPrompt: string; developerPrompt: string; userMessage: string; maxOutputTokens: number; temperature: number; } ) => Promise<{ outputText?: string | null } | null>; }; loadAssistantCanonExcerpt: (maxChars: number) => string; sanitizeOutgoingAssistantText: (text: unknown, fallback?: string) => string; defaultModel: string; defaultBaseUrl: string; defaultApiKey?: string; } function clampLivingChatMaxOutputTokens(value: unknown): number { const numeric = Number(value ?? DEFAULT_LIVING_CHAT_MAX_OUTPUT_TOKENS); return Math.max(LIVING_CHAT_MAX_TOKENS_MIN, Math.min(numeric, LIVING_CHAT_MAX_TOKENS_MAX)); } function compactWhitespace(value: unknown): string { return String(value ?? "") .replace(/\s+/g, " ") .trim(); } function buildLivingChatContextWindow(items: unknown[]): string { const source = Array.isArray(items) ? items.slice(-6) : []; const lines: string[] = []; for (const item of source) { if (!item || typeof item !== "object") { continue; } const role = String((item as { role?: unknown }).role ?? "").trim(); const text = compactWhitespace(String((item as { text?: unknown }).text ?? "")); if (!role || !text) { continue; } const clipped = text.length > 220 ? `${text.slice(0, 220)}...` : text; lines.push(`${role}: ${clipped}`); } return lines.join("\n"); } function buildLivingChatPrompt(userMessage: string, conversationWindow: string): string { const contextBlock = conversationWindow ? `Контекст последних сообщений:\n${conversationWindow}\n\n` : ""; return `${contextBlock}Сообщение пользователя:\n${userMessage}`; } export async function runAssistantLivingChatLlmRuntime( input: RunAssistantLivingChatLlmRuntimeInput ): Promise { const conversationWindow = buildLivingChatContextWindow(input.sessionItems); const userPrompt = buildLivingChatPrompt(input.userMessage, conversationWindow); const canonExcerpt = input.loadAssistantCanonExcerpt(520); const maxOutputTokens = clampLivingChatMaxOutputTokens(input.payload.maxOutputTokens); const temperature = input.payload.temperature ?? DEFAULT_LIVING_CHAT_TEMPERATURE; const systemPrompt = [...LIVING_CHAT_SYSTEM_PROMPT_PARTS, `Канон поведения: ${canonExcerpt}`].join(" "); const chatResponse = await input.chatClient.chat( { llmProvider: input.payload.llmProvider, apiKey: String(input.payload.apiKey ?? input.defaultApiKey ?? ""), model: String(input.payload.model ?? input.defaultModel), baseUrl: input.payload.baseUrl ?? input.defaultBaseUrl, temperature, maxOutputTokens }, { systemPrompt, developerPrompt: LIVING_CHAT_DEVELOPER_PROMPT, userMessage: userPrompt, maxOutputTokens, temperature } ); return input.sanitizeOutgoingAssistantText(chatResponse?.outputText ?? "", LIVING_CHAT_FALLBACK_REPLY); }