Normalize model call output

This commit is contained in:
2026-05-18 18:59:09 +03:00
parent e520c412af
commit 9d6cdb008b
4 changed files with 54 additions and 1 deletions
+1 -1
View File
@@ -80,7 +80,7 @@
## 6. Сделать model_call и tool_loop физически отдельными stages ## 6. Сделать model_call и tool_loop физически отдельными stages
- [ ] Stage `model_call` должен делать только один model request. - [ ] Stage `model_call` должен делать только один model request.
- [ ] Stage `model_call` должен возвращать normalized model output. - [x] Stage `model_call` должен возвращать normalized model output.
- [ ] Stage `tool_loop` должен решать, есть ли tool calls. - [ ] Stage `tool_loop` должен решать, есть ли tool calls.
- [ ] Stage `tool_loop` должен выполнять tools через общий `executeToolBatch`. - [ ] Stage `tool_loop` должен выполнять tools через общий `executeToolBatch`.
- [ ] Stage `tool_loop` должен добавлять tool results в provider adapter. - [ ] Stage `tool_loop` должен добавлять tool results в provider adapter.
+19
View File
@@ -0,0 +1,19 @@
import type {TelegramOutputAttachmentRecord, TelegramToolExecutionRecord} from "./telegram-stream-message.js";
export type NormalizedModelOutput = {
text: string;
toolExecutions: TelegramToolExecutionRecord[];
outputAttachments: TelegramOutputAttachmentRecord[];
};
export function summarizeModelOutput(params: {
text: string;
toolExecutions: readonly TelegramToolExecutionRecord[];
outputAttachments: readonly TelegramOutputAttachmentRecord[];
}): NormalizedModelOutput {
return {
text: params.text.trim(),
toolExecutions: [...params.toolExecutions],
outputAttachments: [...params.outputAttachments],
};
}
+13
View File
@@ -21,6 +21,7 @@ import {runToolRankStage} from "./tool-rank-stage";
import {runOpenAi} from "./unified-ai-runner.openai"; import {runOpenAi} from "./unified-ai-runner.openai";
import {runOllama} from "./unified-ai-runner.ollama"; import {runOllama} from "./unified-ai-runner.ollama";
import {runMistral} from "./unified-ai-runner.mistral"; import {runMistral} from "./unified-ai-runner.mistral";
import {summarizeModelOutput} from "./response-model-output";
import { import {
resolveTextToSpeechProviderForUser, resolveTextToSpeechProviderForUser,
sendSynthesizedSpeech, sendSynthesizedSpeech,
@@ -254,6 +255,13 @@ export async function runUnifiedAiResponsePipeline(params: {
return { return {
stage: "model_call", stage: "model_call",
status: "succeeded", status: "succeeded",
details: {
modelOutput: summarizeModelOutput({
text: streamMessage.getText(),
toolExecutions: streamMessage.getToolExecutions(),
outputAttachments: streamMessage.getOutputAttachments(),
}),
},
}; };
}, },
}, },
@@ -266,6 +274,11 @@ export async function runUnifiedAiResponsePipeline(params: {
status: executions.length ? "succeeded" : "skipped", status: executions.length ? "succeeded" : "skipped",
fallbackAction: executions.length ? undefined : "continue_without_stage", fallbackAction: executions.length ? undefined : "continue_without_stage",
details: { details: {
modelOutput: summarizeModelOutput({
text: streamMessage.getText(),
toolExecutions: executions,
outputAttachments: streamMessage.getOutputAttachments(),
}),
count: executions.length, count: executions.length,
tools: executions.map(execution => ({ tools: executions.map(execution => ({
toolName: execution.toolName, toolName: execution.toolName,
+21
View File
@@ -0,0 +1,21 @@
import test from "node:test";
import assert from "node:assert/strict";
const {summarizeModelOutput} = await import("../dist/ai/response-model-output.js");
test("model output summary trims text and copies attachment records", () => {
const toolExecutions = [{toolName: "read_file", callId: "1", argumentsText: "{}", resultChars: 10}];
const outputAttachments = [{artifactKind: "generated_file", fileName: "out.txt", sizeBytes: 12}];
const summary = summarizeModelOutput({
text: " hello world ",
toolExecutions,
outputAttachments,
});
assert.deepEqual(summary, {
text: "hello world",
toolExecutions,
outputAttachments,
});
});