docs(ai): 修复 Spec Review 发现的 9 个问题

CRITICAL 修复:
- 新增跨 crate 数据访问架构(erp-core HealthDataQuery trait)
- 新增 4 个权限码声明(session.list/manage/history + chat.send)
- 明确 Provider Function Calling 需中等程度重构,补充适配方案

IMPORTANT 修复:
- 说明与 copilot_chat_logs 表的关系(并存不迁移)
- 新增 ai_user_profiles 表(长期记忆/用户画像)
- 定义 DisplayHint 枚举(5 种富消息类型)
- Phase 0 验证标准修正为新端点
- 补充 Taro SSE 兼容层 + Web 从零构建,工期上调
- 新增 PII 脱敏规范(6 类字段处理规则)
- 新增故障处理与降级章节
- Phase 0/2 工作量估算上调,总工期 16-23→20-27 天
This commit is contained in:
iven
2026-05-18 01:51:03 +08:00
parent 1c8319fb4d
commit 6759723731

View File

@@ -1,7 +1,7 @@
# AI Agent 突破口设计规格
> **日期:** 2026-05-18 | **状态:** Draft | **范围:** erp-ai Agent 改造
> **总工期:** 16-23 天 | **方案:** ReAct Agent + Function Calling
> **总工期:** 20-27 天 | **方案:** ReAct Agent + Function Calling
## 1. 背景与动机
@@ -71,7 +71,8 @@ HMS 健康管理平台综合评分 6.8/10功能完整度 87%,但 AI 能力"
|------|------|------|
| Agent 状态管理 | Orchestrator 无状态,会话由 Handler 管理 | 简化 Orchestrator 职责,便于测试 |
| Tool 执行模型 | 同步阻塞,单轮内多个 Tool Call 并行 | LLM 返回多个 call 时并行执行,减少延迟 |
| Provider 复用 | 不改 `AiProvider` trait在调用层传入 function definitions | 最小改动,兼容现有 3 个 Provider |
| Provider 扩展 | 扩展 `GenerateRequest` 添加 tools/functions 字段3 个 Provider 各自适配 | Function Calling 是核心能力,需中等程度重构每个 Provider 的请求/响应结构 |
| 跨 crate 数据访问 | 在 erp-core 定义 `HealthDataQuery` traiterp-health 实现erp-ai 通过 trait 调用 | 保持模块边界erp-ai 不直接依赖 erp-health |
| 安全循环上限 | 单次对话最多 5 轮 Tool Call | 防止无限循环,控制成本 |
| 分析调用模式 | Agent 内走非流式同步调用 | Agent 需要拿到完整结果再决策 |
@@ -103,7 +104,56 @@ pub struct ToolResult {
}
```
### 3.2 Tool 清单
### 3.2 跨 Crate 数据访问架构
erp-ai 不直接依赖 erp-health保持模块边界。数据查询类 Tool 通过以下机制访问健康数据:
**方案:在 erp-core 定义 `HealthDataQuery` traiterp-health 实现**
```rust
// erp-core 中定义
#[async_trait]
pub trait HealthDataQuery: Send + Sync {
async fn query_vitals(&self, tenant_id: Uuid, patient_id: Uuid, days: i32) -> Result<Vec<VitalSummary>>;
async fn query_lab_reports(&self, tenant_id: Uuid, patient_id: Uuid, limit: i32) -> Result<Vec<LabReportSummary>>;
async fn query_patient_profile(&self, tenant_id: Uuid, patient_id: Uuid) -> Result<PatientProfile>;
async fn query_appointments(&self, tenant_id: Uuid, patient_id: Uuid) -> Result<Vec<AppointmentSummary>>;
async fn query_medication(&self, tenant_id: Uuid, patient_id: Uuid) -> Result<Vec<MedicationSummary>>;
}
// 轻量 DTO 定义在 erp-core只含 Tool 需要的字段,非完整 Entity
pub struct VitalSummary { pub indicator_type: String, pub value: f64, pub unit: String, pub recorded_at: DateTime }
pub struct LabReportSummary { pub id: Uuid, pub report_date: DateTime, pub items: Vec<LabItemSummary> }
pub struct LabItemSummary { pub indicator_name: String, pub value: f64, pub unit: String, pub is_abnormal: bool }
pub struct PatientProfile { pub name: String, pub age: i32, pub gender: String, pub conditions: Vec<String> }
pub struct AppointmentSummary { pub id: Uuid, pub department: String, pub doctor_name: String, pub scheduled_at: DateTime, pub status: String }
pub struct MedicationSummary { pub name: String, pub dosage: String, pub frequency: String }
```
**注册机制**`AppState` 中持有 `Arc<dyn HealthDataQuery>`erp-health 模块注册时注入实现。Agent Tool 通过 `ToolContext` 访问。
**对 erp-ai 模块中已有的分析能力**analysis_service、copilot_engine 等),无需跨 crate直接在 erp-ai 内部调用。
### 3.3 DisplayHint 定义
`DisplayHint` 用于给前端提供渲染提示,让 Tool 返回的数据不仅作为 LLM 上下文,还能以富消息形式展示给用户:
```rust
pub enum DisplayHint {
/// 体征数据卡片 — 前端渲染为小图表
VitalCard { indicator_type: String, values: Vec<(DateTime, f64)>, unit: String },
/// 化验报告摘要卡片
LabReportCard { report_date: DateTime, abnormal_count: usize },
/// 操作确认 — 前端渲染为确认按钮
ActionConfirm { action_type: String, summary: String, confirm_payload: serde_json::Value },
/// 风险等级提示
RiskAlert { level: String, message: String },
/// 纯文本(默认)
Text,
}
```
### 3.4 Tool 清单
#### 第一类:数据查询(只读,从 erp-health 取数据)
@@ -131,6 +181,19 @@ pub struct ToolResult {
| `recommend_services` | 根据症状/需求推荐科室或服务 | 新增,基于规则 + 知识库 |
| `check_alert_rules` | 检查是否触发告警阈值 | `local_rules_engine` + `ai_risk_threshold` |
### 3.6 权限码声明
现有权限码 `ai.chat.send` 保留用于发送消息。新增以下权限码:
| 权限码 | 说明 | 适用端点 |
|--------|------|----------|
| `ai.chat.session.list` | 查看会话列表 | `GET /ai/chat/sessions` |
| `ai.chat.session.manage` | 创建/关闭会话 | `POST/DELETE /ai/chat/sessions` |
| `ai.chat.session.history` | 查看会话消息历史 | `GET /ai/chat/sessions/{id}/messages` |
| `ai.chat.send` | 发送消息(触发 Agent | `POST /ai/chat/sessions/{id}/messages`(已存在) |
行动类 Tool`create_appointment``transfer_to_human`)不单独声明权限,由 Agent 内部根据用户角色判断。
#### 第四类:行动(写入操作,需更高权限)
| Tool 名称 | 功能 | 对接现有能力 |
@@ -259,6 +322,8 @@ cache_service → 分析类 Tool 内部复用
#### ai_chat_sessions — AI 会话表
> 与现有 `copilot_chat_logs` 表的关系:`copilot_chat_logs` 记录 Copilot 内部对话AI 分析引擎之间的通信),服务于风险洞察和自动分析。`ai_chat_sessions` / `ai_chat_messages` 记录用户与 AI 客服的面向用户对话。两者并存,数据不迁移。
```sql
CREATE TABLE ai_chat_sessions (
id UUID PRIMARY KEY,
@@ -315,7 +380,91 @@ CREATE TABLE ai_tool_call_logs (
);
```
### 5.3 API 设计
#### ai_user_profiles — 用户长期画像(长期记忆)
```sql
CREATE TABLE ai_user_profiles (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
user_id UUID NOT NULL,
preferences JSONB, -- 用户偏好(如:偏好简洁回复、关心血压问题)
health_interests TEXT[], -- 健康关注点(如:高血压、糖尿病)
frequent_topics TEXT[], -- 常见咨询主题
personality_summary TEXT, -- AI 生成的用户画像摘要
last_updated_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
version INTEGER NOT NULL DEFAULT 1,
UNIQUE(tenant_id, user_id)
);
```
每次会话结束时Agent 自动更新用户画像摘要。新会话开始时加载注入 System Prompt。
### 5.3 PII 脱敏规范
Tool 返回数据在传给 LLM 前必须脱敏。具体规则:
| 字段类型 | 脱敏方式 | 示例 |
|----------|----------|------|
| 患者姓名 | 保留姓氏 + 称呼 | "张爷爷"Agent 用称呼,不用真名) |
| 身份证号 | 不传给 LLM | Tool 层过滤,不出现在 ToolResult |
| 手机号 | 不传给 LLM | 同上 |
| 具体住址 | 不传给 LLM | 同上 |
| 出生日期 | 转为年龄 | "68 岁" |
| 医疗数据 | 正常传递 | 血压值、化验指标等不脱敏 |
脱敏在 `AgentTool::execute()` 实现中统一处理,每个 Tool 的返回值必须经过 `sanitize_for_llm()` 函数。
### 5.3 Provider Function Calling 适配
现有 `AiProvider` trait 的 `GenerateRequest` 不支持 tools/functions 参数,需要扩展:
```rust
// 扩展 GenerateRequest
pub struct GenerateRequest {
pub system_prompt: String,
pub messages: Vec<ChatMessage>, // 从单条 user_prompt 改为多轮消息
pub model: Option<String>,
pub temperature: Option<f32>,
pub max_tokens: Option<u32>,
pub tools: Option<Vec<ToolDefinition>>, // 新增
}
pub struct ChatMessage {
pub role: MessageRole, // User / Assistant / Tool
pub content: String,
pub tool_calls: Option<Vec<ToolCall>>, // assistant 消息中的 tool call
pub tool_call_id: Option<String>, // tool 消息的关联 ID
}
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub parameters: serde_json::Value, // JSON Schema
}
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: serde_json::Value,
}
// GenerateResponse 扩展
pub struct GenerateResponse {
pub content: Option<String>,
pub tool_calls: Option<Vec<ToolCall>>, // LLM 返回的 tool call
pub usage: Option<TokenUsage>,
}
```
**各 Provider 适配工作量**
- **Claude**Anthropic API 使用 `tool_use`/`tool_result` 内容块需重构消息构建和响应解析1 天)
- **OpenAI**:使用 `function``tool` 类型消息相对标准0.5 天)
- **Ollama**Function Calling 支持取决于模型,若不支持则降级为纯文本 Prompt 模式0.5 天)
### 5.4 API 设计
```
POST /api/v1/ai/chat/sessions — 创建会话
@@ -353,20 +502,24 @@ GET /api/v1/ai/chat/sessions/{id}/messages — 消息历史
## 7. 分阶段实施计划
### Phase 0基础设施3-4 天)
### Phase 0基础设施5-6 天)
> 目标Agent 核心循环跑通,能用一个 Tool 完成完整对话
| 任务 | 工作量 |
|------|--------|
| `AgentTool` trait + `ToolRegistry` + `ToolContext` | 0.5 天 |
| `AgentOrchestrator` ReAct 循环(含 Function Calling 消息格式 | 1 天 |
| 数据库迁移:`ai_chat_sessions` + `ai_chat_messages` + `ai_tool_call_logs` | 0.5 天 |
| `GenerateRequest` + `GenerateResponse` 扩展(支持 tools/functions | 1 天 |
| Claude Provider Function Calling 适配(消息构建 + 响应解析 | 1 天 |
| OpenAI + Ollama Provider 适配 | 1 天 |
| `AgentTool` trait + `ToolRegistry` + `ToolContext` + `DisplayHint` | 0.5 天 |
| `AgentOrchestrator` ReAct 循环 | 0.5 天 |
| erp-core `HealthDataQuery` trait 定义 + erp-health 实现 | 1 天 |
| 数据库迁移:`ai_chat_sessions` + `ai_chat_messages` + `ai_tool_call_logs` + `ai_user_profiles` | 0.5 天 |
| 实现 1 个 Tool`query_patient_vitals`(验证端到端链路) | 0.5 天 |
| 改造 `chat_handler`:接入 Orchestrator替换原有简单逻辑 | 0.5 天 |
| 单元测试 + 集成测试 | 0.5 天 |
**交付标准**Postman 调用 `/ai/chat`Agent 能查到患者体征数据并自然回复。
**交付标准**Postman 调用 `/api/v1/ai/chat/sessions/{id}/messages`Agent 能查到患者体征数据并自然回复。
### Phase 1Tool 扩展 + 策略 Prompt5-7 天)
@@ -383,7 +536,7 @@ GET /api/v1/ai/chat/sessions/{id}/messages — 消息历史
**交付标准**:模拟 5 种典型场景(安抚/科普/推荐/预警/引导到院Agent 均能自主选择正确策略和 Tool。
### Phase 2前端升级 + 流式输出(5-7 天)
### Phase 2前端升级 + 流式输出(7-9 天)
> 目标:小程序 + Web 都有完整 AI 客服体验
@@ -391,10 +544,12 @@ GET /api/v1/ai/chat/sessions/{id}/messages — 消息历史
|------|--------|
| 后端:会话 CRUD API创建/列表/历史消息) | 1 天 |
| 后端Agent 最终回复走 SSE 流式输出 | 1 天 |
| 小程序SSE 兼容层Taro 原生不支持 SSE需用 `requestTask` 或轮询适配) | 1 天 |
| 小程序:会话列表页 + 消息历史页 + 富消息渲染 | 2 天 |
| Web同上,复用 API 模块 | 1.5 天 |
| WebAI 客服页面从零构建(会话列表 + 聊天界面 + 富消息) | 2 天 |
| 数据卡片渲染(体征趋势小图表) | 1 天 |
| 前端迁移:本地 Storage → DB 持久化 | 0.5 天 |
| 前端迁移:本地 Storage → DB 持久化 + 旧数据迁移脚本 | 0.5 天 |
| 端到端测试 | 0.5 天 |
**交付标准**:小程序打开 AI 客服,能自然对话,能看到数据卡片,能看到流式输出。
@@ -415,18 +570,36 @@ GET /api/v1/ai/chat/sessions/{id}/messages — 消息历史
### 总工期
```
Phase 0 ████████ (3-4天)
Phase 0 ████████████████ (5-6天)
Phase 1 ████████████████ (5-7天)
Phase 2 ████████████████ (5-7天)
Phase 2 ████████████████████ (7-9天)
Phase 3 ██████████ (3-5天)
────────────────────────
合计 16-23
合计 20-27
```
每个 Phase 结束后都有可演示的交付物。
---
## 9. 故障处理与降级
| 故障场景 | 用户看到什么 | 处理方式 |
|----------|-------------|----------|
| 所有 Provider 不可用 | "小华暂时无法回复,请稍后再试" | 返回固定降级消息,记录到 usage_service |
| Agent 循环超时60s | 已生成的部分回复 + "回复被中断,请重新提问" | SSE 断流 + 超时日志 |
| 单个 Tool 执行超时10s | Agent 跳过该 Tool 继续推理 | ToolResult 返回错误摘要Agent 可选择其他路径 |
| Ollama 不支持 Function Calling | 自动降级为纯文本 Prompt 模式 | Provider 层检测能力,无 Function Calling 时将 Tool 描述注入 System Prompt |
| LLM 返回无效 Tool Call | "抱歉,我刚才思考有误,请再说一次" | Orchestrator 捕获解析错误,返回重试提示 |
---
## 10. 风险与缓解
每个 Phase 结束后都有可演示的交付物。
---
## 8. 风险与缓解
| 风险 | 概率 | 影响 | 缓解措施 |