Cap get_api_docs, break duplicate tool rounds, force a reply after successful writes, and log each tool call so limit hits are diagnosable.
This commit is contained in:
@@ -1,9 +1,16 @@
|
||||
import logger from "@/lib/logger";
|
||||
import { createLlmClient, type ChatMessage, type LlmClient } from "@/lib/llm";
|
||||
import { buildVisionContentParts, textFromMessageContent } from "@/lib/llm/content";
|
||||
import { AGENT_SYSTEM_PROMPT, AGENT_TOOLS, appendAgentRuntimeContext } from "../tools";
|
||||
import type { ClientChatMessage } from "../messages";
|
||||
import { describeToolActivity } from "../tool-labels";
|
||||
import { createApiToolExecutor, type ToolExecutor } from "../tool-executor";
|
||||
import {
|
||||
fingerprintToolCalls,
|
||||
isSuccessfulWrite,
|
||||
summarizeToolTrace,
|
||||
truncateToolResult,
|
||||
} from "./loop-guards";
|
||||
import { resolveAssistantImageDataUrls } from "./resolve-images";
|
||||
import { thinkingLabel, type AgentProgressEvent } from "./progress";
|
||||
|
||||
@@ -41,6 +48,21 @@ async function toLlmUserMessage(message: ClientChatMessage): Promise<ChatMessage
|
||||
};
|
||||
}
|
||||
|
||||
function forceReplyAfterWrite(writeNames: string[]): ChatMessage {
|
||||
return {
|
||||
role: "user",
|
||||
content: `The write already succeeded (${writeNames.join(", ")}). Stop calling tools and reply to the user in one short sentence confirming what you did.`,
|
||||
};
|
||||
}
|
||||
|
||||
function forceReplyAfterRepeat(): ChatMessage {
|
||||
return {
|
||||
role: "user",
|
||||
content:
|
||||
"You repeated the same tool call. Stop calling tools and reply with what you already know, or ask one clarifying question.",
|
||||
};
|
||||
}
|
||||
|
||||
export async function runAgentChat(options: {
|
||||
messages: ClientChatMessage[];
|
||||
request: Request;
|
||||
@@ -62,13 +84,16 @@ export async function runAgentChat(options: {
|
||||
const transcript: ChatMessage[] = [{ role: "system", content: systemPrompt }, ...userMessages];
|
||||
|
||||
const toolCalls: AgentToolCallSummary[] = [];
|
||||
const seenFingerprints = new Set<string>();
|
||||
const successfulWriteNames: string[] = [];
|
||||
let forceReply = false;
|
||||
|
||||
for (let round = 0; round < MAX_TOOL_ROUNDS; round += 1) {
|
||||
onProgress?.({ type: "thinking", label: thinkingLabel(round), round });
|
||||
|
||||
const completion = await llm.chatCompletion({
|
||||
messages: transcript,
|
||||
tools: AGENT_TOOLS,
|
||||
tools: forceReply ? undefined : AGENT_TOOLS,
|
||||
});
|
||||
|
||||
const assistantMessage = completion.message;
|
||||
@@ -76,6 +101,15 @@ export async function runAgentChat(options: {
|
||||
|
||||
if (!assistantMessage.tool_calls?.length) {
|
||||
onProgress?.({ type: "responding", label: "Writing a reply…" });
|
||||
logger.info(
|
||||
{
|
||||
msg: "agent.chat.done",
|
||||
rounds: round + 1,
|
||||
toolCalls: toolCalls.map((call) => `${call.name}:${call.status}`),
|
||||
forcedReply: forceReply,
|
||||
},
|
||||
"agent chat completed",
|
||||
);
|
||||
return {
|
||||
message: {
|
||||
role: "assistant",
|
||||
@@ -87,6 +121,36 @@ export async function runAgentChat(options: {
|
||||
};
|
||||
}
|
||||
|
||||
if (forceReply) {
|
||||
logger.warn(
|
||||
{
|
||||
msg: "agent.chat.forced_tools_ignored",
|
||||
round,
|
||||
names: assistantMessage.tool_calls.map((c) => c.function.name),
|
||||
},
|
||||
"model kept calling tools after force-reply",
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
const fingerprint = fingerprintToolCalls(
|
||||
assistantMessage.tool_calls.map((call) => ({
|
||||
name: call.function.name,
|
||||
arguments: call.function.arguments,
|
||||
})),
|
||||
);
|
||||
|
||||
if (seenFingerprints.has(fingerprint)) {
|
||||
logger.warn(
|
||||
{ msg: "agent.chat.duplicate_tools", round, fingerprint, toolCalls: fingerprint },
|
||||
"duplicate tool round detected",
|
||||
);
|
||||
transcript.push(forceReplyAfterRepeat());
|
||||
forceReply = true;
|
||||
continue;
|
||||
}
|
||||
seenFingerprints.add(fingerprint);
|
||||
|
||||
for (const toolCall of assistantMessage.tool_calls) {
|
||||
const label = describeToolActivity(toolCall.function.name, toolCall.function.arguments);
|
||||
onProgress?.({ type: "tool", name: toolCall.function.name, label });
|
||||
@@ -107,21 +171,69 @@ export async function runAgentChat(options: {
|
||||
}
|
||||
|
||||
toolCalls.push({ name: toolCall.function.name, status });
|
||||
logger.info(
|
||||
{
|
||||
msg: "agent.chat.tool",
|
||||
round,
|
||||
name: toolCall.function.name,
|
||||
status,
|
||||
args: toolCall.function.arguments.slice(0, 300),
|
||||
},
|
||||
"agent tool call",
|
||||
);
|
||||
|
||||
if (isSuccessfulWrite(toolCall.function.name, status, toolCall.function.arguments)) {
|
||||
successfulWriteNames.push(toolCall.function.name);
|
||||
}
|
||||
|
||||
transcript.push({
|
||||
role: "tool",
|
||||
tool_call_id: toolCall.id,
|
||||
name: toolCall.function.name,
|
||||
content: result,
|
||||
content: truncateToolResult(result),
|
||||
});
|
||||
}
|
||||
|
||||
if (successfulWriteNames.length > 0) {
|
||||
transcript.push(forceReplyAfterWrite(successfulWriteNames));
|
||||
forceReply = true;
|
||||
}
|
||||
}
|
||||
|
||||
onProgress?.({ type: "responding", label: "Wrapping up…" });
|
||||
const trace = summarizeToolTrace(toolCalls);
|
||||
|
||||
if (successfulWriteNames.length > 0) {
|
||||
logger.warn(
|
||||
{
|
||||
msg: "agent.chat.forced_summary",
|
||||
rounds: MAX_TOOL_ROUNDS,
|
||||
toolCalls: toolCalls.map((call) => `${call.name}:${call.status}`),
|
||||
},
|
||||
"agent summarizing after write without clean stop",
|
||||
);
|
||||
return {
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: `Done. Completed: ${[...new Set(successfulWriteNames)].join(", ")}.`,
|
||||
},
|
||||
toolCalls,
|
||||
};
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
{
|
||||
msg: "agent.chat.tool_limit",
|
||||
rounds: MAX_TOOL_ROUNDS,
|
||||
toolCalls: toolCalls.map((call) => `${call.name}:${call.status}`),
|
||||
forcedReply: forceReply,
|
||||
},
|
||||
"agent hit tool-call limit",
|
||||
);
|
||||
return {
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: "I hit the tool-call limit for this request. Please try a simpler question.",
|
||||
content: `I hit the tool-call limit for this request. Tools used: ${trace}.`,
|
||||
},
|
||||
toolCalls,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user