feat: Phase 3 - parallel execution, progress reporting, result caching + AgentChat bug fixes
Phase 3 能力: - DAG 并行执行 (workflow_engine): asyncio.gather 并行执行就绪节点 - Debate 并行 (orchestrator): for 循环改为 asyncio.gather - 粒度进度上报 (workflow_engine + tasks + websocket): Redis 推送 + DB 降级 - 工具结果缓存 (tool_manager): 确定性工具默认开启缓存 - LLM 响应缓存 (core): messages[-4:] + model 哈希,5min TTL AgentChat bug 修复 (Gitea #1-#5): - #1 SSE 降级重复空消息: fallback POST 前移除占位消息 - #2 streamTimeout 泄漏: while 正常退出后 clearTimeout - #3 loading 闪烁: final/error 事件中提前设 loading=false - #4 SSE 事件类型对齐: 确认匹配,未知类型加 console.warn - #5 retryMessage 流式残留: 重试时清理占位消息 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -439,6 +439,7 @@ async function sendMessage() {
|
||||
|
||||
// 尝试 SSE 流式(带超时控制)
|
||||
let usedStreaming = false
|
||||
let placeholderIdx = -1
|
||||
streamingActive.value = false
|
||||
const abortController = new AbortController()
|
||||
const streamTimeout = setTimeout(() => abortController.abort(), 60000)
|
||||
@@ -462,8 +463,8 @@ async function sendMessage() {
|
||||
role: 'assistant', content: '', timestamp: Date.now(),
|
||||
steps: [], _traceOpen: true, iterations: 0, tool_calls_made: 0,
|
||||
}
|
||||
const idx = messages.value[key].push(msg) - 1
|
||||
const currentMsg = messages.value[key][idx]
|
||||
placeholderIdx = messages.value[key].push(msg) - 1
|
||||
const currentMsg = messages.value[key][placeholderIdx]
|
||||
|
||||
const reader = resp.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
@@ -532,14 +533,19 @@ async function sendMessage() {
|
||||
sessionId.value[key] = data.session_id
|
||||
}
|
||||
streamingActive.value = false
|
||||
loading.value = false
|
||||
} else if (eventType === 'error') {
|
||||
currentMsg.content = data.content || ''
|
||||
currentMsg.status = 'error'
|
||||
streamingActive.value = false
|
||||
loading.value = false
|
||||
} else {
|
||||
console.warn('[AgentChat] 未知 SSE 事件类型:', eventType, data)
|
||||
}
|
||||
} catch { /* 跳过畸形事件 */ }
|
||||
}
|
||||
}
|
||||
clearTimeout(streamTimeout)
|
||||
}
|
||||
} catch {
|
||||
clearTimeout(streamTimeout)
|
||||
@@ -549,6 +555,11 @@ async function sendMessage() {
|
||||
}
|
||||
|
||||
if (!usedStreaming) {
|
||||
// 移除 SSE 阶段残留的占位消息(Issue #1)
|
||||
if (placeholderIdx >= 0 && placeholderIdx < messages.value[key].length) {
|
||||
messages.value[key].splice(placeholderIdx, 1)
|
||||
}
|
||||
|
||||
// 降级:标准 POST 请求
|
||||
const fallbackEndpoint = currentAgentId.value
|
||||
? `/api/v1/agent-chat/${currentAgentId.value}`
|
||||
@@ -613,9 +624,11 @@ function retryMessage(idx: number) {
|
||||
|
||||
// 查找错误消息之前的最后一条用户消息
|
||||
let userMsg = ''
|
||||
let userIdx = -1
|
||||
for (let i = idx - 1; i >= 0; i--) {
|
||||
if (msgs[i].role === 'user') {
|
||||
userMsg = msgs[i].content
|
||||
userIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -624,15 +637,15 @@ function retryMessage(idx: number) {
|
||||
return
|
||||
}
|
||||
|
||||
// 移除该错误消息及关联的用户消息
|
||||
const removeIndices: number[] = []
|
||||
for (let i = idx - 1; i >= 0; i--) {
|
||||
if (msgs[i].role === 'user' && msgs[i].content === userMsg) {
|
||||
// 收集需要移除的消息:用户消息 + 错误消息 + 中间的流式占位消息
|
||||
const removeIndices: number[] = [userIdx, idx]
|
||||
for (let i = userIdx + 1; i < idx; i++) {
|
||||
const m = msgs[i]
|
||||
// 流式占位消息:assistant 角色,内容为空或有 steps 但无实质内容
|
||||
if (m.role === 'assistant' && (!m.content || m.content === '') && (m.steps?.length || 0) > 0) {
|
||||
removeIndices.push(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
removeIndices.push(idx)
|
||||
|
||||
// 从后往前删除,避免 index 错乱
|
||||
removeIndices.sort((a, b) => b - a)
|
||||
|
||||
Reference in New Issue
Block a user