Browse Source

feat: 增强聊天引擎,处理工具调用记录并优化最终回复内容

feat/agent-independence
npmrun 2 months ago
parent
commit
3deee59f2e
  1. 10
      app/composables/useAgentChat.ts
  2. BIN
      packages/drizzle-pkg/db.sqlite
  3. 33
      server/service/agent/chat-engine.ts

10
app/composables/useAgentChat.ts

@ -423,6 +423,14 @@ export function useAgentChat(options: UseAgentChatOptions) {
type: "text", type: "text",
text: "(模型仅返回了思考过程,未生成正式回复。请尝试更换模型或调整问题后重试。)", text: "(模型仅返回了思考过程,未生成正式回复。请尝试更换模型或调整问题后重试。)",
}); });
} else if (!hasText && hasToolCall) {
finalMsg.parts?.push({
id: generateId(),
type: "text",
text: isStopped.value
? "(对话已停止,以上是已完成的工具调用记录。)"
: "(已达到工具调用次数上限,模型未能生成最终回答。以上是工具调用的执行记录。)",
});
} }
} }
} }
@ -533,6 +541,7 @@ export function useAgentChat(options: UseAgentChatOptions) {
const msg = messages.value[assistantIdx]; const msg = messages.value[assistantIdx];
if (msg) { if (msg) {
cleanupInProgressToolCalls(msg); cleanupInProgressToolCalls(msg);
validateAssistantContent(assistantIdx);
} }
} else { } else {
errorMessage.value = err.message || "请求失败"; errorMessage.value = err.message || "请求失败";
@ -645,6 +654,7 @@ export function useAgentChat(options: UseAgentChatOptions) {
const msg = messages.value[messages.value.indexOf(assistantMsg)]; const msg = messages.value[messages.value.indexOf(assistantMsg)];
if (msg) { if (msg) {
cleanupInProgressToolCalls(msg); cleanupInProgressToolCalls(msg);
validateAssistantContent(messages.value.indexOf(assistantMsg), true);
} }
} else { } else {
errorMessage.value = err.message || "请求失败"; errorMessage.value = err.message || "请求失败";

BIN
packages/drizzle-pkg/db.sqlite

Binary file not shown.

33
server/service/agent/chat-engine.ts

@ -504,6 +504,7 @@ export async function executeChat(params: ChatEngineParams): Promise<ChatEngineR
); );
const parts: Array<Record<string, unknown>> = []; const parts: Array<Record<string, unknown>> = [];
let hasTextPart = false;
for (const step of steps) { for (const step of steps) {
const toolResultMap = new Map(step.toolResults.map((tr) => [tr.toolCallId, tr.output])); const toolResultMap = new Map(step.toolResults.map((tr) => [tr.toolCallId, tr.output]));
const contentArr = step.content as Array<Record<string, unknown>>; const contentArr = step.content as Array<Record<string, unknown>>;
@ -518,6 +519,7 @@ export async function executeChat(params: ChatEngineParams): Promise<ChatEngineR
parts.push({ id: `p_${parts.length}`, type: "reasoning", text: (cp as any).text }); parts.push({ id: `p_${parts.length}`, type: "reasoning", text: (cp as any).text });
} else if (cp.type === "text" && (cp as any).text) { } else if (cp.type === "text" && (cp as any).text) {
parts.push({ id: `p_${parts.length}`, type: "text", text: (cp as any).text }); parts.push({ id: `p_${parts.length}`, type: "text", text: (cp as any).text });
hasTextPart = true;
} else if (cp.type === "tool-approval-request" && (cp as any).approvalId && (cp as any).toolCall) { } else if (cp.type === "tool-approval-request" && (cp as any).approvalId && (cp as any).toolCall) {
const tc = (cp as any).toolCall; const tc = (cp as any).toolCall;
const toolResult = toolResultMap.get(tc.toolCallId); const toolResult = toolResultMap.get(tc.toolCallId);
@ -560,6 +562,16 @@ export async function executeChat(params: ChatEngineParams): Promise<ChatEngineR
} }
} }
const hasToolCallPart = parts.some((p) => p.type === "tool-call");
const stepLimitReached = finishReason === "tool-calls" && !hasTextPart && hasToolCallPart;
let finalText = assistantText;
if (stepLimitReached) {
const noticeText = "(已达到工具调用次数上限,模型未能生成最终回答。以上是工具调用的执行记录。)";
parts.push({ id: `p_${parts.length}`, type: "text", text: noticeText });
finalText = (finalText || "") + noticeText;
logger.info("[%s] [CHAT-ENGINE] step limit reached, appended notice part", requestId);
}
if (isApprovalContinue) { if (isApprovalContinue) {
const lastAssistant = historyMessages.filter((m) => m.role === "assistant").pop(); const lastAssistant = historyMessages.filter((m) => m.role === "assistant").pop();
if (lastAssistant) { if (lastAssistant) {
@ -590,7 +602,7 @@ export async function executeChat(params: ChatEngineParams): Promise<ChatEngineR
newParts[i]!.id = `p_${idOffset + i}`; newParts[i]!.id = `p_${idOffset + i}`;
} }
const mergedParts = [...existingParts, ...newParts]; const mergedParts = [...existingParts, ...newParts];
const mergedContent = (lastAssistant.content || "") + assistantText; const mergedContent = (lastAssistant.content || "") + finalText;
await updateMessageContent(lastAssistant.id, mergedContent); await updateMessageContent(lastAssistant.id, mergedContent);
await updateMessageParts(lastAssistant.id, JSON.stringify(mergedParts)); await updateMessageParts(lastAssistant.id, JSON.stringify(mergedParts));
} }
@ -599,7 +611,7 @@ export async function executeChat(params: ChatEngineParams): Promise<ChatEngineR
await saveMessage({ await saveMessage({
sessionId, sessionId,
role: "assistant", role: "assistant",
content: assistantText, content: finalText,
parts: parts.length > 0 ? JSON.stringify(parts) : null, parts: parts.length > 0 ? JSON.stringify(parts) : null,
modelId: resolvedModel.dbId, modelId: resolvedModel.dbId,
inputTokens: usage?.inputTokens ?? null, inputTokens: usage?.inputTokens ?? null,
@ -641,11 +653,13 @@ export async function executeChat(params: ChatEngineParams): Promise<ChatEngineR
logger.info("[%s] [CHAT-ENGINE] aborted: streamedTextLen=%d streamedToolCalls=%d", requestId, partialText.length, streamedToolCalls.length); logger.info("[%s] [CHAT-ENGINE] aborted: streamedTextLen=%d streamedToolCalls=%d", requestId, partialText.length, streamedToolCalls.length);
const parts: Array<Record<string, unknown>> = []; const parts: Array<Record<string, unknown>> = [];
let hasTextPart = false;
if (streamedReasoning.trim()) { if (streamedReasoning.trim()) {
parts.push({ id: `p_${parts.length}`, type: "reasoning", text: streamedReasoning }); parts.push({ id: `p_${parts.length}`, type: "reasoning", text: streamedReasoning });
} }
if (partialText.trim()) { if (partialText.trim()) {
parts.push({ id: `p_${parts.length}`, type: "text", text: partialText }); parts.push({ id: `p_${parts.length}`, type: "text", text: partialText });
hasTextPart = true;
} }
for (const tc of streamedToolCalls) { for (const tc of streamedToolCalls) {
parts.push({ parts.push({
@ -659,6 +673,15 @@ export async function executeChat(params: ChatEngineParams): Promise<ChatEngineR
}); });
} }
const hasToolCallPart = parts.some((p) => p.type === "tool-call");
let finalText = partialText;
if (!hasTextPart && hasToolCallPart) {
const noticeText = "(对话已停止,以上是已完成的工具调用记录。)";
parts.push({ id: `p_${parts.length}`, type: "text", text: noticeText });
finalText = (finalText || "") + noticeText;
logger.info("[%s] [CHAT-ENGINE] aborted with tool calls but no text, appended notice", requestId);
}
if (isApprovalContinue) { if (isApprovalContinue) {
const lastAssistant = historyMessages.filter((m) => m.role === "assistant").pop(); const lastAssistant = historyMessages.filter((m) => m.role === "assistant").pop();
if (lastAssistant) { if (lastAssistant) {
@ -688,18 +711,18 @@ export async function executeChat(params: ChatEngineParams): Promise<ChatEngineR
newParts[i]!.id = `p_${idOffset + i}`; newParts[i]!.id = `p_${idOffset + i}`;
} }
const mergedParts = [...existingParts, ...newParts]; const mergedParts = [...existingParts, ...newParts];
const mergedContent = (lastAssistant.content || "") + partialText; const mergedContent = (lastAssistant.content || "") + finalText;
await updateMessageContent(lastAssistant.id, mergedContent); await updateMessageContent(lastAssistant.id, mergedContent);
await updateMessageParts(lastAssistant.id, JSON.stringify(mergedParts)); await updateMessageParts(lastAssistant.id, JSON.stringify(mergedParts));
} }
} else { } else {
const hasContent = partialText.trim().length > 0 || parts.length > 0; const hasContent = finalText.trim().length > 0 || parts.length > 0;
if (hasContent) { if (hasContent) {
const assistantSortOrder = (await getMaxSortOrder(sessionId)) + 1; const assistantSortOrder = (await getMaxSortOrder(sessionId)) + 1;
await saveMessage({ await saveMessage({
sessionId, sessionId,
role: "assistant", role: "assistant",
content: partialText, content: finalText,
parts: parts.length > 0 ? JSON.stringify(parts) : null, parts: parts.length > 0 ? JSON.stringify(parts) : null,
modelId: resolvedModel.dbId, modelId: resolvedModel.dbId,
sortOrder: assistantSortOrder, sortOrder: assistantSortOrder,

Loading…
Cancel
Save