From edb979baa966151335f2dab865bbe98053bd4b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E6=9C=88?= Date: Sat, 23 May 2026 21:36:22 +0800 Subject: [PATCH] fix(builtin): disable tools during continue/retry to force frontmatter output Agent was using all continue turns to keep calling tools instead of outputting the required frontmatter. Now continue runs with noTools=true, forcing LLM to emit text-only response. Also supports null tools in chatCompletionWithTools to omit tools from the API request entirely. --- package.json | 2 +- packages/workflow-agent-builtin/src/agent.ts | 4 ++++ packages/workflow-agent-builtin/src/llm/llm.ts | 18 +++++++++++------- packages/workflow-agent-builtin/src/loop.ts | 10 ++++++++-- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 508ef28..fd8e7ef 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "check": "bunx tsc --build && biome check . && bash scripts/lint-log-tags.sh", "typecheck": "bunx tsc --build", "format": "biome format --write .", - "test": "bun run --filter '*' test", + "test": "bun run --filter './packages/*' test", "changeset": "bunx changeset", "version": "bunx changeset version", "release": "bun run build && bun test && node scripts/publish-all.mjs" diff --git a/packages/workflow-agent-builtin/src/agent.ts b/packages/workflow-agent-builtin/src/agent.ts index a71633c..589e605 100644 --- a/packages/workflow-agent-builtin/src/agent.ts +++ b/packages/workflow-agent-builtin/src/agent.ts @@ -66,6 +66,7 @@ async function runBuiltinWithMessages( session: SessionRecord, store: Store, maxTurns: number, + noTools: boolean, ): Promise { const loopResult = await runBuiltinLoop({ provider, @@ -74,6 +75,7 @@ async function runBuiltinWithMessages( maxTurns, storageRoot, sessionId: session.sessionId, + noTools, }); session.messages = loopResult.messages; @@ -119,6 +121,7 @@ async function runBuiltin(ctx: AgentContext): Promise { session, ctx.store, BUILTIN_MAX_TURNS, + false, ); } @@ -141,6 +144,7 @@ async function continueBuiltin( session, store, BUILTIN_CONTINUE_MAX_TURNS, + true, ); } diff --git a/packages/workflow-agent-builtin/src/llm/llm.ts b/packages/workflow-agent-builtin/src/llm/llm.ts index 3beb102..6c85b7c 100644 --- a/packages/workflow-agent-builtin/src/llm/llm.ts +++ b/packages/workflow-agent-builtin/src/llm/llm.ts @@ -96,8 +96,17 @@ function serializeMessage(message: ChatMessage): Record { export async function chatCompletionWithTools( provider: ResolvedLlmProvider, messages: ChatMessage[], - tools: OpenAiToolDefinition[], + tools: OpenAiToolDefinition[] | null, ): Promise { + const body: Record = { + model: provider.model, + messages: messages.map(serializeMessage), + }; + if (tools !== null && tools.length > 0) { + body.tools = tools; + body.tool_choice = "auto"; + } + let response: Response; try { response = await fetch(chatUrl(provider.baseUrl), { @@ -106,12 +115,7 @@ export async function chatCompletionWithTools( Authorization: `Bearer ${provider.apiKey}`, "Content-Type": "application/json", }, - body: JSON.stringify({ - model: provider.model, - messages: messages.map(serializeMessage), - tools, - tool_choice: "auto", - }), + body: JSON.stringify(body), }); } catch (cause) { const message = cause instanceof Error ? cause.message : String(cause); diff --git a/packages/workflow-agent-builtin/src/loop.ts b/packages/workflow-agent-builtin/src/loop.ts index d289c1c..8bf1928 100644 --- a/packages/workflow-agent-builtin/src/loop.ts +++ b/packages/workflow-agent-builtin/src/loop.ts @@ -23,6 +23,8 @@ export type RunBuiltinLoopOptions = { maxTurns: number; storageRoot: string; sessionId: string; + /** When true, do not provide tools — force LLM to emit text only. */ + noTools: boolean; }; export type RunBuiltinLoopResult = { @@ -73,13 +75,17 @@ export async function runBuiltinLoop( options: RunBuiltinLoopOptions, ): Promise { const messages = [...options.messages]; - const openAiTools = builtinToolsToOpenAi(getBuiltinTools()); + const openAiTools = options.noTools ? [] : builtinToolsToOpenAi(getBuiltinTools()); let finalText = ""; let turnCount = 0; for (let turn = 0; turn < options.maxTurns; turn++) { log("8K2M4N7P", `builtin loop turn ${turn + 1}/${options.maxTurns}`); - const response = await chatCompletionWithTools(options.provider, messages, openAiTools); + const response = await chatCompletionWithTools( + options.provider, + messages, + openAiTools.length > 0 ? openAiTools : null, + ); const assistantMessage: ChatMessage = { role: "assistant",