Backend:
- config.ts: unified startup env validation (throw on missing critical vars)
- tool-executor: structured logToolCall() JSON, ToolStatus enum (success/partial_success/fallback_success/error), preValidateToolCall() hardcoded guards for ticket_detail/chart_generate, durationMs/inputSummary/resultCount on all execution log entries
- agent.ts: tool selection decision tree, chart-as-default-path prompt rules, source labels [知识库][工单][网络][推断]
- tool-defs.ts: applicable/not-applicable guidance on all 4 tools
- soc-client.ts: sandbox hardening (10k char limit, 15s timeout, output truncation, error classification), config.* accessors
- router.ts: preCheckRoute() rules — TK-xxx/工单/知识库 → enterprise direct; greetings → generalInput
- supervisor/types.ts: removed dead config fields (model/temperature/maxTokens/systemPrompt)
- Remove chat-agent (legacy entry point)
Frontend:
- MessageBubble: source badge rendering [知识库][工单][网络][推断], CitationChip [1][2] → clickable chips
- ThreadSidebar: auto-title from first message, collapsible search, long title truncation
- ToolCallStatus: tool-specific loading labels, collapse-all toggle for multi-tool
- main.tsx: conclusion-first layout (AI text above artifacts), draft persistence, IME fix, auto chip, multi-tool AnalysisBlock container, sort_key ordering, retry via soc:retry-tool
- chart-result: empty guard, chart/table toggle, multi-chart format support
- ticket-summary/knowledge-result: work-card quick actions (prefill with context)
- ActionBar: structured payload {text, taskType, sourceCardId}, source label UI
- index.css: card-enter slide animation, dot-bounce loading
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
245 lines
13 KiB
Markdown
245 lines
13 KiB
Markdown
# SOC 8项优化 端到端验收测试报告
|
||
|
||
## 测试环境
|
||
- **后端**: http://192.168.30.30:2024
|
||
- **前端**: http://192.168.30.30:5173
|
||
- **代码版本**: commit 4ad169e (main)
|
||
- **测试时间**: 2026-04-11
|
||
|
||
## 基础端点测试
|
||
|
||
| # | 端点 | 方法 | 状态码 | 响应 | 状态 |
|
||
|---|------|------|--------|------|------|
|
||
| 1 | /ok | GET | 200 | `{"ok":true}` | PASS |
|
||
| 2 | /info | GET | 200 | `{"flags":{"assistants":true,"crons":false}}` | PASS |
|
||
| 3 | /threads | POST | 200 | 返回 thread_id, status: idle | PASS |
|
||
| 4 | 前端首页 | GET | 200 | 页面正常加载 | PASS |
|
||
|
||
---
|
||
|
||
## 第一轮:8项优化验收测试
|
||
|
||
| # | 测试项 | 状态 | 备注 |
|
||
|---|--------|------|------|
|
||
| 1 | P0 Daytona sandbox 修复 | PASS | code_execute 工具成功调用,exit_code=0,stdout="5050",耗时1462ms |
|
||
| 2 | KB Agent retry 验证 | PASS | retry.ts 实现完整(3次重试,指数退避1s基准),已在 enterprise/tool-executor 中正确包裹 kb_search;本次测试 KB 正常响应无需重试 |
|
||
| 3 | ticket_detail 修复 | PASS | TK-xxxx 格式工单 ID 可正常查询详情,返回完整工单数据 |
|
||
| 4 | sourceType 字段 | PASS | 所有 ui items 均包含 sourceType 和 confidence 字段:sandbox-result(code_execution/high), ticket-summary(ticket_system/high), ticket-detail(ticket_system/high), canvas-doc(generated_doc/high), knowledge-result(internal_kb), search-result(external_web) |
|
||
| 5 | 自动工具路由准确性 | PASS (5/5) | "写报告"->writer, "网络安全新闻"->searcher, "Python排序算法"->coder, "公司网络安全规范"->enterprise(kb_search), "你好介绍自己"->generalInput |
|
||
| 6 | 企业风格输出 | PASS | 回复包含 [工单数据] 来源标注,结论/依据/建议三层结构完整,有风险提示和下一步建议 |
|
||
| 7 | 会话恢复 | PASS | 新 thread 发送工具触发消息后,GET /threads/{id}/state 返回 values.ui 数组包含 ticket-summary 卡片(含 sourceType/confidence),UI 状态正确持久化到 checkpoint |
|
||
|
||
---
|
||
|
||
## 第二轮:迭代修复验收测试
|
||
|
||
| # | 测试项 | 状态 | 备注 |
|
||
|---|--------|------|------|
|
||
| R1 | ticket_detail 不同工单返回不同数据 | PASS | 查询 TK-2026-AE4DEA 和 TK-2026-9FA15E 分别返回正确的不同工单数据(P1-1 已修复) |
|
||
| R2 | sandbox 失败时友好提示 | PASS | 执行 `import nonexistent_module_xyz` 时 AI 给出中文分析(模块不存在、建议替代方案),未暴露原始技术栈错误 |
|
||
| R3 | 空工单列表友好提示 | PASS (代码验证) | ticket-summary 组件 tickets.length===0 时显示 "暂无工单" 居中提示文案 |
|
||
| R4 | Gen-UI 卡片 error 状态渲染 | PASS (代码验证) | 4个卡片组件均支持 errorMessage prop:有错误时边框变红(border-red-200),显示红色错误文案(text-red-700) |
|
||
| R5 | ToolCallStatus 失败状态红色X | PASS (代码验证) | 使用 XCircle + text-red-500 图标,失败时显示 "失败 . {工具名}" |
|
||
| R6 | ToolCallStatus 骨架屏过渡 | PASS (代码验证) | expanded && !uiItem 时渲染 animate-pulse 骨架屏(4行灰色占位条) |
|
||
|
||
---
|
||
|
||
## 详细测试记录
|
||
|
||
### 测试1:Daytona sandbox
|
||
- **请求**: "帮我用Python计算1到100的和"
|
||
- **路由**: supervisor -> coder
|
||
- **工具调用**: code_execute, language=python, code=`sum(range(1, 101))`
|
||
- **响应**: exit_code=0, stdout="5050\n", duration_ms=1462
|
||
- **UI 卡片**: sandbox-result, props 含 sourceType="code_execution", confidence="high"
|
||
- **判定**: PASS
|
||
|
||
### 测试2:KB Agent retry
|
||
- **代码验证**: `src/agent/utils/retry.ts` 实现 executeWithRetry,支持指数退避
|
||
- **集成验证**: enterprise/tool-executor.ts 中 kb_search 调用已包裹 `executeWithRetry(() => kbSearch(query), 3, { backoffMs: 1000, exponential: true })`
|
||
- **运行时验证**: 发送 "我们公司的网络安全规范是什么",路由到 enterprise,调用 kb_search 成功返回 knowledge-result 卡片
|
||
- **日志检查**: 未触发 retry 日志(KB 服务正常响应,未超时)
|
||
- **判定**: PASS(代码实现正确,运行时无异常)
|
||
|
||
### 测试3:ticket_detail
|
||
- **请求**: "查看最新工单的详情"
|
||
- **工具链**: ticket_list(page=1) -> ticket_detail("TK-2026-296691")
|
||
- **ticket_list 响应**: 3条工单,TK-2026-296691/TK-2026-AE4DEA/TK-2026-9FA15E
|
||
- **ticket_detail 响应**: 返回完整工单详情(含 customer/engineer/urges/sla 等字段)
|
||
- **UI 卡片**: ticket-summary + ticket-detail,均含 sourceType="ticket_system"
|
||
- **判定**: PASS
|
||
|
||
### 测试4:sourceType 字段
|
||
验证所有 ui 卡片 props 中的 sourceType 和 confidence 字段:
|
||
| 卡片类型 | sourceType | confidence |
|
||
|----------|-----------|------------|
|
||
| sandbox-result | code_execution | high |
|
||
| ticket-summary | ticket_system | high |
|
||
| ticket-detail | ticket_system | high |
|
||
| knowledge-result | internal_kb | (已确认存在) |
|
||
| search-result | external_web | (已确认存在) |
|
||
| canvas-doc | generated_doc | high |
|
||
- **判定**: PASS
|
||
|
||
### 测试5:自动工具路由
|
||
| 输入 | 预期路由 | 实际路由 | 触发工具 | 状态 |
|
||
|------|---------|---------|----------|------|
|
||
| "帮我写一个关于本季度运营情况的报告" | writer | writer | doc_create | PASS |
|
||
| "查一下最近的网络安全新闻" | searcher | searcher | google_search | PASS |
|
||
| "用Python写个排序算法" | coder | coder | (code_execute) | PASS |
|
||
| "我们公司的网络安全规范是什么" | enterprise | enterprise | kb_search | PASS |
|
||
| "你好,介绍一下自己" | generalInput | generalInput | (无工具) | PASS |
|
||
- **判定**: PASS (5/5)
|
||
|
||
### 测试6:企业风格输出
|
||
- **请求**: "分析最近3个工单的共同问题"
|
||
- **路由**: enterprise
|
||
- **工具调用**: ticket_list + 3x ticket_detail
|
||
- **输出结构验证**:
|
||
- [工单数据] 来源标注: 出现多处,标注在每个关键结论前
|
||
- 结论段: 有,以"**结论**"开头
|
||
- 依据段: 有,以"**依据**"开头,按来源分组
|
||
- 建议段: 有,以"**建议**"开头,含建议行动/风险提示/下一步
|
||
- **判定**: PASS
|
||
|
||
### 测试7:会话恢复
|
||
- **流程**: 创建 thread -> 发送"查询最近的工单" -> 等待完成 -> GET /threads/{id}/state
|
||
- **Thread ID**: 8be68055-38a5-4c22-b614-601a2eaf206e
|
||
- **验证结果**: values.ui 数组包含 1 个 ticket-summary 卡片,props 含 sourceType="ticket_system" 和 confidence="high"
|
||
- **判定**: PASS(UI 状态正确持久化到 checkpoint,切换会话可恢复)
|
||
|
||
### R1:ticket_detail UUID 映射修复验证
|
||
- **请求**: "分别查看工单TK-2026-AE4DEA和TK-2026-9FA15E的详情"
|
||
- **路由**: enterprise
|
||
- **结果**: 2个 ticket-detail 卡片,id 分别为 TK-2026-AE4DEA (title="12e1") 和 TK-2026-9FA15E (title="123")
|
||
- **toolStatus**: 2x ticket_detail status=ok
|
||
- **判定**: PASS(第一轮 P1-1 问题已修复,不同 TK-xxxx 正确返回不同工单)
|
||
|
||
### R2:sandbox 失败友好提示
|
||
- **请求**: "帮我运行这段Python代码:import nonexistent_module_xyz; print(nonexistent_module_xyz.hello())"
|
||
- **工具结果**: exit_code=1, ModuleNotFoundError
|
||
- **AI 回复**: 中文分析 -- "模块 nonexistent_module_xyz 并不存在",给出表格化失败原因,提供修复建议(替换为真实模块名/自定义示例),未暴露原始 traceback
|
||
- **UI 卡片**: sandbox-result, exit_code=1
|
||
- **判定**: PASS
|
||
|
||
### R3:空工单列表友好提示
|
||
- **代码验证**: ticket-summary/index.tsx 中 `tickets.length === 0` 分支渲染 `<li className="px-4 py-6 text-center text-sm text-muted-foreground">暂无工单</li>`
|
||
- **判定**: PASS(组件正确处理空状态)
|
||
|
||
### R4:Gen-UI 卡片 error 状态
|
||
- **代码验证**: 4个卡片组件均实现 errorMessage prop:
|
||
- knowledge-result: errorMessage 时 border-red-200/dark:border-red-900/40,显示红色文案
|
||
- ticket-summary: 同上
|
||
- search-result: 同上
|
||
- sandbox-result: 同上 + 失败时显示 "失败 (exit {code})"
|
||
- **后端集成**: enterprise/tool-executor.ts 中 kb_search 失败时推送 `sourceType: "error", confidence: "low", errorMessage: "知识库暂时无响应,已切换到网络搜索"` 并触发 google_search fallback
|
||
- **判定**: PASS
|
||
|
||
### R5:ToolCallStatus 失败状态
|
||
- **代码验证**: ToolCallStatus.tsx 中 isFailed 时渲染 `<XCircle className="size-3.5 text-red-500 shrink-0" />`,文案显示 "失败 . {工具中文名}"
|
||
- **判定**: PASS
|
||
|
||
### R6:ToolCallStatus 骨架屏
|
||
- **代码验证**: ToolCallStatus.tsx 中 `expanded && !uiItem` 时渲染:
|
||
```html
|
||
<div className="mt-2 ml-7 space-y-2 animate-pulse">
|
||
<div className="h-3 bg-muted rounded w-3/4" />
|
||
<div className="h-3 bg-muted rounded w-1/2" />
|
||
<div className="h-3 bg-muted rounded w-5/6" />
|
||
<div className="h-8 bg-muted rounded w-full mt-3" />
|
||
</div>
|
||
```
|
||
- **判定**: PASS(展开时 UI 卡片未加载前显示骨架屏过渡动画)
|
||
|
||
---
|
||
|
||
## 第三轮:新功能验收测试
|
||
|
||
| # | 测试项 | 状态 | 备注 |
|
||
|---|--------|------|------|
|
||
| R3-1 | chart-result 自动出图 | PASS | ticket-summary 后自动附带 chart-result 卡片,含2个图表:状态分布(已关闭:2, IN_PROGRESS:1) + 优先级分布(PRIORITY:2, NORMAL:1) |
|
||
| R3-2 | reply-draft 卡片 | PASS | 路由到 writer,调用 reply_draft 工具,返回 reply-draft 卡片(mode="customer",含专业客户回复内容) |
|
||
| R3-3 | next-actions 卡片 | PASS | 工单查询后自动附带 next-actions 卡片,含2条推荐动作;知识库查询后含"生成知识摘要"、"导出到文档" |
|
||
| R3-4 | citations 来源列表 | PASS | knowledge-result 含5条 citations(带 title/source);search-result 含8条 citations(带 title/source/url) |
|
||
| R3-5 | 卡片合并 deduplication | PASS (代码验证) | main.tsx 中 deduplicateUiItems() 按 card_id 去重,同 card_id 只保留最新态 |
|
||
|
||
---
|
||
|
||
### R3-1:chart-result 自动出图
|
||
- **请求**: "查询最近的工单"
|
||
- **路由**: enterprise
|
||
- **UI 卡片**: 3个 -- ticket-summary + chart-result + next-actions
|
||
- **chart-result 详情**:
|
||
- title: "工单分布统计"
|
||
- charts[0]: title="状态分布", data=[{name:"已关闭", value:2}, {name:"IN_PROGRESS", value:1}]
|
||
- charts[1]: title="优先级分布", data=[{name:"PRIORITY", value:2}, {name:"NORMAL", value:1}]
|
||
- **判定**: PASS
|
||
|
||
### R3-2:reply-draft 卡片
|
||
- **请求**: "帮我生成一份客户回复草稿,关于工单TK-2026-296691中LLM请求被拒绝的问题"
|
||
- **路由**: writer
|
||
- **工具调用**: reply_draft + doc_create
|
||
- **UI 卡片**: reply-draft (mode="customer") + canvas-doc
|
||
- **reply-draft 内容**: 专业客户回复,包含问题确认、排查方向(参数规范/鉴权/频率限制/额度)、建议步骤
|
||
- **判定**: PASS
|
||
|
||
### R3-3:next-actions 卡片
|
||
- **工单查询场景**: actions = ["生成处理建议", "生成客户回复"],每条含 prompt 和 icon
|
||
- **知识库查询场景**: actions = ["生成知识摘要", "导出到文档"]
|
||
- **判定**: PASS
|
||
|
||
### R3-4:citations 来源列表
|
||
- **knowledge-result**: 5条 citations,格式 {index, title, source}
|
||
- 示例: {index:1, title:"Taiji Agent 文档中心 - Openclaw相关配置说明", source:null}
|
||
- **search-result**: 8条 citations,格式 {index, title, source, url}
|
||
- 示例: {index:1, title:"推出OpenAI 安全研究员计划", source:"openai.com", url:"https://openai.com/..."}
|
||
- **前端渲染**: knowledge-result/index.tsx 和 search-result/index.tsx 均实现 Citation 接口渲染来源列表
|
||
- **判定**: PASS
|
||
|
||
### R3-5:卡片合并 deduplication
|
||
- **代码验证**: main.tsx:30 `deduplicateUiItems()` 函数:
|
||
- 遍历 items 按 `props.card_id` 建立 Map,记录每个 card_id 最后出现的 index
|
||
- filter 时只保留 card_id 对应最后 index 的项,无 card_id 的项全部保留
|
||
- 在消息渲染和会话恢复两处调用(L439, L588)
|
||
- **判定**: PASS
|
||
|
||
---
|
||
|
||
## 问题清单
|
||
|
||
### 已修复
|
||
- **P1-1 (R2已修复)**: ticket_detail UUID 映射异常
|
||
|
||
### 遗留问题
|
||
无 P0/P1 遗留问题。
|
||
|
||
### P2 建议
|
||
- KB retry 机制在正常情况下无法触发运行时验证,建议后续添加集成测试模拟超时场景
|
||
- 骨架屏和 error 态为代码验证(非运行时触发),建议前端 Storybook 添加对应 story
|
||
- knowledge-result citations 中 source 字段部分为 null,建议后端 kb_search 返回时补充来源文档名
|
||
- chart-result 中 charts 子项缺少 type 字段(pie/bar),前端需靠 fallback 推断图表类型
|
||
|
||
---
|
||
|
||
## 通过率
|
||
|
||
**第一轮: 7/7 通过** | **第二轮: 6/6 通过** | **第三轮: 5/5 通过** | **总计: 18/18**
|
||
|
||
## 整体质量评分
|
||
|
||
**9.2 / 10**
|
||
|
||
- 核心功能(sandbox、工单、知识库、搜索、路由)全部正常
|
||
- sourceType/confidence 字段在所有卡片中正确注入
|
||
- 企业风格输出格式规范,来源标注完整
|
||
- 会话恢复 checkpoint UI 持久化正常
|
||
- KB retry + fallback 机制代码实现完善
|
||
- ticket_detail UUID 映射问题已修复
|
||
- Gen-UI error 态、ToolCallStatus 失败/骨架屏均已实现
|
||
- chart-result 自动出图(状态分布+优先级分布)
|
||
- reply-draft 卡片含 customer/internal 模式
|
||
- next-actions 推荐动作按场景智能生成
|
||
- citations 来源列表完整(知识库+搜索均有)
|
||
- deduplicateUiItems 卡片去重逻辑正确
|
||
- 扣分项:chart-result 缺少 type 字段(-0.3);knowledge citations source 部分为 null(-0.2);部分前端状态为代码验证(-0.3)
|