跳转到内容
快速开始

开始使用

配置

Mirobody 引擎如何解析配置:三层结构、敏感值自动加密,以及 config.yaml 里每个键按用途的分组说明。

引擎的每一项设置都是一个扁平的大写键。键可以来自 YAML 文件、来自远程配置服务,也可以来自进程环境变量,启动时由加载器合并成同一个命名空间。没有按模块拆分的配置文件,进程运行期间也不会热加载:改完配置需要重启。

./deploy.sh 会替你生成 .envconfig.{env}.yaml,之后需要你亲手填的只有下面这些。其余的键都有能用的默认值,等到真的要改再回来看键清单

你要填在哪不填会怎样
ENV · CONFIG_ENCRYPTION_KEY.env起不来:找不到覆盖文件,或解不开加密值
一把 LLM key(如 OPENROUTER_API_KEYconfig.{env}.yaml能登录,但对话没有模型可用
一把 embedding keyGOOGLE_API_KEYDASHSCOPE_API_KEYconfig.{env}.yaml可选:不配它,自由文本检索指标会退回成列出目录,显示名也保持数据源原本的拼写
JWT_KEYconfig.{env}.yaml鉴权层不启用,任何人都是匿名
数据库与 Redis 连接项compose.yaml 已给出用 Compose 时不用改;连外部实例时才要填
.env -> 用哪个环境,以及加密密钥
config.{env}.yaml -> 你的覆盖项
config.yaml -> 仓库自带的模板

优先级自上而下,最先定义该键的来源胜出:

一次查找会先看进程环境变量:先按原样键名找,再按大写键名找;查不到,才轮到合并后的 YAML 映射。合并本身是按加载顺序直接覆盖:config.yaml、远程配置、config.{env}.yaml,最后定义某个键的那个文件才是生效的那个。

文件是否入库由谁写作用
config.yaml上游全部键的默认值。文件开头自己就写着不要编辑本文件
config.{env}.yaml否(.gitignore 里的 *.*.yaml 会命中它)你,或首次运行的 deploy.sh你要改的一切。ENV=localdb 时就是 config.localdb.yaml
.env你,或 deploy.shENV 决定上面那个文件;CONFIG_ENCRYPTION_KEY 用于解开加密值。

.env 会最先加载,其中每一行都变成环境变量,写入方式是 setdefault,所以 shell 里已经 export 的变量优先于文件。这一点值得记住:放进 .env 的任何键都压得过两层 YAML,不只是 ENVCONFIG_ENCRYPTION_KEY

.env
# 'localdb'、'test'、'gray'、'prod',或你自己起的名字。
ENV=localdb
# 最长 32 个字符。用于加密 config.{env}.yaml 里的敏感值。
CONFIG_ENCRYPTION_KEY=Xk3pQ7mZ2vB9nR4tY6wL8sD1fG5hJ0aC

设好三个环境变量,引擎就会在读取本地覆盖文件之前,先通过 HTTP 拉取一份已解析好的 YAML 文档:

Terminal window
CONFIG_SERVER=https://config.example.com
CONFIG_TOKEN=<作为 X-Config-Token 请求头发送>
ENV=prod

实际请求是 GET {CONFIG_SERVER}/api/v1/config/environments/{ENV}/configs/resolved?is_yaml=true。三者中任一为空,或请求失败,引擎会记录原因并继续只用本地文件。远程配置在 config.{env}.yaml 之前加载,所以本地覆盖仍然胜出,适合用来在某台机器上单独钉住一个键。compose.yaml 会把 CONFIG_SERVERCONFIG_TOKEN 透传进容器,所以写在 .env 里就够了。

覆盖文件里的敏感值会在第一次以明文被读到时就地加密。规则完全看键名:顶层的字符串值,只要名字匹配

_KEY _PASSWORD _PASS _PWD _SECRET _SK _TOKEN

就会被加密,只有两个例外:名字以 _URL 结尾的会跳过(所以 GARMIN_TOKEN_URL 保持可读),字面量 REPLACE_THIS_VALUE_IN_PRODUCTION 也不动,模板里的占位符因此永远不会变成密文。

所以你写的是这样:

config.localdb.yaml
JWT_KEY: 7f2Ka9LmQ4xRt6Zv1Bn8Cs3Wd5Yh0Pj2
OPENROUTER_API_KEY: sk-or-v1-abcdef
GARMIN_TOKEN_URL: https://connectapi.garmin.com/oauth-service/oauth/request_token

启动一次之后,磁盘上的文件变成这样:

config.localdb.yaml
JWT_KEY: gAAAAABm9x...已截断...Q3w==
OPENROUTER_API_KEY: gAAAAABm9x...已截断...7Yk=
GARMIN_TOKEN_URL: https://connectapi.garmin.com/oauth-service/oauth/request_token

重写走的是 ruamel.yaml 的往返解析,所以你的注释、键顺序和引号风格都会保留。之后每次加载,加载器都会把以 gAAAA 开头的值识别为 Fernet 令牌并解密到内存里,文件本身不再变动。启动摘要里,*_API_KEY 的值会以 abc******xyz 的形式打码打印。

CONFIG_ENCRYPTION_KEY 是一个原始口令,不是 Fernet 密钥。加载器会去掉首尾空白、最多取前 32 个字符、不足 32 字节时用 0 右填充,再做 base64 编码得出 Fernet 密钥。deploy.sh 生成的是 32 位随机字母数字;任何等价做法都可以:

Terminal window
openssl rand -hex 16 # 32 个字符

除特别说明外,下面的键都存在于 config.yaml。在那里被注释掉的键是未生效的默认值,你可以复制到自己的文件里。

默认值说明
LOG_LEVELDEBUG不区分大小写。无法识别的名字回退到 INFO
LOG_NAMELOG_DIR未设置两个都不设=只输出控制台。文件名形如 {date}_{name}_{time}.log
DEFAULT_TIMEZONEAmerica/Los_Angeles用于尚未自己选时区的用户。
config.localdb.yaml
HTTP_HOST: 0.0.0.0
HTTP_PORT: 18080
HTTP_ROOT: frontend
HTTP_HEADERS:
Access-Control-Allow-Origin: 'http://localhost:18080'
Access-Control-Allow-Credentials: 'true'
Access-Control-Allow-Methods: 'GET, POST, PUT, DELETE, OPTIONS'
Access-Control-Allow-Headers: 'Authorization, Content-Type'
Access-Control-Max-Age: '600'
REQUEST_RATE_LIMITER:
/api/chat: 6
/api/session: 6
默认值说明
HTTP_SERVER_NAMEmirobody同时作为 Server: 响应头输出,后面接版本号。
HTTP_HOST0.0.0.0
HTTP_PORT80模板里是注释掉的,所以从源码运行时若不设置就监听 80
HTTP_URI_PREFIX把全部路由挂到某个子路径下;首尾斜杠会被规范化。
HTTP_ROOTfrontend预构建的 Web 客户端,按运行进程旁边的路径解析。它在 Python 包之外,所以 wheel 装的是引擎,而不是那堆 JavaScript;路径不存在时就是不提供客户端。
HTTP_HEADERS未设置原样输出的响应头,CORS 就写在这里。注意模板自己的示例是固定 origin 搭配 Allow-Credentials,这正是浏览器要求的组合;* 与凭据同时出现会被拒绝。
REQUEST_RATE_LIMITER/api/chat: 6/api/session: 6一个 { "path": 每分钟请求数 } 映射。存在且非空就会装上限流中间件,按用户在 Redis 里计数;见架构概览
USER_INFO_UPDATER未设置一组路径,命中这些路径的请求会顺带刷新调用者的档案。

MCP_PUBLIC_URL 是你对外可达的基础 URL(例如一个 ngrok 域名):远程 MCP 客户端需要它,本地文件系统上的文件通过 {MCP_PUBLIC_URL}/files 提供,启动横幅也用它作为待打开的地址。见 Mirobody MCP Server。模板里另有 MCP_FRONTEND_URLDATA_PUBLIC_URLQR_LOGIN_URL 三个占位键,本版本没有生效的代码路径读它们。

必需:缓存、任务队列与限流都依赖它。

config.localdb.yaml
REDIS_HOST: 127.0.0.1
REDIS_PORT: 18089
REDIS_DB: 0
REDIS_PASSWORD: ''
REDIS_SSL: false
REDIS_SSL_CHECK_HOSTNAME: false
REDIS_SSL_CERT_REQS: none

模板里给的是 REDIS_HOST: 10.108.0.9,即容器在 Compose 桥接网络上的地址;从宿主机访问要用 127.0.0.1 加映射出来的端口。给每个名字加后缀就声明了第二条独立连接REDIS_HOST_LOGREDIS_PORT_LOG 等会被请求 LOG 这条连接的代码取到。

config.localdb.yaml
PG_HOST: 127.0.0.1
PG_PORT: 18082
PG_USER: holistic_user
PG_PASSWORD: ''
PG_DBNAME: holistic_db
PG_SCHEMA: theta_ai
PG_ENCRYPTION_KEY: ''
PG_MIN_CONNECTION: 5
PG_MAX_CONNECTION: 20

默认值是 PG_HOST: 10.108.0.2、用户 holistic_user、库 holistic_db、schema theta_ai、连接池 5–20。PG_ENCRYPTION_KEY 用于加密敏感列,所以它必须在第一次查询前就能解析出来。和 Redis 一样的 _后缀 写法能给你第二条连接,但有一处要注意:加密密钥是不带后缀读取的,因此带后缀的连接复用主 PG_ENCRYPTION_KEY

S3_KEYS3_TOKENS3_REGIONS3_BUCKETS3_PREFIXS3_CDN 在模板里都是注释掉的,而这本身就是一份可用配置:存储工厂逐个尝试云端后端,S3 后端在缺少 access key、secret、region 与 bucket 时拒绝构造,随后回退到本地文件系统。填上那四个必填键即可切到 S3 或任何兼容 S3 的存储;S3_PREFIX 限定键的命名空间,S3_CDN 是拼链接时用的公开基础 URL。加名字后缀可以选第二个 bucket。

config.localdb.yaml
EMAIL_PREDEFINE_CODES:
caregiver@mirobody.ai: '111111'

EMAIL_PREDEFINE_CODES 把邮箱映射到一个固定验证码:这些账号用该验证码登录,且不会真的发信。模板启用了三个演示账号,启动时还会汇总成一张表打印出来,所以一份全新的检出可以立刻用起来。要真正发出验证码,需要配上 EMAIL_SMTP_HOST / EMAIL_SMTP_PORT / EMAIL_SMTP_USER / EMAIL_SMTP_PASS,以及 EMAIL_FROMEMAIL_FROM_NAME

第三方登录按厂商填一块:Google 是 GOOGLE_CLIENT_ID + FIREBASE_PROJECT_ID,Apple 是 APPLE_TEAM_ID / APPLE_KEY_ID / APPLE_PRIVATE_KEY / APPLE_CLIENT_ID(App 与 Web 的 client id 不同时再加 APPLE_CLIENT_ID_APPAPPLE_AUTH_CLIENT_ID)。默认全部注释掉。

JWT_KEY 是 HS256 的密钥,deploy.sh 首次运行时会往你的覆盖文件里写一个随机值。它同时是整套鉴权的开关:把 bearer token 解析成调用方身份的那层中间件,只有在 JWT_KEY 非空时才会被装上。JWT_PRIVATE_KEY 是 RS256 的替代方案。JWT_ISSJWT_AUDJWT_CLIENT_IDJWT_SCOPE 会成为引擎为 MCP 客户端签发的令牌里对应的 claim。

MIROBODY_WEB_CONFIG 是一个嵌套映射,运行时交给随包的 Web 客户端:__IS_*_ON__ 功能开关加上浏览器端的 Firebase 取值。整块在模板里是注释掉的。

config.localdb.yaml
MIROBODY_WEB_CONFIG:
__IS_API_CONFIG_ON__: true
__IS_QR_LOGIN_ON__: false
__IS_GOOGLE_LOGIN_ON__: true
__IS_APPLE_LOGIN_ON__: true
__IS_NEW_FEATURES_ON__:
- MCP
__IS_MOBILE_SOURCE_ON__: true
__FIREBASE_API_KEY__: ""
__FIREBASE_AUTH_DOMAIN__: ""
__FIREBASE_PROJECT_ID__: ""

五个键,每个都是一组目录,告诉引擎启动时去哪里发现你自己的代码。每一项默认只有一条,也就是随包的那条。把你自己的目录加上并放在最前面:它会先于内置目录被扫描,这正是让一个部署在不改目录树的前提下覆盖随包工具或 skill 的机制。

默认值放什么
MCP_TOOL_DIRSmirobody/agent/toolsPython 模块,其顶层函数与 *Service 类会成为工具,见工具与 Agent 概览
MCP_RESOURCE_DIRSmirobody/agent/resourcesMCP UI 资源
AGENT_DIRSmirobody/agentagent 实现,见Agent 类型
PROVIDER_DIRSmirobody/pulse/providers健康数据 provider,见开发一个 Provider
SKILL_DIRSmirobody/agent/skillsskill 目录,每个目录一个 SKILL.md,见Agent Skills

发现动作发生在启动时,所以在任何一处新增内容都意味着加文件再重启。正斜杠会被换成当前平台的分隔符,空列表会回退到默认值。

在哪申请被谁读取
GOOGLE_API_KEYaistudio.google.com/apikeyGoogle GenAI 客户端、文件解析、embedding
OPENAI_API_KEYplatform.openai.com/api-keysOpenAI 兼容客户端
OPENROUTER_API_KEYopenrouter.ai/keys一把密钥通多个模型,模板里的 provider 用的就是它
DASHSCOPE_API_KEYdashscope.console.aliyun.com/apiKeyDashScope 的 OpenAI 兼容端点;也用于语音转写
ANTHROPIC_API_KEYAnthropic 控制台直连 Claude

有四组键以 agent 名字的大写形式作后缀。随包只有两个 agent,所以真正有用的后缀是 DEEPBASE;模板里的 *_RTC 是一个已删除 agent 留下的空键,也不存在 *_MIX。一份最小的 PROVIDERS_DEEP 长这样:

config.localdb.yaml
PROVIDERS_DEEP:
gemini-3.5-flash:
llm_type: google-genai
api_key: GOOGLE_API_KEY
model: gemini-3.5-flash
temperature: 1.0
claude-sonnet:
llm_type: openai
api_key: OPENROUTER_API_KEY
base_url: https://openrouter.ai/api/v1
model: anthropic/claude-sonnet-4.6
temperature: 0.1
DISALLOWED_TOOLS_DEEP:
- task
键族形态说明
PROVIDERS_{NAME}显示名到 provider 配置块的映射用户可选的那些名字。api_key 里放的是某个配置键名,加载时才解析:若解析不到任何值,会留下一个占位客户端来报告缺失的键名,而不是在导入阶段直接失败。
ALLOWED_TOOLS_{NAME}工具名列表白名单。它优先于黑名单;两者都为空=所有被发现的工具都可用。
DISALLOWED_TOOLS_{NAME}工具名列表黑名单。
PROMPTS_{NAME}.jinja 路径列表既可从文件系统解析,也可从已安装的包内解析。path@key 后缀用来显式指定模板名,否则用文件名当名字。

哪个 agent 读哪个后缀、以及各 prompt 名字对那个 agent 意味着什么,见 Agent 类型

config.localdb.yaml
GARMIN_CLIENT_ID: ''
GARMIN_CLIENT_SECRET: ''
GARMIN_REDIRECT_URL: ''
WHOOP_CLIENT_ID: ''
WHOOP_CLIENT_SECRET: ''
WHOOP_REDIRECT_URL: ''
OAUTH_TEMP_TTL_SECONDS: 900

Garmin、Whoop 和 Oura 是随包 provider 里自带 OAuth 凭据的三个。注意 Oura 那三个键(OURA_CLIENT_IDOURA_CLIENT_SECRETOURA_REDIRECT_URL)它的 provider 会读,但模板里没有,需要你自己加。除了 client ID、secret 和回调 URL,模板还钉住了它们的端点:GARMIN_TOKEN_URLGARMIN_AUTH_URLGARMIN_ACCESS_TOKEN_URLGARMIN_API_BASE_URL,以及 WHOOP_TOKEN_URLWHOOP_AUTH_URLWHOOP_API_BASE_URLWHOOP_SCOPES,所以一般你只需填凭据。OAUTH_TEMP_TTL_SECONDS 限定一次待授权能保持多久有效。连接账号的过程见使用 Provider

模板里还带着 VITAL_API_KEYVITAL_ENVIRONMENT、整块 RENPHO_*,以及被注释的 FRONTIERX_CLIENT_ID / FRONTIERX_USER_POOL_ID。本版本中它们背后都没有对应的 provider 包,请当作预留键。

没有凭据的 provider 不会报错,它会拒绝启动并打印 declined to start (not configured),这是诚实状态,不是故障。唯一可以立刻试的是 PostgreSQL provider:设上 ENABLE_PGSQL_DEVICE: 1,下次启动平台就会打印 loaded 1 providers

默认值说明
EMBEDDING_PROVIDERgeminigemini(需要 GOOGLE_API_KEY)或 qwen(需要 DASHSCOPE_API_KEY)。同时决定 embedding 模型和指标检索使用的向量列;未知取值会抛错。这与聊天模型用的是两把不同的 key,而且它是可选的:不配它,指标检索会退回成列出目录,显示名也不会被规整。配了但不可用时,只在 worker 日志里静默失败。见健康指标
<PROVIDER>_VISION_MODEL各 provider 自带覆盖图片与 PDF 解析所用的视觉模型,键名随 provider 变。

DATABASE_DECRYPTION_KEY 在模板里被标注为废弃,未来可能移除:当前的列加密密钥是 PG_ENCRYPTION_KEY

启动时引擎会打印一份已解析配置的摘要:读了哪些文件、环境名、日志与 HTTP 设置、数据存储、发现到的目录,以及找到的 API 密钥(打码)。要确认某一层是否按你预期生效,看这份摘要最快。

Terminal window
docker compose logs mirobody | head -40 # Compose 下
curl http://localhost:18080/api/health # 工具、资源、agent 的数量

如果某个值看起来是旧的,按这个顺序排查:是否有环境变量把它盖住了(包括来自 .env 的),然后你的文件名是否真的是与所设 ENV 对应的 config.{ENV}.yaml,最后键名拼写是否与加载器期望的完全一致。查找会转成大写,所以文件里的大小写无关紧要,但键名的其它部分没有任何容错。