Procházet zdrojové kódy

docs: switch aftersales agent design to langgraph

wzq před 1 měsícem
rodič
revize
0cb5bad243

+ 367 - 72
docs/superpowers/specs/2026-07-08-enterprise-aftersales-agent-design.md

@@ -11,7 +11,7 @@
 首版采用双服务垂直切片:
 
 - Java `zsElectric-boot`:企业业务系统和 MCP Server,负责业务工具、审批、冻结动作、审计、售后政策和数据库。
-- Python `zsElectric-agent-service`:独立兄弟服务,使用 FastAPI 作为服务框架,使用 OpenAI Agents SDK 作为 Agent 编排层,作为 MCP Client 调用 Java MCP Server。
+- Python `zsElectric-agent-service`:独立兄弟服务,使用 FastAPI 作为服务框架,使用 LangGraph 作为 Agent 状态图编排层,作为 MCP Client 调用 Java MCP Server。
 - PostgreSQL + pgvector:作为售后政策 RAG 知识库。
 - Swagger 或 Postman:用于审批演示,不开发审批管理页面。
 
@@ -27,16 +27,18 @@
 ```text
 用户
   -> Python zsElectric-agent-service (FastAPI)
-       -> OpenAI Agents SDK
+       -> LangGraph StateGraph
+       -> LLM Adapter
        -> RAG Retriever (PostgreSQL + pgvector)
        -> MCP Client
+       -> Graph Checkpointer
   -> Java zsElectric-boot MCP Server
        -> tool registry / whitelist / risk policy
        -> approval / pending action / audit log
        -> existing service / mapper / DB
 ```
 
-Python 只负责 Agent 编排:意图判断、对话状态、工具选择、RAG 上下文组装、最终建议生成。
+Python 只负责 Agent 编排:LangGraph 状态流转、意图判断、对话状态、工具选择、RAG 上下文组装、最终建议生成。
 
 Java 负责企业能力暴露:通过 MCP tools 暴露受控业务动作,内部仍然走现有 service 层,保留鉴权、参数校验、风险分级、审批、审计和幂等保护。
 
@@ -65,7 +67,9 @@ Java MCP Server 暴露的工具必须来自白名单,并带明确参数 schema
 ```text
 app
 ├─ main.py          # FastAPI app
-├─ agents           # OpenAI Agents SDK agent 定义
+├─ graph            # LangGraph StateGraph、state、nodes、edges
+├─ checkpoint       # LangGraph checkpoint 持久化适配
+├─ llm              # OpenAI兼容LLM客户端、Prompt、输出解析
 ├─ mcp_client       # 连接 Java MCP Server
 ├─ rag              # pgvector 检索和上下文组装
 ├─ schemas          # 请求、响应、工具结果结构
@@ -82,88 +86,371 @@ GET  /api/v1/agent/runs/{trace_id}
 GET  /health
 ```
 
-## LLM 配置
+## LangGraph 编排
 
-LLM 运行配置归属 Python `zsElectric-agent-service`。Java `zsElectric-boot` 不直接调用大模型,Java 只作为 MCP Server 暴露受控企业工具。这样模型供应商、模型版本、prompt、RAG 参数和工具调用上限都集中在 Agent 编排层管理
+首版不再使用 OpenAI Agents SDK。Python `zsElectric-agent-service` 使用 LangGraph 显式建模售后处理状态图,LLM 只作为图中节点的模型能力,MCP 工具调用、审批暂停、恢复执行和错误降级都由图节点和边控制
 
-首版使用显式模型配置,不依赖 SDK 默认值。默认建议
+状态图节点
 
 ```text
-主 Agent 模型: gpt-5.5
-快速分类/意图模型: gpt-5.4-mini
-Embedding 模型: text-embedding-3-small
-Embedding 维度: 1536
+receive_input
+  -> detect_intent
+  -> retrieve_policy
+  -> query_order
+  -> plan_action
+  -> route_by_risk
+      -> create_work_order
+      -> request_approval
+  -> final_answer
+
+resume_after_approval
+  -> load_run_state
+  -> call_java_resume_tool
+  -> final_answer
 ```
 
-`gpt-5.5` 用于最终建议生成、复杂售后判断和审批前说明;`gpt-5.4-mini` 用于意图分类、轻量改写、简单摘要等低成本步骤。后续如果模型升级,只改配置和评估基线,不改业务工具协议。
+核心状态 `AfterSalesAgentState`:
+
+```text
+trace_id
+thread_id
+user_input
+messages
+intent
+issue_type
+risk_level
+policy_evidence
+order_context
+tool_plan
+tool_results
+approval_id
+pending_action_id
+resume_result
+final_answer
+error
+```
+
+`thread_id` 默认等于 `trace_id`。LangGraph checkpointer 负责保存 Python 图状态,用于 `/resume/{approval_id}` 找回审批前的对话、证据和工具结果;Java `agent_approval` 与 `agent_pending_action` 仍是审批和冻结动作的事实来源。审批通过后,Python 从 checkpointer 读取状态,再通过 MCP 调 Java `resume_after_approval(approval_id)`,不能重新让模型生成退款或补偿参数。
+
+生产环境使用 PostgreSQL checkpointer,测试环境可使用内存 checkpointer。所有高风险中断点必须在进入 `request_approval` 后保存 checkpoint,并把 `approval_id`、`pending_action_id`、`trace_id` 写入状态。
+
+## LLM 与 LangGraph 接入方案
+
+主链路采用 LangGraph 编排:Python `zsElectric-agent-service` 接入真实 LLM,Java `zsElectric-boot` 继续只做 MCP 工具控制面。Python 的 LangGraph 节点负责模型调用、意图识别、工具计划、MCP 调用路由、审批暂停和最终回复;Java 负责工具白名单、风险分级、审批、冻结动作、幂等、审计和真实业务落库。
+
+该方案的核心边界是:LLM 可以“建议调用哪个工具、用什么参数”,但不能直接访问数据库,也不能绕过 Java 的 MCP 工具治理。退款、补偿等高风险动作即使由 LLM 识别出来,也只能通过 Java `request_refund`、`request_compensation` 创建审批和冻结动作,审批通过后再由 `resume_after_approval` 恢复执行。
+
+### 配置归属
+
+LLM 与 LangGraph 配置归属 Python `zsElectric-agent-service`,不复用 Java 现有 `platform.ai` 的 `ai:` 配置作为售后 Agent 主链路配置。原因是售后 Agent 的模型、Prompt、状态图节点、工具计划、RAG 参数、最大步数、checkpoint 和降级策略都属于 Agent 编排层;Java `platform.ai` 可以继续服务后台 AI 命令系统,二者避免共享配置造成运行边界不清。
+
+Python 首版使用 OpenAI 兼容协议,支持 OpenAI、DashScope/Qwen、DeepSeek 等兼容 `/chat/completions` 的模型服务。模型名称不写死在代码里,由环境变量或配置文件决定。
 
 Python `.env` 示例:
 
 ```dotenv
-OPENAI_API_KEY=
-OPENAI_BASE_URL=https://api.openai.com/v1
-OPENAI_DEFAULT_MODEL=gpt-5.5
-
-AGENT_MAIN_MODEL=gpt-5.5
-AGENT_FAST_MODEL=gpt-5.4-mini
-AGENT_TEMPERATURE=0.2
-AGENT_REASONING_EFFORT=medium
-AGENT_MAX_TURNS=8
-AGENT_TIMEOUT_SECONDS=60
-AGENT_TOOL_TIMEOUT_SECONDS=15
-
-EMBEDDING_MODEL=text-embedding-3-small
+AGENT_LLM_ENABLED=true
+AGENT_LLM_PROVIDER=openai
+AGENT_LLM_API_KEY=
+AGENT_LLM_BASE_URL=https://api.openai.com/v1
+AGENT_LLM_MODEL=
+AGENT_LLM_FAST_MODEL=
+AGENT_LLM_TEMPERATURE=0.2
+AGENT_LLM_TIMEOUT_SECONDS=60
+AGENT_LLM_TOOL_TIMEOUT_SECONDS=15
+
+LANGGRAPH_CHECKPOINT_ENABLED=true
+LANGGRAPH_CHECKPOINT_DSN=postgresql+psycopg://agent:agent@127.0.0.1:5432/zselectric_agent
+LANGGRAPH_THREAD_ID_STRATEGY=trace_id
+LANGGRAPH_MAX_STEPS=8
+
+AGENT_PROMPT_VERSION=aftersales-agent-v1
+AGENT_TOOL_PLAN_MODE=structured_json
+AGENT_RULE_FALLBACK_ENABLED=true
+
+EMBEDDING_PROVIDER=openai
+EMBEDDING_API_KEY=
+EMBEDDING_BASE_URL=https://api.openai.com/v1
+EMBEDDING_MODEL=
 EMBEDDING_DIMENSIONS=1536
+
 RAG_TOP_K=5
 RAG_MIN_SCORE=0.75
 RAG_MAX_CONTEXT_TOKENS=3000
 
-MCP_SERVER_URL=http://localhost:8080/api/v1/agent/mcp
-MCP_CONNECT_TIMEOUT_SECONDS=5
-MCP_READ_TIMEOUT_SECONDS=30
-```
-
-Python `settings.yaml` 示例:
-
-```yaml
-llm:
-  provider: openai
-  base_url: ${OPENAI_BASE_URL}
-  default_model: ${OPENAI_DEFAULT_MODEL}
-  agents:
-    aftersales_main:
-      model: ${AGENT_MAIN_MODEL}
-      temperature: ${AGENT_TEMPERATURE}
-      reasoning_effort: ${AGENT_REASONING_EFFORT}
-      max_turns: ${AGENT_MAX_TURNS}
-      timeout_seconds: ${AGENT_TIMEOUT_SECONDS}
-    intent_classifier:
-      model: ${AGENT_FAST_MODEL}
-      temperature: 0
-      timeout_seconds: 20
-  embedding:
-    model: ${EMBEDDING_MODEL}
-    dimensions: ${EMBEDDING_DIMENSIONS}
-
-rag:
-  top_k: ${RAG_TOP_K}
-  min_score: ${RAG_MIN_SCORE}
-  max_context_tokens: ${RAG_MAX_CONTEXT_TOKENS}
-
-mcp:
-  server_url: ${MCP_SERVER_URL}
-  connect_timeout_seconds: ${MCP_CONNECT_TIMEOUT_SECONDS}
-  read_timeout_seconds: ${MCP_READ_TIMEOUT_SECONDS}
-```
-
-配置边界:
-
-- API Key 只允许放在环境变量或密钥管理系统,不写入仓库。
-- `AGENT_MAIN_MODEL`、`AGENT_FAST_MODEL`、`EMBEDDING_MODEL` 必须在启动日志中打印脱敏后的配置摘要,便于排查环境差异。
-- 每次 Agent run 要把 `model_provider`、`model_name`、`prompt_version`、`rag_top_k`、`rag_min_score`、`token_usage` 写入审计日志。
+ZSELECTRIC_BOOT_URL=http://127.0.0.1:8080
+JAVA_MCP_BASE_PATH=/api/v1/agent/mcp
+```
+
+生产环境要求:
+
+- `AGENT_LLM_API_KEY`、`EMBEDDING_API_KEY` 只允许来自环境变量或密钥管理系统,不写入仓库。
+- `AGENT_LLM_MODEL` 必须显式配置;服务启动时不依赖 SDK 默认模型。
+- `AGENT_LLM_FAST_MODEL` 可选;如果为空,快速分类步骤复用 `AGENT_LLM_MODEL`。
+- `LANGGRAPH_CHECKPOINT_ENABLED=true` 时必须配置持久化 checkpointer;生产环境不使用内存 checkpoint。
+- `LANGGRAPH_MAX_STEPS` 必须限制在 3 到 12 之间,防止图循环失控。
 - `EMBEDDING_DIMENSIONS` 必须与 `aftersales_policy_chunk.embedding VECTOR(...)` 一致,不一致时服务启动失败。
-- 高风险工具不允许仅靠 prompt 约束,必须由 Java MCP Server 的风险等级和审批逻辑兜底。
-- 生产环境模型变更必须跑 eval 数据集,确保意图判断、风险分级、审批触发和最终回复没有回归。
+- 启动日志只打印脱敏配置摘要,例如 provider、base_url host、model、prompt_version、RAG 参数,不打印 API Key。
+
+### Python 新增组件
+
+`zsElectric-agent-service` 增加以下组件:
+
+```text
+app
+├─ core
+│  └─ settings.py              # 读取 LLM、LangGraph、RAG、Java MCP 配置
+├─ llm
+│  ├─ client.py                # OpenAI 兼容 chat/completions 客户端
+│  ├─ prompts.py               # Prompt 模板和版本号
+│  └─ planner.py               # plan_action 节点的结构化输出解析
+├─ graph
+│  ├─ state.py                 # AfterSalesAgentState
+│  ├─ nodes.py                 # detect_intent、retrieve_policy、query_order 等节点
+│  ├─ edges.py                 # 风险路由和错误路由
+│  └─ aftersales_graph.py      # StateGraph 编译入口
+├─ checkpoint
+│  └─ store.py                 # PostgreSQL checkpointer 配置
+└─ evals
+   └─ aftersales_cases.jsonl   # 模型回归用例
+```
+
+`settings.py` 负责配置校验,启动时检查:
+
+- LLM 启用时 `AGENT_LLM_API_KEY`、`AGENT_LLM_BASE_URL`、`AGENT_LLM_MODEL` 必填。
+- `LANGGRAPH_MAX_STEPS` 必须大于 0 且不超过 12,防止状态图无限循环。
+- 生产环境必须启用持久化 checkpointer;只有单元测试允许内存 checkpointer。
+- `ZSELECTRIC_BOOT_URL` 必填且不能指向外部未知域名。
+- RAG 参数必须在安全范围内,例如 `RAG_TOP_K` 在 1 到 10 之间。
+
+`client.py` 只处理模型 HTTP 调用,不包含业务规则。它向 `{AGENT_LLM_BASE_URL}/chat/completions` 发送请求,统一返回:
+
+```json
+{
+  "content": "...",
+  "model": "actual-model-name",
+  "provider": "openai",
+  "usage": {
+    "prompt_tokens": 123,
+    "completion_tokens": 45,
+    "total_tokens": 168
+  }
+}
+```
+
+`planner.py` 负责把 `plan_action` 节点的模型输出解析成固定 JSON。如果模型返回非法 JSON、工具名不在 Python 本地白名单、参数缺少必填字段,LangGraph 进入规则降级或人工处理节点,不把非法工具请求传给 Java。
+
+### 工具计划结构
+
+LangGraph 的 `plan_action` 节点只生成工具计划,不直接生成最终客服回复。工具计划必须是结构化 JSON:
+
+```json
+{
+  "intent": "refund_request",
+  "risk_level": "HIGH",
+  "need_order_lookup": true,
+  "tool_calls": [
+    {
+      "tool_name": "retrieve_aftersales_policy",
+      "params": {
+        "issue_type": "refund_request",
+        "keywords": "用户要求退款"
+      }
+    },
+    {
+      "tool_name": "query_charge_order",
+      "params": {
+        "order_no": "CD001"
+      }
+    },
+    {
+      "tool_name": "request_refund",
+      "params": {
+        "order_no": "CD001",
+        "reason": "用户反馈充电失败要求退款"
+      }
+    }
+  ],
+  "customer_message_draft": "该问题涉及退款,需要先进入审批。"
+}
+```
+
+Python 本地白名单必须与 Java `AgentToolCatalog` 保持一致,首版允许的工具名为:
+
+```text
+query_charge_order
+query_charge_session
+retrieve_aftersales_policy
+create_aftersales_work_order
+request_refund
+request_compensation
+get_approval_status
+resume_after_approval
+write_agent_audit
+```
+
+LangGraph 只执行白名单中的工具,并且执行顺序受状态图控制:
+
+1. 总是先执行 `retrieve_aftersales_policy`。
+2. 请求中存在 `order_no`、`user_id` 或 `phone` 时,才允许执行订单查询。
+3. 高风险工具最多执行一个,且必须位于政策检索和必要订单查询之后。
+4. 模型不得直接调用 `resume_after_approval`;该工具只允许 `/api/v1/agent/resume/{approval_id}` 路由进入 `resume_after_approval` 子图后触发。
+
+### 主调用流程
+
+LangGraph 接入后的低风险流程:
+
+```text
+1. FastAPI /chat 接收 ChatRequest,生成 trace_id。
+2. FastAPI 调用 aftersales_graph,thread_id=trace_id。
+3. detect_intent 节点识别意图和问题类型。
+4. retrieve_policy 节点检索售后政策。
+5. query_order 节点按需调用 Java MCP 查询订单或充电记录。
+6. plan_action 节点调用 LLM 生成结构化工具计划。
+7. route_by_risk 节点校验 JSON schema、工具白名单、顺序、参数和最大工具次数。
+8. create_work_order 节点调用 Java MCP 创建售后工单。
+9. final_answer 节点把工具结果、政策证据、工单号交给 LLM 生成最终客服回复。
+10. Python 返回 AgentResponse,并把模型、checkpoint 和工具调用摘要写审计。
+```
+
+LangGraph 接入后的高风险流程:
+
+```text
+1. FastAPI /chat 接收退款或补偿诉求,生成 trace_id。
+2. LangGraph 执行 detect_intent、retrieve_policy、query_order、plan_action。
+3. route_by_risk 节点发现 request_refund 或 request_compensation。
+4. request_approval 节点调用 Java MCP。
+5. Java AgentToolOrchestrator 判断 HIGH 风险,创建 agent_approval 和 agent_pending_action。
+6. Java 冻结 tool_name、tool_version、params_json、params_hash、idempotency_key。
+7. Java 返回 pending_approval、approval_id、pending_action_id。
+8. LangGraph 将 approval_id 和 pending_action_id 写入 checkpoint。
+9. final_answer 节点不让 LLM 编造执行结果,只生成“已进入审批”的客服回复。
+10. 审批人通过 Java 审批接口 approve 或 reject。
+11. 前端或运营后台调用 Python /resume/{approval_id}。
+12. Python 从 checkpoint 找回 trace_id/thread_id 和审批前状态。
+13. resume_after_approval 子图只透传 approval_id 调 Java resume_after_approval。
+14. Java 校验审批状态、冻结参数 hash、幂等状态后执行冻结动作。
+15. final_answer 节点根据 Java 结果生成审批后的最终回复。
+```
+
+### Prompt 约束
+
+系统 Prompt 首版固定为 `aftersales-agent-v1`,核心规则:
+
+```text
+你是充电业务售后 Agent。
+你只能根据工具返回结果回答,不得编造订单、退款、补偿或审批状态。
+你必须先检索售后政策,再决定是否需要查询订单或创建工单。
+退款、补偿、账户权益变更属于高风险动作,只能调用 request_refund 或 request_compensation 发起审批。
+当 Java 工具返回 pending_approval 时,你必须明确告知需要人工审批,不能声称退款或补偿已经完成。
+当工具失败或证据不足时,你必须建议人工核查。
+你必须输出符合 JSON schema 的工具计划,不得输出额外解释。
+```
+
+最终回复 Prompt 与工具计划 Prompt 分开。最终回复只接收已执行的工具结果,不允许再产生新工具调用,避免模型在回复阶段绕过工具治理。
+
+### 降级策略
+
+LangGraph 节点或 LLM 调用失败时:
+
+- 如果 `AGENT_RULE_FALLBACK_ENABLED=true`,回退到当前规则编排:关键词判断退款/补偿,低风险建工单,高风险进审批。
+- 如果规则也无法判断,返回“当前智能判断不可用,请人工核查”,并写失败审计。
+- 任何节点失败都必须写入 `state.error` 和 `agent_audit_log`,不能跳过审计直接结束。
+
+LLM 输出非法工具计划时:
+
+- 记录 `tool_plan_parse_failed` 审计。
+- 不调用 Java MCP。
+- 尝试一次低温重试;重试仍失败则进入规则降级。
+
+Java MCP 工具失败时:
+
+- Python 不让 LLM 猜测结果。
+- 将错误、tool_name、trace_id 写审计。
+- 最终回复固定为需要人工核查。
+
+RAG 无命中时:
+
+- 允许查询订单和创建普通工单。
+- 不允许发起退款或补偿审批,除非用户明确表达退款/补偿诉求且订单证据完整。
+
+### 审计与可观测性
+
+每次 Agent run 必须写入以下审计信息:
+
+```text
+trace_id
+thread_id
+checkpoint_id
+graph_version
+graph_node
+prompt_version
+llm_provider
+llm_model
+fast_model
+embedding_model
+rag_top_k
+rag_min_score
+tool_plan_json
+tool_call_count
+token_usage_json
+fallback_used
+final_status
+```
+
+审计写入分两层:
+
+- Python 侧记录 LLM 输入输出摘要、工具计划、token usage、fallback 状态。
+- LangGraph checkpointer 保存图状态,但不替代审计日志。
+- Java 侧继续记录工具调用、审批创建、审批决策、冻结动作恢复和业务执行结果。
+
+两侧用同一个 `trace_id` 串联。高风险动作以 Java 审计为准,Python 审计不能替代 Java 审批和冻结动作记录。
+
+### 安全约束
+
+- LLM 不接触数据库连接串,不直连 Java 业务库。
+- LLM 不接触完整手机号、身份证、支付密钥等敏感字段;进入 prompt 前必须脱敏。
+- Python 本地工具白名单和 Java 工具白名单双重校验。
+- 高风险工具必须由 Java 判断风险并创建审批,不能只靠 Prompt 文案限制。
+- `resume_after_approval` 不允许从 LLM 工具计划触发,只能由 `/resume/{approval_id}` 入口触发。
+- 模型输出的金额、原因、证据只能作为申请参数,真实执行仍以 Java 冻结参数和审批状态为准。
+- 每次模型或 Prompt 版本变更,必须跑 eval 数据集后再进入生产。
+
+### 测试策略
+
+Python 新增测试:
+
+- LLM 配置缺失时启动失败。
+- LLM 工具计划 JSON 解析成功。
+- 非白名单工具被拒绝,且不调用 Java MCP。
+- 模型非法 JSON 时重试一次,然后规则降级。
+- 低风险意图生成 `create_aftersales_work_order`。
+- 退款意图生成 `request_refund`,Java 返回 `pending_approval` 后不生成“已退款”回复。
+- `/resume/{approval_id}` 只能调用 `resume_after_approval`,不走 LLM 工具计划。
+- 高风险审批返回后写入 LangGraph checkpoint。
+- `/resume/{approval_id}` 能从 checkpoint 找回审批前状态。
+- checkpoint 丢失时返回可解释错误,并提示人工核查。
+- token usage、model、prompt_version 写入审计。
+
+Java 现有测试保持:
+
+- 高风险工具创建审批和冻结动作。
+- 审批通过后按冻结参数恢复执行。
+- `params_hash` 篡改保护。
+- 重复恢复返回历史结果,避免重复执行。
+
+集成 eval 用例至少覆盖:
+
+```text
+充电失败咨询 -> 检索政策 + 查询订单 + 创建工单
+充电失败要求退款 -> 检索政策 + 查询订单 + 创建审批
+用户要求补偿余额 -> 检索政策 + 查询订单 + 创建审批
+只有手机号没有订单号 -> 查询最近订单后建单或进入人工核查
+政策无命中 -> 不自动退款/补偿
+Java MCP 超时 -> 返回人工核查
+审批通过后 resume -> 执行冻结动作
+审批拒绝后 resume -> 返回 rejected
+```
 
 ## MCP 工具
 
@@ -366,6 +653,8 @@ approval_id 存在
 CREATE TABLE agent_audit_log (
   id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
   trace_id VARCHAR(64) NOT NULL COMMENT 'Agent运行链路ID',
+  thread_id VARCHAR(64) NULL COMMENT 'LangGraph线程ID,默认等于trace_id',
+  checkpoint_id VARCHAR(128) NULL COMMENT 'LangGraph checkpoint ID',
   span_id VARCHAR(64) NULL COMMENT '步骤Span ID',
   parent_span_id VARCHAR(64) NULL COMMENT '父级Span ID',
   approval_id VARCHAR(64) NULL COMMENT '关联审批ID',
@@ -375,6 +664,8 @@ CREATE TABLE agent_audit_log (
   actor_name VARCHAR(128) NULL COMMENT '操作者名称',
   step VARCHAR(64) NOT NULL COMMENT '步骤: user_input, intent_detected, rag_retrieved, tool_called, approval_created, approval_decided, action_resumed, final_answer',
   event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
+  graph_version VARCHAR(64) NULL COMMENT 'LangGraph图版本',
+  graph_node VARCHAR(128) NULL COMMENT '当前图节点',
   tool_name VARCHAR(128) NULL COMMENT '工具名',
   risk_level VARCHAR(16) NULL COMMENT '风险等级',
   model_provider VARCHAR(64) NULL COMMENT '模型供应商,如openai',
@@ -390,8 +681,11 @@ CREATE TABLE agent_audit_log (
   user_agent VARCHAR(512) NULL COMMENT '客户端User-Agent',
   create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   KEY idx_agent_audit_trace_time (trace_id, create_time),
+  KEY idx_agent_audit_thread_time (thread_id, create_time),
+  KEY idx_agent_audit_checkpoint (checkpoint_id),
   KEY idx_agent_audit_approval (approval_id),
   KEY idx_agent_audit_action (pending_action_id),
+  KEY idx_agent_audit_graph_node (graph_node, create_time),
   KEY idx_agent_audit_tool_time (tool_name, create_time),
   KEY idx_agent_audit_model_time (model_name, create_time),
   KEY idx_agent_audit_status_time (status, create_time)
@@ -611,7 +905,7 @@ Python FastAPI 测试:
 
 ### M2 Python FastAPI Agent
 
-交付 FastAPI 服务、OpenAI Agents SDK agent、LLM 配置管理、MCP client、基础 RAG 检索、`/chat` 和 `/resume`。
+交付 FastAPI 服务、LangGraph 状态图、持久化 checkpoint、LLM 配置管理、MCP client、基础 RAG 检索、`/chat` 和 `/resume`。
 
 ### M3 售后业务闭环
 
@@ -631,5 +925,6 @@ Python FastAPI 测试:
 - 审批拒绝、审批过期、重复恢复、参数篡改都有确定结果。
 - 每次 Agent 运行都能通过 `trace_id` 查到完整审计链路。
 - 每次 Agent 运行都能在审计日志中看到实际模型、prompt 版本、RAG 参数和 token 用量。
+- 高风险审批暂停后,LangGraph checkpoint 能保存审批前状态,`/resume/{approval_id}` 能从 checkpoint 恢复上下文。
 - Embedding 模型维度与 pgvector 表结构不一致时,Python 服务启动失败。
-- 项目文档能解释 RAG、MCP Tool Calling、权限、审批、状态恢复、审计和测试案例。
+- 项目文档能解释 LangGraph 状态图、RAG、MCP Tool Calling、权限、审批、状态恢复、审计和测试案例。