2026-07-20-langgraph-migration-design.md 22 KB

LangGraph Sidecar 迁移设计

日期: 2026-07-20 状态: 草稿 版本: 1.0


1. 概述

将 cfc-backend 的 AI 能力从 Dify 迁移到自建的 Python LangGraph sidecar 服务。Java 端的 JDK 8 / Spring Boot 2.7.18 保持不变。

目标

  • 消除对 Dify 平台的依赖(数据安全、黑盒 prompt、不可控的发布节奏)
  • 获得 LangGraph 的 Agent 编排、有状态持久化、HITL 能力
  • 团队完全掌控 AI 层的 prompt、模型、知识库

非目标

  • 不改动 Java 端 JDK/Spring Boot 版本
  • 不改动 cfc 业务逻辑
  • 不引入额外的中间件(PG 可选, Chroma 文件模式优先)

2. 架构

┌──────────────────────────────────────────────────────────────────────┐
│                          User (微信小程序 / Web)                       │
└─────────────────────────────┬────────────────────────────────────────┘
                              │ HTTP
┌─────────────────────────────▼────────────────────────────────────────┐
│                    cfc-backend (Java 8, Spring Boot 2.7.18)           │
│                                                                       │
│  ┌──────────────┐  ┌──────────────────┐  ┌───────────────────────┐   │
│  │ 业务 Controller│  │ AI Gateway       │  │ Context Provider      │   │
│  │ (不变)        │  │ AIService.java   │  │ AiContextService.java │   │
│  │              │  │ (HTTP→Python)     │  │ (Java→Python 数据源)  │   │
│  └──────────────┘  │ + Dify Fallback   │  └───────────────────────┘   │
│                    └────────┬─────────┘                               │
│                             │ HTTP (内网)                             │
└─────────────────────────────┼────────────────────────────────────────┘
                              │
┌─────────────────────────────▼────────────────────────────────────────┐
│                   langgraph-service (Python 3.11)                     │
│                                                                       │
│  ┌──────────────────────────────────────────────────────────────┐    │
│  │                     FastAPI 网关 (port 9000)                   │    │
│  │  POST /api/v1/chat      → ChatAgent                           │    │
│  │  POST /api/v1/recommend → RecommendAgent                      │    │
│  │  POST /api/v1/analyze   → AnalysisAgent                       │    │
│  │  POST /api/v1/agent     → LangGraph Orchestrator              │    │
│  │  POST /api/v1/tongue    → MultiModalAgent                     │    │
│  └──────────────────────────────────────────────────────────────┘    │
│                              │                                        │
│  ┌──────────────────────────▼───────────────────────────────────┐    │
│  │                    LangGraph Engine                           │    │
│  │                                                               │    │
│  │  ┌────────────────┐ ┌────────────────┐ ┌────────────────┐    │    │
│  │  │ StateGraph      │ │ Checkpointer   │ │ Memory Manager │    │    │
│  │  │ (条件路由/循环)  │ │ (PG / Memory)  │ │ (3层记忆)      │    │    │
│  │  └────────────────┘ └────────────────┘ └────────────────┘    │    │
│  └──────────────────────────────────────────────────────────────┘    │
│                              │                                        │
│  ┌──────────────────────────▼───────────────────────────────────┐    │
│  │                    RAG Pipeline                                │    │
│  │  ┌────────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐   │    │
│  │  │Document    │→│Text      │→│Embedding │→│VectorStore│   │    │
│  │  │Loader      │  │Splitter  │  │          │  │(Chroma)  │   │    │
│  │  └────────────┘  └──────────┘  └──────────┘  └──────────┘   │    │
│  │                                    │                          │    │
│  │  ┌─────────────────────────────────▼──────────────────────┐   │    │
│  │  │ EnsembleRetriever (向量 + BM25) → Rerank → Compressor  │   │    │
│  │  └────────────────────────────────────────────────────────┘   │    │
│  └──────────────────────────────────────────────────────────────┘    │
│                              │                                        │
│  ┌──────────────────────────▼───────────────────────────────────┐    │
│  │                    Agent 工具层                                │    │
│  │  @tool search_product()    — HTTP → Java / 直连 MySQL         │    │
│  │  @tool get_health_report() — HTTP → Java AiContextService     │    │
│  │  @tool get_emotion_data()  — HTTP → Java EmotionCheckin      │    │
│  │  @tool search_knowledge()  — 本地 RAG 检索                     │    │
│  └──────────────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────────────┐
│                           LLM API                                     │
│  DeepSeek-V3 (聊天/分析) / Qwen2.5 (推荐) / Embedding API            │
└──────────────────────────────────────────────────────────────────────┘

关键设计决策

  1. Tool 通过 HTTP 调 Java 而非直连 DB — Python 不直连 MySQL,数据主权在 Java 侧,安全可控
  2. ChromaDB 文件模式 — 无需额外容器,数据存在 data/chroma_db/,打包备份方便
  3. Checkpointer 先用 MemorySaver — Phase 1-2 不依赖 PostgreSQL,后续按需升级
  4. 与现有 Dify 体系共存 — 配置开关控制流量切分,异常自动 fallback

3. 项目结构

cfc-langgraph/                          # 新建 Python 项目
├── pyproject.toml                      # 依赖管理 (pip / poetry)
├── Dockerfile                          # 容器构建
├── docker-compose.yml                  # 编排 (可选)
├── .env.example                        # 环境变量模板
│
├── app/
│   ├── __init__.py
│   ├── main.py                         # FastAPI 入口 + 路由注册
│   ├── config.py                       # 配置管理
│   │
│   ├── api/                            # HTTP 接口层
│   │   ├── __init__.py
│   │   ├── chat.py                     # POST /api/v1/chat
│   │   ├── recommend.py                # POST /api/v1/recommend
│   │   ├── analyze.py                  # POST /api/v1/analyze
│   │   └── health.py                   # GET /api/v1/health
│   │
│   ├── agents/                         # LangGraph Agent 定义
│   │   ├── __init__.py
│   │   ├── chat_agent.py               # 家庭聊天 Agent
│   │   ├── recommend_agent.py          # 推荐 Agent
│   │   ├── analysis_agent.py           # 报告解读 Agent
│   │   ├── task_agent.py               # 任务解析 Agent
│   │   └── orchestrator.py             # 主管 Agent (多Agent编排)
│   │
│   ├── graphs/                         # LangGraph StateGraph 定义
│   │   ├── __init__.py
│   │   ├── chat_graph.py               # 聊天流程图
│   │   ├── recommend_graph.py          # 推荐流程图
│   │   └── analysis_graph.py           # 分析流程图
│   │
│   ├── tools/                          # Agent Tool 定义
│   │   ├── __init__.py
│   │   ├── product_tools.py            # 商品/活动/文章检索
│   │   ├── report_tools.py             # 健康报告查询
│   │   ├── emotion_tools.py            # 情绪数据查询
│   │   ├── user_tools.py               # 用户信息查询
│   │   └── knowledge_tools.py          # 知识库 RAG 检索
│   │
│   ├── rag/                            # RAG Pipeline
│   │   ├── __init__.py
│   │   ├── loader.py                   # 文档加载器
│   │   ├── splitter.py                 # 文本分块策略
│   │   ├── embeddings.py              # Embedding 封装
│   │   └── retriever.py               # 混合检索 + 重排序
│   │
│   ├── memory/                         # 记忆管理
│   │   ├── __init__.py
│   │   ├── store.py                    # 记忆存储接口
│   │   └── schemas.py                  # 记忆数据结构
│   │
│   └── models/                         # Pydantic 数据模型
│       ├── __init__.py
│       ├── chat.py                     # 聊天请求/响应
│       ├── recommend.py                # 推荐请求/响应
│       └── common.py                   # 通用模型(用户/上下文)
│
├── data/                               # 运行时数据 (gitignore)
│   ├── chroma_db/                      # ChromaDB 持久化文件
│   └── logs/                           # 日志
│
└── tests/                              # 测试
    ├── conftest.py
    ├── test_agents/
    ├── test_tools/
    └── test_rag/

4. API 设计

4.1 聊天

POST /api/v1/chat
Content-Type: application/json

{
    "query": "小明最近肠胃不好,有什么建议?",
    "user_id": 12345,
    "conversation_id": "conv_abc",     // 空串=新会话
    "context": {                        // Java 侧传来的业务上下文
        "child_id": 67890,
        "report_id": 1024,
        "mascot_code": "dolphin"
    }
}

Response:
{
    "answer": "根据小明的健康报告...",
    "conversation_id": "conv_abc",
    "sources": [
        {"type": "knowledge", "title": "儿童肠胃调理指南", "score": 0.92},
        {"type": "tool", "name": "get_health_report", "result": "..."}
    ],
    "tasks": [                          // 自动提取的任务
        {"title": "每天喝一杯益生菌", "dimension": "身", "points": 10}
    ],
    "trace_id": "trc_xxx"              // LangSmith Trace ID
}

4.2 推荐

POST /api/v1/recommend
Content-Type: application/json

{
    "user_id": 12345,
    "tags": ["肠胃", "益生菌", "消化"],
    "types": ["product", "activity"],   // 不传=全类型
    "limit": 5,
    "context": { "family_id": 333 }
}

Response:
{
    "items": [
        {
            "type": "product",
            "id": 201,
            "name": "益生菌粉剂",
            "score": 0.95,
            "reason": "适合肠胃调理"
        }
    ],
    "source": "hybrid"                  // sql / vector / hybrid
}

4.3 健康

POST /api/v1/analyze
Content-Type: application/json

{
    "report_id": 1024,
    "user_id": 12345,
    "focus": "nutrition"               // 可选: nutrition/gut/chronic/overall
}

Response:
{
    "summary": "...",
    "findings": [...],
    "recommendations": [...]
}

5. 记忆设计

┌──────────────────────────────────────────────────────────────┐
│                       Memory Manager                          │
├──────────────────────────────────────────────────────────────┤
│  Layer 1: 工作记忆 (BufferMemory)                             │
│  ├─ 当前会话最后 20 轮 (内存)                                 │
│  └─ 超出 → ConversationSummaryBufferMemory 自动摘要           │
│                                                              │
│  Layer 2: 长期事实 (EntityMemory)                             │
│  ├─ 自动提取实体: 偏好 / 健康指标 / 家庭成员                  │
│  ├─ 跨会话跟踪, 按置信度排序                                  │
│  └─ 替代现有 AiUserFact (Java 侧可同步)                      │
│                                                              │
│  Layer 3: 语义记忆 (VectorStoreRetrieverMemory)               │
│  ├─ 每次对话结束后自动向量化存储                              │
│  ├─ 新对话时召回前 3 条最相似的历史片段                      │
│  └─ 实现"我记得你上次说…"的能力                               │
└──────────────────────────────────────────────────────────────┘

同步策略:
  Python 是记忆的主写入端
  Java 侧的 ChatMirrorService 作为只读镜像 (同步 conversation + message)
  AiUserFact / AiConversationSummary → 随迁移推进逐渐废弃

6. 迁移阶段

Phase 1 (第1-2周): 基建 + 推荐 Agent

目标: Python 服务跑起来, 第一个 Agent 上线

Java 侧修改:
  ├── 新增 AiGateway.java (HTTP → Python, fallback → Dify)
  │   ├── 连接池: 5连接, 超时 15s
  │   ├── 熔断: 3次失败→停30s
  │   └── 配置开关: python.enabled=true/false
  └── RecommendationController.search()
      先调 Python → 超时/异常 → fallback 现有 Java SQL 搜索

Python 侧:
  ├── FastAPI 骨架 + 配置管理
  ├── RecommendAgent (最简单的单个 Agent, 无图)
  │   ├── @tool: search_product, search_article
  │   └── 调用 LLM 返回推荐理由
  └── ChromaDB 初始化 + 知识库文章加载

交付物:
  ├── Python 服务可启动, health check 通过
  ├── /api/v1/recommend 可返回结果
  └── Java 侧灰度开关: 5% 流量走 Python

Phase 2 (第3-4周): 聊天 Agent + RAG

目标: 替代 Dify 核心聊天能力

Python 侧:
  ├── ChatAgent (LangGraph StateGraph)
  │   ├── Node: classify_intent → 意图分类
  │   ├── Node: load_context → 调 Java Context API
  │   ├── Node: llm_call → LLM + Tool
  │   ├── Node: parse_tasks → [TASK] 提取
  │   └── Edge: 条件路由(chat/analysis/recommend)
  ├── Memory 接入
  │   ├── SummaryBufferMemory
  │   └── VectorStoreRetrieverMemory
  └── RAG Pipeline
      ├── 混合检索 (向量 + BM25)
      └── 重排序 + 压缩

Java 侧:
  ├── AIService.sendMessage() → 优先调 Python
  ├── AiContextService 改为 HTTP 数据接口 (Python 调)
  └── AiUserFact / AiConversationSummary 设为只读

交付物:
  ├── 家庭聊天通过 Python 完成
  ├── 记忆层可追溯
  └── 灰度 30% 流量

Phase 3 (第5-6周): 高级 Agent + 知识库管理

目标: 剩下场景迁移 + 知识库自动化

Python 侧:
  ├── AnalysisAgent (报告解读)
  │   ├── @tool: get_report_detail, get_survey_data
  │   └── LangGraph 图: 分析→发现→建议
  ├── MultiModalAgent (舌诊, 保留 Dify Workflow fallback)
  ├── KnowledgePipeline (自动化知识库更新)
  │   ├── 定时任务: 从 Java 拉文章→分块→向量化
  │   └── 增量更新: 只处理有变动的文档
  └── LangSmith Trace 接入 (调试用)

Java 侧:
  ├── DifySyncService.java 标记废弃
  └── 知识库管理接口调整为 Python 侧触发

交付物:
  ├── 全场景 AI 能力由 Python 覆盖
  ├── 知识库自动同步
  └── 灰度 100% (Dify 降级为冷备)

Phase 4 (第7-8周): 收尾

目标: 运维完善, 代码清理

Python 侧:
  ├── 性能调优 (连接池、缓存、异步)
  ├── 监控接入 (指标暴露 / 日志聚合)
  ├── 部署文档 + 运维手册
  └── Docker Compose 编排

Java 侧:
  ├── 清理废弃代码 (Dify 相关)
  ├── 调整 ChatMirrorService 同步策略
  └── 移除 Dify 配置项

交付物:
  ├── 运维手册完成
  ├── 老代码清理 PR
  └── 架构文档更新

7. 部署方案

方案 A: Docker Compose (推荐)

version: "3.8"
services:
  cfc-backend:
    build: ./cfc-backend
    ports: "9082:9082"
    networks: [app-net]

  langgraph-svc:
    build: ./cfc-langgraph
    ports: "9000:9000"
    environment:
      - LLM_API_KEY=${LLM_API_KEY}
      - LLM_BASE_URL=${LLM_BASE_URL}
      - JAVA_CONTEXT_URL=http://cfc-backend:9082/api/ai/context
      - CHROMA_DB_PATH=/data/chroma_db
      - LANGCHAIN_TRACING_V2=true
      - LANGCHAIN_API_KEY=${LANGCHAIN_API_KEY}
    volumes:
      - langgraph-data:/data
    networks: [app-net]
    restart: unless-stopped

networks:
  app-net:

volumes:
  langgraph-data:

方案 B: 直接 systemd + 文件模式

Python:  systemd 管理, Python 3.11 venv
Chroma:  文件模式, 目录 /var/lib/cfc-langgraph/chroma_db
日志:    文件 → rsyslog / 直接 stdout
部署:    git pull → pip install -r requirements → systemctl restart

资源需求

资源 最低 推荐
CPU 0.5 核 1 核
内存 256MB 512MB
磁盘 1GB (代码+Chroma) 5GB (含日志)

8. 风险与缓解

风险 概率 影响 缓解措施
Python 服务不稳定 Phase 1 灰度 5% + Dify Fallback + 熔断
LangGraph 学习成本 Phase 1 从最简 Agent 开始, 不追逐复杂图结构
记忆数据迁移丢失 Phase 2 前 Java 侧保持双写, 验证对齐后再切
LLM API 费用增加 Agent 多轮多约 10% token, 对比 Dify 调用成本基本持平
ChromaDB 性能瓶颈 10 万条内单机足够, 超出后切 PGVector 或 Milvus

9. 目录清单

cfc-langgraph/
├── pyproject.toml
├── Dockerfile
├── docker-compose.yml
├── .env.example
├── .gitignore
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── config.py
│   ├── api/
│   ├── agents/
│   ├── graphs/
│   ├── tools/
│   ├── rag/
│   ├── memory/
│   └── models/
├── data/
└── tests/
cfc-backend (修改清单):
  src/main/java/.../service/AiGateway.java        (新增)
  src/main/java/.../service/AIService.java         (改造: 加 Python 路由)
  src/main/java/.../service/AiContextService.java  (改造: 加 HTTP API)
  src/main/java/.../controller/ai/AIChatController.java (改造: 调 AiGateway)
  application.yml                                  (加 python.* 配置)