7.1 KiB
MCP 后台计算队列
API 保留 uvicorn --workers 4。长计算在项目 BizDB 持久化入队,由独立计算进程领取;所有 API 进程读取相同任务状态,不依赖粘性会话。HTTP 的 5 分钟空闲超时保留,Agent 的提交和状态查询采用 30 秒调用预算、35 秒宿主预算。timeout_seconds 对入队工具表示 Worker 的执行预算,不是 HTTP 等待时间,也不是模拟时间 duration。
前端 / Agent → API × 4 → 项目 BizDB calculation_jobs
↑
独立计算 supervisor
↓
受管理的计算子进程组
工具契约
原工具名继续使用。以下工具提交后返回 QueryResult,其中 semantics=task,data 包含 job_id、status、tool 和 poll_after_ms;这只证明提交状态,不是分析结果或调查证据:
run_burst_location、run_leakage_identification、run_burst_detectionrun_simulation、run_burst_simulation、run_contaminant_simulationanalyze_valve_closure、analyze_flushing、analyze_pressure_regulationoptimize_sensor_placementsave_simulation_scenario、create_sensor_placement的dry_run=false执行阶段
后两项保留预览、摘要校验和原有审批;提交与执行时均校验权限及预览摘要,已有写入回执继续约束重复请求。analyze_valve_isolation、calculate_demand_allocation 保持同步执行。普通查询和其他写入不进入此队列。
| MCP 工具 | 输入 | 返回 |
|---|---|---|
get_calculation_job |
job_id |
状态、工具、时间和安全错误码 |
get_calculation_result |
job_id,可选 data_path/limit/offset |
成功后读取原分析结果;未成功返回任务状态 |
list_calculation_jobs |
可选 status/limit/offset |
当前授权会话及调查轮次的任务列表 |
cancel_calculation_job |
job_id |
排队任务可直接取消;运行任务先进入 cancel_requested |
状态为 queued/running/cancel_requested/succeeded/failed/cancelled/timed_out/interrupted。任务取消不等于在线模型写入回滚;发生失败、超时或中断时,需要核查已有写入,不能自动重算整个窗口。
当前使用普通 MCP 业务工具承载队列契约,没有宣称已实现协议级 Tasks 扩展。Server 和 Agent 目录必须配套更新,旧 Agent 不应接收新的队列返回契约。
身份、幂等和结果
任务记录保存提交人的元数据用户 ID、项目、原工具参数和受限授权快照,不保存 Bearer、数据库 DSN 或短期 context_handle。执行和读取结果时重新查询当前用户、成员关系、项目路由、权限及调查运行租约。任务不会因为短期 MCP 句柄失效而丢失;也不会借持久化任务绕过撤销的权限。
普通计算使用宿主从原生工具调用记录解析出的稳定调用 ID;同一次传输重试复用任务,不将新的业务计算自动按参数合并。重写工具同时保留原审批 request_id 幂等语义。不同参数复用同一操作编号会被拒绝。
调查的排队回执不写成分析证据。成功结果读取时,按原计算来源、任务及运行验证,并幂等地保存证据。未完成的计算阻止提交最终报告、insufficient_data 或将运行标记成功;中间进度仍可保存。
Agent 调查执行在原生回合空闲后由宿主短轮询等待,继续维持调查心跳,计算进入终态后再触发模型读取结果。普通聊天展示任务编号,可在当前会话后续查询。异常流结束时若无法安全继续处理新结果,会明确失败而不是虚报完成;Server 中的计算结果仍保留。
并发、取消和恢复
一个 supervisor 默认全项目合计并发 2 项,独立于 API 的 4 个进程。它持有元数据库的会话级 advisory lock,第二个 supervisor 会拒绝启动;当前不是多台计算节点各自扩容的模式。各项目轮转领取,使用 PostgreSQL 短事务 SKIP LOCKED,租约代次约束心跳和结果写回。
每项任务有独立子进程组;取消、执行超时或失去租约时先停止进程组,再写终态。守护管道在 supervisor 被强杀后关闭,负责终止仍在运行的算法进程。API 重启不影响计算 supervisor;计算进程崩溃后,旧任务标为中断,不自动重放。
原生临时文件写入独立任务目录。临时模型数据库创建前登记到该目录的账本,正常结束或取消后只清理明确登记的临时数据库。清理失败返回 CLEANUP_FAILED 并保留恢复目录;启动时检查项目、任务和执行代次,确认旧租约过期后恢复清理。无法验证归属或清理失败时停止领取新工作。
部署
- 先对每个目标项目的 BizDB 应用
resources/sql/026_calculation_jobs.sql,包括未来项目使用的相应业务模板。不要应用到 TimescaleDB。迁移为新增表及索引,不删除业务数据。 - 配置
CALCULATION_WORKER_PROJECT_IDS为明确的项目 UUID 列表,配置现有元数据库连接及数据库路由解密密钥。Worker 不需要新 OAuth 密钥;仅部署侧工作进程读取这些设置。 - 同步发布 Server 和 Agent,在接入长计算前启动独立 Worker。不要从 API
lifespan启动,不要把调查 Worker 当作计算 Worker。
从 Server 仓库启动:
conda run -n server python scripts/run_calculation_worker.py
systemd 模板:infra/systemd/tjwater-calculation-worker.service。Docker 使用同一 Server 镜像的独立服务:
docker compose -f infra/docker/docker-compose.yml --profile calculations up -d calculation-worker
Compose 使用 ../../.env 注入配置,需确保其中数据库地址在容器内可达。calculation-state volume 保存任务恢复账本,不能当作临时缓存删除。非容器启动可用 CALCULATION_WORKER_STATE_DIR 指定持久目录。
并发默认 2、轮询默认 2 秒、租约 30 秒、心跳 5 秒。算法内部并行度仍消耗额外 CPU/内存;调整并发必须依据实际资源测量。
回滚前暂停接收新的队列计算并等待或明确取消在途任务,核对有副作用任务的写入。保留任务表、结果和恢复目录;旧同步版本不支持继续处理这些作业,不能直接删除记录后重新提交。
验证
conda run -n server python -m pytest tests/unit/test_mcp_calculation_jobs.py tests/unit/test_calculation_worker.py tests/unit/test_calculation_cleanup.py tests/unit/test_mcp_query.py tests/unit/test_mcp_analysis.py tests/unit/test_operational_tasks.py -q
真实 PostgreSQL 并发测试为 tests/integration/test_calculation_queue_storage.py。它要求 CALCULATION_QUEUE_TEST_DSN 指向名称以 tjwater_calc_test_ 开头的专用可丢弃数据库,不能指向业务库。覆盖四连接重复提交、参数冲突、并发领取、项目和用户隔离、取消竞争、失效代次拒绝和过期租约恢复。
这些验证不代替真实项目算法运行、浏览器断线重连或部署验收。生产接入还应观察超过 5 分钟的真实计算、恢复后结果读取和原生临时资源清理。