fix: 修复 Agent 流式对话无响应和工具 schema 兼容性问题

- 在 `run_stream()` LLM 调用前 yield `think` 事件,前端即时显示"思考中..."
- 修复 tool schema 规范化逻辑:`{"function":{...}}` 格式缺少 `type` 字段导致 LLM API 拒绝
- 启动时从数据库加载自定义工具(`load_tools_from_db`),解决重启后工具丢失
- 前端 SSE 添加 60s 超时保护,任何事件类型均触发 `receivedFirstEvent`
- 流式失败自动降级到非流式 POST
- 添加 `scripts/seed_coding_agent.py` 和 `scripts/test_coding_agent.py`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
renjianbo
2026-05-02 00:38:41 +08:00
parent 342f3fcb16
commit 7aba0f9bc5
10 changed files with 662 additions and 36 deletions

View File

@@ -396,9 +396,11 @@ async function sendMessage() {
? `/api/v1/agent-chat/${currentAgentId.value}/stream`
: '/api/v1/agent-chat/bare/stream'
// 尝试 SSE 流式
// 尝试 SSE 流式(带超时控制)
let usedStreaming = false
streamingActive.value = false
const abortController = new AbortController()
const streamTimeout = setTimeout(() => abortController.abort(), 60000)
try {
const token = localStorage.getItem('token') || ''
const resp = await fetch(streamEndpoint, {
@@ -408,6 +410,7 @@ async function sendMessage() {
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
},
body: JSON.stringify({ message: text, session_id: sessId || undefined }),
signal: abortController.signal,
})
if (resp.ok && resp.body) {
@@ -447,16 +450,17 @@ async function sendMessage() {
try {
const data = JSON.parse(dataStr)
// 首个事件到达 → 隐藏 loading dots
if (!receivedFirstEvent && (eventType === 'think' || eventType === 'tool_call' || eventType === 'tool_result')) {
// 首个事件到达 → 隐藏 loading dots,无论什么事件类型
if (!receivedFirstEvent) {
receivedFirstEvent = true
streamingActive.value = true
}
if (eventType === 'think') {
const thinkContent = data.content || '思考中...'
currentMsg.steps!.push({
iteration: data.iteration, type: 'think',
content: data.content || '',
content: thinkContent,
reasoning: data.reasoning,
tool_name: data.tool_names?.[0],
})
@@ -491,7 +495,10 @@ async function sendMessage() {
}
}
}
} catch { /* 流式不可用,降级到普通 POST */
} catch {
clearTimeout(streamTimeout)
// 流式失败时标记为非流式,让 fallback POST 兜底
usedStreaming = false
streamingActive.value = false
}