docs(iit): Add IIT Manager Agent V2.9 development plan with multi-agent architecture
Features: - Add V2.9 enhancements: Cron Skill, User Profiling, Feedback Loop, Multi-Intent Handling - Create modular development plan documents (database, engines, services, memory, tasks) - Add V2.5/V2.6/V2.8/V2.9 design documents for architecture evolution - Add system design white papers and implementation guides Architecture: - Dual-Brain Architecture (SOP + ReAct engines) - Three-layer memory system (Flow Log, Hot Memory, History Book) - ProfilerService for personalized responses - SchedulerService with Cron Skill support Also includes: - Frontend nginx config updates - Backend test scripts for WeChat signature - Database backup files Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
# **IIT Manager Agent V2.7:基于“双层文本记忆”的极简架构**
|
||||
|
||||
**版本:** V2.7 (Clawbot Memory Edition)
|
||||
|
||||
**日期:** 2026-02-05
|
||||
|
||||
**核心变更:** 废弃复杂的 pgvector 语义检索,采用 Clawbot 式的“流水账 \+ 沉淀物”双层文本记忆。
|
||||
|
||||
**优势:** 记忆可读、可改、透明、零黑盒。
|
||||
|
||||
## **1\. 记忆架构重构 (Memory Refactoring)**
|
||||
|
||||
我们将记忆分为两层,完全模拟人类大脑运作,且摒弃不可解释的向量数据。
|
||||
|
||||
graph TD
|
||||
subgraph "层级 1: 流水账 (Daily Stream)"
|
||||
Log\[Conversation History\]
|
||||
Note\[Raw Actions\]
|
||||
desc1\[特点: Append-Only, 巨量, 易遗忘\]
|
||||
end
|
||||
|
||||
subgraph "层级 2: 沉淀物 (The Sediment)"
|
||||
ProjectMem\[项目级 MEMORY.md\]
|
||||
PatientMem\[患者级 MEMORY.md\]
|
||||
UserMem\[用户级 MEMORY.md\]
|
||||
desc2\[特点: 精炼, 结构化, 人类可编辑\]
|
||||
end
|
||||
|
||||
Log \--\>|每晚 Cron 归纳 (Gardening)| ProjectMem
|
||||
Admin\[管理员/医生\] \--\>|手动修正/编辑| ProjectMem
|
||||
|
||||
ProjectMem \--\>|注入 System Prompt| LLM
|
||||
|
||||
## **2\. 数据库设计变更 (Schema Changes)**
|
||||
|
||||
### **2.1 新增 iit\_memory\_files 表**
|
||||
|
||||
我们不用磁盘文件,用数据库表来模拟文件,方便 Web 端管理。
|
||||
|
||||
model IitMemoryFile {
|
||||
id String @id @default(uuid())
|
||||
projectId String
|
||||
targetType String // PROJECT | PATIENT | USER
|
||||
targetId String // 对应的 ID (如 projectId, recordId, userId)
|
||||
|
||||
// 核心:这就是那个 MEMORY.md 的内容
|
||||
content String @db.Text
|
||||
|
||||
lastUpdatedBy String // 'system\_daily\_job' 或 'user\_001'
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique(\[projectId, targetType, targetId\])
|
||||
@@map("iit\_memory\_files")
|
||||
@@schema("iit\_schema")
|
||||
}
|
||||
|
||||
### **2.2 废弃计划**
|
||||
|
||||
* **废弃**:iit\_conversation\_history 中的 embedding 字段。
|
||||
* **废弃**:Phase 5 中的向量检索服务。
|
||||
|
||||
## **3\. 核心服务实现:MemoryService (V2.7)**
|
||||
|
||||
### **3.1 读取记忆 (Read)**
|
||||
|
||||
在 ChatService 启动对话前,简单的把文本读出来拼接到 Prompt 里。
|
||||
|
||||
// backend/src/modules/iit-manager/services/MemoryService.ts
|
||||
|
||||
export class MemoryService {
|
||||
|
||||
async getContext(projectId: string, userId: string, recordId?: string) {
|
||||
// 1\. 获取项目级记忆 (规则、偏好)
|
||||
const projectMem \= await this.loadMemory('PROJECT', projectId);
|
||||
|
||||
// 2\. 获取用户级记忆 (习惯)
|
||||
const userMem \= await this.loadMemory('USER', userId);
|
||||
|
||||
// 3\. (可选) 获取患者级记忆
|
||||
const patientMem \= recordId ? await this.loadMemory('PATIENT', recordId) : '';
|
||||
|
||||
return \`
|
||||
\=== 长期记忆 (Long-Term Memory) \===
|
||||
\[项目背景\]:
|
||||
${projectMem}
|
||||
|
||||
\[用户偏好\]:
|
||||
${userMem}
|
||||
|
||||
\[患者备注\]:
|
||||
${patientMem}
|
||||
\================================
|
||||
\`;
|
||||
}
|
||||
|
||||
private async loadMemory(type: string, id: string) {
|
||||
const mem \= await prisma.iitMemoryFile.findUnique({
|
||||
where: { projectId\_targetType\_targetId: { ... } }
|
||||
});
|
||||
return mem?.content || '';
|
||||
}
|
||||
}
|
||||
|
||||
### **3.2 记忆维护 (Gardening \- The Clawbot Way)**
|
||||
|
||||
这是最精彩的部分。我们不实时更新长期记忆(太乱),而是每天晚上让 AI 当“园丁”,修剪记忆。
|
||||
|
||||
**任务:Daily Memory Consolidation**
|
||||
|
||||
* **触发**:每天凌晨 2 点。
|
||||
* **输入**:当天的对话流水 (iit\_conversation\_history) \+ 旧的 MEMORY.md。
|
||||
* **Prompt**:"这是今天的对话流水。请提取其中新的重要事实(如用户偏好、新的关键决策、患者的新状态),合并到旧的记忆文件中。保持 Markdown 格式。如果没有新信息,保持不变。"
|
||||
* **输出**:新的 Markdown 文本,覆盖数据库。
|
||||
|
||||
// SchedulerService.ts
|
||||
|
||||
async runDailyMemoryConsolidation() {
|
||||
const logs \= await this.getDailyLogs();
|
||||
const oldMem \= await this.memoryService.loadMemory('PROJECT', projectId);
|
||||
|
||||
const newMem \= await this.llm.chat(\[
|
||||
{ role: 'system', content: '你是记忆整理员...' },
|
||||
{ role: 'user', content: \`旧记忆:\\n${oldMem}\\n\\n今日流水:\\n${logs}\` }
|
||||
\]);
|
||||
|
||||
await this.memoryService.saveMemory('PROJECT', projectId, newMem);
|
||||
}
|
||||
|
||||
## **4\. 调整后的开发计划 (Phase 5 重构)**
|
||||
|
||||
我们将原计划 Week 5 的“向量检索”替换为“文本记忆系统”。
|
||||
|
||||
### **Week 5:Clawbot 式记忆系统**
|
||||
|
||||
| 时间 | 任务 | 说明 |
|
||||
| :---- | :---- | :---- |
|
||||
| **Day 24** | **记忆表结构与 CRUD** | 创建 iit\_memory\_files 表,实现读写 API。 |
|
||||
| **Day 25** | **记忆注入 Prompt** | 修改 ChatService,在 System Prompt 头部注入 Markdown 内容。 |
|
||||
| **Day 26** | **记忆整理 Worker** | 实现“每日记忆整理”的 Cron Job (pg-boss)。 |
|
||||
| **Day 27** | **记忆编辑 UI** | 在 Admin 后台增加一个简单的文本框,允许人工修改 MEMORY.md。 |
|
||||
|
||||
## **5\. 为什么这比向量库好?**
|
||||
|
||||
1. **完全透明 (White Box)**:
|
||||
* 向量库出了问题(比如 AI 突然变傻),你只能重新 Embed,甚至不知道哪条数据坏了。
|
||||
* 文本记忆出了问题,你打开 Admin 界面,看到一行:“用户不喜欢红烧肉”,你把它删了,问题立刻解决。**这对医疗系统排查问题是无价的。**
|
||||
2. **Token 可控**:
|
||||
* 向量库如果不加限制,可能召回 5000 字的无关内容。
|
||||
* 文本记忆由“每日整理”压缩过,通常只有几百字的核心干货,Token 消耗极低。
|
||||
3. **技术栈简化**:
|
||||
* 不需要 pgvector 插件(虽然你们装了,但不用也没关系)。
|
||||
* 不需要 Embedding 模型调用。
|
||||
* 纯字符串处理,Node.js 最擅长。
|
||||
|
||||
## **6\. 结论**
|
||||
|
||||
这个建议**极具战略价值**。它把一个“高科技难题”(如何做语义检索)变成了一个“管理学问题”(如何整理笔记)。
|
||||
|
||||
**执行建议:**
|
||||
|
||||
全盘接受这个建议。在 V2.6 架构中,**移除 Embedding 层,替换为 Memory File 层。** 这会让你们的系统在面对 PI 的刁钻问题时,显得更有“记性”,同时让你们的运维工作变得无比轻松。
|
||||
@@ -0,0 +1,134 @@
|
||||
# **IIT Manager Agent V2.8:基于“周报卷叠”的终极记忆架构**
|
||||
|
||||
**版本:** V2.8 (The Chronicle Edition)
|
||||
|
||||
**日期:** 2026-02-05
|
||||
|
||||
**核心变更:** 采纳“以周报代替向量检索”的建议。利用 LLM 的长窗口能力,通过周期性压缩数据,实现全生命周期的可读记忆。
|
||||
|
||||
**适用场景:** 1-3 年周期的临床研究项目。
|
||||
|
||||
## **1\. 核心理念:把“大数据”变成“厚书”**
|
||||
|
||||
我们不再试图在海量碎片数据中大海捞针(Vector Search),而是把数据写成一本\*\*“编年史”\*\*。
|
||||
|
||||
* **每日**:忠实记录流水账。
|
||||
* **每周**:LLM 阅读本周流水账,写一页“历史书”(周报)。
|
||||
* **查询时**:LLM 直接阅读整本“历史书”。
|
||||
|
||||
### **优势分析**
|
||||
|
||||
| 维度 | 向量检索 (V2.6/2.7) | 周报卷叠 (V2.8) |
|
||||
| :---- | :---- | :---- |
|
||||
| **准确性** | 模糊匹配,容易漏掉关键细节 | **全量阅读**,拥有上帝视角,极准 |
|
||||
| **可解释性** | 黑盒向量,不知道 AI 看了啥 | **白盒周报**,医生可以随时翻阅、修正周报 |
|
||||
| **技术难度** | 高 (Embedding, Vector DB) | **低** (Cron Job \+ LLM Summary) |
|
||||
| **成本** | 检索便宜,但索引维护贵 | 生成周报耗 Token,但查询极其高效 |
|
||||
|
||||
## **2\. 数据库设计 (Schema)**
|
||||
|
||||
### **2.1 新增 iit\_weekly\_reports 表**
|
||||
|
||||
model IitWeeklyReport {
|
||||
id String @id @default(uuid())
|
||||
projectId String
|
||||
|
||||
weekNumber Int // 例如: 202605 (2026年第5周)
|
||||
startDate DateTime
|
||||
endDate DateTime
|
||||
|
||||
// 核心:LLM 生成的高浓缩总结
|
||||
// 包含:入组进度、发生的AE、关键沟通结论
|
||||
summary String @db.Text
|
||||
|
||||
// 结构化数据 (可选,用于画图)
|
||||
stats Json? // { "enrolled": 5, "queries": 2 }
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique(\[projectId, weekNumber\])
|
||||
@@map("iit\_weekly\_reports")
|
||||
@@schema("iit\_schema")
|
||||
}
|
||||
|
||||
## **3\. 核心流程实现**
|
||||
|
||||
### **3.1 写入端:每周一凌晨的“编史官” (Cron Job)**
|
||||
|
||||
**SchedulerService.ts:**
|
||||
|
||||
// 每周一凌晨 02:00 执行
|
||||
async generateWeeklyMemory(projectId: string) {
|
||||
// 1\. 获取上周所有的原始对话 & 操作日志
|
||||
const logs \= await this.rawLogs.get({
|
||||
projectId,
|
||||
from: lastMonday,
|
||||
to: thisSunday
|
||||
});
|
||||
|
||||
// 2\. 调用 LLM 进行“有损压缩”
|
||||
const prompt \= \`
|
||||
你是一个临床项目经理。请阅读上周的项目流水账,生成一份【周报记忆块】。
|
||||
要求:
|
||||
1\. 忽略闲聊和无关信息。
|
||||
2\. 重点记录:入组人数变化、新增不良事件(AE)、主要方案偏离、PI的关键决策。
|
||||
3\. 格式为 Markdown,字数控制在 500 字以内。
|
||||
|
||||
流水账数据:
|
||||
${JSON.stringify(logs)}
|
||||
\`;
|
||||
|
||||
const summary \= await this.llm.chat(prompt);
|
||||
|
||||
// 3\. 存入数据库 (成为历史书的一页)
|
||||
await prisma.iitWeeklyReport.create({
|
||||
data: { projectId, summary, ... }
|
||||
});
|
||||
}
|
||||
|
||||
### **3.2 读取端:查询时的“速读” (Context Injection)**
|
||||
|
||||
当意图识别发现用户在问“过去”或“趋势”时,直接把**所有周报**读出来。
|
||||
|
||||
**ChatService.ts:**
|
||||
|
||||
async buildHistoryContext(projectId: string): Promise\<string\> {
|
||||
// 1\. 取出该项目所有历史周报 (按时间正序)
|
||||
// 3年也就 150 条,Postgres 毫秒级返回
|
||||
const reports \= await prisma.iitWeeklyReport.findMany({
|
||||
where: { projectId },
|
||||
orderBy: { weekNumber: 'asc' },
|
||||
select: { weekNumber: true, summary: true }
|
||||
});
|
||||
|
||||
// 2\. 拼接成一本“书”
|
||||
// 格式:
|
||||
// \[Week 2026-01\]: 入组 2 人,无异常。
|
||||
// \[Week 2026-02\]: P003 发生 SAE,已上报。
|
||||
const chronicle \= reports.map(r \=\> \`\[Week ${r.weekNumber}\]: ${r.summary}\`).join('\\n');
|
||||
|
||||
return \`
|
||||
\=== 项目编年史 (Project Chronicle) \===
|
||||
${chronicle}
|
||||
\====================================
|
||||
\`;
|
||||
}
|
||||
|
||||
## **4\. 最终开发计划修正 (Phase 5\)**
|
||||
|
||||
我们将 Week 5 的任务彻底简化:
|
||||
|
||||
| 时间 | 任务 | 说明 |
|
||||
| :---- | :---- | :---- |
|
||||
| **Day 24** | **原始日志表** | 确保 iit\_conversation\_history 记录完整。 |
|
||||
| **Day 25** | **周报表结构** | 创建 iit\_weekly\_reports 表。 |
|
||||
| **Day 26** | **编史官 Worker** | 写一个 Cron Job,每周把 Log 压缩成 Report。 |
|
||||
| **Day 27** | **记忆注入** | 在回答 QA\_QUERY 类问题时,将历史周报注入 Context。 |
|
||||
|
||||
## **5\. 总结:为什么这是终极方案?**
|
||||
|
||||
1. **解决了“上下文遗忘”**:150 个周报拼接起来,刚好填满 DeepSeek 的上下文窗口。AI 可以看到**完整**的项目生命周期,这是 RAG 切片做不到的。
|
||||
2. **解决了“幻觉”**:周报是持久化的,医生可以去后台检查某一周的周报写得对不对。如果 AI 瞎写,医生可以手动修正。**修正后的周报就是新的真理。**
|
||||
3. **极简运维**:不需要维护向量库索引,不需要调优 Embedding 模型。就是简单的 SQL 查询和文本拼接。
|
||||
|
||||
**就用这个方案。这是目前为止最完美、最优雅的解决路径。**
|
||||
@@ -0,0 +1,83 @@
|
||||
# **IIT Manager Agent V2.8:记忆信息映射指南**
|
||||
|
||||
**核心逻辑:** \> \* **即时/全局信息** ![][image1] 存入 **Hot Memory (Markdown)** ![][image1] 每次对话都带。
|
||||
|
||||
* **历史/阶段信息** ![][image1] 存入 **Weekly Reports (Database)** ![][image1] 查历史时全量读取。
|
||||
|
||||
## **1\. 信息存储位置映射表**
|
||||
|
||||
| 信息类型 | 存储位置 | 存储形式 | 举例 | 谁来维护? |
|
||||
| :---- | :---- | :---- | :---- | :---- |
|
||||
| **用户偏好** (User Preferences) | **Hot Memory** (Level 3\) | iit\_project\_memory 中的 Markdown 列表 | "PI 喜欢简报格式" "不要在周末发消息" | **人工编辑** (Admin) 或 **每日 AI 提炼** |
|
||||
| **经常出现的上下文** (Frequent Context) | **Hot Memory** (Level 3\) | iit\_project\_memory 中的 Markdown 文本 | "当前处于入组冲刺阶段" "P001 是重点关注对象" | **每日 AI 提炼** (Cron Job) |
|
||||
| **关键决策** (Key Decisions) | **Weekly Reports** (Level 2\) | iit\_weekly\_reports 中的 summary 字段 | "2026-02-01 决定放宽入排标准" "确认 P003 不良事件不相关" | **每周 AI 归档** (Scheduler) |
|
||||
| **踩过的坑/经验教训** (Lessons Learned) | **双重存储** | **Hot** (如果是永久教训) **Weekly** (如果是历史记录) | **Hot**: "严禁直接调用 API 修改数据" **Weekly**: "W5 因参数错误导致同步失败" | **人工** (定规矩) **AI** (记流水) |
|
||||
|
||||
## **2\. 详细存储机制**
|
||||
|
||||
### **2.1 用户偏好 & 经常出现的上下文 \-\> Hot Memory (Markdown)**
|
||||
|
||||
这部分信息需要\*\*“时刻生效”\*\*,所以必须存成轻量级的 Markdown,每次对话都注入 System Prompt。
|
||||
|
||||
* **存储表**:iit\_project\_memory (config 字段)
|
||||
* **内容示例**:
|
||||
\# User Preferences
|
||||
\- \[PI\]: 汇报时只看数据,不要废话。
|
||||
\- \[CRC\]: 下午 2 点后比较忙,尽量上午推任务。
|
||||
|
||||
\# Active Context
|
||||
\- 当前重点任务:清理 3 月份的 Query。
|
||||
\- 风险提示:P005 患者依从性差,需每日提醒。
|
||||
|
||||
* **更新机制**:
|
||||
1. **被动更新**:管理员在后台手动修改。
|
||||
2. **主动更新**:每天凌晨,AI 扫描昨日对话,如果发现用户说了“以后别...”,自动追加到这里。
|
||||
|
||||
### **2.2 关键决策 & 踩过的坑 \-\> Weekly Reports (编年史)**
|
||||
|
||||
这部分信息属于\*\*“项目历史”\*\*,不需要每次对话都挂在嘴边,但当用户问“回顾一下”时,需要能查到。
|
||||
|
||||
* **存储表**:iit\_weekly\_reports
|
||||
* **内容示例 (Week 2026-05)**:
|
||||
\[进度\]: 本周入组 3 人,累计 15 人。
|
||||
\[决策\]: 2月3日 PI 会议决定:暂停筛选 "间质性肺炎" 既往史患者。
|
||||
\[问题\]: 曾尝试自动录入化验单,但因 OCR 精度不足失败(踩坑记录),已回退为人工复核模式。
|
||||
|
||||
* **使用机制**:
|
||||
* 当用户问:“我们之前为什么暂停筛选肺炎患者?”
|
||||
* 意图识别判断为 QUERY\_HISTORY。
|
||||
* 系统一次性拉取过去所有周报 (150条记录),拼接成“编年史”,喂给 LLM 阅读并回答。
|
||||
|
||||
## **3\. 为什么这样设计?**
|
||||
|
||||
1. **偏好 (Preferences)** 必须是 **Hot** 的:
|
||||
* 如果存到周报里,AI 可能聊着聊着就忘了“老板不喜欢废话”这个规矩。只有放在 Hot Memory (System Prompt) 里,才能保证每一句回复都符合老板口味。
|
||||
2. **决策 (Decisions)** 必须是 **Time-Series** 的:
|
||||
* 决策往往有时间背景。存到周报里,天然带有时间戳(Week 5 做的决定)。这样 AI 才能回答“上个月做了什么决定”。
|
||||
3. **踩坑 (Lessons)** 需要 **人工干预**:
|
||||
* 如果只是 AI 自动总结,可能会漏。
|
||||
* V2.8 允许你在 Hot Memory 里手动写上一条:“【系统禁令】严禁在未授权情况下删除数据”。这条“人工植入的记忆”比任何 AI 总结都管用。
|
||||
|
||||
## **4\. 总结:信息流向图**
|
||||
|
||||
graph TD
|
||||
User\[用户输入\] \--\>|对话流| Raw\[原始日志\]
|
||||
|
||||
subgraph "实时影响 (Hot)"
|
||||
Raw \--\>|每日提炼| Prefs\[用户偏好\]
|
||||
Raw \--\>|每日提炼| Context\[高频上下文\]
|
||||
Admin\[人工干预\] \--\>|手动编辑| Prefs
|
||||
end
|
||||
|
||||
subgraph "历史归档 (History)"
|
||||
Raw \--\>|每周汇总| Decisions\[关键决策\]
|
||||
Raw \--\>|每周汇总| Lessons\[踩坑记录\]
|
||||
end
|
||||
|
||||
Prefs \--\>|注入| NextPrompt\[下一次对话\]
|
||||
Context \--\>|注入| NextPrompt
|
||||
|
||||
Decisions \-.-\>|按需查询| NextPrompt
|
||||
|
||||
|
||||
[image1]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAXCAYAAADpwXTaAAAAX0lEQVR4XmNgGAWjYCQAeXn50+hiZAOgYU/QxcgGcnJy2kA8HV2cbAB03SwgDkIXB0lIkomnAfF1oBHM1DBsERCfRzGMHCCPy5ukAmgETEAXJwvIUzFpMMpTM9GOcAAAmV0cRTlI2MMAAAAASUVORK5CYII=>
|
||||
@@ -0,0 +1,143 @@
|
||||
# **IIT Manager Agent V2.8:记忆检索与路由逻辑详解**
|
||||
|
||||
**核心机制:** 意图驱动的按需检索 (Intent-Driven Retrieval)
|
||||
|
||||
**目的:** 既保证 AI 懂当下(Hot),又保证 AI 懂历史(History),同时节省 Token。
|
||||
|
||||
## **1\. 记忆存储全景图 (Storage Map)**
|
||||
|
||||
在 V2.8 中,我们有三个核心存储位置,各司其职:
|
||||
|
||||
| 记忆类型 | 存储表 | 字段 | 内容特征 | 更新频率 |
|
||||
| :---- | :---- | :---- | :---- | :---- |
|
||||
| **1\. 流水账 (Raw Stream)** | iit\_conversation\_history | content (JSON) | 未经加工的原始对话。 | **实时** (每秒) |
|
||||
| **2\. 热记忆 (Hot Context)** | iit\_project\_memory | config (Markdown) | 用户偏好、当前状态、系统禁令。 | **每日** (AI 提炼) |
|
||||
| **3\. 历史书 (Weekly Reports)** | iit\_weekly\_reports | summary (Text) | 高度浓缩的周报、关键决策、踩坑记录。 | **每周** (AI 归档) |
|
||||
|
||||
## **2\. 调取顺序与逻辑 (Retrieval Logic)**
|
||||
|
||||
当 PI 发问时,系统并不是一股脑把所有记忆都塞进去,而是分三步走:
|
||||
|
||||
### **第一步:无条件注入 (Always On)**
|
||||
|
||||
**无论 PI 问什么,必须先注入“热记忆”。**
|
||||
|
||||
* **来源**:iit\_project\_memory
|
||||
* **内容**:用户偏好(如“PI 喜欢简报”)、当前项目状态。
|
||||
* **理由**:这是 Agent 的“人设”和“底线”,一刻也不能忘。
|
||||
|
||||
### **第二步:意图识别 (Intent Detection)**
|
||||
|
||||
系统分析 PI 的问题,判断他想问“现在”还是“过去”。
|
||||
|
||||
* **情况 A:问现在/执行任务** (如 "查下 P001", "生成本周周报")
|
||||
* **动作**:**不调取** 历史书。
|
||||
* **理由**:解决当下问题不需要翻阅 3 年前的老黄历,避免干扰 AI。
|
||||
* **Context** \= Hot Memory \+ Current Task Data
|
||||
* **情况 B:问历史/趋势/回顾** (如 "回顾下去年的入组情况", "我们为什么暂停了筛选?")
|
||||
* **动作**:**全量调取** 历史书 (iit\_weekly\_reports)。
|
||||
* **理由**:需要上帝视角。
|
||||
* **Context** \= Hot Memory \+ All Weekly Reports
|
||||
|
||||
### **第三步:上下文组装 (Prompt Assembly)**
|
||||
|
||||
最终发给 LLM 的 Prompt 是这样组装的:
|
||||
|
||||
\[System Prompt\]
|
||||
你是一个临床研究助手...
|
||||
|
||||
\[Hot Memory\] (来自 iit\_project\_memory)
|
||||
\- 用户偏好: 简洁、数据驱动
|
||||
\- 当前状态: 入组阶段
|
||||
|
||||
\[History Context\] (仅在情况 B 下注入)
|
||||
Week 1: 启动项目...
|
||||
Week 2: 发现 P001 不良事件...
|
||||
...
|
||||
Week 50: 决定放宽标准...
|
||||
|
||||
\[User Question\]
|
||||
我们之前为什么暂停筛选?
|
||||
|
||||
## **3\. 实战场景演示 (Scenario Walkthrough)**
|
||||
|
||||
### **场景 1:PI 问 "P001 入组了吗?"**
|
||||
|
||||
1. **加载 Hot Memory**:获取偏好(简洁回复)。
|
||||
2. **意图识别**:QUERY\_DATA (查数据)。
|
||||
3. **决策**:**不需要** 查历史书。
|
||||
4. **行动**:调用 read\_clinical\_data 工具去 REDCap 查实时数据。
|
||||
5. **回答**:"已入组,时间是 2026-02-05。"
|
||||
|
||||
### **场景 2:PI 问 "最近入组太慢了,我们之前有没有讨论过怎么解决?"**
|
||||
|
||||
1. **加载 Hot Memory**:获取偏好。
|
||||
2. **意图识别**:QUERY\_HISTORY (查历史决策)。
|
||||
3. **决策**:**需要** 查历史书。
|
||||
4. **行动**:
|
||||
* 从 iit\_weekly\_reports 拉取过去 20 周的 summary。
|
||||
* 拼接成 3000 字的文本。
|
||||
* 喂给 LLM。
|
||||
5. **LLM 思考**:阅读周报,发现 Week 12 记录了“增加受试者交通补贴”的决策,Week 15 记录了“在门诊增派 CRC”的决策。
|
||||
6. **回答**:"我们曾在第 12 周尝试增加补贴,第 15 周增派了 CRC,当时效果有短暂提升..."
|
||||
|
||||
### **场景 3:PI 问 "我上次跟你说的那个只要看结果的规矩,你记得吗?"**
|
||||
|
||||
1. **加载 Hot Memory**:获取偏好。
|
||||
2. **AI 自检**:在 Hot Memory 里看到了 \- \[PI\]: 只要看结果 这条记录。
|
||||
3. **回答**:"记得的,教授。您要求汇报时只列数据结论,不要冗长的过程描述。我会严格遵守。"
|
||||
* *注意:这个问题不需要查周报,也不需要查流水账,直接看 Hot Memory 就行。*
|
||||
|
||||
## **4\. 总结:给开发者的伪代码**
|
||||
|
||||
// ChatService.ts
|
||||
|
||||
async handleMessage(userId, message) {
|
||||
// 1\. 总是加载热记忆 (Hot)
|
||||
const hotMem \= await prisma.iitProjectMemory.findUnique(...);
|
||||
|
||||
// 2\. 意图识别
|
||||
const intent \= await this.intentService.detect(message);
|
||||
|
||||
let historyContext \= "";
|
||||
|
||||
// 3\. 按需加载历史书 (History)
|
||||
if (intent.type \=== 'QUERY\_HISTORY' || intent.type \=== 'ANALYZE\_TREND') {
|
||||
const reports \= await prisma.iitWeeklyReport.findMany({
|
||||
orderBy: { weekNumber: 'asc' }
|
||||
});
|
||||
historyContext \= reports.map(r \=\> \`\[W${r.weekNumber}\]: ${r.summary}\`).join('\\n');
|
||||
}
|
||||
|
||||
// 4\. 组装最终 Prompt
|
||||
const finalPrompt \= \`
|
||||
${hotMem.content}
|
||||
${historyContext}
|
||||
User: ${message}
|
||||
\`;
|
||||
|
||||
// 5\. 调用 LLM
|
||||
return await this.llm.chat(finalPrompt);
|
||||
}
|
||||
|
||||
**记住这个口诀:**
|
||||
|
||||
**“偏好时刻带,历史按需查,流水账只用来生成周报。”**
|
||||
|
||||
## **5\. 复杂度控制与防失控指南 (Safety Guardrails)**
|
||||
|
||||
为了防止系统变得“复杂不可控”,请严格遵守以下开发军规:
|
||||
|
||||
### **5.1 容量限制 (The Cap)**
|
||||
|
||||
* **Hot Memory**: 限制在 **2000 Tokens** 以内。如果超标,触发 MemoryPruningJob (记忆修剪任务),让 AI 自动总结或通知管理员手动删除。
|
||||
* **History Book**: 每次加载不超过 **50 周** 的周报。如果项目运行了 10 年,只加载最近 1 年的周报,或者先让 AI 生成“年度总结”。
|
||||
|
||||
### **5.2 隔离原则 (Isolation)**
|
||||
|
||||
* **任务隔离**:执行 QC\_TASK (质控) 时,**严禁**加载 History Context。质控必须基于当下的事实,不能受历史干扰。
|
||||
* **数据隔离**:LLM 永远没有权限直接读取 iit\_conversation\_history 表。这条物理隔绝保证了 Agent 永远不会被海量噪音淹没。
|
||||
|
||||
### **5.3 人工介入 (The Kill Switch)**
|
||||
|
||||
* **可读可改**:后台必须提供 Hot Memory 的编辑器。如果 Agent 记住了错误的规则(例如“不需要查年龄”),医生可以直接进去删掉那一行 Markdown。**这是系统失控时的“急停按钮”。**
|
||||
@@ -0,0 +1,210 @@
|
||||
# **IIT Manager Agent V2.9 补充设计:主动性与用户画像增强**
|
||||
|
||||
**目标:** 响应智能化评估中的 P0 需求,补齐“主动服务”和“个性化”短板,并增强复杂任务处理能力。
|
||||
|
||||
**原则:** 坚持 **Postgres-Only**,复用现有的 **Skill (JSON)** 和 **ToolsService**。
|
||||
|
||||
## **1\. 主动提醒机制设计 (The Proactive Engine)**
|
||||
|
||||
我们不需要写死一堆 cron 脚本,而是将“主动提醒”抽象为一种特殊的 **Skill**。
|
||||
|
||||
### **1.1 架构复用逻辑**
|
||||
|
||||
* **触发源**:pg-boss 的定时任务 (Scheduler)。
|
||||
* **规则载体**:iit\_skills 表(新增 trigger\_type: 'cron')。
|
||||
* **执行者**:QcEngineService (复用现有的逻辑)。
|
||||
|
||||
### **1.2 数据库 Schema 扩展**
|
||||
|
||||
在 iit\_skills 表中增加调度字段:
|
||||
|
||||
model IitSkill {
|
||||
// ... 原有字段
|
||||
triggerType String @default("webhook") // 'webhook' | 'cron' | 'event'
|
||||
cronSchedule String? // 例如: "0 9 \* \* \*" (每天9点)
|
||||
|
||||
// 核心配置 (JSON)
|
||||
config Json
|
||||
}
|
||||
|
||||
### **1.3 Skill 配置示例:访视提醒**
|
||||
|
||||
这是一个“每天早上9点检查即将到期访视”的技能配置。
|
||||
|
||||
{
|
||||
"name": "即将到期访视提醒",
|
||||
"description": "扫描未来3天内需要访视的患者",
|
||||
|
||||
// 核心:复用 Engine A 的逻辑来筛选数据
|
||||
"hard\_rules": \[
|
||||
// 这里我们稍微扩展一下 hard\_rules 的能力,支持 SQL-like 的筛选
|
||||
// 或者直接调用工具获取列表
|
||||
\],
|
||||
|
||||
// 核心:复用 Engine B (LLM) 来生成人性化通知
|
||||
"soft\_instructions": \[
|
||||
{
|
||||
"instruction": "请调用 \`get\_upcoming\_visits(days=3)\` 工具。如果有数据,请根据【用户偏好】生成一条提醒消息。如果没有数据,什么都不做。",
|
||||
"tools": \["get\_upcoming\_visits", "send\_wechat\_msg"\]
|
||||
}
|
||||
\]
|
||||
}
|
||||
|
||||
### **1.4 代码实现 (SchedulerService.ts)**
|
||||
|
||||
// SchedulerService.ts
|
||||
|
||||
async initCronJobs() {
|
||||
// 1\. 从数据库加载所有 triggerType \= 'cron' 的 Skill
|
||||
const cronSkills \= await prisma.iitSkill.findMany({
|
||||
where: { triggerType: 'cron', isActive: true }
|
||||
});
|
||||
|
||||
for (const skill of cronSkills) {
|
||||
// 2\. 注册到 pg-boss
|
||||
await this.boss.schedule(
|
||||
\`run-skill-${skill.id}\`,
|
||||
skill.cronSchedule,
|
||||
{ skillId: skill.id }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Worker 处理逻辑
|
||||
async handleCronSkill(job) {
|
||||
const { skillId } \= job.data;
|
||||
const skill \= await prisma.iitSkill.findUnique({ where: { id: skillId } });
|
||||
|
||||
// 3\. 复用现有的引擎!
|
||||
// 就像处理 Webhook 一样处理 Cron,架构极其统一
|
||||
await this.qcEngine.runSoftAgent(
|
||||
skill.config.soft\_instructions\[0\].instruction,
|
||||
{ source: 'cron\_job' } // Context
|
||||
);
|
||||
}
|
||||
|
||||
**效果:** 你只需要在后台配一个 JSON,Agent 就会每天早上 9 点醒来,查数据,然后发微信:“张医生,P005 明天该来复查了。”
|
||||
|
||||
## **2\. 用户画像增强 (User Profiling)**
|
||||
|
||||
我们已经在 V2.8 中设计了 iit\_project\_memory (Hot Memory),现在只需要把“画像”结构化。
|
||||
|
||||
### **2.1 存储设计**
|
||||
|
||||
在 iit\_user\_preferences 表中,我们将 preferences 字段细分为两类:
|
||||
|
||||
1. **显性偏好 (Explicit)**:用户明确说的(“我要简报”)。
|
||||
2. **隐性画像 (Implicit)**:系统分析出来的(“他是 PI,关注宏观数据”)。
|
||||
|
||||
// preferences 字段内容示例
|
||||
{
|
||||
"communication\_style": "concise", // 简练 | 详尽
|
||||
"role\_tag": "PI", // PI | CRC | CRA
|
||||
"focus\_areas": \["AE", "Enrollment"\], // 关注点
|
||||
"notification\_time": "08:30", // 接收时间
|
||||
"feedback\_history": { // 简单的反馈记录
|
||||
"thumbs\_up": 12,
|
||||
"thumbs\_down": 1
|
||||
}
|
||||
}
|
||||
|
||||
### **2.2 自动画像更新 (The Profiler)**
|
||||
|
||||
我们在 ChatService 结束一次对话后,异步触发一个微型任务:**“画像侧写”**。
|
||||
|
||||
// ProfilerService.ts
|
||||
|
||||
async updateProfile(userId: string, lastConversation: any\[\]) {
|
||||
const currentProfile \= await this.getProfile(userId);
|
||||
|
||||
// 让 LLM 当心理分析师
|
||||
const prompt \= \`
|
||||
基于刚刚的对话,更新用户画像。
|
||||
对话:${JSON.stringify(lastConversation)}
|
||||
旧画像:${JSON.stringify(currentProfile)}
|
||||
|
||||
提取规则:
|
||||
1\. 如果用户抱怨"太长了",将 style 设为 concise。
|
||||
2\. 如果用户问了"安全性",将 AE 加入 focus\_areas。
|
||||
|
||||
返回新的 JSON。
|
||||
\`;
|
||||
|
||||
const newProfile \= await this.llm.chat(prompt);
|
||||
await this.saveProfile(userId, newProfile);
|
||||
}
|
||||
|
||||
## **3\. 反馈循环 (Feedback Loop \- Lite)**
|
||||
|
||||
不要做复杂的强化学习(RLHF),只做简单的\*\*“点赞/点踩”\*\*。
|
||||
|
||||
### **3.1 交互设计**
|
||||
|
||||
在 Agent 回复的每条消息下面(如果是小程序/网页),加两个小按钮:👍 / 👎。
|
||||
|
||||
### **3.2 逻辑闭环**
|
||||
|
||||
1. **用户点踩 (👎)**。
|
||||
2. **前端**:弹窗询问“哪里不好?”(选项:太啰嗦、不准确、没听懂)。
|
||||
3. **后端**:
|
||||
* 将这条 Negative Feedback 写入 iit\_conversation\_history。
|
||||
* **关键动作**:触发一次 Hot Memory 更新。
|
||||
* **Prompt**:"用户对刚才的回答点了踩,原因是'太啰嗦'。请在 Hot Memory 中记下一条禁令:\[针对该用户,严禁输出超过 100 字的废话\]。"
|
||||
|
||||
**结果**:Agent 下次遇到这个用户,真的会改。这就是最真实的“智能感”。
|
||||
|
||||
## **4\. 多意图与任务规划增强 (Multi-Intent Handling)**
|
||||
|
||||
针对“先做 A,再做 B”的复杂指令(如:“查一下 P001 的状态,然后通知张医生”),我们不需要引入重型 Planner,而是通过 **ReAct \+ Prompt Engineering** 实现。
|
||||
|
||||
### **4.1 核心策略:ReAct 自然涌现**
|
||||
|
||||
**结论**:ReAct 引擎本身就是一个轻量级 Planner。
|
||||
|
||||
不需要额外的 Planner 模块,只需要在 **System Prompt** 中显式教导 Agent 如何拆解任务。
|
||||
|
||||
### **4.2 Prompt 增强设计**
|
||||
|
||||
在 backend/src/modules/iit-manager/engines/ReActEngine.ts 的系统提示词中加入以下**思维链(Chain of Thought)指令**:
|
||||
|
||||
const REACT\_SYSTEM\_PROMPT \= \`
|
||||
你是一个临床研究助手。你可以使用工具来回答问题。
|
||||
|
||||
核心原则:
|
||||
1\. \*\*任务拆解\*\*:如果用户请求包含多个步骤(例如"先...再...","查完...发给..."),请务必分步执行。
|
||||
\- 不要试图在一个步骤里做完所有事。
|
||||
\- 比如:先调用查询工具,获得结果后,再调用发送工具。
|
||||
2\. \*\*依赖管理\*\*:如果步骤 B 依赖步骤 A 的结果,必须先执行 A,观察 A 的结果,再执行 B。
|
||||
3\. \*\*完成检查\*\*:在输出 Final Answer 之前,检查是否完成了用户的所有指令。
|
||||
|
||||
思考格式示例:
|
||||
Thought: 用户想查 P001 并通知张医生。我有两个任务。首先,我需要查 P001 的状态。
|
||||
Action: 调用 read\_clinical\_data(id='P001')...
|
||||
Observation: {"status": "Enrolled"}
|
||||
Thought: 我已经查到了状态。现在我需要执行第二个任务:发消息给张医生。
|
||||
Action: 调用 send\_wechat\_msg(to='Dr. Zhang', content='P001 status is Enrolled')...
|
||||
...
|
||||
\`;
|
||||
|
||||
### **4.3 为什么不用 Planner?**
|
||||
|
||||
对于 2 人团队,显式 Planner(先生成计划列表,再循环执行)过于沉重且难以维护状态。ReAct 的 **“边走边想”** 模式完全能够覆盖 90% 的“线性多步任务”。
|
||||
|
||||
## **5\. 总结与建议**
|
||||
|
||||
| 需求 | 解决方案 | 复杂度 | 推荐实施阶段 |
|
||||
| :---- | :---- | :---- | :---- |
|
||||
| **主动提醒** | **Cron Skill** (复用 pg-boss \+ QC Engine) | 低 | **Phase 4** (与周报一起做) |
|
||||
| **用户画像** | **Profiler Job** (对话后异步分析) | 中 | **Phase 5** (智能化增强) |
|
||||
| **反馈循环** | **点赞按钮 \+ 记忆写入** | 低 | **Phase 5** |
|
||||
| **多意图处理** | **ReAct Prompt 优化** (无需新模块) | 极低 | **Phase 5** (开发 ReActEngine 时同步完成) |
|
||||
|
||||
### **对你的建议**
|
||||
|
||||
1. **全盘接受 P0 建议**:主动提醒和用户画像是成本低、收益大的功能。
|
||||
2. **技术上保持克制**:
|
||||
* 别写专门的 Alert Engine,用 Cron Skill 复用现有的引擎。
|
||||
* 别搞复杂的推荐算法,用 JSON 存画像就够了。
|
||||
* 别搞复杂的 Planner,用 **Prompt** 教会 ReAct 拆解任务就够了。
|
||||
|
||||
这样,你的 V2.6+ 架构就真正补齐了“右脑”的情商短板,变成了一个 **“有记忆、会主动、懂人情、能办事”** 的完整 Agent。
|
||||
44
docs/03-业务模块/IIT Manager Agent/05-测试文档/开发计划审核备忘录.md
Normal file
44
docs/03-业务模块/IIT Manager Agent/05-测试文档/开发计划审核备忘录.md
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
|
||||
## **1\. 风险预警 (Risk Alert)**
|
||||
|
||||
### **🔴 高风险:Phase 4 排期过饱和**
|
||||
|
||||
* **问题**:Week 4 同时安排了"视觉识别"、"周报系统"、"高级伦理规则"开发,对于 2 人团队工作量过载。
|
||||
* **建议**:**战略性放弃视觉识别的自动录入功能**,将其降级为 MVP(仅识别展示)。将资源集中在"周报系统"上,因为周报对 PI 的价值感知更强。
|
||||
|
||||
### **🟠 中风险:SOP 状态丢失**
|
||||
|
||||
* **问题**:SopEngine 在内存中运行 while 循环。若 Node.js 服务在长任务中途重启(如发布新版),当前流程会丢失。
|
||||
* **建议**:在 while 循环的每一步,增加 await updateTaskProgress(),将 currentNode 写入数据库,确保有据可查。
|
||||
|
||||
### **🟠 中风险:AI 自动写入合规性**
|
||||
|
||||
* **问题**:Phase 4 提到"高置信度自动录入"。在 GCP 原则下,AI 直接修改源数据存在合规隐患。
|
||||
* **建议**:坚持 **Shadow State (影子状态)** 原则。VisionService 的结果应生成 PROPOSED 状态的记录,必须由 CRC 人工点击确认。
|
||||
|
||||
## **2\. 架构优化建议 (Architecture Tuning)**
|
||||
|
||||
### **2.1 增加 "Dry Run" 机制**
|
||||
|
||||
* **痛点**:JSON 配置写在数据库里,出错难排查。
|
||||
* **方案**:在 ToolsService 中增加 simulate() 方法,允许在不产生副作用的情况下测试 Skill 逻辑。
|
||||
|
||||
### **2.2 强化字段映射管理**
|
||||
|
||||
* **痛点**:iit\_field\_mapping 表如果只能通过 SQL 修改,运营成本极高。
|
||||
* **方案**:在 Week 3 增加一个简单的 Admin API 或界面,用于管理字段别名。
|
||||
|
||||
## **3\. 调整后的推荐路线图 (Refined Roadmap)**
|
||||
|
||||
* **Week 1 (基础)**:数据库 \+ HardRuleEngine \+ ToolsService (不变)
|
||||
* **Week 2 (引擎)**:SoftRuleEngine \+ SopEngine (**增加状态持久化**)
|
||||
* **Week 3 (联调)**:配置第一个 Skill \+ 端到端测试 (**增加 Dry Run 工具**)
|
||||
* **Week 4 (扩展)**:
|
||||
* **P0**: 定时任务 \+ 周报系统 (Scheduler \+ Report)
|
||||
* **P1**: 高级质控工具 (Visit Window)
|
||||
* **P2 (延后)**: 视觉识别 (VisionService) \-\> **建议移至 V2.6**
|
||||
|
||||
## **4\. 结论**
|
||||
|
||||
架构选型(Postgres-Only \+ Service-First)非常精准。只需在 Phase 4 做适当减法,并注意状态持久化细节,即可确保项目按时、高质量交付。
|
||||
169
docs/03-业务模块/IIT Manager Agent/05-测试文档/微信开发技术问题咨询.md
Normal file
169
docs/03-业务模块/IIT Manager Agent/05-测试文档/微信开发技术问题咨询.md
Normal file
@@ -0,0 +1,169 @@
|
||||
|
||||
|
||||
**第二章 微信服务号集成与Error 200002根因深度剖析**
|
||||
|
||||
用户提到的“配置消息推送失败: invalid args, 200002”且“后端无日志”,是微信开发中极其典型且令人抓狂的现象。这通常不是单一的代码错误,而是网络层、网关层与应用层叠加的系统性故障。
|
||||
|
||||
### **2.1 现象解码:“无日志”的恐怖**
|
||||
|
||||
当微信后台提示配置失败,而您的服务器访问日志(Access Log)空空如也时,这意味着**请求根本没有到达您的应用服务器**。
|
||||
|
||||
#### **2.1.1 网络层的隐形墙:防火墙与安全组**
|
||||
|
||||
微信服务器位于腾讯的公网IP池中。当它发起HTTP/HTTPS请求时,如果您的服务器部署在阿里云、腾讯云或AWS等云平台,\*\*安全组(Security Group)\*\*是第一道关卡。
|
||||
|
||||
* **诊断**:大多数云服务器默认只开放22端口(SSH)。
|
||||
* **排查**:检查云控制台的入站规则。必须允许0.0.0.0/0对TCP 80(HTTP)和443(HTTPS)的访问。
|
||||
* **误区**:不要试图将微信的IP加入白名单。微信的出口IP是不定期的海量IP池,必须全量开放。
|
||||
|
||||
#### **2.1.2 接入层的黑洞:Nginx配置错误**
|
||||
|
||||
如果您使用了Nginx作为反向代理,请求可能在Nginx层被丢弃,导致后端Java/Python应用收不到日志。
|
||||
|
||||
* **Server Name匹配**:如果Nginx配置了server\_name www.example.com,而您在微信后台填写的是直接IP地址,Nginx可能因为找不到匹配的Host而直接拒绝连接。
|
||||
* **SSL握手失败**:如果配置的是HTTPS URL,但服务器的SSL证书不完整(如缺少中间证书)或协议版本过旧(TLS 1.0),微信服务器会终止握手,请求甚至不会进入Nginx日志。
|
||||
|
||||
### **2.2 Error 200002 “invalid args” 的代码级解剖**
|
||||
|
||||
如果请求成功到达服务器(可以通过Nginx日志确认),但微信仍然报200002,则问题出在**握手协议的实现**上。这是最考验开发者基本功的环节。
|
||||
|
||||
#### **2.2.1 握手协议的三大铁律**
|
||||
|
||||
微信在验证服务器有效性时,发送signature, timestamp, nonce, echostr四个参数。开发者必须完成以下步骤:
|
||||
|
||||
1. **字典序排序(Lexicographical Sorting)**:
|
||||
* **错误高发点**:很多开发者将数字类型的timestamp直接参与排序,或者使用了错误的排序算法。
|
||||
* **正确逻辑**:将\[token, timestamp, nonce\]放入一个字符串数组,调用标准的字符串排序方法(如Python的list.sort(),Java的Arrays.sort())。
|
||||
2. **SHA1哈希(Hashing)**:
|
||||
* **错误高发点**:编码问题。在Python 3中,hashlib.sha1()接受的是字节流(bytes),必须先对拼接后的字符串进行.encode('utf-8')。
|
||||
3. **返回值的纯净性(Response Purity)**:
|
||||
* **错误高发点**:这是导致200002最隐蔽的原因。微信要求**原样返回**echostr的明文。
|
||||
* **忌讳**:
|
||||
* 不能返回JSON格式(如{"ret": "success", "echo": "..."})。
|
||||
* 不能包含引号(如"abc...")。
|
||||
* 不能包含换行符或空格。
|
||||
* **Content-Type**:虽然微信不强制,但最佳实践是设置Response Header为text/plain。
|
||||
|
||||
#### **2.2.2 深度调试代码示例(Python Flask版)**
|
||||
|
||||
以下是一个通过了生产环境验证的标准校验代码,用于替换可能存在缺陷的逻辑:
|
||||
|
||||
Python
|
||||
|
||||
import hashlib
|
||||
from flask import Flask, request, make\_response
|
||||
|
||||
app \= Flask(\_\_name\_\_)
|
||||
|
||||
@app.route('/wechat', methods=)
|
||||
def check\_signature():
|
||||
\# 1\. 获取参数
|
||||
token \= "YOUR\_TOKEN" \# 必须与微信后台填写的完全一致
|
||||
signature \= request.args.get('signature')
|
||||
timestamp \= request.args.get('timestamp')
|
||||
nonce \= request.args.get('nonce')
|
||||
echostr \= request.args.get('echostr')
|
||||
|
||||
\# 2\. 空值校验(防御性编程)
|
||||
if not all(\[signature, timestamp, nonce, token\]):
|
||||
return "Invalid Request", 400
|
||||
|
||||
\# 3\. 字典序排序
|
||||
li \= \[token, timestamp, nonce\]
|
||||
li.sort()
|
||||
|
||||
\# 4\. 拼接与哈希
|
||||
temp\_str \= "".join(li)
|
||||
hash\_str \= hashlib.sha1(temp\_str.encode('utf-8')).hexdigest()
|
||||
|
||||
\# 5\. 对比与返回
|
||||
if hash\_str \== signature:
|
||||
\# 关键:使用make\_response确保返回的是纯文本,不受框架序列化影响
|
||||
response \= make\_response(echostr)
|
||||
response.headers\['content-type'\] \= 'text/plain'
|
||||
return response
|
||||
else:
|
||||
\# 记录错误日志以便排查
|
||||
app.logger.error(f"Sig check failed. Calc: {hash\_str}\!= Req: {signature}")
|
||||
return "Signature Failed", 403
|
||||
|
||||
if \_\_name\_\_ \== '\_\_main\_\_':
|
||||
app.run(port=80)
|
||||
|
||||
### **2.3 “我很痛苦”的终结方案:抓包与模拟**
|
||||
|
||||
如果依然失败,请停止盲目修改代码,采用**证据驱动调试**。
|
||||
|
||||
1. **自测脚本**:编写一个Python脚本,模拟微信的逻辑,生成签名并发送GET请求给自己的服务器。如果自测通过但微信不通过,问题一定在网络层(防火墙/CDN/WAF)。
|
||||
2. **网络抓包**:在服务器上使用tcpdump \-i eth0 port 80 \-w wechat\_debug.pcap。在微信后台点击提交后,停止抓包并用Wireshark分析。
|
||||
* 如果抓不到包 \-\> 网络不通。
|
||||
* 如果抓到包但应用没日志 \-\> Nginx配置错误。
|
||||
* 如果应用返回了200但微信报错 \-\> 检查Response Body是否有隐藏字符(BOM头、换行符)。
|
||||
|
||||
## ---
|
||||
|
||||
**第三章 全场景调试指南:工具链与最佳实践**
|
||||
|
||||
对于“如何调试”、“本地还是远程”的疑问,行业内的最佳范式是\*\*“本地隧道调试”\*\*(Local Tunnel Debugging)。直接在远程服务器上修改代码并重启服务来调试微信接口,效率极低且风险极高。
|
||||
|
||||
### **3.1 本地调试的核心逻辑:内网穿透**
|
||||
|
||||
微信服务器无法直接访问你笔记本电脑上的localhost:8080。因此,需要一个“隧道”,将公网请求转发到本地。
|
||||
|
||||
#### **3.1.1 工具选型与配置**
|
||||
|
||||
针对中国网络环境,推荐以下工具链:
|
||||
|
||||
| 工具名称 | 适用场景 | 优点 | 缺点 | 推荐指数 |
|
||||
| :---- | :---- | :---- | :---- | :---- |
|
||||
| **ngrok** | 快速验证 | 命令简单,全球通用 | 免费版域名随机变化,国内连接有时不稳定 | ⭐⭐⭐ |
|
||||
| **frp** | 长期开发 | **国内标准**。需一台公网VPS。稳定,域名固定 | 配置稍繁琐,需维护服务端 | ⭐⭐⭐⭐⭐ |
|
||||
| **cpolar/Natapp** | 无VPS用户 | 专为国内优化,速度快 | 免费版有限制,自定义域名需付费 | ⭐⭐⭐⭐ |
|
||||
|
||||
#### **3.1.2 FRP实战配置SOP**
|
||||
|
||||
假设您有一台腾讯云/阿里云服务器(IP: 1.2.3.4),这是最稳定的方案。
|
||||
|
||||
1. **服务端(VPS)配置 frps.ini**:
|
||||
Ini, TOML
|
||||
\[common\]
|
||||
bind\_port \= 7000 \# 用于frp内部通信
|
||||
vhost\_http\_port \= 8080 \# 微信访问的端口
|
||||
|
||||
启动:./frps \-c frps.ini
|
||||
2. **客户端(本地电脑)配置 frpc.ini**:
|
||||
Ini, TOML
|
||||
\[common\]
|
||||
server\_addr \= 1.2.3.4
|
||||
server\_port \= 7000
|
||||
|
||||
\[wechat-debug\]
|
||||
type \= http
|
||||
local\_port \= 5000 \# 本地Python/Java服务的端口
|
||||
custom\_domains \= wechat.your-startup.com \# 解析到1.2.3.4的域名
|
||||
|
||||
启动:./frpc \-c frpc.ini
|
||||
3. **调试闭环**:
|
||||
* 在微信后台配置URL为:http://wechat.your-startup.com:8080/callback
|
||||
* 当微信发送请求时,流量路径为:微信 \-\> VPS(7000) \-\> 隧道 \-\> 本地电脑(5000)。
|
||||
* **效果**:您可以在本地IDE(PyCharm/VSCode)中打断点,实时查看微信发来的XML数据包,单步调试每一行代码。这是解决“痛苦”的根本途径。
|
||||
|
||||
### **3.2 微信官方调试工具的使用**
|
||||
|
||||
除了内网穿透,腾讯提供了**微信开发者工具**(主要用于小程序,但也包含公众号调试)和**在线接口调试工具**。
|
||||
|
||||
* **在线接口调试工具** 6:
|
||||
* 地址:[https://developers.weixin.qq.com/apiExplorer](https://developers.weixin.qq.com/apiExplorer)
|
||||
* 用途:当您的Token配置成功,但消息推送失败时,可以用它模拟微信服务器向您的URL发送POST请求。它会详细显示您的服务器返回了什么(HTTP Code, Body),帮助快速定位“回包格式错误”。
|
||||
|
||||
### **3.3 行业最佳实践范式**
|
||||
|
||||
1. **TraceID全链路追踪**:在入口处生成一个UUID作为TraceID,贯穿Nginx日志、应用日志和数据库日志。当某个课题组反馈机器人不回话时,通过TraceID可秒级定位问题环节。
|
||||
2. **日志分级与脱敏**:
|
||||
* **DEBUG级**:打印完整的XML/JSON请求体(注意:生产环境需对患者姓名、ID进行掩码脱敏)。
|
||||
* **INFO级**:记录核心链路节点(收到消息 \-\> 开始推理 \-\> 推送完成)。
|
||||
* **ERROR级**:记录所有的API调用非200状态码及异常堆栈。
|
||||
3. **容错与重试**:微信接口偶尔会出现网络抖动。在调用send接口时,务必封装重试逻辑(Exponential Backoff),遇到超时或5xx错误时自动重试3次。
|
||||
|
||||
## ---
|
||||
|
||||
64
docs/03-业务模块/IIT Manager Agent/05-测试文档/潜在的具体风险与问题.md
Normal file
64
docs/03-业务模块/IIT Manager Agent/05-测试文档/潜在的具体风险与问题.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# **潜在的具体风险与问题**
|
||||
|
||||
即使架构完美,细节仍可能翻车。以下是代码落地时可能遇到的“暗礁”以及针对性的应对策略。
|
||||
|
||||
## **🔴 风险一:意图路由的“延迟感”**
|
||||
|
||||
* **位置**:IntentService (Day 20-21)
|
||||
* **问题**:用户发一句话,系统先调一次 LLM (路由),再调一次 SOP/ReAct (执行)。
|
||||
* **后果**:响应延迟 \= LLM(路由) \+ LLM(执行) \+ 网络开销。可能导致用户发完消息后 **5-8 秒** 才有反应。在微信的即时通讯场景下,这个体感很差,用户容易认为系统卡死。
|
||||
* **对策**:
|
||||
1. **流式欺骗 (UX Trick)**:收到消息立刻回一个“正在思考...”或“正在查询...”的状态(企业微信 API 支持中间态更新或 typing 状态)。
|
||||
2. **快速通道 (Fast Path)**:对于极短的、特征明显的指令(如“质控”、“报表”、“帮助”),先用 **正则 (Regex) / 关键词 (Keywords)** 进行拦截。只有正则拦截不住的复杂长句,再走 LLM 意图路由。
|
||||
* *原则*:**混合路由 \> 纯 LLM 路由**。
|
||||
|
||||
## **🔴 风险二:ReAct 的“多嘴”风险**
|
||||
|
||||
* **位置**:ReActEngine (Day 22-23)
|
||||
* **问题**:ReAct 的核心机制是“思考-行动-观察”循环。模型倾向于在思考过程中输出大量中间步骤,例如:
|
||||
* *AI Thought*: "用户想查 P001,我需要先调用 search 工具,然后..."
|
||||
* *AI Thought*: "哎呀,没查到数据,可能需要换个参数..."
|
||||
* **后果**:如果将这些中间思考过程(Internal Monologue)全部实时推送给用户,用户会觉得“这个机器人话痨”、“不够干练”甚至“不自信”。
|
||||
* **对策**:
|
||||
1. **UI 静默处理**:只把 Final Answer 发送给用户。
|
||||
2. **调试可见**:中间的 Trace (思考轨迹) 只存入后台日志,或者仅在 Admin 管理端的 Debug 模式下显示,用于排查问题。
|
||||
|
||||
## **🔴 风险三:SOP 状态机的“死锁”**
|
||||
|
||||
* **位置**:SopEngine (Day 8-9)
|
||||
* **问题**:SOP 流程中经常包含“人工介入”环节。例如,如果 SOP 走到了 review\_required(等待人工复核)节点,流程会暂停。如果 CRC 一直不点击确认,或者忘记处理,这个任务就会一直挂在内存或队列中。
|
||||
* **后果**:pg-boss 可能会认为任务超时(Timeout),触发反复重试机制,最终判定失败(Failed),导致流程异常终止。
|
||||
* **对策**:
|
||||
1. **区分状态**:在系统设计中严格区分 **“系统阻塞 (System Blocking)”** 和 **“业务等待 (Business Waiting)”**。
|
||||
2. **挂起机制**:如果是“等人”,任务状态应设为 SUSPENDED(挂起)。此时应从活动队列中移除,不再占用 Worker 资源。
|
||||
3. **唤醒机制**:当 CRC 在前端点击“确认”后,触发一个回调,创建一个新的 Job 来唤醒流程,继续执行后续步骤。
|
||||
|
||||
## **3\. 给 2 人团队的“瘦身版”执行建议**
|
||||
|
||||
基于 V2.6 计划,为了确保在有限人力下 100% 交付核心价值,建议执行以下 **“瘦身”** 策略:
|
||||
|
||||
### **✂️ 裁剪清单 (不做或延后)**
|
||||
|
||||
1. **\[延后\] Phase 6 全阶段 (视觉能力)**:放到 V3.0 再说。目前的文本交互和结构化数据处理已经足够复杂,且 OCR 准确率调优是个无底洞。
|
||||
2. **\[裁剪\] Phase 5 的语义记忆 (pgvector)**:暂时只做简单的文本记录(如 JSON 或 Markdown)。跨度极大的语义召回在初期并非刚需。
|
||||
3. **\[简化\] 伦理合规检测 (T11.3)**:伦理规则极度复杂且非结构化。建议先写死几条最核心的规则(如“知情同意日期必须早于入组日期”),暂不做通用的伦理引擎。
|
||||
|
||||
### **✨ 聚焦清单 (必须做好)**
|
||||
|
||||
1. **P0: ToolsService 的健壮性**:这是整个系统的地基。一定要加上 V2.3 文档中提到的 **“字段模糊映射”**,否则 LLM 根本调不通工具,体验会崩塌。
|
||||
2. **P0: 每日早报 (Morning Brief)**:这是用户(PI)感知最强的功能。一定要做得漂亮、准时,这是体现 Agent “主动性”的关键。
|
||||
3. **P0: 意图路由的混合模式**:正则 \+ LLM 双保险,这是保证响应速度和降低 Token 成本的关键。
|
||||
|
||||
## **4\. 最终评价**
|
||||
|
||||
这份 **V2.6 计划** 是一个经过深思熟虑的、高水平的架构设计。
|
||||
|
||||
* **它懂业务**:SOP 状态机完美解决了医疗合规与流程锁定的痛点。
|
||||
* **它懂用户**:双脑架构(ReAct \+ SOP)解决了“机器人太死板”的交互体验痛点。
|
||||
* **它懂工程**:极简架构(Postgres-Only \+ Service Class)解决了小团队维护复杂微服务的痛点。
|
||||
|
||||
**结论**:只要你们能忍住不去做“视觉识别”和“复杂语义记忆”这些锦上添花的功能,按部就班地把前 5 个 Phase 做完,这将是一个在医疗垂直领域极具竞争力的产品。
|
||||
|
||||
**最后一步建议:**
|
||||
|
||||
把这份文档发给团队,然后开一个 **Kick-off Meeting**,明确告诉大家:“我们要造的是**双脑 Agent**,但我们从\*\*左脑(SOP)\*\*开始造,视觉能力先放进冰箱。”
|
||||
Reference in New Issue
Block a user