2026-07-20-langgraph-phase4.md 24 KB

LangGraph Sidecar — Phase 4 实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 性能调优、监控接入、部署标准化、清理 Dify 遗留代码, 使 LangGraph sidecar 达到可长期维护的生产状态。

Architecture: Prometheus 指标暴露 + 结构化日志 + Docker Compose 生产编排 + Java 端废弃代码清理。

Tech Stack: Python 3.11, FastAPI, Prometheus (client), Docker Compose, SLF4J

Global Constraints

  • Python 3.11+,性能调优不改变外部接口签名
  • 监控指标通过 HTTP /metrics 暴露 (Prometheus 拉取模式)
  • Docker Compose 同时编排 Java + Python 服务
  • Java 端只清理 Dify 相关代码, 不动业务逻辑
  • 所有项目文件路径相对于 D:\workspace\cfc\

Task 1: 性能调优

Files:

  • Modify: cfc-langgraph/app/tools/java_client.py (连接池复用)
  • Modify: cfc-langgraph/app/rag/embeddings.py (缓存)

  • [x] Step 1: 优化 JavaClient 连接池

    # app/tools/java_client.py — 确保 httpx.AsyncClient 复用连接池
    
    import httpx
    from typing import Optional
    from app.config import settings
    import logging
    
    logger = logging.getLogger(__name__)
    
    
    class JavaClient:
    """Java 后端 HTTP 客户端 (单例, 复用连接池)"""
    
    _instance: Optional["JavaClient"] = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._client = None
            cls._instance._base_url = settings.java_base_url
        return cls._instance
    
    async def _get_client(self) -> httpx.AsyncClient:
        if self._client is None:
            limits = httpx.Limits(
                max_connections=10,
                max_keepalive_connections=5,
                keepalive_expiry=30,
            )
            self._client = httpx.AsyncClient(
                base_url=self._base_url,
                timeout=httpx.Timeout(10.0, connect=3.0),
                limits=limits,
            )
        return self._client
    
    async def close(self):
        if self._client:
            await self._client.aclose()
            self._client = None
            JavaClient._instance = None
    
    # ... 其余方法保持与 Phase 1/2/3 一致
    
  • [x] Step 2: Embedding 实例缓存 (单例模式)

    # app/rag/embeddings.py — 已实现单例, 验证即可
    # 确保 _embeddings 不会被重复创建
    
  • [x] Step 3: 添加请求耗时中间件

    # 在 app/main.py 中追加
    
    import time
    from fastapi import Request
    import logging
    
    logger = logging.getLogger(__name__)
    
    @app.middleware("http")
    async def timing_middleware(request: Request, call_next):
    start = time.perf_counter()
    response = await call_next(request)
    elapsed = time.perf_counter() - start
        
    # 慢查询日志: > 5s 的记录
    if elapsed > 5:
        logger.warning("SLOW_REQUEST: %s %s took %.2fs",
                       request.method, request.url.path, elapsed)
    else:
        logger.debug("REQUEST: %s %s took %.2fs",
                     request.method, request.url.path, elapsed)
        
    response.headers["X-Response-Time"] = f"{elapsed:.3f}s"
    return response
    
  • [x] Step 4: ChromaDB 批量写入优化

知识库同步时使用批量写入替代逐条写入:

# app/tasks/knowledge_sync.py — 确认已使用 aadd_documents 批量 API
# 分块数 < 100 时一次提交, > 100 时分批

BATCH_SIZE = 100

async def sync_knowledge_base():
    # ... 前置逻辑不变 ...
    
    # 分批写入
    for i in range(0, len(chunks), BATCH_SIZE):
        batch = chunks[i:i + BATCH_SIZE]
        await vectorstore.aadd_documents(batch)
        vectorstore.persist()
        logger.debug("知识库同步进度: %d/%d", i + len(batch), len(chunks))
    
    # ... 后续逻辑不变 ...
  • [x] Step 5: Commit

    git add cfc-langgraph/app/tools/java_client.py \
      cfc-langgraph/app/main.py \
      cfc-langgraph/app/tasks/knowledge_sync.py
    git commit -m "perf(langgraph): connection pool, timing middleware, batch write"
    

Task 2: 监控指标暴露

Files:

  • Create: cfc-langgraph/app/monitoring.py
  • Modify: cfc-langgraph/app/main.py (注册 metrics 路由)

  • [x] Step 1: 创建 app/monitoring.py

    """Prometheus 监控指标"""
    from prometheus_client import Counter, Histogram, Gauge, generate_latest
    from fastapi import APIRouter, Response
    import time
    import functools
    
    # ── 指标定义 ──
    
    # LLM 调用
    llm_calls_total = Counter(
    "llm_calls_total", "Total LLM API calls",
    ["model", "status"],
    )
    llm_duration_seconds = Histogram(
    "llm_duration_seconds", "LLM call duration",
    ["model"],
    buckets=(0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 30.0),
    )
    llm_tokens_total = Counter(
    "llm_tokens_total", "Total tokens used",
    ["model", "type"],  # type = input / output
    )
    
    # RAG 检索
    rag_retrievals_total = Counter(
    "rag_retrievals_total", "Total RAG retrievals",
    ["method"],  # method = vector / bm25 / ensemble
    )
    rag_duration_seconds = Histogram(
    "rag_duration_seconds", "RAG retrieval duration",
    ["method"],
    buckets=(0.01, 0.05, 0.1, 0.5, 1.0),
    )
    
    # Agent 调用
    agent_calls_total = Counter(
    "agent_calls_total", "Total Agent invocations",
    ["agent_type"],  # agent_type = chat / recommend / analysis
    )
    agent_duration_seconds = Histogram(
    "agent_duration_seconds", "Agent execution duration",
    ["agent_type"],
    buckets=(0.5, 1.0, 2.0, 5.0, 10.0, 30.0, 60.0),
    )
    
    # Java 后端调用
    java_calls_total = Counter(
    "java_calls_total", "Total calls to Java backend",
    ["endpoint", "status"],
    )
    java_duration_seconds = Histogram(
    "java_duration_seconds", "Java backend call duration",
    ["endpoint"],
    buckets=(0.01, 0.05, 0.1, 0.5, 1.0, 2.0),
    )
    
    # 知识库同步
    kb_sync_duration = Gauge(
    "kb_sync_duration_seconds", "Last knowledge base sync duration"
    )
    kb_sync_documents = Gauge(
    "kb_sync_documents_total", "Documents processed in last sync"
    )
    
    # ── 装饰器 ──
    
    def monitor_agent(agent_type: str):
    """Agent 性能监控装饰器"""
    def decorator(func):
        @functools.wraps(func)
        async def wrapper(*args, **kwargs):
            agent_calls_total.labels(agent_type=agent_type).inc()
            start = time.perf_counter()
            try:
                result = await func(*args, **kwargs)
                agent_duration_seconds.labels(agent_type=agent_type).observe(
                    time.perf_counter() - start)
                return result
            except Exception as e:
                agent_duration_seconds.labels(agent_type=agent_type).observe(
                    time.perf_counter() - start)
                raise
        return wrapper
    return decorator
    
    
    # ── Metrics 路由 ──
    
    router = APIRouter(tags=["monitoring"])
    
    @router.get("/metrics")
    async def metrics():
    return Response(
        content=generate_latest(),
        media_type="text/plain; charset=utf-8",
    )
    
    
    @router.get("/api/v1/health")
    async def detailed_health():
    """详细健康检查 (含组件状态)"""
    status = {"status": "ok", "components": {}}
    
    # 检查 ChromaDB
    try:
        from app.config import settings
        import os
        chroma_path = settings.chroma_db_path
        status["components"]["chromadb"] = {
            "status": "ok",
            "path": chroma_path,
            "exists": os.path.exists(chroma_path),
        }
    except Exception as e:
        status["components"]["chromadb"] = {"status": "error", "message": str(e)}
        status["status"] = "degraded"
    
    # 检查 LLM API
    try:
        from langchain_openai import ChatOpenAI
        llm = ChatOpenAI(
            model=settings.llm_model,
            api_key=settings.llm_api_key,
            base_url=settings.llm_base_url,
            max_tokens=5,
        )
        await llm.ainvoke("ping")
        status["components"]["llm"] = {"status": "ok"}
    except Exception as e:
        status["components"]["llm"] = {"status": "error", "message": str(e)}
        status["status"] = "degraded"
    
    # 检查 Java 后端
    try:
        from app.tools.java_client import JavaClient
        client = JavaClient()
        jc = await client._get_client()
        resp = await jc.get("/health")
        if resp.status_code == 200:
            status["components"]["java_backend"] = {"status": "ok"}
        else:
            status["components"]["java_backend"] = {"status": "error", "code": resp.status_code}
            status["status"] = "degraded"
    except Exception as e:
        status["components"]["java_backend"] = {"status": "error", "message": str(e)}
        status["status"] = "degraded"
    
    return status
    
  • [x] Step 2: 注册 metrics 路由到 app/main.py

    from app import monitoring  # 新增
    
    app.include_router(monitoring.router)  # 新增
    
  • [x] Step 3: 在 Agent 调用处插入监控装饰器

    # app/graphs/recommend_graph.py — RecommendAgent.run()
    
    from app.monitoring import monitor_agent
    
    class RecommendAgent:
    @monitor_agent("recommend")
    async def run(self, query: str, tags: list[str], limit: int = 5) -> dict:
        # ... 原有代码不变 ...
    

同理在 chat_graph.pyllm_call 节点和 analysis_graph.pyanalyze 节点也加上。

  • [x] Step 4: 添加 prometheus-client 依赖到 pyproject.toml

    dependencies = [
    # ... 现有依赖保持不变 ...
    "prometheus-client>=0.21",
    ]
    
  • [x] Step 5: 验证 metrics 端点

    cd cfc-langgraph
    pip install -e .
    uvicorn app.main:app --port 9000 &
    curl http://localhost:9000/metrics
    # 预期: 以 # HELP / # TYPE 开头的 Prometheus 格式文本
    kill %1
    
  • [x] Step 6: Commit

    git add cfc-langgraph/app/monitoring.py \
      cfc-langgraph/app/main.py \
      cfc-langgraph/app/graphs/recommend_graph.py \
      cfc-langgraph/app/graphs/chat_graph.py \
      cfc-langgraph/app/graphs/analysis_graph.py \
      cfc-langgraph/pyproject.toml
    git commit -m "feat(langgraph): Prometheus metrics and detailed health check"
    

Task 3: 结构化日志

Files:

  • Create: cfc-langgraph/app/log_config.py
  • Modify: cfc-langgraph/app/main.py

  • [x] Step 1: 创建 app/log_config.py

    """结构化日志配置"""
    import logging
    import json
    import sys
    from datetime import datetime, timezone
    
    
    class JsonFormatter(logging.Formatter):
    """JSON 日志格式化器 (适合生产环境日志聚合)"""
    
    def format(self, record: logging.LogRecord) -> str:
        log_entry = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
        }
        if hasattr(record, "trace_id"):
            log_entry["trace_id"] = record.trace_id
        if record.exc_info and record.exc_info[0]:
            log_entry["exception"] = self.formatException(record.exc_info)
        return json.dumps(log_entry, ensure_ascii=False)
    
    
    def setup_logging(level: str = "INFO", json_format: bool = False):
    """配置日志
    
    Args:
        level: 日志级别
        json_format: True=JSON 格式, False=开发友好格式
    """
    root = logging.getLogger()
    root.setLevel(getattr(logging, level.upper(), logging.INFO))
    
    # 清除已有 handler
    root.handlers.clear()
    
    handler = logging.StreamHandler(sys.stdout)
    
    if json_format:
        handler.setFormatter(JsonFormatter())
    else:
        handler.setFormatter(logging.Formatter(
            "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S",
        ))
    
    root.addHandler(handler)
    
    # 第三方库日志级别
    logging.getLogger("httpx").setLevel(logging.WARNING)
    logging.getLogger("chromadb").setLevel(logging.WARNING)
    logging.getLogger("langchain").setLevel(logging.WARNING)
    
  • [x] Step 2: 在 app/main.py 启动时配置

    @app.on_event("startup")
    async def startup():
    import os
    json_logs = os.getenv("JSON_LOGS", "false").lower() == "true"
    setup_logging(level=settings.log_level, json_format=json_logs)
    # ... 其余初始化代码 ...
    
  • [x] Step 3: Commit

    git add cfc-langgraph/app/log_config.py cfc-langgraph/app/main.py
    git commit -m "feat(langgraph): structured JSON logging"
    

Task 4: 生产级 Docker Compose 编排

Files:

  • Create: cfc-langgraph/docker-compose.yml (覆盖 Phase 1 的开发版)
  • Create: cfc-langgraph/Dockerfile (多阶段构建)

  • [x] Step 1: 优化 Dockerfile (多阶段构建)

    # ── 构建阶段 ──
    FROM python:3.11-slim AS builder
    
    WORKDIR /build
    COPY pyproject.toml .
    RUN pip install --no-cache-dir -e . && \
    pip install --no-cache-dir gunicorn
    
    # ── 运行阶段 ──
    FROM python:3.11-slim
    
    # 时区
    ENV TZ=Asia/Shanghai
    RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
    
    WORKDIR /app
    
    COPY --from=builder /usr/local/lib/python3.11/site-packages/ /usr/local/lib/python3.11/site-packages/
    COPY --from=builder /usr/local/bin/ /usr/local/bin/
    
    COPY app/ app/
    COPY data/ data/ 2>/dev/null || true
    
    RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
    USER appuser
    
    EXPOSE 9000
    
    CMD ["gunicorn", "app.main:app", \
     "--worker-class", "uvicorn.workers.UvicornWorker", \
     "--bind", "0.0.0.0:9000", \
     "--workers", "2", \
     "--timeout", "60", \
     "--keep-alive", "10", \
     "--access-logfile", "-", \
     "--error-logfile", "-"]
    
  • [x] Step 2: 创建生产 docker-compose.yml

    version: "3.8"
    
    services:
    cfc-backend:
    build:
      context: ../cfc-backend
      dockerfile: Dockerfile
    ports:
      - "9082:9082"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - python.enabled=true
      - python.base-url=http://langgraph-svc:9000
    networks:
      - cfc-net
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9082/actuator/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    
    langgraph-svc:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "9000:9000"
    env_file:
      - .env.production
    environment:
      - JAVA_BASE_URL=http://cfc-backend:9082
      - CHROMA_DB_PATH=/data/chroma_db
      - JSON_LOGS=true
      - LANGCHAIN_TRACING_V2=${LANGCHAIN_TRACING_V2:-false}
    volumes:
      - langgraph-data:/data
    networks:
      - cfc-net
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/api/v1/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    depends_on:
      - cfc-backend
    
    # Prometheus (可选: 指标采集)
    prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    networks:
      - cfc-net
    restart: unless-stopped
    profiles:
      - monitoring
    
    networks:
    cfc-net:
    
    volumes:
    langgraph-data:
    prometheus-data:
    
  • [x] Step 3: 创建 Prometheus 配置

    # cfc-langgraph/prometheus.yml
    global:
    scrape_interval: 15s
    evaluation_interval: 15s
    
    scrape_configs:
    - job_name: "langgraph-svc"
    static_configs:
      - targets: ["langgraph-svc:9000"]
    metrics_path: /metrics
    
  • [x] Step 4: 创建生产环境 .env.production 模板

    # cfc-langgraph/.env.production (不上传 git, 手动部署时创建)
    LLM_API_KEY=sk-your-production-key
    LLM_BASE_URL=https://api.deepseek.com/v1
    LLM_MODEL=deepseek-chat
    
    EMBEDDING_API_KEY=${LLM_API_KEY}
    EMBEDDING_BASE_URL=${LLM_BASE_URL}
    EMBEDDING_MODEL=text-embedding-v3
    
    LANGCHAIN_TRACING_V2=false
    LANGCHAIN_API_KEY=
    LANGCHAIN_PROJECT=cfc-langgraph-prod
    
    LOG_LEVEL=info
    JSON_LOGS=true
    
  • [x] Step 5: Commit

    git add cfc-langgraph/Dockerfile \
      cfc-langgraph/docker-compose.yml \
      cfc-langgraph/prometheus.yml \
      cfc-langgraph/.env.production
    git commit -m "ops(langgraph): production Docker Compose with monitoring"
    

Task 5: Java 端废弃代码清理

Files:

  • Modify: cfc-backend/src/main/java/com/etotem/cfc/service/DifySyncService.java (标记废弃)
  • Modify: cfc-backend/src/main/java/com/etotem/cfc/service/AIService.java (简化, 移除 Dify 直接调用)
  • Delete: cfc-backend/src/main/resources/application.yml 中 dify.* 配置 (可选保留 Dify fallback)

  • [x] Step 1: 标记 DifySyncService 废弃

    // DifySyncService.java 头部加 @Deprecated 注解
    
    /**
    * 知识库同步服务
    * @deprecated 知识库同步已迁移到 LangGraph Python 服务 (cfc-langgraph/app/tasks/knowledge_sync.py)
    * 计划在下一个大版本移除
    */
    @Deprecated
    @Service
    public class DifySyncService {
    // ... 代码保持不变 ...
    }
    
  • [x] Step 2: 简化 AIService.java

Dify 相关的方法合并为单一的 fallback 块:

// AIService.java
// sendMessageToDify() 保留为私有 fallback 方法
// 公开的 sendMessage() 优先 Python, 仅当 Python 不可用时调用 sendMessageToDify()

/**
 * 发送聊天消息 (自动选择后端)
 * 优先 LangGraph Python 服务, 失败时回退 Dify
 */
public Map<String, Object> sendMessage(String query, String userId,
                                       String conversationId,
                                       Map<String, Object> inputs) {
    // Phase 2 已实现的逻辑: aiGateway.chat() → fallback sendMessageToDify()
    // 如果 aiGateway 长期稳定, 可在此处移除 Dify fallback
}
  • [x] Step 3: 清理 application.yml 中的 Dify 配置 (保留 fallback 选项)

    # application.yml — 保留 dify.* 配置但标记为降级通道
    dify:
    base-url: http://dify.bianwoyou.cn/v1
    api-key: ${DIFY_API_KEY:}
    nutrition-api-key: ${DIFY_NUTRITION_API_KEY:}
    tongue-api-key: ${DIFY_TONGUE_API_KEY:}
    callback-secret: ${DIFY_CALLBACK_SECRET:}
    # 注意: 以上配置仅作为 LangGraph 不可用时的降级通道
    # 正常情况下应通过 python.enabled=true 启用 LangGraph
    
  • [x] Step 4: 编译验证

    cd cfc-backend
    mvn clean compile -q
    # 预期: BUILD SUCCESS (可能有 @Deprecated 警告, 不影响编译)
    
  • [x] Step 5: Commit

    git add cfc-backend/src/main/java/com/etotem/cfc/service/DifySyncService.java \
      cfc-backend/src/main/java/com/etotem/cfc/service/AIService.java \
      cfc-backend/src/main/resources/application.yml
    git commit -m "chore(backend): mark DifySyncService as deprecated, clean up AIService"
    

Task 6: 运维文档

Files:

  • Create: cfc-langgraph/docs/deployment.md
  • Create: cfc-langgraph/docs/operations.md

  • [x] Step 1: 创建 docs/deployment.md

    # LangGraph Sidecar 部署文档
    
    ## 前置依赖
    
    - Docker + Docker Compose (推荐)
    - 或 Python 3.11+ + pip (直接部署)
    - LLM API Key (DeepSeek / OpenAI 兼容)
    
    ## 目录结构
    
    

cfc-langgraph/ ├── Dockerfile ├── docker-compose.yml ├── prometheus.yml ├── .env.production # 生产配置 (手动创建, 不上传 git) ├── app/ # 服务代码 │ ├── main.py # FastAPI 入口 │ ├── config.py # 配置管理 │ ├── api/ # HTTP 接口 │ ├── agents/ # Agent 定义 │ ├── graphs/ # LangGraph StateGraph │ ├── tools/ # Agent Tool │ ├── rag/ # RAG Pipeline │ ├── memory/ # 三层记忆 │ └── tasks/ # 定时任务 ├── data/ │ └── chroma_db/ # ChromaDB 持久化 (自动创建) └── docs/

├── deployment.md        # 本文件
└── operations.md        # 运维手册

## Docker Compose 部署

bash

1. 创建生产配置

cp .env.example .env.production

编辑 .env.production 填入 LLM_API_KEY

2. 构建并启动

docker-compose up -d

3. 验证

curl http://localhost:9000/api/v1/health

4. 查看日志

docker-compose logs -f langgraph-svc


## 直接部署 (无 Docker)

bash

1. 安装依赖

pip install -e .

2. 配置环境变量

export LLM_API_KEY=sk-xxx export JAVA_BASE_URL=http://localhost:9082

3. 启动

gunicorn app.main:app \ --worker-class uvicorn.workers.UvicornWorker \ --bind 0.0.0.0:9000 \ --workers 2 \ --timeout 60 \ --access-logfile - \ --error-logfile -


## 环境变量说明

| 变量 | 必填 | 说明 |
|------|------|------|
| LLM_API_KEY | 是 | LLM API Key |
| LLM_BASE_URL | 否 | 默认 https://api.deepseek.com/v1 |
| LLM_MODEL | 否 | 默认 deepseek-chat |
| JAVA_BASE_URL | 是 | Java 后端地址 |
| CHROMA_DB_PATH | 否 | ChromaDB 持久化路径, 默认 ./data/chroma_db |
| LOG_LEVEL | 否 | 日志级别, 默认 info |
| JSON_LOGS | 否 | 启用 JSON 日志格式, 默认 false |

## 健康检查

GET /api/v1/health

Response: { "status": "ok", "components": {

"chromadb": {"status": "ok", "path": "...", "exists": true},
"llm": {"status": "ok"},
"java_backend": {"status": "ok"}

} }

  • [x] Step 2: 创建 docs/operations.md

    # LangGraph Sidecar 运维手册
    
    ## 日常监控
    
    ### 1. 健康检查
    
    生产环境建议配置 30 秒定时健康检查:
    
    

    bash

curl -f http://localhost:9000/api/v1/health


预期返回 `{"status":"ok"}`。组件降级时返回 `{"status":"degraded"}`。

### 2. Prometheus 指标

GET /metrics

关键指标:

  • agent_calls_total{agent_type="chat|recommend|analysis"}
  • agent_duration_seconds{agent_type="..."}
  • llm_calls_total{model="deepseek-chat",status="ok|error"}
  • llm_duration_seconds{model="..."}
  • kb_sync_duration_seconds # 知识库同步耗时

    
    ### 3. 日志
    
    JSON 格式日志可直接接入 ELK / Loki:
    
    

    json

{"timestamp":"2026-07-20T10:00:00","level":"WARN","logger":"app.main","message":"SLOW_REQUEST: POST /api/v1/chat took 8.23s"}


## 常见问题

### Python 服务无法启动

bash

检查依赖

pip list | grep -E "fastapi|langchain|langgraph"

检查配置

python -c "from app.config import settings; print(settings.llm_model)"

检查端口占用

netstat -ano | grep 9000


### LLM 调用失败

1. 检查 `.env.production` 中的 `LLM_API_KEY` 是否有效
2. 检查 `LLM_BASE_URL` 是否可访问
3. 查看日志: `docker-compose logs langgraph-svc | grep llm_call`

### ChromaDB 损坏

bash

删除后重建 (知识库会自动同步)

rm -rf data/chroma_db/ docker-compose restart langgraph-svc

或触发手动同步

curl -X POST http://localhost:9000/api/v1/admin/kb-sync


### 知识库同步失败

bash

检查 Java 后端是否可访问

curl http://localhost:9082/health

检查文章接口

curl -X POST http://localhost:9082/api/article/updated-since \ -H "Content-Type: application/json" \ -d '{"since":"2026-01-01T00:00:00","status":"published"}'

手动触发同步

docker-compose exec langgraph-svc python -c " import asyncio from app.tasks.knowledge_sync import sync_knowledge_base asyncio.run(sync_knowledge_base()) "


### Java 端回退 Dify

如果 Python 服务异常, Java 会自动回退 Dify:

yaml

application.yml 检查配置

python: enabled: true circuit-breaker:

failure-threshold: 3
reset-timeout-ms: 30000

熔断器打开时, Java 侧日志会输出 `AiGateway 熔断器已打开`, 等待 `reset-timeout-ms` 后自动半开重试。

## 扩缩容

LangGraph 服务是无状态的 (ChromaDB 在共享存储上):

yaml

docker-compose 增加副本数

services: langgraph-svc:

deploy:
  replicas: 2

注意: ChromaDB 文件模式不支持并发写入, 多副本时知识库同步需加锁或切 PGVector。

## 备份

bash

ChromaDB 数据

tar czf chromabackup$(date +%Y%m%d).tar.gz data/chroma_db/

  • [x] Step 3: Commit

    git add cfc-langgraph/docs/
    git commit -m "docs(langgraph): deployment and operations manual"
    

Phase 4 自审清单

  • Python 连接池复用 (单例 + httpx limits)
  • 请求耗时中间件 + 慢查询告警
  • ChromaDB 批量写入 (每批 100 条)
  • Prometheus 指标: LLM/RAG/Agent/Java/知识库
  • 详细健康检查: ChromaDB/LLM/Java 组件状态
  • Agent 监控装饰器
  • 结构化 JSON 日志
  • 生产 Dockerfile (多阶段构建)
  • Docker Compose 编排 (Java + Python + Prometheus)
  • DifySyncService @Deprecated 标记
  • AIService 简化
  • 部署手册 (deployment.md)
  • 运维手册 (operations.md)