413 lines
8.6 KiB
Markdown
413 lines
8.6 KiB
Markdown
|
|
# AI Agent 前端展示 - 完整实现文档
|
|||
|
|
|
|||
|
|
## 一、概述
|
|||
|
|
|
|||
|
|
本文档描述了 AI Agent 对话系统的前端展示实现,包括:
|
|||
|
|
- 思考过程展示
|
|||
|
|
- 工具调用与结果展示
|
|||
|
|
- 最终回答流式渲染
|
|||
|
|
- 人工介入确认显示
|
|||
|
|
|
|||
|
|
## 二、后端 SSE 事件类型
|
|||
|
|
|
|||
|
|
### 事件类型汇总
|
|||
|
|
|
|||
|
|
| 事件类型 | 说明 | 数据结构 |
|
|||
|
|
|---------|------|---------|
|
|||
|
|
| `node_start` | 节点开始执行 | `{ type: "node_start", node: string }` |
|
|||
|
|
| `node_end` | 节点执行结束 | `{ type: "node_end", node: string }` |
|
|||
|
|
| `reasoning` | 思考过程 token | `{ type: "reasoning", node: string, content: string }` |
|
|||
|
|
| `tool_call_start` | 工具调用开始 | `{ type: "tool_call_start", tool: string, args: any, id: string }` |
|
|||
|
|
| `tool_call_end` | 工具调用结束 | `{ type: "tool_call_end", tool: string, id: string, result: string }` |
|
|||
|
|
| `llm_token` | 最终回答 token | `{ type: "llm_token", node: string, content: string }` |
|
|||
|
|
| `human_review_request` | 人工审核请求 | `{ type: "human_review_request", review_id: string, content: string }` |
|
|||
|
|
| `state_update` | 状态更新 | `{ type: "state_update", data: any }` |
|
|||
|
|
| `custom` | 自定义事件 | `{ type: "custom", data: any }` |
|
|||
|
|
| `done` | 对话完成 | `{ type: "done" }` |
|
|||
|
|
|
|||
|
|
### 事件处理流程
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户消息
|
|||
|
|
↓
|
|||
|
|
[node_start] llm_call
|
|||
|
|
↓
|
|||
|
|
[reasoning] 思考过程流式输出
|
|||
|
|
↓
|
|||
|
|
[tool_call_start] 工具调用开始
|
|||
|
|
↓
|
|||
|
|
[node_end] llm_call
|
|||
|
|
↓
|
|||
|
|
[node_start] tool_node
|
|||
|
|
↓
|
|||
|
|
[tool_call_end] 工具调用完成,返回结果
|
|||
|
|
↓
|
|||
|
|
[node_end] tool_node
|
|||
|
|
↓
|
|||
|
|
[node_start] llm_call
|
|||
|
|
↓
|
|||
|
|
[llm_token] 最终回答流式输出
|
|||
|
|
↓
|
|||
|
|
[node_end] llm_call
|
|||
|
|
↓
|
|||
|
|
[human_review_request] 人工审核请求(如有)
|
|||
|
|
↓
|
|||
|
|
[done]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 三、前端组件结构
|
|||
|
|
|
|||
|
|
### 组件树
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
ChatContainer (主容器)
|
|||
|
|
├── useChat (自定义 Hook)
|
|||
|
|
│ ├── ApiClient (API 客户端)
|
|||
|
|
│ └── 状态管理
|
|||
|
|
├── UserMessage (用户消息)
|
|||
|
|
└── AssistantMessage (AI 消息)
|
|||
|
|
├── ReasoningSection (思考过程)
|
|||
|
|
├── ToolCallCard[] (工具调用卡片)
|
|||
|
|
├── HumanReviewCard (人工审核卡片)
|
|||
|
|
└── 最终回答内容
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 四、视觉设计规范
|
|||
|
|
|
|||
|
|
### 1. 思考区(Reasoning Section)
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
// 设计规范
|
|||
|
|
{
|
|||
|
|
icon: '💭',
|
|||
|
|
style: {
|
|||
|
|
background: 'bg-gray-50',
|
|||
|
|
border: 'border-gray-200',
|
|||
|
|
text: 'text-gray-600 italic',
|
|||
|
|
},
|
|||
|
|
interaction: {
|
|||
|
|
collapsible: true,
|
|||
|
|
streaming: true,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 工具调用区(Tool Call Card)
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
// 设计规范
|
|||
|
|
{
|
|||
|
|
icon: '⚙️',
|
|||
|
|
statusIcons: {
|
|||
|
|
pending: '⏳',
|
|||
|
|
running: '🔄',
|
|||
|
|
success: '✅',
|
|||
|
|
error: '❌',
|
|||
|
|
},
|
|||
|
|
colors: {
|
|||
|
|
pending: 'border-gray-300 bg-gray-50',
|
|||
|
|
running: 'border-blue-300 bg-blue-50',
|
|||
|
|
success: 'border-green-300 bg-green-50',
|
|||
|
|
error: 'border-red-300 bg-red-50',
|
|||
|
|
},
|
|||
|
|
features: {
|
|||
|
|
argsCollapsible: true,
|
|||
|
|
resultCollapsible: true,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 最终回答区(Final Answer)
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
// 设计规范
|
|||
|
|
{
|
|||
|
|
style: {
|
|||
|
|
background: 'bg-blue-50',
|
|||
|
|
border: 'border-blue-100',
|
|||
|
|
},
|
|||
|
|
interaction: {
|
|||
|
|
streaming: true,
|
|||
|
|
cursorBlink: true,
|
|||
|
|
},
|
|||
|
|
actions: {
|
|||
|
|
copy: true,
|
|||
|
|
feedback: true,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 人工审核区(Human Review Card)
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
// 设计规范
|
|||
|
|
{
|
|||
|
|
icon: '👤',
|
|||
|
|
style: {
|
|||
|
|
background: 'bg-yellow-50',
|
|||
|
|
border: 'border-yellow-300',
|
|||
|
|
},
|
|||
|
|
actions: {
|
|||
|
|
approve: true,
|
|||
|
|
reject: true,
|
|||
|
|
modify: true,
|
|||
|
|
},
|
|||
|
|
fields: {
|
|||
|
|
contentToReview: true,
|
|||
|
|
comment: true,
|
|||
|
|
modifiedContent: true,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 五、使用示例
|
|||
|
|
|
|||
|
|
### 基本使用
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
import React from 'react';
|
|||
|
|
import ChatContainer from './components/ChatContainer';
|
|||
|
|
|
|||
|
|
function App() {
|
|||
|
|
return (
|
|||
|
|
<div className="app">
|
|||
|
|
<ChatContainer
|
|||
|
|
model="zhipu"
|
|||
|
|
threadId="my-thread-123"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default App;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 自定义 API 客户端
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
import { ApiClient } from './components/useChat';
|
|||
|
|
|
|||
|
|
const customClient = new ApiClient('http://my-custom-backend:8080');
|
|||
|
|
|
|||
|
|
// 流式对话
|
|||
|
|
async function streamChat() {
|
|||
|
|
for await (const event of customClient.chatStream(
|
|||
|
|
'你好',
|
|||
|
|
'thread-1',
|
|||
|
|
'zhipu'
|
|||
|
|
)) {
|
|||
|
|
console.log('Event:', event);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 审核操作
|
|||
|
|
await customClient.approveReview('review-123', 'user@example.com', '内容正确');
|
|||
|
|
await customClient.rejectReview('review-123', 'user@example.com', '内容有误');
|
|||
|
|
await customClient.modifyReview(
|
|||
|
|
'review-123',
|
|||
|
|
'user@example.com',
|
|||
|
|
'修改后的内容',
|
|||
|
|
'调整了措辞'
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 自定义样式
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
import { AssistantMessage } from './components/ChatContainer';
|
|||
|
|
|
|||
|
|
// 自定义组件
|
|||
|
|
const CustomAssistantMessage = ({ message }) => (
|
|||
|
|
<div className="my-custom-style">
|
|||
|
|
{/* 自定义思考区 */}
|
|||
|
|
<MyCustomReasoning content={message.reasoning} />
|
|||
|
|
|
|||
|
|
{/* 自定义工具卡片 */}
|
|||
|
|
{message.toolCalls.map(tc => (
|
|||
|
|
<MyCustomToolCall key={tc.id} toolCall={tc} />
|
|||
|
|
))}
|
|||
|
|
|
|||
|
|
{/* 自定义审核卡片 */}
|
|||
|
|
{message.humanReview && (
|
|||
|
|
<MyCustomReview review={message.humanReview} />
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* 自定义回答 */}
|
|||
|
|
<div className="my-answer">{message.content}</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 六、后端修改说明
|
|||
|
|
|
|||
|
|
### 修改的文件
|
|||
|
|
|
|||
|
|
1. `backend/app/agent/service.py` - 补充完整 SSE 事件类型
|
|||
|
|
|
|||
|
|
### 新增的事件处理逻辑
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 节点开始/结束事件
|
|||
|
|
if node_name != current_node:
|
|||
|
|
if current_node:
|
|||
|
|
yield { "type": "node_end", "node": current_node }
|
|||
|
|
yield { "type": "node_start", "node": node_name }
|
|||
|
|
current_node = node_name
|
|||
|
|
|
|||
|
|
# 思考过程事件
|
|||
|
|
if reasoning_token:
|
|||
|
|
yield { "type": "reasoning", "node": node_name, "content": reasoning_token }
|
|||
|
|
|
|||
|
|
# 工具调用开始事件
|
|||
|
|
if tool_call_id not in tool_calls_in_progress:
|
|||
|
|
yield {
|
|||
|
|
"type": "tool_call_start",
|
|||
|
|
"tool": tool_name,
|
|||
|
|
"args": tool_args,
|
|||
|
|
"id": tool_call_id
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 工具调用结束事件
|
|||
|
|
if msg.get("role") == "tool":
|
|||
|
|
yield {
|
|||
|
|
"type": "tool_call_end",
|
|||
|
|
"tool": tool_name,
|
|||
|
|
"id": tool_call_id,
|
|||
|
|
"result": tool_output
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 人工审核请求事件
|
|||
|
|
if "review_pending" in serialized_data and serialized_data["review_pending"]:
|
|||
|
|
yield {
|
|||
|
|
"type": "human_review_request",
|
|||
|
|
"review_id": review_id,
|
|||
|
|
"content": content_to_review
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 七、状态管理
|
|||
|
|
|
|||
|
|
### Message 接口
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
interface Message {
|
|||
|
|
id: string;
|
|||
|
|
role: 'user' | 'assistant';
|
|||
|
|
content: string;
|
|||
|
|
reasoning: string;
|
|||
|
|
toolCalls: ToolCall[];
|
|||
|
|
humanReview?: HumanReview;
|
|||
|
|
isLoading: boolean;
|
|||
|
|
timestamp: Date;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### ToolCall 接口
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
interface ToolCall {
|
|||
|
|
id: string;
|
|||
|
|
tool: string;
|
|||
|
|
args: any;
|
|||
|
|
status: 'pending' | 'running' | 'success' | 'error';
|
|||
|
|
result?: string;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### HumanReview 接口
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
interface HumanReview {
|
|||
|
|
reviewId: string;
|
|||
|
|
content: string;
|
|||
|
|
status: 'pending' | 'approved' | 'rejected' | 'modified';
|
|||
|
|
comment?: string;
|
|||
|
|
modifiedContent?: string;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 八、文件清单
|
|||
|
|
|
|||
|
|
### 后端文件
|
|||
|
|
|
|||
|
|
| 文件 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| `backend/app/agent/service.py` | 补充完整 SSE 事件类型 |
|
|||
|
|
| `backend/app/agent_subgraphs/common/human_review.py` | 人工审核功能(已有) |
|
|||
|
|
|
|||
|
|
### 前端文件
|
|||
|
|
|
|||
|
|
| 文件 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| `frontend/src/components/useChat.ts` | 自定义 Hook + API 客户端 |
|
|||
|
|
| `frontend/src/components/ChatContainer.tsx` | 完整 UI 组件 |
|
|||
|
|
|
|||
|
|
### 文档文件
|
|||
|
|
|
|||
|
|
| 文件 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| `backend/docs/RAG_EVALUATION_GUIDE.md` | RAG 评估指南 |
|
|||
|
|
| `frontend/FRONTEND_GUIDE.md` | 本文档 |
|
|||
|
|
|
|||
|
|
## 九、测试方法
|
|||
|
|
|
|||
|
|
### 测试用例 1:简单对话
|
|||
|
|
|
|||
|
|
输入:
|
|||
|
|
```
|
|||
|
|
你好,请介绍一下你自己
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
预期输出:
|
|||
|
|
```
|
|||
|
|
[思考过程] 我需要介绍自己...
|
|||
|
|
[最终回答] 你好!我是 AI Agent...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 测试用例 2:工具调用
|
|||
|
|
|
|||
|
|
输入:
|
|||
|
|
```
|
|||
|
|
请帮我查询北京的天气
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
预期输出:
|
|||
|
|
```
|
|||
|
|
[思考过程] 用户要查询天气...
|
|||
|
|
[tool_call_start] get_weather { city: "北京" }
|
|||
|
|
[tool_call_end] 结果: "北京, 晴天, 25°C"
|
|||
|
|
[最终回答] 北京今天天气是晴,气温 25°C...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 测试用例 3:人工审核
|
|||
|
|
|
|||
|
|
输入:
|
|||
|
|
```
|
|||
|
|
请生成一份重要邮件内容
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
预期输出:
|
|||
|
|
```
|
|||
|
|
[思考过程] 用户要生成邮件...
|
|||
|
|
[最终回答] 邮件内容...
|
|||
|
|
[human_review_request] 需要审核...
|
|||
|
|
[审核卡片] 通过 / 拒绝 / 修改
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 十、常见问题
|
|||
|
|
|
|||
|
|
### Q: 如何自定义样式?
|
|||
|
|
|
|||
|
|
A: 复制组件文件,修改 className 或样式对象即可。
|
|||
|
|
|
|||
|
|
### Q: 如何添加新的事件类型?
|
|||
|
|
|
|||
|
|
A: 1. 在后端添加 yield 语句,2. 在前端 useChat 中添加 case 分支,3. 在 UI 中添加展示组件。
|
|||
|
|
|
|||
|
|
### Q: 如何处理多个工具调用?
|
|||
|
|
|
|||
|
|
A: ToolCallCard 组件支持数组,每个工具调用独立显示。
|
|||
|
|
|
|||
|
|
### Q: 如何集成现有项目?
|
|||
|
|
|
|||
|
|
A: 1. 复制后端修改,2. 复制前端组件,3. 根据项目调整 API 端点。
|