# AG-UI 聊天功能设计文档 ## 1. 概述 本文档描述如何使用 AG-UI 协议实现 AI 聊天功能,包括: - 消息的发送与接收(通过 AG-UI 事件流) - AI 工具调用(Tool Call)机制 - 日历卡片作为 Tool Result 渲染 - 前端工具注册与执行 - 本地持久化 ## 2. 架构设计 ### 2.1 整体流程 ``` 用户输入消息 ↓ AgUiService.sendMessage() ↓ [Mock Mode] 规则引擎决策 → 事件流模拟 [Real Mode] POST /api/chat → SSE 监听 ↓ ┌─────────────────────────────────────────────────────────────┐ │ AG-UI Event Stream (按序处理) │ ├─────────────────────────────────────────────────────────────┤ │ TEXT_MESSAGE_START → TEXT_MESSAGE_CONTENT* → TEXT_MESSAGE_END │ │ TOOL_CALL_START → TOOL_CALL_ARGS* → TOOL_CALL_END │ │ TOOL_CALL_RESULT │ │ RUN_STARTED → ... → RUN_FINISHED │ └─────────────────────────────────────────────────────────────┘ ↓ ChatListItem 渲染 ``` ### 2.2 核心组件 | 组件 | 职责 | |------|------| | `AgUiEvent` | AG-UI 事件数据模型 | | `AgUiService` | 事件流处理:发送消息、解析事件 | | `ToolRegistry` | 前端工具注册表:定义工具 + handler | | `AiDecisionEngine` | Mock 模式:规则引擎决定是否调用工具 | | `UiSchemaParser` | 解析 tool result 中的 UI Schema | | `UiSchemaRenderer` | 根据 schema 渲染对应组件 | | `ChatHistoryRepository` | 本地持久化:IndexedDB/localStorage | ### 2.3 状态管理 ``` ChatState { messages: ChatListItem[] // 渲染列表 pendingToolCalls: Map isLoading: bool runId: string | null } ``` ## 3. 数据模型 ### 3.1 AG-UI 事件模型 ```dart // 基类 abstract class AgUiEvent { final String type; final String? timestamp; } // 生命周期事件 class RunStartedEvent extends AgUiEvent { final String threadId; final String runId; final String? parentRunId; } class RunFinishedEvent extends AgUiEvent { final String threadId; final String runId; final dynamic result; } // 文本消息事件 class TextMessageStartEvent extends AgUiEvent { final String messageId; final String role; // "user" | "assistant" | "system" } class TextMessageContentEvent extends AgUiEvent { final String messageId; final String delta; } class TextMessageEndEvent extends AgUiEvent { final String messageId; } // 工具调用事件 class ToolCallStartEvent extends AgUiEvent { final String toolCallId; final String toolCallName; final String? parentMessageId; } class ToolCallArgsEvent extends AgUiEvent { final String toolCallId; final String delta; // JSON fragment } class ToolCallEndEvent extends AgUiEvent { final String toolCallId; } class ToolCallResultEvent extends AgUiEvent { final String messageId; final String toolCallId; final ToolResult result; // 给 AI 的原始结果 final UiCard? ui; // 给 UI 的渲染数据 } class ToolCallErrorEvent extends AgUiEvent { final String toolCallId; final String error; final String? code; } ``` ### 3.2 Tool Result Schema(v1) ```json { "type": "tool_result", "version": "v1", "call_id": "call_abc123", "tool_name": "create_calendar_event", "result": { "eventId": "evt_xxx", "ok": true, "message": "日程已创建" }, "ui": { "type": "card", "cardType": "calendar_card.v1", "data": { "id": "evt_xxx", "title": "产品评审会议", "description": "讨论Q2路线图", "startAt": "2026-03-01T10:00:00+08:00", "endAt": "2026-03-01T11:00:00+08:00", "timezone": "Asia/Shanghai", "location": "会议室A", "color": "#4F46E5", "sourceType": "agentGenerated" }, "actions": [ {"type": "open", "label": "打开", "target": "calendar/evt_xxx"}, {"type": "edit", "label": "编辑", "action": "edit_event"}, {"type": "delete", "label": "删除", "action": "delete_event"} ] } } ``` ### 3.3 工具定义(前端 Tool Registry) ```dart // 工具定义 class ToolDefinition { final String name; final String description; final Map parameters; final ToolHandler handler; } // create_calendar_event 工具 { "name": "create_calendar_event", "description": "创建一个日历事件或待办事项", "parameters": { "type": "object", "properties": { "title": { "type": "string", "description": "事件标题", "minLength": 1, "maxLength": 100 }, "description": { "type": "string", "description": "事件描述" }, "startAt": { "type": "string", "format": "date-time", "description": "开始时间 (ISO8601)" }, "endAt": { "type": "string", "format": "date-time", "description": "结束时间 (ISO8601)" }, "timezone": { "type": "string", "default": "Asia/Shanghai" }, "location": { "type": "string" }, "notes": { "type": "string" } }, "required": ["title", "startAt"] } } ``` ### 3.4 ChatListItem 模型 ```dart enum ChatItemType { message, // 纯文本消息 toolCall, // 工具调用中 toolResult, // 工具结果卡片 schedule // 日历事件(兼容旧数据) } abstract class ChatListItem { String get id; DateTime get timestamp; ChatItemType get type; MessageSender get sender; } class TextMessageItem extends ChatListItem { final String id; final String content; final DateTime timestamp; final MessageSender sender; final bool isStreaming; // 是否正在流式输出 } class ToolCallItem extends ChatListItem { final String id; final String callId; final String toolName; final Map args; // 解析后的参数 final ToolCallStatus status; // pending | executing | completed | error final ToolResult? result; final UiCard? uiCard; } class CalendarCardItem extends ChatListItem { final String id; final String callId; // 关联的 tool call final CalendarCardData data; final List actions; } ``` ## 4. 核心流程 ### 4.1 发送消息 ```dart Future sendMessage(String content) async { // 1. 添加用户消息到列表 final userMessage = TextMessageItem( id: generateId(), content: content, timestamp: DateTime.now(), sender: MessageSender.user, ); _chatItems.add(userMessage); // 2. 发起请求 if (Env.isMockApi) { await _mockEventStream(content); } else { await _realEventStream(content); } } ``` ### 4.2 Mock 事件流(规则引擎) ```dart class AiDecisionEngine { // 意图关键词映射 static final Map> _intentPatterns = { Intent.createEvent: [ RegExp(r'提醒|开会|预约|日程|安排'), RegExp(r'明天|今天|后天|下周'), RegExp(r'\d{1,2}点|\d{1,2}:\d{2}'), ], Intent.searchEvent: [ RegExp(r'查看|有什么|今天.*日程|明天.*安排'), ], }; Intent? matchIntent(String text) { for (final entry in _intentPatterns.entries) { for (final pattern in entry.value) { if (pattern.hasMatch(text)) { return entry.key; } } } return null; } // 支持强制触发:#tool:create_calendar_event {"title": "test"} bool tryForceTrigger(String text) {...} } ``` ### 4.3 事件解析与处理 ```dart Future _processEvent(AgUiEvent event) async { switch (event.type) { case 'TEXT_MESSAGE_START': _handleTextMessageStart(event); break; case 'TEXT_MESSAGE_CONTENT': _handleTextMessageContent(event); break; case 'TEXT_MESSAGE_END': _handleTextMessageEnd(event); break; case 'TOOL_CALL_START': _handleToolCallStart(event); break; case 'TOOL_CALL_ARGS': _handleToolCallArgs(event); break; case 'TOOL_CALL_END': await _handleToolCallEnd(event); break; case 'TOOL_CALL_RESULT': _handleToolCallResult(event); break; case 'TOOL_CALL_ERROR': _handleToolCallError(event); break; } } void _handleToolCallStart(ToolCallStartEvent event) { // 创建 pending 状态的 tool call item final item = ToolCallItem( id: event.toolCallId, callId: event.toolCallId, toolName: event.toolCallName, args: {}, status: ToolCallStatus.pending, ); _chatItems.add(item); } Future _handleToolCallEnd(ToolCallEndEvent event) async { // 1. 找到对应的 pending tool call final toolCall = _findPendingToolCall(event.toolCallId); if (toolCall == null) return; // 2. 校验参数 JSON Schema final validation = validateToolArgs(toolCall.toolName, toolCall.args); if (!validation.ok) { _emitToolCallError(event.toolCallId, validation.error); return; } // 3. 执行工具 handler toolCall.status = ToolCallStatus.executing; final result = await ToolRegistry.execute( toolCall.toolName, toolCall.args, ); // 4. 构建 tool result(包含 result + ui) final toolResult = ToolResult( eventId: result['eventId'], ok: result['ok'] ?? true, message: result['message'], ); final uiCard = _buildUiCard(toolCall.toolName, result); // 5. 发送 TOOL_CALL_RESULT 事件 _emitToolCallResult(event.toolCallId, toolResult, uiCard); } ``` ### 4.4 UI Schema 渲染 ```dart class UiSchemaRenderer { static final Map _renderers = { 'calendar_card.v1': (card) => CalendarCardWidget( data: CalendarCardData.fromJson(card.data), actions: card.actions, ), }; static Widget render(UiCard card) { final renderer = _renderers[card.cardType]; if (renderer != null) { return renderer(card); } // Unknown card type fallback return _renderUnknownCard(card); } static Widget _renderUnknownCard(UiCard card) { return GenericCardWidget( rawJson: jsonEncode(card.toJson()), cardType: card.cardType, ); } } ``` ### 4.5 日历卡片组件 ```dart class CalendarCardWidget extends StatelessWidget { final CalendarCardData data; final List actions; @override Widget build(BuildContext context) { final color = ColorExt.parse(data.color ?? '#4F46E5'); return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [...], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 颜色条 Container( height: 4, color: color, ), // 内容 Padding( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(data.title, style: ...), if (data.description != null) ..., _buildTimeRow(), if (data.location != null) ..., ], ), ), // Actions if (actions.isNotEmpty) _buildActions(actions), ], ), ); } } ``` ## 5. 持久化设计 ### 5.1 存储结构 ```dart // localStorage / IndexedDB { "chat_sessions": { "current_thread_id": { "messages": [...], // ChatListItem JSON "lastRunId": "run_xxx", "updatedAt": "2026-02-28T12:00:00Z" } }, "calendar_events": { "evt_xxx": {...} // 独立存储的日历事件 } } ``` ### 5.2 恢复逻辑 ```dart Future restoreSession() async { final session = await ChatHistoryRepository.load('current_thread_id'); if (session != null) { _chatItems.clear(); _chatItems.addAll(session.messages); _runId = session.lastRunId; } } ``` ## 6. 错误处理 ### 6.1 Tool Call 错误 ```dart void _emitToolCallError(String callId, String error) { // 1. 更新 item 状态 final item = _findToolCallItem(callId); item?.status = ToolCallStatus.error; item?.errorMessage = error; // 2. 渲染错误卡片 final errorCard = UiCard( cardType: 'error_card.v1', data: {'message': error}, ); // 3. 触发 UI 更新 notifyListeners(); } ``` ### 6.2 事件流重连 ```dart // 断线重连时从 snapshot 恢复 Future reconnect() async { final snapshot = await _fetchMessagesSnapshot(); _chatItems.clear(); _chatItems.addAll(snapshot.messages); // 重新订阅事件流 _subscribeToEvents(); } ``` ## 7. 实施计划 ### Phase 1: 基础框架 - [ ] 定义 AG-UI 事件模型 - [ ] 实现 AgUiService 基础结构 - [ ] 实现 ToolRegistry ### Phase 2: Mock 实现 - [ ] 实现 AiDecisionEngine 规则引擎 - [ ] 实现 Mock 事件流 - [ ] 集成现有 HomeScreen ### Phase 3: UI 渲染 - [ ] 实现 UiSchemaParser - [ ] 实现 CalendarCardWidget - [ ] 实现 ToolPending / ToolError 状态卡片 ### Phase 4: 持久化 - [ ] 实现 ChatHistoryRepository - [ ] 实现会话恢复 ### Phase 5: 真实后端对接 - [ ] 实现 SSE 客户端 - [ ] 实现事件流解析器 ## 8. 版本历史 | 版本 | 日期 | 变更 | |------|------|------| | v1.0 | 2026-02-28 | 初始版本 |