|
|
@ -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, |
|
|
|