跳转到内容
快速开始

引擎

架构概览

Mirobody Python 引擎的组成:三个步骤各占一块,共享一层基础设施与一层 HTTP,运行成两个进程。

Mirobody 是一个 Python 引擎(3.12 及以上)。它由三块组成,与三个步骤一一对应:采集接入数据、标准化把读数解析到规范编码、问答由 agent 在其上作答。三者共用一层基础设施(账号、任务队列、MCP、配置),上面盖着薄薄一层 HTTP。

它运行成两个进程,共用一个 PostgreSQL 库和一个 Redis:mirobody serve 提供 HTTP,mirobody worker 消费后台队列。所有代码在同一个可导入的包里,因此一次部署就是一份配置文件加一句 docker compose up,不需要构建。

LangChain 及其相关依赖允许出现在问答那一块,构建时由 import 契约强制。因此 pip install mirobody 装出来的采集与标准化,环境里只有 numpy 一个第三方包 —— 这也是它能当普通库用的原因,见引擎即库

这样切开是有意的:运行一趟几分钟的 embedding 扫描,不该由处理 HTTP 请求的那个进程来干。

进程命令干什么
HTTP 服务mirobody serve加载配置、引导数据库 schema、装配路由与中间件,在 HTTP_HOST:HTTP_PORT 上提供服务。
后台 workermirobody worker读同一份配置,消费 Redis 上的后台任务:补齐指标编码与 embedding、重建用户画像。不监听端口。

两者都需要 [agents] extra,未安装时命令会打印一行「该装什么」再退出。收到 SIGINT / SIGTERM 时,worker 把手头的活收尾后再退出。

compose.yaml 里这是两个容器:一个发布 18080 跑 HTTP 服务,一个不发布端口跑 worker。

按请求穿过的顺序,每一层的行为与它读的配置键:

  1. 响应压缩 超过 10 000 字节的响应会被 gzip 压缩。
  2. CORS HTTP_HEADERS 读取。把 Access-Control-Allow-Origin: * 与携带凭据同时设置时引擎会告警,因为浏览器不接受这种组合。
  3. JWT 校验 bearer token 并解析出调用方(匿名为 0),同时从请求头取 languagetimezonetrace_id。只有配了 JWT_KEY 才启用。
  4. 限流 按用户、按路径每分钟计数,超出返回 429 并带 Retry-After。由 REQUEST_RATE_LIMITER 驱动,默认 /api/chat: 6/api/session: 6
  5. 回写用户偏好 USER_INFO_UPDATER 列出的路径,把调用方的语言与时区写回用户记录。

启动时,只要 ENV 不是 TEST / GRAY / PROD / TEST-INLOCAL 之一,引擎就会创建配置里写的 schema 并执行随包的建表脚本。这是本地引导路径;生产库的迁移是手工做的。

区块目录里面是什么
① 采集pulse/设备 provider、端上批量样本导入、文件解析、归一化写入、日聚合与洞察引擎。见数据流
② 标准化indicator/概念图、基于 embedding 的解析、UCUM 单位族与临床分类体系。见健康指标
③ 问答agent/两个 agent、/api/* 对话接口面、MCP 工具面、Agent Skills 与提示词。见 Agent 类型
共享基础设施server/ · mcp/ · task/ · user/ · utils/HTTP 装配、MCP 服务、后台任务、账号与鉴权、配置与数据库访问。

其中五个配置键是目录驱动的:MCP_TOOL_DIRSAGENT_DIRSPROVIDER_DIRSMCP_RESOURCE_DIRSSKILL_DIRS,各自指定启动时要扫描的目录。新增一个工具、agent、provider 或 skill,就是丢一个文件进去再重启,不需要在任何注册表里登记。每个键默认只有包内那一条;要用自己的目录,把它列在最前面。

两个路由家族的前缀规则不同,把引擎放到按路径转发的代理后面之前需要先明确:

  • 对话、MCP、登录、OAuth 这些路由在设了 HTTP_URI_PREFIX 时会带上前缀。
  • Pulse、分享、文件、指标 这些路由的前缀声明在自己身上,不受 HTTP_URI_PREFIX 影响。

OAuth 发现文档、SPA 兜底路由、/charts/mirobody.json 在两种情况下都不带前缀。

完整路由清单
区域路由
健康检查GET /api/health
对话GET /api/models · /api/agents · /api/providers · /api/promptsPOST /api/sessionGET /api/history · /api/history_by_personPOST /api/history/delete · /api/rating · /api/chatGET /api/beneficiary-users
按用户的配置GET / POST /api/user/mcpPOST /api/user/mcp/set · /api/user/mcp/deleteGET / POST /api/user/promptPOST /api/user/prompt/set · /api/user/prompt/delete
MCPPOST / GET /mcp/mcp/{secret}POST /personal/mcp
OAuth/.well-known/oauth-authorization-server/.well-known/oauth-authorization-server/mcp/.well-known/mcp-configuration/oauth/register · /oauth/authorize · /oauth/token · /oauth/introspect/oauth2/authorize/oauth2/check_state/{state}
登录POST /email/login · /email/verify · /email/bindPOST /apple/verify · /google/verifyPOST /user/del · /user/update_name
WebAuthn/auth/webauthn/{register,login,upgrade}/{options,verify}/auth/session/renew/auth/session/reauth/{options,verify}
文件POST /files/uploadGET /files/{file_path}GET /api/v1/data/uploaded-files · /api/v1/data/data-distributionPOST /api/v1/data/delete-files、WebSocket /ws/upload-health-report
指标GET /api/v1/health-indicatorsPOST /api/v1/health-indicators/reading
Pulse(公开)GET /api/v1/pulse/providers · /user/providers · /user-data-sources · /user/insights · /indicators · /unitsPOST /api/v1/pulse/user/providers/link · /unlinkPOST /api/v1/pulse/{platform}/webhook · /{platform}/tokenGET /api/v1/pulse/{platform}/{provider}/callback
端上批量样本POST /apple/health · /apple/statistics · /apple/cda,同时也挂在 /api/v1/pulse/apple/* 之下,因为上传方可能指向其中任意一套
Pulse(管理)/api/v1/manage/pulse/*(指标、单位、user-health-data、monitor、insight、data-quality)、/api/v1/manage/theta/pull/*POST /api/v1/manage/aggregate/recalculate-range
分享POST /api/share/createGET /api/share/{share_session_id}/invitation/shared-by-me/*/invitation/shared-with-me/*/invitation/permissions/list
用户设置GET / POST /api/user/settingsPOST /api/user/virtual
Web 客户端以上都没命中、又不在后端自有前缀下的请求兜底到 SPA 外壳;/mirobody.json/__/auth/init.json/charts 是显式注册的

SPA 兜底会对 /api/mcp/oauth/oauth2/invitation/apple/google/email/personal/auth/session/auth/webauthn/.well-known 之下未命中的 GET 返回 404,而不是外壳:后端路径打错是个错误,不是客户端深链。

  • 数据库:PostgreSQL,带 vectorpg_trgmpgcrypto 扩展,默认 schema 是 theta_ai。compose 文件把镜像钉在 pgvector/pgvector:pg17-trixie,发布 18082:5432。连接项是 PG_HOST / PG_PORT / PG_USER / PG_PASSWORD / PG_DBNAME / PG_SCHEMA / PG_MIN_CONNECTION / PG_MAX_CONNECTION
  • 缓存与队列:Redis(redis:7.0-alpine,发布 18089:6379)。它支撑限流、后台任务队列与 provider 拉取用的锁。
  • 对象存储:优先使用配好的云端后端,没有则退到本地磁盘。S3 后端读 S3_KEY / S3_TOKEN / S3_REGION / S3_BUCKET / S3_PREFIX / S3_CDN
  • 配置:三层,仓库模板 config.yaml、你自己的 config.{env}.yaml,以及 .envENV + CONFIG_ENCRYPTION_KEY)。环境变量优先于两个 YAML 文件。用 CONFIG_SERVER + CONFIG_TOKEN 可以指向远端配置服务。见配置
  • 鉴权:多用户 JWT、一个带发现文档(供 MCP 客户端使用)的 OAuth 2.0 授权服务器、邮箱一次性验证码、Apple 与 Google 登录,以及 WebAuthn。
  • Web 客户端:一个预构建的 SPA,从 HTTP_ROOT 提供,每个请求都从磁盘读,因此换掉构建产物不需要重启。它刻意放在 Python 包之外:wheel 装的是引擎,不是那堆 JavaScript。页面是 /data(文档与读数)与 /ask(agent),另有关爱圈共享。