feat: 更新工具调用处理逻辑,支持新旧格式兼容并优化工具调用参数合并

This commit is contained in:
Wenjie Zhang 2025-10-27 18:41:35 +08:00
parent 107ec04dba
commit 093084ef7f
4 changed files with 151 additions and 9 deletions

View File

@ -131,6 +131,7 @@ uv run scripts/batch_upload.py \
--wait-for-completion \
--poll-interval 5 \
--recursive \
--enable-ocr mineru_ocr \ # mineru_official, paddlex_ocr, onnx_rapid_ocr
--record-file scripts/tmp/batch_processed_files_1029.txt
```

View File

@ -388,6 +388,7 @@ const resetOnGoingConv = (threadId = null, preserveMessages = false) => {
const _processStreamChunk = (chunk, threadId) => {
const { status, msg, request_id, message } = chunk;
const threadState = getThreadState(threadId);
// console.log('msg:', msg);
if (!threadState) return false;

View File

@ -33,8 +33,8 @@
<span v-else-if="message.error_type === 'unexpect'">生成过程中出现异常</span>
</div>
<div v-if="message.tool_calls && Object.keys(message.tool_calls).length > 0" class="tool-calls-container">
<div v-for="(toolCall, index) in message.tool_calls || {}" :key="index" class="tool-call-container">
<div v-if="validToolCalls && validToolCalls.length > 0" class="tool-calls-container">
<div v-for="(toolCall, index) in validToolCalls" :key="toolCall.id || index" class="tool-call-container">
<div v-if="toolCall" class="tool-call-display" :class="{ 'is-collapsed': !expandedToolCalls.has(toolCall.id) }">
<div class="tool-header" @click="toggleToolCall(toolCall.id)">
<span v-if="!toolCall.tool_call_result">
@ -47,15 +47,17 @@
</span>
</div>
<div class="tool-content" v-show="expandedToolCalls.has(toolCall.id)">
<div class="tool-params" v-if="toolCall.args || toolCall.function.arguments">
<div class="tool-params" v-if="toolCall.args || toolCall.function?.arguments">
<div class="tool-params-content">
<strong>参数:</strong> {{ toolCall.args || toolCall.function.arguments }}
<strong>参数:</strong>
<span v-if="getFormattedToolArgs(toolCall)">{{ getFormattedToolArgs(toolCall) }}</span>
<span v-else>{{ toolCall.args || toolCall.function?.arguments }}</span>
</div>
</div>
<div class="tool-result" v-if="toolCall.tool_call_result && toolCall.tool_call_result.content">
<div class="tool-result-content" :data-tool-call-id="toolCall.id">
<ToolResultRenderer
:tool-name="toolCall.name || toolCall.function.name"
:tool-name="toolCall.name || toolCall.function?.name"
:result-content="toolCall.tool_call_result.content"
/>
</div>
@ -146,13 +148,51 @@ const { availableTools } = storeToRefs(agentStore);
//
const getToolNameByToolCall = (toolCall) => {
const toolId = toolCall.name || toolCall.function.name;
const toolId = toolCall.name || toolCall.function?.name;
const toolsList = availableTools.value ? Object.values(availableTools.value) : [];
const tool = toolsList.find(t => t.id === toolId);
return tool ? tool.name : toolId;
};
const getFormattedToolArgs = (toolCall) => {
const args = toolCall.args || toolCall.function?.arguments;
if (!args) return '';
try {
// JSON
if (typeof args === 'string' && args.trim().startsWith('{')) {
const parsed = JSON.parse(args);
return JSON.stringify(parsed, null, 2);
}
} catch (e) {
//
}
return args;
};
//
const validToolCalls = computed(() => {
if (!props.message.tool_calls || !Array.isArray(props.message.tool_calls)) {
return [];
}
return props.message.tool_calls.filter(toolCall => {
//
return toolCall &&
(toolCall.id || toolCall.name) &&
(toolCall.args !== undefined ||
toolCall.function?.arguments !== undefined ||
toolCall.tool_call_result !== undefined);
});
});
const parsedData = computed(() => {
//
if (validToolCalls.value && validToolCalls.value.length > 0) {
console.log('Valid tool calls in message:', validToolCalls.value);
}
// Start with default values from the prop to avoid mutation.
let content = props.message.content.trim() || '';
let reasoning_content = props.message.additional_kwargs?.reasoning_content || '';

View File

@ -12,8 +12,12 @@ export class MessageProcessor {
// 构建工具响应映射
for (const item of msgs) {
if (item.type === 'tool' && item.tool_call_id) {
toolResponseMap.set(item.tool_call_id, item);
if (item.type === 'tool') {
// 使用多种可能的ID字段来匹配工具调用
const toolCallId = item.tool_call_id || item.id;
if (toolCallId) {
toolResponseMap.set(toolCallId, item);
}
}
}
@ -127,13 +131,14 @@ export class MessageProcessor {
result.additional_kwargs.reasoning_content += chunk.additional_kwargs.reasoning_content;
}
// 合并tool_calls
// 合并tool_calls (处理新的数据结构)
MessageProcessor._mergeToolCalls(result, chunk);
}
// 处理AIMessageChunk类型
if (result.type === 'AIMessageChunk') {
result.type = 'ai';
// 将tool_calls从additional_kwargs移到顶层并确保格式正确
if (result.additional_kwargs?.tool_calls) {
result.tool_calls = result.additional_kwargs.tool_calls;
}
@ -149,6 +154,7 @@ export class MessageProcessor {
* @param {Object} chunk - 当前块
*/
static _mergeToolCalls(result, chunk) {
// 1. 处理 additional_kwargs.tool_calls (旧格式,保持兼容性)
if (chunk.additional_kwargs?.tool_calls) {
if (!result.additional_kwargs) result.additional_kwargs = {};
if (!result.additional_kwargs.tool_calls) result.additional_kwargs.tool_calls = [];
@ -169,6 +175,100 @@ export class MessageProcessor {
}
}
}
// 2. 处理顶层的 tool_calls (新格式)
if (chunk.tool_calls) {
if (!result.tool_calls) result.tool_calls = [];
for (const toolCall of chunk.tool_calls) {
// 过滤掉无效的工具调用没有id或name的空对象
if (!toolCall.id && !toolCall.name) {
continue;
}
const existingToolCall = result.tool_calls.find(t => t.id === toolCall.id);
if (existingToolCall) {
// 合并相同ID的tool call的args
if (toolCall.args && existingToolCall.args !== undefined) {
existingToolCall.args += toolCall.args;
}
} else {
// 添加新的tool call
result.tool_calls.push(JSON.parse(JSON.stringify(toolCall)));
}
}
}
// 3. 处理 tool_call_chunks (分片的工具调用参数)
if (chunk.tool_call_chunks) {
if (!result.tool_call_chunks) result.tool_call_chunks = [];
for (const toolCallChunk of chunk.tool_call_chunks) {
// 过滤掉无效的chunk没有id、name和args的空对象
if (!toolCallChunk.id && !toolCallChunk.name && !toolCallChunk.args) {
continue;
}
const existingChunk = result.tool_call_chunks.find(
t => (t.id === toolCallChunk.id || (t.index === toolCallChunk.index && t.index !== null))
);
if (existingChunk) {
// 合并参数字符串
if (toolCallChunk.args && existingChunk.args !== undefined) {
existingChunk.args += toolCallChunk.args;
}
// 更新工具名称
if (toolCallChunk.name && !existingChunk.name) {
existingChunk.name = toolCallChunk.name;
}
} else {
// 添加新的chunk
result.tool_call_chunks.push(JSON.parse(JSON.stringify(toolCallChunk)));
}
}
}
// 4. 同步 tool_call_chunks 到 tool_calls (构建完整的工具调用)
if (result.tool_call_chunks && result.tool_call_chunks.length > 0) {
if (!result.tool_calls) result.tool_calls = [];
for (const chunk of result.tool_call_chunks) {
if (chunk.name && chunk.args) {
const existingToolCall = result.tool_calls.find(t => t.id === chunk.id);
if (!existingToolCall) {
// 创建新的完整工具调用
result.tool_calls.push({
id: chunk.id,
name: chunk.name,
args: chunk.args,
type: 'tool_call'
});
} else {
// 更新现有工具调用的参数
existingToolCall.args = chunk.args;
}
}
}
}
// 5. 清理无效的工具调用
if (result.tool_calls) {
result.tool_calls = result.tool_calls.filter(toolCall => {
// 保留有id或有name的工具调用并且不是空的args对象
return (toolCall.id || toolCall.name) &&
(toolCall.args !== undefined || toolCall.function?.arguments);
});
}
// 清理空的tool_call_chunks
if (result.tool_call_chunks) {
result.tool_call_chunks = result.tool_call_chunks.filter(chunk => {
return (chunk.id || chunk.name || chunk.args);
});
}
}
/**