引擎
架构概览
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 上提供服务。 |
| 后台 worker | mirobody worker | 读同一份配置,消费 Redis 上的后台任务:补齐指标编码与 embedding、重建用户画像。不监听端口。 |
两者都需要 [agents] extra,未安装时命令会打印一行「该装什么」再退出。收到 SIGINT / SIGTERM 时,worker 把手头的活收尾后再退出。
在 compose.yaml 里这是两个容器:一个发布 18080 跑 HTTP 服务,一个不发布端口跑 worker。
请求经过的中间件
Section titled “请求经过的中间件”按请求穿过的顺序,每一层的行为与它读的配置键:
- 响应压缩 超过 10 000 字节的响应会被 gzip 压缩。
- CORS 从
HTTP_HEADERS读取。把Access-Control-Allow-Origin: *与携带凭据同时设置时引擎会告警,因为浏览器不接受这种组合。 - JWT 校验 bearer token 并解析出调用方(匿名为 0),同时从请求头取
language、timezone与trace_id。只有配了JWT_KEY才启用。 - 限流 按用户、按路径每分钟计数,超出返回
429并带Retry-After。由REQUEST_RATE_LIMITER驱动,默认/api/chat: 6、/api/session: 6。 - 回写用户偏好 对
USER_INFO_UPDATER列出的路径,把调用方的语言与时区写回用户记录。
启动时,只要 ENV 不是 TEST / GRAY / PROD / TEST-INLOCAL 之一,引擎就会创建配置里写的 schema 并执行随包的建表脚本。这是本地引导路径;生产库的迁移是手工做的。
三个步骤与共享基础设施
Section titled “三个步骤与共享基础设施”| 区块 | 目录 | 里面是什么 |
|---|---|---|
| ① 采集 | pulse/ | 设备 provider、端上批量样本导入、文件解析、归一化写入、日聚合与洞察引擎。见数据流。 |
| ② 标准化 | indicator/ | 概念图、基于 embedding 的解析、UCUM 单位族与临床分类体系。见健康指标。 |
| ③ 问答 | agent/ | 两个 agent、/api/* 对话接口面、MCP 工具面、Agent Skills 与提示词。见 Agent 类型。 |
| 共享基础设施 | server/ · mcp/ · task/ · user/ · utils/ | HTTP 装配、MCP 服务、后台任务、账号与鉴权、配置与数据库访问。 |
其中五个配置键是目录驱动的:MCP_TOOL_DIRS、AGENT_DIRS、PROVIDER_DIRS、MCP_RESOURCE_DIRS、SKILL_DIRS,各自指定启动时要扫描的目录。新增一个工具、agent、provider 或 skill,就是丢一个文件进去再重启,不需要在任何注册表里登记。每个键默认只有包内那一条;要用自己的目录,把它列在最前面。
HTTP 路由
Section titled “HTTP 路由”两个路由家族的前缀规则不同,把引擎放到按路径转发的代理后面之前需要先明确:
- 对话、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/prompts、POST /api/session、GET /api/history · /api/history_by_person、POST /api/history/delete · /api/rating · /api/chat、GET /api/beneficiary-users |
| 按用户的配置 | GET / POST /api/user/mcp、POST /api/user/mcp/set · /api/user/mcp/delete、GET / POST /api/user/prompt、POST /api/user/prompt/set · /api/user/prompt/delete |
| MCP | POST / 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/bind、POST /apple/verify · /google/verify、POST /user/del · /user/update_name |
| WebAuthn | /auth/webauthn/{register,login,upgrade}/{options,verify}、/auth/session/renew、/auth/session/reauth/{options,verify} |
| 文件 | POST /files/upload、GET /files/{file_path}、GET /api/v1/data/uploaded-files · /api/v1/data/data-distribution、POST /api/v1/data/delete-files、WebSocket /ws/upload-health-report |
| 指标 | GET /api/v1/health-indicators、POST /api/v1/health-indicators/reading |
| Pulse(公开) | GET /api/v1/pulse/providers · /user/providers · /user-data-sources · /user/insights · /indicators · /units、POST /api/v1/pulse/user/providers/link · /unlink、POST /api/v1/pulse/{platform}/webhook · /{platform}/token、GET /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/create、GET /api/share/{share_session_id}、/invitation/shared-by-me/*、/invitation/shared-with-me/*、/invitation/permissions/list |
| 用户设置 | GET / POST /api/user/settings、POST /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,带
vector、pg_trgm与pgcrypto扩展,默认 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,以及.env(ENV+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),另有关爱圈共享。
健康数据的三条接入路径,以及数据最终的落点
指标登记、单位换算与语义检索
运行期的工具发现与 MCP 接口面是怎么合起来的
deploy.sh、compose.yaml,以及那两个容器