Skip to content

Latest commit

 

History

History
973 lines (802 loc) · 50.3 KB

File metadata and controls

973 lines (802 loc) · 50.3 KB

procurepilot 代码库结构与复习指南

文档基于 2026-08-21 的当前成品代码整理。它面向“以后重新阅读项目时,能快速知道代码在哪里、为什么存在、从哪里开始追踪”这一目标。

本文描述的是当前仓库中的实际实现。.venv/node_modules/dist/、缓存、真实 .envsrc/download/ 等本地运行产物不属于源码目录树。

1. 如何使用这份手册

第一次复习项目时,建议依次阅读:

  1. 本文的“系统全景”和“六条核心调用链”,先建立全局心智模型。
  2. src/api_view/agent_loader.py,理解 Web 请求如何获得当前用户的 Agent 和沙箱。
  3. src/agent/graphs/main_agent.py,理解主 Agent、同步子 Agent、Middleware、Store 和沙箱如何组装。
  4. src/agent/middlewares/user_async_subagents.pysrc/agent/graphs/async_analyst.py,理解异步分析闭环。
  5. src/agent/backends/,理解本项目最核心的用户级 OpenSandbox 机制。
  6. src/mcp_server/,理解 Agent 如何访问 Java ERP。
  7. src/api_view/api/chat.pyfrontend/src/api/chat.js,从后端到浏览器追踪 SSE 事件。
  8. frontend/src/App.vue,理解前端状态机、HITL 恢复和任务轮询。

如果只想定位某个功能,可以直接跳到“按需求定位代码”章节。

2. 系统一眼看懂

procurepilot 是一个多用户、多 Agent 的采购协作系统。浏览器只连接 FastAPI;FastAPI 负责认证、会话归属、SSE 和用户 Agent 生命周期;Agent 通过 MCP 访问 Java ERP,通过 OpenSandbox 执行代码和文件操作,通过 MongoDB 保存会话、记忆、技能、任务和沙箱绑定。

flowchart LR
    B[Vue 3 浏览器] -->|JWT + REST/SSE| W[FastAPI :8090]
    W --> L[AgentLoader]
    L --> M[主 Agent Graph]

    M -->|同步 task| O[procurement-order]
    M -->|start_async_task| P[Agent Protocol :2024]
    P --> A[procurement-analyst]

    O --> E[FastMCP ERP :8000/mcp]
    A --> E
    E -->|HTTP REST| J[外部 Java ERP :8080/api]
    J --> Y[(MySQL)]

    M --> S[用户私有 OpenSandbox]
    A --> S
    S --> OS[OpenSandbox Server :8100]

    W --> D[(MongoDB :27017)]
    M --> D
    P --> D
Loading

2.1 服务与端口

服务 默认端口 代码入口 主要职责
Vue 前端 3000 frontend/src/main.js 登录、聊天、历史、工具调用、HITL、异步任务抽屉
FastAPI Web API 8090 src/api_view/web_main.py JWT、会话隔离、SSE、任务查询、Agent 生命周期
LangGraph Agent Protocol 2024 langgraph.json 执行异步采购分析 graph/thread/run
FastMCP ERP Server 8000 src/mcp_server/server_main.py 把 Java ERP REST API 转换成 MCP 工具
Java ERP 8080 外部项目,不在本仓库 供应商、零件、库存、采购订单业务接口
OpenSandbox Server 宿主机 8100 docker/docker-compose.yml 创建和连接用户隔离执行容器
MongoDB 27017 Docker Compose 用户、checkpoint、展示消息、Store、任务、沙箱绑定
MySQL 通常 3306 motorparts_db.sql Java ERP 的业务数据库

2.2 三类 Agent

Agent 所在进程 执行方式 主要责任
主 Agent FastAPI 进程 请求内同步 理解意图、读偏好、路由任务、汇总结果、管理技能
procurement-order 主 Graph 的同步子 Agent 同步 task 查询、创建、修改采购订单;执行两层 HITL
procurement-analyst Agent Protocol 进程 后台异步 run ERP/外部数据收集、采购分析、图表和报告生成

主 Agent 是协调者。订单操作必须委派给订单子 Agent;耗时分析必须通过 start_async_task 发往 Agent Protocol,不能用同步 task 阻塞聊天请求。

3. 当前有效目录树

以下是源码提交视角的目录树。锁文件、SQL 和图片等也列出,但依赖目录、编译缓存和真实密钥文件被排除。

procurepilot/
├── .env.example
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── langgraph.json
├── motorparts_db.sql
├── pyproject.toml
├── pytest.ini
├── requirements.txt
├── start_web.py
├── uv.lock
│
├── docker/
│   ├── docker-compose.yml
│   ├── opensandbox.toml
│   ├── README.md
│   └── sandbox-image/
│       ├── Dockerfile
│       └── requirements-sandbox.txt
│
├── docs/
│   ├── CODEBASE_GUIDE.md
│   ├── agent-backend-stress-test-report.md
│   └── frontend-multi-user-api.md
│
├── frontend/
│   ├── index.html
│   ├── package.json
│   ├── package-lock.json
│   ├── vite.config.js
│   ├── public/assets/
│   │   ├── whale-girl-hero.png
│   │   └── whale-girl-logo.png
│   └── src/
│       ├── main.js
│       ├── App.vue
│       ├── api/
│       │   ├── auth.js
│       │   ├── chat.js
│       │   ├── client.js
│       │   ├── history.js
│       │   └── tasks.js
│       ├── components/
│       │   ├── AsyncTaskPanel.vue
│       │   ├── AuthScreen.vue
│       │   ├── ChatArea.vue
│       │   ├── InputArea.vue
│       │   ├── InterruptBanner.vue
│       │   ├── MarkdownRenderer.vue
│       │   ├── MessageItem.vue
│       │   ├── Sidebar.vue
│       │   └── ToolCallPanel.vue
│       ├── config/assets.js
│       └── styles/main.css
│
├── reports/
│   ├── benchmark-20260821-141746.json
│   ├── benchmark-20260821-141746.md
│   ├── benchmark-20260821-141814.json
│   ├── benchmark-20260821-141814.md
│   ├── benchmark-20260821-141913.json
│   └── benchmark-20260821-141913.md
│
├── scripts/
│   ├── generate_report.py
│   ├── run_benchmark.py
│   ├── run_agent_smoke.py
│   └── run_mcp_smoke.py
│
├── src/
│   ├── agent/
│   │   ├── __init__.py
│   │   ├── core/
│   │   │   ├── __init__.py
│   │   │   ├── async_sandbox_claims.py
│   │   │   ├── config.py
│   │   │   ├── env_utils.py
│   │   │   └── schema.py
│   │   ├── graphs/
│   │   │   ├── __init__.py
│   │   │   ├── async_analyst.py
│   │   │   └── main_agent.py
│   │   ├── backends/
│   │   │   ├── __init__.py
│   │   │   ├── custom_opensandbox.py
│   │   │   ├── sandbox_manager.py
│   │   │   ├── sandbox_proxy.py
│   │   │   └── sandbox_setup.py
│   │   ├── memory/
│   │   │   ├── __init__.py
│   │   │   ├── AGENTS.md
│   │   │   └── prompts.py
│   │   ├── middlewares/
│   │   │   ├── __init__.py
│   │   │   ├── context_injection.py
│   │   │   ├── factories.py
│   │   │   ├── memory_update.py
│   │   │   ├── sandbox_breaker.py
│   │   │   ├── sandbox_health.py
│   │   │   ├── skills_sync.py
│   │   │   ├── tools_summarization.py
│   │   │   ├── user_async_subagents.py
│   │   │   └── user_skills_restore.py
│   │   ├── stores/
│   │   │   ├── __init__.py
│   │   │   ├── async_task_store.py
│   │   │   └── mongodb_store.py
│   │   ├── subagents/
│   │   │   ├── __init__.py
│   │   │   ├── loader.py
│   │   │   └── configs/
│   │   │       ├── __init__.py
│   │   │       ├── procurement_analyst.yaml
│   │   │       └── procurement_order.yaml
│   │   └── tools/
│   │       ├── __init__.py
│   │       ├── assign_skill.py
│   │       ├── chart_generator.py
│   │       ├── download_sandbox_file.py
│   │       ├── hitl_tools.py
│   │       ├── mcp_client.py
│   │       └── web_search.py
│   │
│   ├── api_view/
│   │   ├── __init__.py
│   │   ├── agent_loader.py
│   │   ├── auth_service.py
│   │   ├── web_config.py
│   │   ├── web_main.py
│   │   └── api/
│   │       ├── __init__.py
│   │       ├── auth.py
│   │       ├── chat.py
│   │       ├── history.py
│   │       └── tasks.py
│   │
│   ├── mcp_server/
│   │   ├── __init__.py
│   │   ├── http_base.py
│   │   ├── server_config.py
│   │   ├── server_main.py
│   │   └── tools/
│   │       ├── __init__.py
│   │       ├── inventory_tools.py
│   │       ├── order_tools.py
│   │       ├── parts_tools.py
│   │       └── suppliers_tools.py
│   │
│   ├── skills/
│   │   ├── main/skill-management/
│   │   │   ├── SKILL.md
│   │   │   └── scripts/download_skill.py
│   │   └── procurement/
│   │       ├── chart_params.md
│   │       ├── procurement-analysis/SKILL.md
│   │       ├── supplier-price-urls/
│   │       │   ├── SKILL.md
│   │       │   └── data/url_mapping.yaml
│   │       ├── web-content-fetcher/
│   │       │   ├── SKILL.md
│   │       │   ├── _meta.json
│   │       │   ├── fetch.sh
│   │       │   └── fetch_content.py
│   │       └── web-scraper/
│   │           ├── SKILL.md
│   │           ├── _meta.json
│   │           └── scrape_page.py
└── tests/
    ├── conftest.py
    ├── test_all_tools.py
    ├── test_async_sandbox_binding.py
    ├── test_auth_isolation.py
    ├── test_mcp_integration.py
    ├── test_mongodb_store_namespace.py
    ├── test_sandbox_prebuilt_environment.py
    ├── test_sse_stream.py
    └── test_user_skill_isolation.py

4. 六条核心调用链

4.1 一键启动链路

python start_web.py
  ├─ 启动 langgraph dev :2024
  │   └─ langgraph.json 加载 procurement_analyst_async graph
  ├─ 等待 /ok
  ├─ 启动 uvicorn api_view.web_main:app :8090
  │   └─ FastAPI lifespan
  │       ├─ initialize_auth()
  │       ├─ sandbox_manager.initialize()
  │       ├─ precompute_agent_context()
  │       └─ 后台预热一个 OpenSandbox
  ├─ 等待 /health
  └─ 启动 Vite :3000

start_web.py 不负责启动 Docker、Java ERP 和 FastMCP。它们必须提前可用,尤其 FastMCP 必须在 FastAPI 预计算 MCP 工具前启动。

4.2 登录与用户隔离链路

AuthScreen.vue
  → frontend/src/api/auth.js
  → POST /api/auth/register 或 /api/auth/login
  → auth_service.py
      ├─ 用户名规范化
      ├─ scrypt 密码哈希/校验
      ├─ MongoDB users 集合
      └─ 签发 HS256 JWT
  → token 保存到浏览器 sessionStorage
  → 后续 authFetch 自动添加 Authorization: Bearer

每次访问会话时还会通过 session_owners 校验 thread_id + user_id。浏览器传入别人的 thread ID 不能绕过后端归属检查。

4.3 普通同步对话链路

App.vue handleSend()
  → streamChat()
  → POST /api/chat/stream
  → chat_stream()
      ├─ 生成/接收 thread_id
      ├─ claim_thread(thread_id, user_id)
      └─ stream_chat_response()
          ├─ get_agent_for_user()
          │   ├─ ensure_sandbox_for_user()
          │   └─ create_main_agent()
          ├─ agent.astream(..., subgraphs=True, stream_mode=[messages, values])
          ├─ 转换 token/tool/interrupt 为 SSE
          └─ 保存 session_display_messages
  → frontend/src/api/chat.js 解析 SSE
  → App.vue 更新 messages
  → ChatArea/MessageItem 渲染

4.4 订单与两层 HITL 链路

flowchart TD
    U[用户提出订单操作] --> M[主 Agent]
    M -->|同步 task| O[procurement-order]
    O --> C{必填字段齐全?}
    C -- 否 --> R[request_order_info interrupt]
    R --> F[前端 InterruptBanner 补充自由文本]
    F -->|POST /resume| O
    C -- 是 --> H{order_create/update HITL 审批}
    H -- reject --> X[结束且不写 ERP]
    H -- approve --> MCP[调用 FastMCP]
    MCP --> J[Java ERP 写操作]
Loading

第一层由 hitl_tools.py 主动调用 interrupt();第二层由订单 YAML 中的 interrupt_on 自动拦截真正的写工具。前端统一通过 InterruptBanner.vue 收集补充或审批,并由 resumeChat() 恢复同一 LangGraph thread。

4.5 异步分析链路

主 Agent 判断为耗时分析
  → start_async_task(description, procurement-analyst)
  → user_async_subagents.py
      ├─ Agent Protocol 创建 thread
      ├─ task_id = thread_id
      ├─ 创建 user_id + sandbox_id + task_id 的短期签名 claim
      ├─ 创建 run,目标 graph_id=procurement_analyst_async
      └─ async_tasks 集合保存任务元数据
  → 主 Agent 立即向用户返回 task_id

Agent Protocol 进程
  → langgraph.json
  → async_analyst.create_procurement_analyst(config)
      ├─ 校验 claim,取得可信 user_id/sandbox_id
      ├─ 连接并续期同一用户沙箱
      ├─ 创建 /analysis/tasks/{task_id}/
      ├─ 加载采购分析工具、Skills 与提示词
      └─ 后台执行分析并写入任务专属目录

浏览器
  → 每 5 秒 GET /api/tasks
  → tasks.py 查询 Agent Protocol run 状态和最终 thread messages
  → AsyncTaskPanel.vue 自动显示执行中/成功/失败

这里的 Agent Protocol 不是“只转发命令的网关”,而是异步 graph 的执行和 run/thread 管理服务。真正的文件与命令操作仍由异步 Agent 通过 OpenSandbox 客户端发送给用户沙箱。

4.6 用户沙箱生命周期

ensure_sandbox_for_user(user_id)
  ├─ 内存命中 → echo ok → 直接返回
  ├─ MongoDB sandbox_registry 有绑定 → connect(sandbox_id)
  │   ├─ 成功 → 更新实际 ID 并返回
  │   └─ 失败/过期 → 创建替代沙箱
  ├─ 新用户且有预热沙箱 → 认领预热实例
  └─ 否则 → 创建新沙箱

创建/重连后
  ├─ 包装为 OpenSandboxBackend
  ├─ 包装为 SandboxBackendProxy 稳定句柄
  ├─ 创建 /analysis/temp、/analysis/tasks、/data
  ├─ 同步预置 Skills
  └─ 检查 /opt/skills-venv 预装依赖

SandboxBackendProxy 是关键设计:Graph 和工具始终持有同一个代理对象;沙箱失效重建时只替换代理内部 backend,因此不需要重建所有引用。

5. 根目录文件说明

文件 作用 复习重点
.env.example 无真实密钥的环境变量模板 模型密钥、JWT、MongoDB、Agent Protocol、沙箱镜像和测试端点
.gitignore 排除密钥、依赖、缓存、构建产物、日志和下载文件 .env 被忽略而 .env.example 被保留;src/download/ 不提交
README.md 对外项目说明和部署/架构总文档 面向使用者;本文则面向代码复习
CONTRIBUTING.md 贡献流程、验证命令和 PR 清单 强调 QA 数据清理、ERP 写测试显式开启
SECURITY.md 漏洞报告方式和生产安全边界 沙箱、Docker Socket、CORS、密钥、ERP 写权限
LICENSE MIT License 仅覆盖本项目自有代码,第三方服务与素材仍受自身条款约束
pyproject.toml Python 项目元数据、依赖、构建和 Ruff 配置 分发名 procurepilot;包发现覆盖 agent/api_view/mcp_server
uv.lock uv 的完整可复现依赖锁 不手工修改,由 uv lock/uv sync 维护
requirements.txt pip 安装依赖快照 标准虚拟环境安装路径;版本应与 pyproject/uv.lock 协调
pytest.ini pytest 发现范围和 marker 声明 默认只跑离线测试;integration 需要外部服务,live 还需要真实模型服务
langgraph.json Agent Protocol graph 注册 procurement_analyst_async 指向 graphs/async_analyst.py:create_procurement_analyst
start_web.py Windows/本地一键启动器 启动顺序、健康等待、日志转发和 Ctrl+C 清理
motorparts_db.sql Java ERP 的 MySQL 示例结构与数据 包含 customerinventorylogisticsorder_detailpartpurchase_ordersupplieruser 八张表

真实 .env 只存在于本地,绝不能写入文档、日志或提交。项目当前并非所有连接配置都统一读取环境变量;复习配置时需要同时看 agent/core/config.pyapi_view/web_config.pymcp_server/server_config.pytools/mcp_client.py

6. docker/:基础设施与沙箱镜像

文件 作用 关键内容
docker/docker-compose.yml 编排 MongoDB、OpenSandbox Server 和一次性沙箱镜像构建任务 MongoDB 命名卷、OpenSandbox 状态卷、Docker Socket、8100/27017 端口
docker/opensandbox.toml OpenSandbox Server 配置 Docker runtime、bridge 网络、动态端口范围、capability drop、no-new-privileges、SQLite 状态库
docker/README.md Docker 子系统启动与排障说明 构建镜像、健康检查、数据卷和 Mongo URI
docker/sandbox-image/Dockerfile 构建项目专用 Code Interpreter 镜像 创建 /opt/skills-venv,安装依赖,创建分析目录并写镜像 marker
docker/sandbox-image/requirements-sandbox.txt 沙箱内部 Python 依赖锁 pandas、numpy、matplotlib、requests、BeautifulSoup、lxml、markdownify

开发配置目前使用 OPENSANDBOX_INSECURE_SERVER=YES,并将宿主机 Docker Socket 挂入 OpenSandbox Server。它适合可信本机开发,不应原样暴露公网。

7. src/api_view/:FastAPI Web 聚合层

7.1 顶层文件

文件 作用 关键对象/逻辑
web_main.py FastAPI 应用入口 lifespan() 初始化认证、AgentLoader;注册 auth/chat/history/tasks 路由;提供 /health
web_config.py Web API 配置 MongoDB、JWT、API 元信息和若干历史路径常量
auth_service.py MongoDB 用户认证和 JWT 解析 AuthenticatedUser、scrypt 哈希、注册/登录、get_current_user 依赖
agent_loader.py Web 与 Agent 之间的核心生命周期层 单例、启动预计算、按用户取沙箱、请求级 Graph、会话归属、展示消息持久化
__init__.py Python 包标记 无运行逻辑

7.2 AgentLoader 的职责边界

AgentLoader 不把一个全局 Graph 共享给所有用户。它在启动阶段只缓存与用户无关的 MCP 工具和 YAML 配置;每个请求调用 get_agent_for_user() 时,根据用户获取稳定沙箱代理并创建请求级 Graph。

主要数据操作:

  • session_owners:保存 thread 所属用户,阻止跨用户访问。
  • checkpoints:由 LangGraph/MongoDBSaver 保存 Graph 状态。
  • session_display_messages:保存前端真正显示的 user/assistant/tool 混合消息。
  • 删除会话时同时删除 checkpoint、展示消息和归属记录。
  • 每条展示消息独立存为一个文档,并对大文本字段截断,规避 MongoDB 16 MB 单文档上限。

7.3 api/ 路由文件

文件 路由 作用
api/auth.py /api/auth/register/login/me Pydantic 请求校验、调用认证服务、返回 token 与公开用户信息
api/chat.py /api/chat/stream/{thread_id}/resume、状态与 history 将 LangGraph 流转换成 SSE;处理工具调用栈、子 Agent 来源、图片、中断和展示消息
api/history.py /api/history/{thread_id}/messages、DELETE、PATCH 会话列表、展示消息读取、旧 checkpoint 序列化、删除、预留标题更新
api/tasks.py /api/tasks 按用户列出异步任务,向 Agent Protocol 刷新 run 状态并读取最终结果
api/__init__.py Python 包标记 无运行逻辑

chat.py 是整个 SSE 链路最重要的后端文件。它使用 stream_mode=["messages", "values"]messages 用于 token 和工具事件,values 用于发现 LangGraph interrupt。subgraphs=True 让同步子 Agent 的消息也进入流。

8. src/agent/:Agent 核心

8.1 Graph、核心配置与共享类型

文件 作用 关键内容
graphs/main_agent.py 主 Agent Graph Factory PrecomputedContextprecompute_agent_context()create_main_agent()
graphs/async_analyst.py Agent Protocol 异步采购分析 graph 工厂 校验 sandbox claim、连接同一用户沙箱、创建任务目录、构建 analyst
core/async_sandbox_claims.py 异步沙箱绑定签名 6 小时 HS256 claim;绑定 user_id + sandbox_id + task_id + purpose
core/config.py 模型、Mongo Store/checkpointer、沙箱与路径常量 MAIN/SUMMARY/FALLBACK 模型,OpenSandbox 客户端配置,MongoDBStore/MongoDBSaver
core/env_utils.py .env 读取模型相关环境变量 OpenAI、DeepSeek、智谱、MiniMax、阿里、K2、Daytona 等兼容配置
middlewares/factories.py 子 Agent Middleware 工厂 analyst 的摘要/调用限制;order 的模型/工具调用限制
core/schema.py 运行时上下文、偏好、消息、会话和 SSE 类型 ProcurementContext 是 Graph 的 context schema
memory/prompts.py 主 Agent 精简系统提示词 协调者角色、订单/分析路由、偏好文件和摘要要求
memory/AGENTS.md 上传到沙箱的完整全局行为准则 委派模板、记忆规则、技能管理、安全边界和文件策略
__init__.pycore/__init__.pygraphs/__init__.pymemory/__init__.py 包标记 无运行逻辑

8.2 graphs/main_agent.py 的九个组装阶段

  1. backend_factory 创建 CompositeBackend
  2. 默认路径走用户 OpenSandbox。
  3. /memories/ 路由到 StoreBackend,按用户保存长期偏好。
  4. /persisted-skills/ 路由到用户私有技能 namespace。
  5. 上传 AGENTS.md 到当前用户沙箱。
  6. 使用启动期缓存的 MCP、图表和 YAML 配置。
  7. 创建与当前沙箱绑定的 assign_skilldownload_sandbox_file
  8. 只把 procurement-order 作为同步子 Agent;analyst 被排除并改走异步 Middleware。
  9. 创建主 Middleware 栈,再调用 create_deep_agent()

主 Agent 的工具参数里只显式传入通用工具;同步子 Agent 工具由 subagents 配置持有;异步任务工具由 Middleware 注入。

9. src/agent/backends/:OpenSandbox 核心

文件 作用 关键点
custom_opensandbox.py 将 OpenSandbox SDK 适配为 DeepAgents BaseSandbox 实现命令执行、上传、下载;注入沙箱 PATH;映射超时和退出码
sandbox_proxy.py 稳定代理 完整转发同步/异步文件与命令协议;replace_backend() 支持热替换
sandbox_setup.py 创建/连接沙箱并初始化运行环境 目录创建、Skills 播种、预构建 venv 快速检查、可选现场安装回退
sandbox_manager.py 当前正式的异步多用户沙箱管理器 Mongo 绑定、用户锁、内存缓存、预热池、重连、重建、清理和关闭
__init__.py 包标记 无运行逻辑

9.1 custom_opensandbox.py

  • execute() 给非交互 shell 注入 Python/Node/Go/Java 等 PATH。
  • 使用 RunCommandOpts(timeout=...) 把超时真正传给 OpenSandbox 服务端。
  • stdout 与 stderr 合并为 DeepAgents ExecuteResponse
  • 上传和下载只接受绝对路径;返回框架定义的 FileUpload/FileDownload 响应。

9.2 sandbox_setup.py

新沙箱默认资源为 2 CPU、4 GiB、2 小时 TTL,镜像为 erp-openclaw-sandbox:v1。初始化阶段会创建 /analysis/temp/analysis/tasks/data,同步本地技能,并执行一次快速 import 检查。

默认不允许现场 pip install 修复损坏镜像。只有显式设置 SANDBOX_RUNTIME_INSTALL_FALLBACK=true 才进入开发回退路径。

9.3 sandbox_manager.py

主要全局状态:

  • SANDBOX_BACKENDS[user_id]:用户到稳定代理的进程内缓存。
  • sandbox_registry:MongoDB 中用户到 sandbox ID 的持久绑定。
  • _user_locks:每用户生命周期锁,防止并发重复创建。
  • _warm_reserve:供真正新用户认领的预热沙箱。

应用关闭时只删除未被认领的预热沙箱;用户已分配沙箱保留,以便下次启动重连。

10. src/agent/middlewares/:行为与可靠性层

主 Agent 中间件顺序会影响行为。当前顺序如下:

顺序 文件/中间件 作用
1 sandbox_health.py 每轮开始前 ping 用户沙箱;失败则重建并重新上传 AGENTS.md
2 context_injection.py user_id、用户名、偏好文件路径注入 SystemMessage
3 skills_sync.py 比较哈希,将本地 src/skills/ 增量同步到沙箱 /skills/
4 user_skills_restore.py 分页读取用户 Store 中持久化技能并恢复到沙箱
5 tools_summarization.py 自动摘要并提供 compact_conversation 主动压缩工具
6 memory_update.py 回合结束后识别采购实体,自动更新 recent suppliers/queries
7 sandbox_breaker.py 连续沙箱错误达到阈值后跳到 end,防止无限恢复循环
8 user_async_subagents.py 提供 start/check/update/cancel/list 异步任务工具,并绑定私有沙箱
9 框架 ModelCallLimitMiddleware 单次 run 最多 50 次模型调用
10 框架 ToolCallLimitMiddleware 单次 run 最多 200 次工具调用

各文件补充说明:

  • context_injection.py:不直接读取偏好,而是告诉 Agent 使用虚拟文件系统读取。
  • memory_update.py:只处理有采购关键词或发生子 Agent 委派的对话;内部摘要模型调用带 internal-memory-update tag,Web SSE 会过滤其 token。
  • skills_sync.py:预置技能来源是本地文件,属于系统级内容。
  • user_skills_restore.py:持久化技能来源是 Store,namespace 含用户 ID,属于用户私有内容。
  • tools_summarization.py:压缩前的完整历史由框架写入 backend 文件系统。
  • sandbox_breaker.py:与主动恢复配对;Health 负责恢复,Breaker 负责停止无效循环。
  • user_async_subagents.py:替换框架默认的 start/update 工具,但保留 check/cancel/list 标准工具。
  • middlewares/__init__.py:包标记,无运行逻辑。

11. src/agent/stores/:持久化适配

文件 作用 关键点
mongodb_store.py LangGraph BaseStore 的 MongoDB 实现 支持 Get/Put/Search/ListNamespaces;不支持 TTL 和语义向量搜索
async_task_store.py 异步任务元数据存储 async_tasks 集合;按用户列任务;从历史展示消息发现旧 task ID
__init__.py 导出 Store 类型/包标记 轻量入口

MongoDBStore 用 JSON 字符串编码完整 namespace,并以 namespace_path + key 建唯一索引,避免 MongoDB 数组 multikey 索引让不同用户 namespace 发生碰撞。

12. src/agent/subagents/:YAML 驱动的专业 Agent

文件 作用 关键点
loader.py 读取 YAML、校验必填字段、解析工具名 工具使用子串匹配并去重;可合并额外 Middleware
configs/procurement_order.yaml 同步订单 Agent 定义 订单工具、request_order_info、两层 HITL、订单字段与结果格式
configs/procurement_analyst.yaml 异步分析 Agent 定义 查询/图表/搜索工具、采购 Skills、任务目录和报告规则
各级 __init__.py 包标记 无运行逻辑

修改子 Agent 时优先改 YAML,而不是继续扩大 graphs/main_agent.py。但如果新增的是异步 Agent,还需要在 langgraph.json 注册 graph,并在异步 Middleware 中声明 graph ID 和 URL。

13. src/agent/tools/:Agent 直接使用的工具

文件 暴露能力 主要职责
mcp_client.py load_mcp_tools() 连接本地 ERP MCP 和第三方图表 MCP,按 analyst/order/chart 前缀分组
chart_generator.py generate_visualization 把约 26 个 generate_* 图表工具合成一个 chart_type + chart_config 路由入口
web_search.py web_search(query) 通过智谱搜狗 Web Search 返回最多 3 条外部信息
hitl_tools.py request_order_info 触发 LangGraph interrupt,等待用户补充订单字段
assign_skill.py assign_skill 校验技能、按 scope 复制到目标 Agent 目录,并写入用户 Store
download_sandbox_file.py download_sandbox_file 从当前用户沙箱下载文件到本机 src/download/,并防止文件名路径穿越
__init__.py 工具包标记/轻量导出 无核心逻辑

13.1 MCP 分组规则

  • supplier_part_inventory_ → analyst 查询工具。
  • order_ → order 工具。
  • 第三方 MCP 中 generate_ → chart 工具。
  • 图表 MCP 连接失败时只降级可视化,不阻止 ERP 核心工具启动。

13.2 图表上下文优化

chart_generator.py 不把 26 份大 Schema 同时暴露给模型,而是:

  1. 工具描述提供紧凑速查表。
  2. 完整参考放在 /skills/procurement/chart_params.md
  3. 一个统一工具根据 chart_type 路由到底层 MCP 工具。
  4. 类似 spreadsheet 的非可视化工具保留为独立工具。

这既降低工具 Schema token,也让 Agent 能按需读取完整参数。

14. src/mcp_server/:Java ERP 的 MCP 适配层

14.1 顶层文件

文件 作用 关键点
server_main.py FastMCP 独立入口 注册四组工具,以 Streamable HTTP 监听 127.0.0.1:8000/mcp
server_config.py Java ERP 与 MCP 地址配置 当前 Java base URL 为 http://127.0.0.1:8080/api
http_base.py FastMCP lifespan 创建一个共享 httpx.AsyncClient 连接池,关闭服务时统一释放
__init__.py 包标记 无运行逻辑

14.2 工具与 REST 映射

文件 MCP 工具 Java REST API 读/写
tools/suppliers_tools.py supplier_query GET /suppliers/search
tools/parts_tools.py part_query GET /parts/page
tools/parts_tools.py part_search GET /parts/search
tools/parts_tools.py part_by_supplier GET /parts/supplier/{id}
tools/inventory_tools.py inventory_warning GET /inventory/warning
tools/order_tools.py order_search_details GET /orders/search-details
tools/order_tools.py order_create POST /orders/create
tools/order_tools.py order_update PUT /orders/update/{id}

order_tools.py 还负责自动生成 PO + 日期 + 3 位随机数 的订单号、补默认下单时间、过滤 None,并递归把 Decimal/date 转成 JSON 可序列化值。

15. src/skills/:渐进式披露技能

Skills 不等于普通 Python 工具。工具是固定可调用函数;Skill 是 Agent 按需读取的操作手册、脚本和数据资源。这样大段流程说明不会永久占用系统提示词。

15.1 主 Agent 技能管理

文件 作用
main/skill-management/SKILL.md 用户技能下载、创建、测试、分配、持久化和恢复的完整流程
main/skill-management/scripts/download_skill.py 标准库实现的 ZIP 下载器;解析 slug、解压、校验 SKILL frontmatter、清理压缩包并输出 JSON

15.2 采购分析技能

文件 作用
procurement/procurement-analysis/SKILL.md 需求拆解、ERP/外部数据收集、Python 分析、图表和报告的五步工作流
procurement/chart_params.md 26 类可视化的 data 格式、特殊参数和通用参数速查
procurement/supplier-price-urls/SKILL.md 指导 Agent 按供应商/零件查报价 URL
procurement/supplier-price-urls/data/url_mapping.yaml 供应商与物料到报价页面的静态映射数据
procurement/web-content-fetcher/SKILL.md 通过 Jina/markdown.new/defuddle 等外部转换服务获取网页 Markdown 的说明
procurement/web-content-fetcher/fetch_content.py 多服务 HTTP 获取、指定方法或 fallback 的 Python CLI
procurement/web-content-fetcher/fetch.sh 同能力的 Shell CLI
procurement/web-content-fetcher/_meta.json 技能来源、slug、版本和发布时间元数据
procurement/web-scraper/SKILL.md 沙箱内直接抓网页并保存 Markdown 的说明,适合内网 URL
procurement/web-scraper/scrape_page.py requests + BeautifulSoup + markdownify/手工 fallback 的完整 HTML→Markdown CLI
procurement/web-scraper/_meta.json web-scraper 技能元数据

web-scraperweb-content-fetcher 的区别:前者从沙箱直接请求目标 URL,能访问沙箱网络可见的内网;后者依赖外部网页转 Markdown 服务,适合公开网页快速提取。

16. frontend/:Vue 3 用户界面

16.1 构建与入口

文件 作用
index.html Vite HTML 入口,提供 #app 挂载点
package.json 前端元数据、dev/build/preview 脚本和依赖
package-lock.json npm 完整依赖锁,不手工修改
vite.config.js Vue 插件、3000 端口、/api → localhost:8090 代理和 dist 输出
src/main.js 创建 Vue app、加载全局 CSS、挂载 App.vue
src/styles/main.css 全局重置、颜色、排版和页面基础样式
src/config/assets.js 根据 Vite base URL 生成 logo/hero 图片地址
public/assets/whale-girl-logo.png 侧边栏品牌图
public/assets/whale-girl-hero.png 聊天空状态主视觉

16.2 API 客户端

文件 作用 关键点
src/api/client.js JWT fetch 基础层 token 存 sessionStorage;401 清 token 并广播 auth-expired
src/api/auth.js 注册、登录、恢复登录、退出 注册/登录成功后保存 access token
src/api/chat.js POST SSE 客户端 解析 token/tool_start/tool_args/tool_result/tool_end/interrupt/done/error;支持 AbortController
src/api/history.js 会话列表、消息、删除和预留标题更新 所有请求通过 authFetch
src/api/tasks.js 异步任务列表 GET /api/tasks?limit=

chat.js 使用工具栈而不是单一“当前工具”,因此能处理主 Agent 的 task 包住子 Agent 工具调用的嵌套顺序。

16.3 主状态机 App.vue

App.vue 持有全局页面状态:

  • currentUserauthChecking:认证状态。
  • sessionscurrentThreadId:会话导航。
  • messages:按时间混合存放 user/assistant/tool。
  • isStreamingabortController:流式请求和停止。
  • interruptDataisResuming:HITL 暂停与恢复。
  • asyncTaskstasksRefreshingtaskRefreshTimer:后台任务轮询。

挂载后先恢复 JWT,再加载会话和任务,并每 5 秒刷新任务。发送与恢复使用两套相似回调,把 SSE 事件增量更新到 messages。中断存在时隐藏普通输入框,显示 InterruptBanner

16.4 组件文件

文件 作用 输入/输出或重要行为
AuthScreen.vue 登录/注册双模式页面 调用 auth API;成功后 emit authenticated
Sidebar.vue 品牌、会话搜索、会话选择/删除、用户与退出 emit select/new/delete/logout;本地过滤会话标题
ChatArea.vue 聊天空状态与消息滚动容器 根据 showToolCalls 过滤工具消息;深度监听并自动滚到底部
MessageItem.vue user/assistant/tool 三种消息渲染 assistant/tool 文本交给 MarkdownRenderer;工具参数和结果可折叠;支持图片预览
MarkdownRenderer.vue Markdown、代码高亮和图片渲染 使用 markdown-it + highlight.js + v-html;当前原始 HTML 开启且未做 DOM 清洗,是已知安全待办
InputArea.vue 文本输入、自动高度、发送/停止和工具开关 Enter 发送,Shift+Enter 换行;流式时切换为停止按钮
InterruptBanner.vue 订单字段补充和最终审批 UI emit resume;补充格式 {supplement},审批格式 {decisions}
AsyncTaskPanel.vue 页面右侧可收缩异步任务抽屉 展开状态保存在 localStorage;展示 running/success/error/cancelled 等状态
ToolCallPanel.vue 旧的独立工具调用详情面板 当前没有被 App.vue 或其他组件导入;工具信息已改由 MessageItem 内联显示

17. SSE 事件协议

事件 后端产生位置 前端处理 用途
token chat.py AIMessage/Chunk chat.js onToken 增量追加助手文本,并保留 source
tool_start 发现 tool call name onToolStart 创建 calling 状态的工具消息
tool_args tool call 参数 chunk onToolArgs 追加栈顶工具参数
tool_result ToolMessage onToolResult 填充文本、图片并出栈
tool_end 工具结果之后 onToolEnd 兜底标记工具完成
interrupt values stream 中的 interrupts onInterrupt 显示数据补充或审批 UI
done 正常结束或中断保存后 onDone 结束流、更新 thread、刷新会话/任务
error stream 异常 onError 显示错误并恢复界面状态

消息 source 从 LangGraph namespace 提取。namespace 中有 tools: 段时视为子 Agent;否则为 main

18. 数据与存储架构

18.1 MongoDB 集合

集合 写入者 保存内容 隔离键
users auth_service.py 用户名、显示名、scrypt 密码哈希、状态 _id/唯一 normalized username
session_owners AgentLoader thread 所属用户和时间 唯一 thread_id + user_id 校验
checkpoints MongoDBSaver LangGraph 状态、interrupt 和消息 thread_id
checkpoint_writes LangGraph checkpoint 实现 pending writes/内部状态 LangGraph 配置
session_display_messages AgentLoader 前端混合展示消息,每条一个文档 thread_id + user_id + index
store_items MongoDBStore 用户偏好与持久化技能 namespace_path + key
sandbox_registry backends/sandbox_manager.py 用户到沙箱 ID 的绑定 唯一 user_id
async_tasks async_task_store.py task/run/status/result/error task_id + user_id

18.2 CompositeBackend 路由

默认路径,例如 /analysis、/data、/skills
  → 当前用户 OpenSandbox

/memories/
  → MongoDB StoreBackend
  → namespace 由 runtime.context.user_id 决定

/persisted-skills/
  → MongoDB StoreBackend
  → namespace = (users, user_id, skills)

这让 Agent 看见的是统一文件系统,但不同目录实际落在不同存储介质。

18.3 浏览器存储

Key 存储类型 用途
erp_agent_access_token sessionStorage 当前标签页 JWT,关闭标签页后清除
erp_async_tasks_collapsed localStorage 记住异步任务抽屉是否收起

18.4 MySQL 示例库

motorparts_db.sql 是 Java ERP 的业务数据,而不是 Agent 自身状态。八张表分别覆盖客户、库存、物流、订单明细、零部件、采购订单、供应商和 ERP 用户。

19. API 路由速查

方法 路径 认证 作用
POST /api/auth/register 注册并签发 JWT
POST /api/auth/login 登录并签发 JWT
GET /api/auth/me 恢复当前用户
POST /api/chat/stream 新建/继续对话,返回 SSE
POST /api/chat/{thread_id}/resume 恢复 HITL 中断,返回 SSE
GET /api/chat/{thread_id} 当前 checkpoint 消息状态
GET /api/chat/{thread_id}/history checkpoint 状态历史
GET /api/history 当前用户会话列表
GET /api/history/{thread_id}/messages 当前用户完整展示消息
DELETE /api/history/{thread_id} 删除会话相关数据
PATCH /api/history/{thread_id} 标题更新预留接口,当前不真正持久化
GET /api/tasks 当前用户异步任务及刷新后的状态/结果
GET /health FastAPI 健康检查

20. scripts/tests/

20.1 自动化脚本

文件 作用 是否产生数据
scripts/run_benchmark.py 检查 Web/Java 服务,压测只读库存接口,可选真实 SSE 并发样本 reports/*.json--include-llm 会注册 qa_bench_* 用户
scripts/generate_report.py 把 benchmark JSON 转成 Markdown 摘要 写同名 .md
scripts/run_agent_smoke.py 终端交互式调用 Agent、显示流和处理两类 interrupt 手工调试入口,不由 pytest 收集
scripts/run_mcp_smoke.py MCP/Agent 客户端连接示例 手工连接辅助,不由 pytest 收集

20.2 tests/:单元、隔离与服务验收测试

文件 作用 注意事项
tests/conftest.py 服务 URL、随机 QA 凭据、live LLM 开关 用户名前缀 qa_auto_
tests/test_async_sandbox_binding.py claim round-trip、防跨 task 重放、异步工具集完整性 单元测试
tests/test_mongodb_store_namespace.py namespace 完整身份和分隔符无歧义 单元测试
tests/test_sandbox_prebuilt_environment.py 镜像快速检查和禁止隐式现场安装 单元测试
tests/test_user_skill_isolation.py 用户技能 namespace、同名技能隔离和分页恢复 单元测试
tests/test_all_tools.py MCP 全工具和 Agent 业务链路测试;随机数据生成 integration;含 ERP 写测试,必须谨慎
tests/test_auth_isolation.py 注册、JWT /me、空历史、未授权 401 会创建 QA 用户,应清理
tests/test_mcp_integration.py 检查 8 个 ERP MCP 工具并真实查询火花塞 只读,需要 MCP 与 Java ERP
tests/test_sse_stream.py 验证真实 token→done SSE 生命周期 默认跳过;需 RUN_LIVE_LLM_TESTS=1,会创建 QA 用户/会话

test_all_tools.py 属于 integration 测试,其中的写测试只有显式设置 RUN_ERP_WRITE_TESTS=1 才应执行。任何会注册账号或写数据库的测试,都应在 finally/fixture 中补充自动清理。

21. reports/docs/

21.1 文档

文件 作用
docs/CODEBASE_GUIDE.md 当前文件:面向开发者的目录、调用链和复习手册
docs/agent-backend-stress-test-report.md 后端功能、高压联调、修复记录、性能观察和剩余风险
docs/frontend-multi-user-api.md 前端接入 JWT、多用户隔离、聊天/历史 API 的精简接口说明

21.2 历史性能快照

reports/ 中每个时间点有一份原始 JSON 和一份 Markdown 摘要:

  • benchmark-20260821-141746.*:库存预警 10 样本,平均约 0.038 秒。
  • benchmark-20260821-141814.*:库存预警 10 样本,平均约 0.016 秒。
  • benchmark-20260821-141913.*:库存预警 10 样本,平均约 0.107 秒。

这些是历史证据,不是运行配置。旧报告标题仍可能使用项目旧称,不影响当前 generate_report.py 以后生成 procurepilot 标题。

22. 按需求定位代码

需求 第一入口 继续追踪
修改主 Agent 路由规则 agent/memory/prompts.py graphs/main_agent.pymemory/AGENTS.md
新增同步子 Agent subagents/configs/*.yaml subagents/loader.pygraphs/main_agent.py
新增异步子 Agent langgraph.json 异步 graph 工厂、user_async_subagents.py
新增 ERP MCP 工具 mcp_server/tools/ server_main.pymcp_client.py、子 Agent YAML
修改订单字段/审批 procurement_order.yaml order_tools.pyhitl_tools.pyInterruptBanner.vue
修改 SSE 事件 api_view/api/chat.py frontend/src/api/chat.jsApp.vue
修改会话历史展示 agent_loader.py api/history.pyhistory.jsSidebar.vue
修改异步任务面板 api/tasks.py tasks.jsApp.vueAsyncTaskPanel.vue
修改用户登录 auth_service.py api/auth.pyapi/auth.jsAuthScreen.vue
修改用户沙箱生命周期 backends/sandbox_manager.py sandbox_setup.pysandbox_proxy.pysandbox_health.py
修改沙箱命令执行 backends/custom_opensandbox.py Docker 镜像与 OpenSandbox 配置
新增预置 Skill src/skills/{scope}/ skills_sync.py、对应子 Agent YAML
修改持久化技能 assign_skill.py user_skills_restore.pymongodb_store.py
修改长期偏好 context_injection.py memory_update.py、CompositeBackend /memories/ 路由
修改图表类型 chart_generator.py skills/procurement/chart_params.md
修改模型 agent/core/config.py .env.examplecore/env_utils.py
修改服务端口 各服务配置 start_web.py、README、Vite proxy、Docker Compose

23. 推荐复习路线

23.1 只复习“多 Agent 编排”

memory/prompts.py
  → graphs/main_agent.py
  → subagents/loader.py
  → configs/procurement_order.yaml
  → configs/procurement_analyst.yaml
  → middlewares/factories.py

23.2 只复习“异步 Agent”

graphs/main_agent.py 中的 ASYNC_ANALYST_INSTRUCTIONS
  → user_async_subagents.py
  → core/async_sandbox_claims.py
  → langgraph.json
  → graphs/async_analyst.py
  → async_task_store.py
  → api_view/api/tasks.py
  → frontend AsyncTaskPanel.vue

23.3 只复习“沙箱机制”

docker-compose.yml / opensandbox.toml
  → sandbox-image/Dockerfile
  → custom_opensandbox.py
  → sandbox_setup.py
  → sandbox_manager.py
  → sandbox_proxy.py
  → sandbox_health.py
  → sandbox_breaker.py

23.4 只复习“多用户隔离”

auth_service.py
  → AgentLoader.claim/assert_thread_owner
  → backends/sandbox_manager.py
  → config.user_skills_namespace
  → MongoDBStore namespace_path
  → async sandbox claim

23.5 只复习“SSE 与前端状态机”

api_view/api/chat.py
  → frontend/src/api/chat.js
  → frontend/src/App.vue
  → ChatArea.vue
  → MessageItem.vue
  → InterruptBanner.vue

24. 本地生成目录和忽略项

以下内容可能出现在当前电脑,但不应当作为项目源码阅读或提交:

路径 来源 是否可删除/重建
.env 本地真实密钥 不提交;删除前先确认密钥有备份
.venv/ Python 虚拟环境 可由 uv sync 或 pip 重建
frontend/node_modules/ npm 依赖 可由 npm install 重建
frontend/dist/ Vite 生产构建 可由 npm run build 重建
__pycache__/.pytest_cache/.ruff_cache/ Python/测试/静态检查缓存 可安全重建
*.egg-info/build/dist/ Python 构建产物 可由 uv build 重建
.langgraph_api/ LangGraph 开发运行状态 本地运行产物
src/download/ 从用户沙箱下载到宿主机的报告/文件 可能含用户数据,不提交;删除前确认是否需要保留
*.logtemp/.tmp-* 运行日志和临时文件 通常可重建,排障时可能有价值

25. 当前代码边界与复习时容易混淆的点

  1. Java ERP 源码不在本仓库;src/mcp_server/ 是它的 MCP 适配层,不是 ERP 本身。
  2. Agent Protocol 主要承载异步采购分析;同步聊天和订单子 Agent 在 FastAPI 进程创建的主 Graph 中执行。
  3. 同步和异步 Agent 都可以操作 OpenSandbox,但异步 Agent 必须通过签名 claim 连接当前用户的同一个私有沙箱。
  4. 用户沙箱生命周期统一由 src/agent/backends/sandbox_manager.py 管理,包括预热、重连、重建和 MongoDB 绑定。
  5. ToolCallPanel.vue 当前未接入页面;实际工具调用由 MessageItem.vue 内联渲染。
  6. session_display_messages 与 checkpoint 故意分开:前者保证 UI 顺序与子 Agent 工具细节,后者保证 Graph 恢复和 HITL。
  7. /memories//persisted-skills/ 看起来像文件夹,实际由 CompositeBackend 路由到 MongoDB Store。
  8. src/download/ 是宿主机下载落点,不是沙箱内 /analysis/tasks/{task_id}/
  9. langgraph dev 是开发运行时;生产异步执行需要独立部署和持久性方案。
  10. MarkdownRenderer.vue 当前允许原始 HTML 并用 v-html,尚未完成 DOMPurify/协议白名单修复,复习前端安全时应优先查看。
  11. Docker 默认配置为本地开发模式,OpenSandbox 无鉴权且挂载 Docker Socket;公网部署必须收紧。
  12. 前端任务完成更新当前依赖 5 秒轮询,不是服务端主动推送。

26. 术语表

术语 在本项目中的含义
Graph Factory 启动期预计算重组件,请求期按用户沙箱创建轻量 Graph
Agent Protocol LangGraph 的 thread/run/graph HTTP 执行服务,本项目用于异步 analyst
MCP Agent 工具协议;本地 FastMCP 把 Java ERP REST API 暴露成工具
HITL Human-in-the-Loop;字段补充和订单最终审批两类中断
Checkpoint LangGraph 可恢复执行状态,包含 interrupt 和消息状态
Display Messages 专门供前端重放的 user/assistant/tool 混合消息
StoreBackend 把 LangGraph Store 映射成 Agent 可见文件路径的后端
CompositeBackend 按路径把文件访问分流到沙箱或 Store 的虚拟文件系统
SandboxBackendProxy 可热替换底层沙箱、但保持 Graph 引用不变的稳定代理
Skill Agent 按需读取的操作手册、脚本和资源,不等同于固定函数工具
Offloading 大工具结果自动写文件,只在上下文保留路径和预览
Summarization 上下文接近上限时把旧消息压缩为摘要
task_id 异步任务的 LangGraph thread ID,也是任务目录和查询主键

27. 最短记忆总结

如果很久以后只记得五句话,请记住:

  1. AgentLoader 是 Web 到用户 Agent/沙箱的入口。
  2. graphs/main_agent.py 是整个主 Graph 的装配中心。
  3. backends/sandbox_manager.py + sandbox_proxy.py 是用户隔离和故障恢复的核心。
  4. user_async_subagents.py + graphs/async_analyst.py + langgraph.json 构成异步分析闭环。
  5. chat.py + frontend/api/chat.js + App.vue 构成从 LangGraph 到浏览器的 SSE 状态机。