Skip to content

Latest commit

 

History

History

README.md

backend/api — OpenMathModel API 与当前本地运行面

FastAPI + SQLAlchemy 2 + Alembic。契约见 packages/contracts(事实来源 schemas/v1,本服务响应模型直接使用 omm_contracts 生成模型)。

当前边界:API 负责鉴权、项目、TaskRun、审批、事件、Artifact 与工作台投影;本地默认还由 API 进程内 RunnerThread 推进 agents/core 状态机和 SimStageNode。backend/worker 是尚未接入 API 调度链的独立执行面原型。

本地运行

完整 Web + API 联调(推荐)

首次在仓库根安装依赖后,统一启动入口会自动确保本地 PostgreSQL 在运行(未运行则拉起),再启动或复用 API,健康检查成功后启动 Web:

# 仓库根
npm run dev

登录和工作台需要 API;npm run dev:web 只启动 Vite。完整安装、健康检查和登录→Project→TaskRun→run_id 的可复制验证流程见根 README。

单独启动 API

# 首次:仓库根目录
py -3.12 -m venv .venv
.venv\Scripts\python -m pip install -e packages/contracts -e agents/core -e agents/skills -e "backend/api[dev]"

# 启动(热重载,参数已钉死):
npm run dev:api

# 等价的完整命令。数据库为 PostgreSQL(见下节)。启动时先探库,本地 pg-dev 实例没起会自动 start 一次;
# --reload-dir 必带——不加时 --reload 监视整个 cwd(含 backend/api/data/),沙盒每写一个 .py
# 就把 API 重启一次、打断运行中的阶段;--timeout-graceful-shutdown 让 SSE 长连接不拖死重载
.venv\Scripts\python -m uvicorn omm_api.asgi:app --app-dir backend/api --reload --reload-dir backend/api/omm_api --reload-dir agents --timeout-graceful-shutdown 5 --port 8000

漏了 --reload-dir 时 API 不会拒绝启动,但会在启动日志里打一块 !!! 包住的 ERROR (omm.reload_guard:「热重载配置会让实验阶段无法完成」,写明监视目录、沙盒目录与修正命令), 之后每次被打断的阶段修复落定时 omm.engine 也会重复这条诊断。页面上用户只会看到 「上一次执行在中途意外中断,已自动重新开始第 N 次尝试」——不带任何开发细节,且重跑不设次数上限。

Invoke-RestMethod http://127.0.0.1:8000/api/health

数据库为本地 PostgreSQL(默认 127.0.0.1:5433),Artifact 存放于 backend/api/data/artifacts/。

数据库:限定 PostgreSQL

OMM_DATABASE_URL 的代码默认值即路径 A 的本地实例,正常情况无需设置任何环境变量。SQLite 不再是任何默认路径(仅测试夹具的临时隔离库使用,见下文「测试」)。

# A. 免安装用户级 PG(tools/pg-dev.ps1,port 5433;默认连接目标,无 Docker 即可用)
.\tools\pg-dev.ps1 init      # 首次建库;之后 npm run dev 与 API 启动探库都会自动 start(OMM_LOCAL_PG_AUTOSTART=false 可关)

# B. Docker 底座(tools/dev-up.ps1,port 5432,见 infra/docker/compose.dev.yaml)——需显式覆盖连接串
$env:OMM_DATABASE_URL="postgresql+psycopg://openmathmodel:openmathmodel-dev@127.0.0.1:5432/openmathmodel"

# schema 以 Alembic 迁移为准(启动时 create_all 仅兜底建缺失表)
cd backend/api
..\..\.venv\Scripts\python -m alembic upgrade head

路径 A 已在 Windows PowerShell 5.1 下端到端验证:init → Alembic 迁移 → API 连接 → 全量测试套件通过。历史 SQLite 数据可用 tools/migrate-sqlite-to-pg.py 整库迁入(含 timestamptz 补时区与孤儿行过滤)。

环境变量(前缀 OMM_)

变量 默认 说明
OMM_DATABASE_URL postgresql+psycopg://openmathmodel:openmathmodel@127.0.0.1:5433/openmathmodel 数据库限定 PostgreSQL;仅端口/凭据不同(如 Docker 底座 5432)时覆盖。PG 单次建连上限固定 5 秒,库没起时快速报错而不是拖到驱动超时
OMM_LOCAL_PG_AUTOSTART true 启动探库失败且目标是 tools/pg-dev.ps1 管的本地实例(Windows、127.0.0.1/localhost:5433)时自动 start 一次;Docker 5432、远端库、非 Windows 不触发
OMM_SECRET_KEY dev-secret-change-me 2FA 挑战令牌签名密钥,生产必须覆盖
OMM_RUNNER_ENABLED true API 进程内推进线程;当前由 agents/core 与 SimStageNode 驱动
OMM_RUNNER_TICK_SECONDS 1.2 推进节奏
OMM_DATABASE_POOL_SIZE 10 PostgreSQL 连接池常驻连接数:运行并行推进,每个在途运行都在用连接,推进锁另占一条
OMM_DATABASE_MAX_OVERFLOW 40 连接池可临时溢出的连接数
OMM_DEFAULT_MAX_CONCURRENT_RUNS 空(不限) 「最大并发任务」的部署默认上限(用户没设时生效);用户自己选的上限不超过 8
OMM_AVATARS_DIR backend/api/data/avatars 用户头像内容存储根,与运行产物目录分开
OMM_AVATAR_MAX_BYTES 2097152 单个头像上限;前端会先压到 256×256,这是服务端兜底
OMM_ATTACHMENT_TEXT_MAX_BYTES 33554432 正文抽取上限,比上传上限更严;超过的附件只留原文件不抽正文
OMM_OCR_LANGUAGES chi_sim+eng 图片 OCR 语言包(Tesseract 回落路径);未安装 Tesseract 时不生效
OMM_OCR_API_KEY 空 远程 OCR(讯飞星辰 MaaS · PaddleOCR,OpenAI 兼容协议)的 API key;留空 = 功能关闭。敏感项,放 backend/api/.env 或环境变量
OMM_OCR_API_BASE_URL https://maas-api.cn-huabei-1.xf-yun.com/v2 远程 OCR 的 OpenAI 兼容 Base URL
OMM_OCR_API_MODEL xoppaddleocrv16 远程 OCR 的 Model ID
OMM_OCR_API_TIMEOUT_SECONDS 60 单次识别调用(每页一次)的超时
OMM_RUN_MAX_TOKENS 无上限 单次运行的 token 硬停(E310)。默认不设限,填正整数才启用;0 / 负数 / 不填 = 关闭
OMM_RUN_MAX_LLM_CALLS 无上限 单次运行的模型调用次数硬停(E310),同上
OMM_RUN_MAX_SANDBOX_RUNS 无上限 单次运行的沙箱执行次数硬停(E310,按次预付),同上
OMM_NODE_MAX_TOKENS 无上限 单个阶段节点的 token 硬停(E320),同上
OMM_TECTONIC_PATH 空(探测 PATH) .tex 源编译 PDF 用的 Tectonic 可执行文件(ADR-0012);找不到时该类导出落 UNSUPPORTED 并说明启用途径
OMM_CHROMIUM_PATH 空(自动探测) HTML 源打印 PDF 用的 Chrome / Edge / Chromium(ADR-0025,论文编辑器「导出 PDF」走这条);空 = 先 PATH、再各平台默认安装位置。找不到时落 UNSUPPORTED,前端退回浏览器打印
OMM_PAPER_EXPORT_TIMEOUT_SECONDS 120 单次编译 / 打印的超时,到点强杀子进程
OMM_PAPER_EXPORT_MAX_BYTES 2097152 source_tex 的 UTF-8 字节上限
OMM_PAPER_EXPORT_HTML_MAX_BYTES 33554432 source_html 的 UTF-8 字节上限:图片与 KaTeX 字体都以 data URL 内联在 HTML 里,所以比 .tex 宽得多
OMM_PAPER_EXPORT_QUEUE_LIMIT 20 全局排队上限(超出 409 QUEUE_FULL);另有每用户同时只排一个 PDF(409 CONCURRENCY_LIMIT)

四项资源预算 2026-09-08 起默认关闭。它们原本要防的失控是「后端进程反复重启 → 阶段无上限重跑」,那条的根子是热重载监视到了沙盒目录(启动参数问题):2026-09-19 起 API 启动时与每次中断修复落定时都会在服务端日志里诊断(omm.reload_guard / omm.engine,见上方启动命令说明),被中断的阶段无上限自动重跑、不再判死运行; 而额度一旦烧光是整个运行不可逆卡死——账本按 run 累计,人工 「重试」也会在调用前的预检处被拦。要恢复硬闸就填正整数,改完需重启 API 进程; 每发起一轮修订,run/node 两级的有限额度各追加一份(ADR-0013 §3.1),不设限的 维度不受影响。用量记录不受开关影响,「设置中心 → 用量监控」的月度费用预算与 硬限制是另一套闸门,仍然照常生效。

测试

# 测试夹具默认用 SQLite 临时库(仅测试隔离用途,快且零依赖;产品运行限定 PostgreSQL)
.venv\Scripts\python -m pytest backend/api/tests -q

# 对真实 PostgreSQL 实跑同一套件(每用例独立 schema,用完清理)
$env:OMM_TEST_DATABASE_URL="postgresql+psycopg://openmathmodel:openmathmodel@127.0.0.1:5433/openmathmodel_test"
.venv\Scripts\python -m pytest backend/api/tests -q

鉴权

/api/v1 全部资源要求登录(httpOnly Cookie 会话):先 POST /api/auth/register 或 login。 项目/任务按 owner(用户 ID)隔离,他人资源一律 404。登录限速为数据库实现(login_attempts 表,多实例一致)。

设计要点

  • 当前推进器 = API 内嵌 RunnerThread + agents/core 引擎:run_domain_events 表是执行事实来源(append-only 领域事件,重放即恢复),v1 行(task_runs/step_runs/artifacts/approvals/agent_events)是其投影,同一事务提交;胶水层见 omm_api/engine_glue.py。每个节拍给每个可推进的运行派一个工作线程推进一个阶段步骤(运行之间并行,同一运行同一时刻只有一个线程;跨进程由 PostgreSQL advisory lock 互斥);完整真实 Skills 节点与独立 Worker 接线仍在后续阶段。
  • 契约对齐 schemas/v1:status 是生命周期枚举(QUEUED/RUNNING/WAITING_APPROVAL/...),current_node 是领域阶段(PROBLEM_ANALYSIS/...),两轴分离(规划 §12.3)。
  • 统一错误信封 {code, message, request_id, details};X-Request-Id 响应头贯穿日志。
  • agent_events 是 UI 时间线唯一事实来源;(run_id, sequence) 唯一、单调递增。
  • SSE GET /api/v1/task-runs/{id}/events 支持 Last-Event-ID/after 断线补拉,终态自动 stream.end;历史补拉走 /events/history。
  • 写操作幂等:创建与动作支持 Idempotency-Key 请求头(同键同体重放首响,异体 409);approve 另支持 client_token。
  • 动作 approve/pause/resume/cancel/retry 按状态机校验;approve 的 reject 选项退回重做 MODEL_PLANNING 并再次请求确认。
  • 失败注入:params.fail_at / params.fail_attempts(兼容 goal 含 [fail:experiment]),用于验证 FAILED → retry 链路。
  • Artifact 存储闭环(B4):二进制内容按 sha256 内容寻址存放在 data/artifacts/(协议可替换,MinIO/S3 待底座就绪);上传 POST /api/v1/projects/{id}/artifacts(multipart,服务端重算哈希),下载 GET /api/v1/artifacts/{id}/download(下载即核验,哈希不一致返回 ARTIFACT_CORRUPTED);模拟工作流产物经同一存储端口真实落盘、可下载。
  • 附件正文抽取:GET /api/v1/artifacts/{id}/text 返回 Agent 可读的纯文本。抽取放在读取时而不是上传时——上传要对用户即时响应,而几十兆的 PDF 抽一遍要好几秒;产物内容寻址、字节不可变,因此结果缓存在 artifact_texts 表里长期复用,服务端补装依赖后用 ?refresh=true 重跑。status 五档:ready/partial/empty/unsupported/failed,后三档也是 200,调用方要的是原因而不是错误码。docx/pptx/xlsx/ODF/压缩包/纯文本全部用标准库 zipfile + ElementTree 解(零额外依赖),PDF 用 pypdf;旧版 .doc(按 FIB 分片表抽正文)、.xls、RTF 需要 pip install -e "backend/api[legacy-docs]"。图片与扫描件 PDF 的识别走远程 OCR(讯飞星辰 MaaS 上的 PaddleOCR,OpenAI 兼容协议,配置 OMM_OCR_API_KEY 启用;输出 Markdown、公式为 LaTeX、engine="paddleocr-api"),其中扫描件 PDF 还需 [pdf-ocr] 附加项(pypdfium2 逐页渲染成图片再上送);未配置 key 时图片回落本地 Tesseract([ocr] 附加项 + 系统 Tesseract 与语言包)。缺依赖/未配置时返回 unsupported/empty 并说明原因,不抛 500。
  • 用户头像:POST /api/account/avatar(multipart)、DELETE /api/account/avatar、GET /api/account/avatar。内容走与 Artifact 相同的内容寻址实现但独立目录 data/avatars/(归属与回收边界不同),users 表只存 avatar_sha256 与服务端识别的 avatar_media_type。格式按文件魔数判定(PNG/JPEG/WebP/GIF),声明的 Content-Type 不作数——头像以同源 URL 回给浏览器,放行 SVG 等同于同源脚本注入;响应固定带 X-Content-Type-Options: nosniff。读取只按当前会话返回本人头像,不提供按 user_id 的公开地址。user_payload.avatar_url 带内容摘要查询串,换图后 URL 自动变化。
  • 论文导出(ADR-0012 / ADR-0025):POST /api/v1/paper-exports 的源二选一——source_tex 交 Tectonic 编译(format=pdf|tex),source_html 交无头 Chrome / Edge 按 A4 打印(只用于 format=pdf)。源与 PDF 都落 kind="paper" 产物,PDF 的 inputs 指向源产物,编译失败时源仍可下载排查。论文编辑器的「导出 PDF」走 HTML 源;Word / LaTeX / HTML 三种在浏览器本机生成,不经服务端。打印隔离:HTML 经 DevTools Page.setDocumentContent 写进 about:blank(读不到 file://),文档最前强插 CSP(禁脚本、禁外部资源,只认 data: 图片与字体),浏览器进程再挂一个不存在的代理让漏网请求落空;独立临时用户目录、超时强杀。Linux 服务器要装中文字体(如 fonts-noto-cjk),否则中文打印成方框。Tectonic 首次编译要联网拉宏包,离线部署需在镜像里先编译一份样例预热缓存(Windows 在 %LOCALAPPDATA%\TectonicProject\Tectonic,Linux 在 ~/.cache/Tectonic)。两种引擎缺失时任务都落 UNSUPPORTED、detail 写明启用途径。
  • SQLite 补列机制(仅显式 SQLite 路径生效):create_all 只建新表、不改已存在的表,因此对 SQLite 库启动时额外补齐模型新增的可空列(omm_api/db.py)。数据库已限定 PostgreSQL(以 Alembic 为准)后,该机制只服务测试夹具与应急排查场景;两种方言的 schema 一致性由 tests/test_migrations.py 守住。

当前与目标边界

维度 当前默认 目标演进
数据库 PostgreSQL(已限定,开发与部署统一;SQLite 仅测试夹具) PostgreSQL
工作流推进 API 进程内 RunnerThread API 发布幂等任务,独立 Worker 消费
阶段节点 agents/core + SimStageNode 版本化真实 Skills 节点
文件存储 本地内容寻址目录 S3 兼容对象存储

API 路径、共享契约和页面工作台投影在迁移期间保持稳定。最新系统级事实见系统架构。