Files
AIclinicalresearch/docs/08-项目管理/05-技术债务/通用对话服务抽取计划.md
HaHafeng 5f089516cb feat(iit-manager): Day 3 企业微信集成开发完成
- 新增WechatService(企业微信推送服务,支持文本/卡片/Markdown消息)
- 新增WechatCallbackController(异步回复模式,5秒内响应)
- 完善iit_quality_check Worker(调用WechatService推送通知)
- 新增企业微信回调路由(GET验证+POST接收消息)
- 实现LLM意图识别(query_weekly_summary/query_patient_info等)
- 安装依赖:@wecom/crypto, xml2js
- 更新开发记录文档和MVP开发计划

技术要点:
- 使用异步回复模式规避企业微信5秒超时限制
- 使用@wecom/crypto官方库处理XML加解密
- 使用setImmediate实现后台异步处理
- 支持主动推送消息返回LLM处理结果
- 完善审计日志记录(WECHAT_NOTIFICATION_SENT/WECHAT_INTERACTION)

相关文档:
- docs/03-业务模块/IIT Manager Agent/06-开发记录/Day3-企业微信集成开发完成记录.md
- docs/03-业务模块/IIT Manager Agent/04-开发计划/最小MVP闭环开发计划.md
- docs/03-业务模块/IIT Manager Agent/00-模块当前状态与开发指南.md
2026-01-03 09:39:39 +08:00

12 KiB
Raw Blame History

技术债务:通用对话服务抽取计划

文档类型: 技术债务
创建日期: 2025-12-06
优先级: P2中期优化
预计工时: 2-3天
影响模块: AIA、PKB、Tool C、未来AI知识库模块


📋 问题描述

当前现状

后端对话能力分布

✅ 通用能力层common/
├── llm/adapters/              ← LLM适配器完整
│   ├── LLMFactory.ts          ← 统一工厂类
│   ├── DeepSeekAdapter.ts
│   ├── QwenAdapter.ts
│   ├── GPT5Adapter.ts
│   └── ClaudeAdapter.ts
├── rag/                       ← RAG检索完整
│   └── DifyClient.ts
└── storage/, logging/, cache/ ← 基础设施(完整)

⚠️ 业务层legacy/
└── services/
    └── conversationService.ts  ← 对话管理逻辑625行
        ├── 创建对话
        ├── 发送消息
        ├── 流式输出
        ├── 上下文组装
        ├── 历史管理
        └── 知识库检索集成

前端组件分布

❌ 缺少通用对话组件
各模块重复实现:
├── AIA模块 - 自己实现对话UI
├── PKB模块 - 自己实现对话UI
└── Tool C模块 - 自己实现对话UIDay 3

🎯 问题分析

代码重复

功能 当前状态 重复度
LLM调用 已抽取common/llm 0%
对话管理 ⚠️ 未抽取legacy中 80%
消息存储 ⚠️ 各模块独立表 60%
上下文组装 ⚠️ 各模块重复实现 70%
流式输出 ⚠️ 各模块重复实现 80%
前端对话UI 完全未抽取 90%

影响范围

现有模块

  • AIAAI智能问答- 使用legacy/conversationService
  • PKB个人知识库- 使用legacy/conversationService
  • Tool C数据清洗- Day 3自己实现~150行重复代码

未来模块

  • AI知识库模块 - 需要重复实现
  • 其他AI对话场景 - 需要重复实现

💡 优化方案

方案:抽取通用对话服务

第一阶段:后端服务抽取

创建通用对话服务

// backend/src/common/conversation/ConversationService.ts
export class ConversationService {
  /**
   * 通用对话接口
   * 支持多种场景AIA、PKB、Tool C等
   */
  async chat(config: ChatConfig): Promise<ChatResponse> {
    // 1. 构建消息上下文
    const messages = await this.buildContext(config);
    
    // 2. 调用LLM复用LLMFactory
    const llm = LLMFactory.createAdapter(config.modelType);
    const response = await llm.chat(messages, config.options);
    
    // 3. 保存消息(根据配置决定存储位置)
    await this.saveMessage(config.conversationId, config.userMessage, response);
    
    return response;
  }
  
  /**
   * 流式对话
   */
  async streamChat(config: ChatConfig): AsyncGenerator<StreamChunk> {
    const messages = await this.buildContext(config);
    const llm = LLMFactory.createAdapter(config.modelType);
    
    for await (const chunk of llm.chatStream(messages, config.options)) {
      yield chunk;
    }
    
    // 保存完整响应
    await this.saveMessage(...);
  }
  
  /**
   * 获取对话历史
   * 支持多种存储方式(统一接口,不同表)
   */
  async getHistory(conversationId: string, options?: HistoryOptions) {
    // 根据conversationType路由到不同的表
    const adapter = this.getStorageAdapter(options.conversationType);
    return await adapter.getHistory(conversationId, options);
  }
  
  /**
   * 保存消息
   * 支持多种存储方式
   */
  async saveMessage(conversationId: string, userMsg: string, aiResponse: any) {
    const adapter = this.getStorageAdapter(this.config.conversationType);
    await adapter.saveMessage(conversationId, userMsg, aiResponse);
  }
  
  /**
   * 构建上下文
   * 支持System Prompt + 历史消息 + 当前消息 + RAG检索
   */
  private async buildContext(config: ChatConfig): Promise<Message[]> {
    const messages: Message[] = [];
    
    // 1. System Prompt
    if (config.systemPrompt) {
      messages.push({ role: 'system', content: config.systemPrompt });
    }
    
    // 2. 历史消息
    if (config.includeHistory) {
      const history = await this.getHistory(config.conversationId, {
        limit: config.historyLimit || 5
      });
      messages.push(...history);
    }
    
    // 3. RAG检索如果需要
    if (config.knowledgeBaseIds?.length > 0) {
      const ragContext = await this.retrieveRAGContext(
        config.userMessage,
        config.knowledgeBaseIds
      );
      messages.push({ role: 'system', content: ragContext });
    }
    
    // 4. 当前用户消息
    messages.push({ role: 'user', content: config.userMessage });
    
    return messages;
  }
}

// 配置接口
interface ChatConfig {
  conversationId: string;
  conversationType: 'aia' | 'pkb' | 'tool-c' | 'knowledge-base';  // 路由到不同表
  modelType: ModelType;
  userMessage: string;
  systemPrompt?: string;
  includeHistory?: boolean;
  historyLimit?: number;
  knowledgeBaseIds?: string[];
  options?: {
    temperature?: number;
    maxTokens?: number;
    topP?: number;
  };
}

存储适配器(支持不同模块的不同表):

// backend/src/common/conversation/adapters/StorageAdapter.ts
interface ConversationStorageAdapter {
  getHistory(conversationId: string, options?: HistoryOptions): Promise<Message[]>;
  saveMessage(conversationId: string, userMsg: string, aiResponse: any): Promise<void>;
}

// AIA/PKB使用通用表
class GeneralStorageAdapter implements ConversationStorageAdapter {
  async getHistory(conversationId: string) {
    return await prisma.generalMessage.findMany({
      where: { conversationId },
      orderBy: { createdAt: 'desc' },
      take: options.limit
    });
  }
}

// Tool C使用独立表
class ToolCStorageAdapter implements ConversationStorageAdapter {
  async getHistory(conversationId: string) {
    return await prisma.dcToolCAiHistory.findMany({
      where: { sessionId: conversationId },
      orderBy: { createdAt: 'desc' },
      take: options.limit
    });
  }
}

第二阶段:前端组件抽取

创建通用对话组件库

// frontend-v2/src/shared/components/Chat/
├── ChatContainer.tsx          // 对话容器(布局)
├── MessageList.tsx            // 消息列表(虚拟滚动)
├── MessageItem.tsx            // 单条消息(用户/AI
├── MessageInput.tsx           // 输入框(支持多行、快捷键)
├── StreamingMessage.tsx       // 流式渲染(打字机效果)
├── CitationBadge.tsx          // 引用标记
├── LoadingIndicator.tsx       // 加载动画
└── index.ts

// 使用示例
import { ChatContainer } from '@/shared/components/Chat';

<ChatContainer
  conversationId={sessionId}
  conversationType="tool-c"
  systemPrompt={buildSystemPrompt()}
  onSendMessage={handleSendMessage}
  enableHistory={true}
  enableRAG={false}
/>

组件特性

  • 支持流式/非流式渲染
  • 支持引用跳转
  • 支持历史消息加载
  • 支持Markdown渲染
  • 支持代码高亮
  • 响应式布局

📊 改造前后对比

代码量对比

模块 改造前 改造后 减少
后端
AIA对话逻辑 200行 50行调用通用服务 -75%
PKB对话逻辑 180行 50行 -72%
Tool C对话逻辑 150行 50行 -67%
通用服务 0行 300行新建 +300行
总计 530行 450行 -15%
前端
各模块对话UI 600行×3 200行×3调用通用组件 -67%
通用组件 0行 500行新建 +500行
总计 1800行 1100行 -39%
全部合计 2330行 1550行 -33%

质量提升

指标 改造前 改造后 提升
代码复用率 20% 80% +300%
统一交互体验 100%
未来扩展成本 高(每次重复) 低(直接复用) -80%
维护成本 高(多处修改) 低(单点修改) -70%

🚀 实施计划

阶段1后端服务抽取1.5天)

Day 1上午设计

  • 设计ConversationService接口
  • 设计StorageAdapter接口
  • 设计ChatConfig配置结构

Day 1下午实现核心服务

  • 实现ConversationService核心逻辑
  • 实现GeneralStorageAdapter
  • 实现ToolCStorageAdapter

Day 2上午迁移现有模块

  • AIA模块改造使用通用服务
  • PKB模块改造使用通用服务
  • Tool C模块改造使用通用服务

Day 2下午测试

  • 单元测试
  • 集成测试
  • 回归测试(确保原功能正常)

阶段2前端组件抽取1天

Day 3上午设计与实现

  • 设计ChatContainer API
  • 实现核心组件6个
  • 样式统一

Day 3下午迁移与测试

  • AIA模块前端改造
  • PKB模块前端改造
  • Tool C模块前端改造
  • UI测试

阶段3文档与培训0.5天)

  • 编写使用文档
  • 编写最佳实践
  • 团队培训

📝 验收标准

功能验收

  • AIA模块对话功能正常
  • PKB模块对话功能正常
  • Tool C模块对话功能正常
  • 流式输出正常
  • 历史消息加载正常
  • RAG检索集成正常

代码质量

  • 代码复用率≥80%
  • 单元测试覆盖率≥80%
  • 无TypeScript错误
  • 无ESLint警告

用户体验

  • 对话交互流畅(响应<2秒
  • 流式输出流畅(无卡顿)
  • UI统一美观
  • 移动端适配良好

💰 收益分析

短期收益1个月内

  1. 代码质量提升

    • 减少重复代码33%
    • 提升代码复用率至80%
    • 降低维护成本70%
  2. 开发效率提升

    • 新模块对话功能开发时间从2天→0.5天(-75%
    • Bug修复效率单点修改影响全局+200%
  3. 用户体验统一

    • 统一交互模式
    • 统一视觉风格
    • 统一性能标准

长期收益3-6个月

  1. 支持未来模块

    • AI知识库模块直接复用0额外开发
    • 其他AI对话场景快速实现
  2. 技术架构优化

    • 真正实现分层架构(业务层→通用层)
    • 为微服务拆分做准备
  3. 商业价值

    • 模块独立部署更容易
    • 模块独立售卖更灵活
    • 客户定制成本降低

⚠️ 风险与应对

风险1回归测试工作量大

应对

  • 优先实现自动化测试
  • 分模块逐步迁移
  • 保留原代码作为备份

风险2历史数据迁移

应对

  • 不需要迁移数据
  • 只迁移代码逻辑
  • 各模块保留独立表

风险3前端组件复杂度

应对

  • 采用渐进式重构
  • 先抽取核心组件
  • 后续迭代优化

📅 建议执行时间

推荐时间窗口

  • 选项1Tool C MVP完成后Day 10-13
  • 选项2所有DC模块完成后Week 8-9
  • 选项3AI知识库模块启动前Quarter 2

当前决策延后至Tool C MVP完成后


🔗 相关文档


📝 更新记录

日期 版本 更新内容 更新人
2025-12-06 V1.0 初始创建 AI Assistant

文档状态: 已创建
优先级: P2中期优化
下一步: 完成Tool C Day 3 MVP后重新评估