Summarize tool loop output
This commit is contained in:
+2
-2
@@ -81,12 +81,12 @@
|
||||
|
||||
- [ ] Stage `model_call` должен делать только один model request.
|
||||
- [x] Stage `model_call` должен возвращать normalized model output.
|
||||
- [ ] Stage `tool_loop` должен решать, есть ли tool calls.
|
||||
- [x] Stage `tool_loop` должен решать, есть ли tool calls.
|
||||
- [ ] Stage `tool_loop` должен выполнять tools через общий `executeToolBatch`.
|
||||
- [ ] Stage `tool_loop` должен добавлять tool results в provider adapter.
|
||||
- [ ] Stage `tool_loop` должен управлять max rounds.
|
||||
- [ ] Stage `tool_loop` должен сохранять tool result artifacts.
|
||||
- [ ] Stage `tool_loop` должен уметь завершаться без tools как `skipped`.
|
||||
- [x] Stage `tool_loop` должен уметь завершаться без tools как `skipped`.
|
||||
- [ ] Убрать tool loop из `runOpenAi`.
|
||||
- [ ] Убрать tool loop из `runMistral`.
|
||||
- [ ] Убрать tool loop из `runOllama`.
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import type {PipelineArtifact} from "./user-request-pipeline/types.js";
|
||||
import type {TelegramOutputAttachmentRecord, TelegramToolExecutionRecord} from "./telegram-stream-message.js";
|
||||
import {summarizeModelOutput} from "./response-model-output.js";
|
||||
|
||||
export type ToolLoopSummary = {
|
||||
status: "succeeded" | "skipped";
|
||||
fallbackAction?: "continue_without_stage";
|
||||
details: {
|
||||
modelOutput: ReturnType<typeof summarizeModelOutput>;
|
||||
count: number;
|
||||
tools: Array<{
|
||||
toolName: string;
|
||||
callId: string;
|
||||
resultChars: number;
|
||||
}>;
|
||||
};
|
||||
artifacts?: PipelineArtifact[];
|
||||
};
|
||||
|
||||
export function summarizeToolLoop(params: {
|
||||
text: string;
|
||||
executions: readonly TelegramToolExecutionRecord[];
|
||||
outputAttachments: readonly TelegramOutputAttachmentRecord[];
|
||||
}): ToolLoopSummary {
|
||||
const count = params.executions.length;
|
||||
const tools = params.executions.map(execution => ({
|
||||
toolName: execution.toolName,
|
||||
callId: execution.callId,
|
||||
resultChars: execution.resultChars,
|
||||
}));
|
||||
|
||||
return {
|
||||
status: count ? "succeeded" : "skipped",
|
||||
fallbackAction: count ? undefined : "continue_without_stage",
|
||||
details: {
|
||||
modelOutput: summarizeModelOutput({
|
||||
text: params.text,
|
||||
toolExecutions: params.executions,
|
||||
outputAttachments: params.outputAttachments,
|
||||
}),
|
||||
count,
|
||||
tools,
|
||||
},
|
||||
artifacts: count ? [{
|
||||
kind: "tool_result",
|
||||
stage: "tool_loop",
|
||||
createdAt: new Date().toISOString(),
|
||||
toolName: "summary",
|
||||
callId: "tool_loop_summary",
|
||||
resultText: JSON.stringify({
|
||||
count,
|
||||
tools,
|
||||
}),
|
||||
}] : undefined,
|
||||
};
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import {runOpenAi} from "./unified-ai-runner.openai";
|
||||
import {runOllama} from "./unified-ai-runner.ollama";
|
||||
import {runMistral} from "./unified-ai-runner.mistral";
|
||||
import {summarizeModelOutput} from "./response-model-output";
|
||||
import {summarizeToolLoop} from "./tool-loop-summary";
|
||||
import {
|
||||
resolveTextToSpeechProviderForUser,
|
||||
sendSynthesizedSpeech,
|
||||
@@ -269,38 +270,15 @@ export async function runUnifiedAiResponsePipeline(params: {
|
||||
name: "tool_loop",
|
||||
async run() {
|
||||
const executions = streamMessage.getToolExecutions();
|
||||
const summary = summarizeToolLoop({
|
||||
text: streamMessage.getText(),
|
||||
executions,
|
||||
outputAttachments: streamMessage.getOutputAttachments(),
|
||||
});
|
||||
|
||||
return {
|
||||
stage: "tool_loop",
|
||||
status: executions.length ? "succeeded" : "skipped",
|
||||
fallbackAction: executions.length ? undefined : "continue_without_stage",
|
||||
details: {
|
||||
modelOutput: summarizeModelOutput({
|
||||
text: streamMessage.getText(),
|
||||
toolExecutions: executions,
|
||||
outputAttachments: streamMessage.getOutputAttachments(),
|
||||
}),
|
||||
count: executions.length,
|
||||
tools: executions.map(execution => ({
|
||||
toolName: execution.toolName,
|
||||
callId: execution.callId,
|
||||
resultChars: execution.resultChars,
|
||||
})),
|
||||
},
|
||||
artifacts: executions.length ? [{
|
||||
kind: "tool_result",
|
||||
stage: "tool_loop",
|
||||
createdAt: new Date().toISOString(),
|
||||
toolName: "summary",
|
||||
callId: "tool_loop_summary",
|
||||
resultText: JSON.stringify({
|
||||
count: executions.length,
|
||||
tools: executions.map(execution => ({
|
||||
toolName: execution.toolName,
|
||||
callId: execution.callId,
|
||||
resultChars: execution.resultChars,
|
||||
})),
|
||||
}),
|
||||
}] : undefined,
|
||||
...summary,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const {summarizeToolLoop} = await import("../dist/ai/tool-loop-summary.js");
|
||||
|
||||
test("tool loop summary skips empty tool execution batches", () => {
|
||||
const summary = summarizeToolLoop({
|
||||
text: "answer",
|
||||
executions: [],
|
||||
outputAttachments: [],
|
||||
});
|
||||
|
||||
assert.equal(summary.status, "skipped");
|
||||
assert.equal(summary.fallbackAction, "continue_without_stage");
|
||||
assert.equal(summary.details.count, 0);
|
||||
assert.deepEqual(summary.details.tools, []);
|
||||
assert.deepEqual(summary.details.modelOutput, {
|
||||
text: "answer",
|
||||
toolExecutions: [],
|
||||
outputAttachments: [],
|
||||
});
|
||||
assert.equal(summary.artifacts, undefined);
|
||||
});
|
||||
|
||||
test("tool loop summary reports executions and summary artifact", () => {
|
||||
const summary = summarizeToolLoop({
|
||||
text: "answer",
|
||||
executions: [{
|
||||
toolName: "read_file",
|
||||
callId: "call-1",
|
||||
argumentsText: "{}",
|
||||
resultChars: 12,
|
||||
}],
|
||||
outputAttachments: [],
|
||||
});
|
||||
|
||||
assert.equal(summary.status, "succeeded");
|
||||
assert.equal(summary.fallbackAction, undefined);
|
||||
assert.equal(summary.details.count, 1);
|
||||
assert.deepEqual(summary.details.tools, [{
|
||||
toolName: "read_file",
|
||||
callId: "call-1",
|
||||
resultChars: 12,
|
||||
}]);
|
||||
assert.equal(summary.artifacts?.[0]?.kind, "tool_result");
|
||||
assert.equal(summary.artifacts?.[0]?.stage, "tool_loop");
|
||||
});
|
||||
Reference in New Issue
Block a user