fix: intent streaming defaults and UI stability
This commit is contained in:
parent
713659a644
commit
4262922694
@ -35,6 +35,7 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
|
||||
"disabled_tool_categories": [],
|
||||
"default_run_mode": None,
|
||||
"auto_generate_title": True,
|
||||
"tool_intent_enabled": True,
|
||||
}
|
||||
|
||||
__all__ = [
|
||||
@ -124,6 +125,12 @@ def sanitize_personalization_payload(
|
||||
else:
|
||||
base["thinking_interval"] = _sanitize_thinking_interval(base.get("thinking_interval"))
|
||||
|
||||
# 工具意图提示开关
|
||||
if "tool_intent_enabled" in data:
|
||||
base["tool_intent_enabled"] = bool(data.get("tool_intent_enabled"))
|
||||
else:
|
||||
base["tool_intent_enabled"] = bool(base.get("tool_intent_enabled"))
|
||||
|
||||
if "disabled_tool_categories" in data:
|
||||
base["disabled_tool_categories"] = _sanitize_tool_categories(data.get("disabled_tool_categories"), allowed_tool_categories)
|
||||
else:
|
||||
|
||||
@ -45,7 +45,7 @@ import {
|
||||
import {
|
||||
getToolIcon,
|
||||
getToolAnimationClass,
|
||||
getToolStatusText,
|
||||
getToolStatusText as baseGetToolStatusText,
|
||||
getToolDescription,
|
||||
cloneToolArguments,
|
||||
buildToolLabel,
|
||||
@ -1653,6 +1653,8 @@ const appOptions = {
|
||||
id: toolCall.id,
|
||||
name: toolCall.function.name,
|
||||
arguments: arguments_obj,
|
||||
intent_full: arguments_obj.intent || '',
|
||||
intent_rendered: arguments_obj.intent || '',
|
||||
status: 'preparing',
|
||||
result: null
|
||||
},
|
||||
@ -2585,7 +2587,14 @@ const appOptions = {
|
||||
},
|
||||
getToolIcon,
|
||||
getToolAnimationClass,
|
||||
getToolStatusText,
|
||||
getToolStatusText(tool: any) {
|
||||
const personalization = usePersonalizationStore();
|
||||
const intentEnabled =
|
||||
personalization?.form?.tool_intent_enabled ??
|
||||
personalization?.tool_intent_enabled ??
|
||||
true;
|
||||
return baseGetToolStatusText(tool, { intentEnabled });
|
||||
},
|
||||
getToolDescription,
|
||||
cloneToolArguments,
|
||||
buildToolLabel,
|
||||
|
||||
@ -22,7 +22,14 @@
|
||||
</div>
|
||||
|
||||
<div class="stacked-viewport" :style="{ height: `${viewportHeight}px` }">
|
||||
<div class="stacked-inner" ref="inner" :style="{ transform: `translateY(${innerOffset}px)` }">
|
||||
<TransitionGroup
|
||||
name="stacked"
|
||||
tag="div"
|
||||
class="stacked-inner"
|
||||
ref="inner"
|
||||
:style="{ transform: `translateY(${innerOffset}px)` }"
|
||||
appear
|
||||
>
|
||||
<div v-for="(action, idx) in stackableActions" :key="blockKey(action, idx)" class="stacked-item">
|
||||
<div
|
||||
v-if="action.type === 'thinking'"
|
||||
@ -109,7 +116,7 @@
|
||||
<div v-if="isToolProcessing(action)" class="progress-indicator"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -235,7 +242,8 @@ const setShellMetrics = (expandedOverride: Record<string, boolean> = {}) => {
|
||||
const contentHeight = content ? Math.ceil(content.scrollHeight) : 0;
|
||||
nextContentHeights[key] = contentHeight;
|
||||
const contentH = expanded ? contentHeight : 0;
|
||||
const progressH = progress ? progress.getBoundingClientRect().height : 0;
|
||||
// 进度条是绝对定位的,不应计入布局高度,否则会导致块高度在运行时膨胀几像素
|
||||
const progressH = 0;
|
||||
const borderH = idx < children.length - 1 ? parseFloat(getComputedStyle(el).borderBottomWidth) || 0 : 0;
|
||||
heights.push(Math.ceil(headerH + contentH + progressH + borderH));
|
||||
});
|
||||
|
||||
@ -39,6 +39,75 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
|
||||
const pendingToolEvents: Array<{ event: string; data: any; handler: () => void }> = [];
|
||||
|
||||
const startIntentTyping = (action: any, intentText: any) => {
|
||||
if (!action || !action.tool) {
|
||||
return;
|
||||
}
|
||||
if (typeof intentText !== 'string') {
|
||||
return;
|
||||
}
|
||||
const text = intentText.trim();
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
if (action.tool.intent_full === text) {
|
||||
if (console && console.debug) {
|
||||
console.debug('[tool_intent] skip (same text)', {
|
||||
id: action?.id || action?.tool?.id,
|
||||
name: action?.tool?.name,
|
||||
text
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (typeof console !== 'undefined' && console.debug) {
|
||||
console.debug('[tool_intent] typing start', {
|
||||
id: action?.id || action?.tool?.id,
|
||||
name: action?.tool?.name,
|
||||
text
|
||||
});
|
||||
}
|
||||
if (action.tool.intent_full === text && action.tool.intent_rendered === text) {
|
||||
return;
|
||||
}
|
||||
// 清理旧定时器
|
||||
if (action.tool.intent_typing_timer) {
|
||||
clearInterval(action.tool.intent_typing_timer);
|
||||
action.tool.intent_typing_timer = null;
|
||||
}
|
||||
action.tool.intent_full = text;
|
||||
action.tool.intent_rendered = '';
|
||||
const duration = 1000;
|
||||
const step = Math.max(10, Math.floor(duration / Math.max(1, text.length)));
|
||||
let index = 0;
|
||||
action.tool.intent_typing_timer = window.setInterval(() => {
|
||||
action.tool.intent_rendered = text.slice(0, index + 1);
|
||||
if (index === 0 && console && console.debug) {
|
||||
console.debug('[tool_intent] typing tick', {
|
||||
id: action?.id || action?.tool?.id,
|
||||
name: action?.tool?.name,
|
||||
next: action.tool.intent_rendered
|
||||
});
|
||||
}
|
||||
index += 1;
|
||||
if (index >= text.length) {
|
||||
clearInterval(action.tool.intent_typing_timer);
|
||||
action.tool.intent_typing_timer = null;
|
||||
action.tool.intent_rendered = text;
|
||||
if (console && console.debug) {
|
||||
console.debug('[tool_intent] typing done', {
|
||||
id: action?.id || action?.tool?.id,
|
||||
name: action?.tool?.name,
|
||||
full: text
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof ctx?.$forceUpdate === 'function') {
|
||||
ctx.$forceUpdate();
|
||||
}
|
||||
}, step);
|
||||
};
|
||||
|
||||
const hasPendingStreamingText = () =>
|
||||
!!streamingState.buffer.length ||
|
||||
!!streamingState.pendingCompleteContent ||
|
||||
@ -845,6 +914,7 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
scheduleStreamingFlush();
|
||||
}
|
||||
ctx.monitorEndModelOutput();
|
||||
flushPendingToolEvents();
|
||||
});
|
||||
|
||||
// 工具提示事件(可选)
|
||||
@ -863,46 +933,76 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
return;
|
||||
}
|
||||
socketLog('工具准备中:', data.name);
|
||||
if (typeof console !== 'undefined' && console.debug) {
|
||||
console.debug('[tool_intent] preparing', {
|
||||
id: data?.id,
|
||||
name: data?.name,
|
||||
intent: data?.intent,
|
||||
convo: data?.conversation_id
|
||||
});
|
||||
}
|
||||
if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) {
|
||||
socketLog('跳过tool_preparing(对话不匹配)', data.conversation_id);
|
||||
return;
|
||||
}
|
||||
const handler = () => {
|
||||
const msg = ctx.chatEnsureAssistantMessage();
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
const action = {
|
||||
id: data.id,
|
||||
type: 'tool',
|
||||
tool: {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
arguments: {},
|
||||
argumentSnapshot: null,
|
||||
argumentLabel: '',
|
||||
status: 'preparing',
|
||||
result: null,
|
||||
message: data.message || `准备调用 ${data.name}...`
|
||||
},
|
||||
timestamp: Date.now()
|
||||
};
|
||||
msg.actions.push(action);
|
||||
ctx.preparingTools.set(data.id, action);
|
||||
ctx.toolRegisterAction(action, data.id);
|
||||
ctx.toolTrackAction(data.name, action);
|
||||
ctx.$forceUpdate();
|
||||
ctx.conditionalScrollToBottom();
|
||||
if (ctx.monitorPreviewTool) {
|
||||
ctx.monitorPreviewTool(data);
|
||||
}
|
||||
};
|
||||
|
||||
if (hasPendingStreamingText()) {
|
||||
pendingToolEvents.push({ event: 'tool_preparing', data, handler });
|
||||
} else {
|
||||
handler();
|
||||
const msg = ctx.chatEnsureAssistantMessage();
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
const action = {
|
||||
id: data.id,
|
||||
type: 'tool',
|
||||
tool: {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
arguments: {},
|
||||
argumentSnapshot: null,
|
||||
argumentLabel: '',
|
||||
status: 'preparing',
|
||||
result: null,
|
||||
message: data.message || `准备调用 ${data.name}...`,
|
||||
intent_full: data.intent || '',
|
||||
intent_rendered: data.intent || ''
|
||||
},
|
||||
timestamp: Date.now()
|
||||
};
|
||||
msg.actions.push(action);
|
||||
ctx.preparingTools.set(data.id, action);
|
||||
ctx.toolRegisterAction(action, data.id);
|
||||
ctx.toolTrackAction(data.name, action);
|
||||
if (data.intent) {
|
||||
startIntentTyping(action, data.intent);
|
||||
}
|
||||
ctx.$forceUpdate();
|
||||
ctx.conditionalScrollToBottom();
|
||||
if (ctx.monitorPreviewTool) {
|
||||
ctx.monitorPreviewTool(data);
|
||||
}
|
||||
});
|
||||
|
||||
// 工具意图(流式增量)事件
|
||||
ctx.socket.on('tool_intent', (data) => {
|
||||
if (ctx.dropToolEvents) {
|
||||
return;
|
||||
}
|
||||
if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) {
|
||||
return;
|
||||
}
|
||||
if (typeof console !== 'undefined' && console.debug) {
|
||||
console.debug('[tool_intent] update', {
|
||||
id: data?.id,
|
||||
name: data?.name,
|
||||
intent: data?.intent,
|
||||
convo: data?.conversation_id
|
||||
});
|
||||
}
|
||||
const target =
|
||||
ctx.toolFindAction(data.id, data.preparing_id, data.execution_id) ||
|
||||
ctx.toolGetLatestAction(data.name);
|
||||
if (target) {
|
||||
startIntentTyping(target, data.intent);
|
||||
}
|
||||
ctx.$forceUpdate();
|
||||
});
|
||||
|
||||
// 工具状态更新事件 - 实时显示详细状态
|
||||
@ -919,6 +1019,9 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
if (target) {
|
||||
target.tool.statusDetail = data.detail;
|
||||
target.tool.statusType = data.status;
|
||||
if (data.intent) {
|
||||
startIntentTyping(target, data.intent);
|
||||
}
|
||||
ctx.$forceUpdate();
|
||||
return;
|
||||
}
|
||||
@ -927,6 +1030,9 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
fallbackAction.tool.statusDetail = data.detail;
|
||||
fallbackAction.tool.statusType = data.status;
|
||||
ctx.toolRegisterAction(fallbackAction, data.execution_id || data.id || data.preparing_id);
|
||||
if (data.intent) {
|
||||
startIntentTyping(fallbackAction, data.intent);
|
||||
}
|
||||
ctx.$forceUpdate();
|
||||
}
|
||||
});
|
||||
@ -977,6 +1083,9 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
action.tool.message = null;
|
||||
action.tool.id = data.id;
|
||||
action.tool.executionId = data.id;
|
||||
if (data.arguments && data.arguments.intent) {
|
||||
startIntentTyping(action, data.arguments.intent);
|
||||
}
|
||||
ctx.toolRegisterAction(action, data.id);
|
||||
ctx.toolTrackAction(data.name, action);
|
||||
ctx.$forceUpdate();
|
||||
@ -984,11 +1093,7 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
ctx.monitorQueueTool(data);
|
||||
};
|
||||
|
||||
if (hasPendingStreamingText()) {
|
||||
pendingToolEvents.push({ event: 'tool_start', data, handler });
|
||||
} else {
|
||||
handler();
|
||||
}
|
||||
handler();
|
||||
});
|
||||
|
||||
// 更新action(工具完成)
|
||||
@ -1039,6 +1144,9 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
if (data.message !== undefined) {
|
||||
targetAction.tool.message = data.message;
|
||||
}
|
||||
if (data.arguments && data.arguments.intent) {
|
||||
startIntentTyping(targetAction, data.arguments.intent);
|
||||
}
|
||||
if (data.awaiting_content) {
|
||||
targetAction.tool.awaiting_content = true;
|
||||
} else if (data.status === 'completed') {
|
||||
@ -1051,6 +1159,9 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
targetAction.tool.arguments = data.arguments;
|
||||
targetAction.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments);
|
||||
targetAction.tool.argumentLabel = ctx.buildToolLabel(targetAction.tool.argumentSnapshot);
|
||||
if (data.arguments.intent) {
|
||||
startIntentTyping(targetAction, data.arguments.intent);
|
||||
}
|
||||
}
|
||||
ctx.toolRegisterAction(targetAction, data.execution_id || data.id);
|
||||
if (data.status && ["completed", "failed", "error"].includes(data.status) && !data.awaiting_content) {
|
||||
@ -1079,11 +1190,7 @@ export async function initializeLegacySocket(ctx: any) {
|
||||
ctx.monitorResolveTool(data);
|
||||
};
|
||||
|
||||
if (hasPendingStreamingText()) {
|
||||
pendingToolEvents.push({ event: 'update_action', data, handler });
|
||||
} else {
|
||||
handler();
|
||||
}
|
||||
handler();
|
||||
});
|
||||
|
||||
ctx.socket.on('append_payload', (data) => {
|
||||
|
||||
@ -11,21 +11,38 @@ type ScrollContext = {
|
||||
};
|
||||
|
||||
export function scrollToBottom(ctx: ScrollContext) {
|
||||
setTimeout(() => {
|
||||
const messagesArea = ctx.getMessagesAreaElement?.();
|
||||
if (!messagesArea) {
|
||||
return;
|
||||
}
|
||||
if (typeof ctx._setScrollingFlag === 'function') {
|
||||
ctx._setScrollingFlag(true);
|
||||
}
|
||||
messagesArea.scrollTop = messagesArea.scrollHeight;
|
||||
setTimeout(() => {
|
||||
const messagesArea = ctx.getMessagesAreaElement?.();
|
||||
if (!messagesArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
const attempts = [0, 16, 60, 150, 320, 520]; // 多次尝试覆盖布局抖动/异步伸缩
|
||||
let cancelled = false;
|
||||
|
||||
const perform = (idx: number) => {
|
||||
if (cancelled) return;
|
||||
if (ctx.userScrolling) {
|
||||
cancelled = true;
|
||||
if (typeof ctx._setScrollingFlag === 'function') {
|
||||
ctx._setScrollingFlag(false);
|
||||
}
|
||||
}, 100);
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx === 0 && typeof ctx._setScrollingFlag === 'function') {
|
||||
ctx._setScrollingFlag(true);
|
||||
}
|
||||
|
||||
messagesArea.scrollTop = messagesArea.scrollHeight;
|
||||
|
||||
if (idx < attempts.length - 1) {
|
||||
setTimeout(() => perform(idx + 1), attempts[idx + 1] - attempts[idx]);
|
||||
} else if (typeof ctx._setScrollingFlag === 'function') {
|
||||
setTimeout(() => ctx._setScrollingFlag && ctx._setScrollingFlag(false), 40);
|
||||
}
|
||||
};
|
||||
|
||||
perform(0);
|
||||
}
|
||||
|
||||
export function conditionalScrollToBottom(ctx: ScrollContext) {
|
||||
|
||||
@ -5,6 +5,7 @@ type RunMode = 'fast' | 'thinking' | 'deep';
|
||||
interface PersonalForm {
|
||||
enabled: boolean;
|
||||
auto_generate_title: boolean;
|
||||
tool_intent_enabled: boolean;
|
||||
self_identify: string;
|
||||
user_name: string;
|
||||
profession: string;
|
||||
@ -54,6 +55,7 @@ const EXPERIMENT_STORAGE_KEY = 'agents_personalization_experiments';
|
||||
const defaultForm = (): PersonalForm => ({
|
||||
enabled: false,
|
||||
auto_generate_title: true,
|
||||
tool_intent_enabled: true,
|
||||
self_identify: '',
|
||||
user_name: '',
|
||||
profession: '',
|
||||
@ -176,6 +178,7 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
this.form = {
|
||||
enabled: !!data.enabled,
|
||||
auto_generate_title: data.auto_generate_title !== false,
|
||||
tool_intent_enabled: !!data.tool_intent_enabled,
|
||||
self_identify: data.self_identify || '',
|
||||
user_name: data.user_name || '',
|
||||
profession: data.profession || '',
|
||||
|
||||
@ -406,8 +406,10 @@
|
||||
transform: translateY(14px);
|
||||
}
|
||||
|
||||
.stacked-enter-active {
|
||||
.stacked-enter-active,
|
||||
.stacked-appear-active {
|
||||
transition: all 220ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-delay: 120ms;
|
||||
}
|
||||
|
||||
.stacked-leave-active {
|
||||
@ -421,6 +423,11 @@
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
|
||||
.stacked-appear-from {
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
}
|
||||
|
||||
.stacked-move {
|
||||
transition: transform 220ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
@ -440,6 +447,7 @@
|
||||
animation: progress-bar 2s ease-in-out infinite;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.collapsible-block.processing .progress-indicator {
|
||||
|
||||
@ -148,10 +148,28 @@ function describeReadFileResult(tool: any): string {
|
||||
return '文件读取完成';
|
||||
}
|
||||
|
||||
export function getToolStatusText(tool: any): string {
|
||||
export function getToolStatusText(tool: any, opts?: { intentEnabled?: boolean }): string {
|
||||
if (!tool) {
|
||||
return '';
|
||||
}
|
||||
const intentEnabled = opts?.intentEnabled !== false;
|
||||
const intentText = tool.intent_rendered || tool.intent_full || '';
|
||||
const hasIntent = !!intentText;
|
||||
|
||||
// 错误优先展示
|
||||
if (
|
||||
tool.message &&
|
||||
(tool.status === 'failed' || tool.status === 'error' || (tool.result && tool.result.error))
|
||||
) {
|
||||
return tool.message;
|
||||
}
|
||||
|
||||
// 开启时:有 intent 就只显示 intent
|
||||
if (intentEnabled && hasIntent) {
|
||||
return intentText;
|
||||
}
|
||||
|
||||
// 关闭 intent 或无 intent 时,显示状态文案
|
||||
if (tool.message) {
|
||||
return tool.message;
|
||||
}
|
||||
@ -186,7 +204,10 @@ export function getToolStatusText(tool: any): string {
|
||||
}
|
||||
return COMPLETED_STATUS_TEXTS[tool.name] || '执行完成';
|
||||
}
|
||||
return `${tool.name} - ${tool.status}`;
|
||||
if (tool.status) {
|
||||
return `${tool.name} - ${tool.status}`;
|
||||
}
|
||||
return tool.name || '';
|
||||
}
|
||||
|
||||
export function getToolDescription(tool: any): string {
|
||||
|
||||
@ -2984,6 +2984,7 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
last_tool_name = ""
|
||||
auto_fix_attempts = 0
|
||||
last_tool_call_time = 0
|
||||
detected_tool_intent: Dict[str, str] = {}
|
||||
|
||||
# 设置最大迭代次数
|
||||
max_iterations = MAX_ITERATIONS_PER_TASK
|
||||
@ -2993,6 +2994,17 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
pending_modify = None # {"path": str, "tool_call_id": str, "buffer": str, ...}
|
||||
modify_probe_buffer = ""
|
||||
|
||||
def extract_intent_from_partial(arg_str: str) -> Optional[str]:
|
||||
"""从不完整的JSON字符串中粗略提取 intent 字段,容错用于流式阶段。"""
|
||||
if not arg_str or "intent" not in arg_str:
|
||||
return None
|
||||
import re
|
||||
# 匹配 "intent": "xxx" 形式,允许前面有换行或空格;宽松匹配未闭合的引号
|
||||
match = re.search(r'"intent"\s*:\s*"([^"]{0,128})', arg_str, re.IGNORECASE | re.DOTALL)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return None
|
||||
|
||||
def resolve_monitor_path(args: Dict[str, Any], fallback: Optional[str] = None) -> Optional[str]:
|
||||
candidates = [
|
||||
args.get('path'),
|
||||
@ -3987,24 +3999,62 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
for existing in tool_calls:
|
||||
if existing.get("index") == tc.get("index"):
|
||||
if "function" in tc and "arguments" in tc["function"]:
|
||||
existing["function"]["arguments"] += tc["function"]["arguments"]
|
||||
arg_chunk = tc["function"]["arguments"]
|
||||
existing_fn = existing.get("function", {})
|
||||
existing_args = existing_fn.get("arguments", "")
|
||||
existing_fn["arguments"] = (existing_args or "") + arg_chunk
|
||||
existing["function"] = existing_fn
|
||||
|
||||
combined_args = existing_fn.get("arguments", "")
|
||||
tool_id = existing.get("id") or tc.get("id")
|
||||
tool_name = (
|
||||
existing_fn.get("name")
|
||||
or tc.get("function", {}).get("name", "")
|
||||
)
|
||||
intent_value = extract_intent_from_partial(combined_args)
|
||||
if (
|
||||
intent_value
|
||||
and tool_id
|
||||
and detected_tool_intent.get(tool_id) != intent_value
|
||||
):
|
||||
detected_tool_intent[tool_id] = intent_value
|
||||
brief_log(f"[intent] 增量提取 {tool_name}: {intent_value}")
|
||||
sender('tool_intent', {
|
||||
'id': tool_id,
|
||||
'name': tool_name,
|
||||
'intent': intent_value,
|
||||
'conversation_id': conversation_id
|
||||
})
|
||||
debug_log(f" 发送工具意图: {tool_name} -> {intent_value}")
|
||||
await asyncio.sleep(0.01)
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found and tc.get("id"):
|
||||
tool_id = tc["id"]
|
||||
tool_name = tc.get("function", {}).get("name", "")
|
||||
arguments_str = tc.get("function", {}).get("arguments", "") or ""
|
||||
|
||||
# 新工具检测到,立即发送准备事件
|
||||
if tool_id not in detected_tools and tool_name:
|
||||
detected_tools[tool_id] = tool_name
|
||||
|
||||
# 尝试提前提取 intent
|
||||
intent_value = None
|
||||
if arguments_str:
|
||||
intent_value = extract_intent_from_partial(arguments_str)
|
||||
if intent_value:
|
||||
detected_tool_intent[tool_id] = intent_value
|
||||
brief_log(f"[intent] 预提取 {tool_name}: {intent_value}")
|
||||
|
||||
# 立即发送工具准备中事件
|
||||
brief_log(f"[tool] 准备调用 {tool_name} (id={tool_id}) intent={intent_value or '-'}")
|
||||
sender('tool_preparing', {
|
||||
'id': tool_id,
|
||||
'name': tool_name,
|
||||
'message': f'准备调用 {tool_name}...'
|
||||
, 'conversation_id': conversation_id
|
||||
'message': f'准备调用 {tool_name}...',
|
||||
'intent': intent_value,
|
||||
'conversation_id': conversation_id
|
||||
})
|
||||
debug_log(f" 发送工具准备事件: {tool_name}")
|
||||
await asyncio.sleep(0.1)
|
||||
@ -4015,9 +4065,22 @@ async def handle_task_with_sender(terminal: WebTerminal, workspace: UserWorkspac
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": tool_name,
|
||||
"arguments": tc.get("function", {}).get("arguments", "")
|
||||
"arguments": arguments_str
|
||||
}
|
||||
})
|
||||
# 尝试从增量参数中抽取 intent,并单独推送
|
||||
if tool_id and arguments_str:
|
||||
intent_value = extract_intent_from_partial(arguments_str)
|
||||
if intent_value and detected_tool_intent.get(tool_id) != intent_value:
|
||||
detected_tool_intent[tool_id] = intent_value
|
||||
sender('tool_intent', {
|
||||
'id': tool_id,
|
||||
'name': tool_name,
|
||||
'intent': intent_value,
|
||||
'conversation_id': conversation_id
|
||||
})
|
||||
debug_log(f" 发送工具意图: {tool_name} -> {intent_value}")
|
||||
await asyncio.sleep(0.01)
|
||||
debug_log(f" 新工具: {tool_name}")
|
||||
|
||||
# 检查是否被停止
|
||||
|
||||
Loading…
Reference in New Issue
Block a user