构建智能体

🔵 创建智能体的完整路径——只需 YAML,以及可选的脚本。先讲哲学,再讲机制。


4.1 哲学

在编写 agent.yaml 之前,值得先理解你究竟在创建什么。

智能体是描述,而非代码

在 Agent OS 中,智能体是由 YAML 组装而成的:身份、工具、钩子、预算。 代码(Rhai/JS)只有在需要自定义工具或钩子时才需要。 这是一种声明式组装:你在“职位说明书”中描述一名员工, 而不是编写他的“大脑”。所有繁重的部分——回合循环、工具调用处理、 token 核算、隔离、日志记录、规划——都由内核完成。

定义智能体的五个问题

任何智能体都是对五个问题的回答。每一个都是独立的一面,在配置中都有独立的位置:

问题 字段 这是什么
他是谁? instructions 角色、身份、行为规则和回复格式
他会什么? tools / tool_files 内置工具和 YAML/脚本工具
他在生命周期中如何表现? hooks / hook_files 记忆、缓存、升级、上下文注入
他有多少额度? budget / budget_group token 限额(这就是钱)
请求从哪来? telegram、email、widget、API 输入通道

职责分离

每一面都存在于自己的文件中,因此可以单独修改它们: 替换工具而不动提示词;重写提示词而不动钩子; 把限额移到 budgets.yaml 中,完全不碰智能体。这正是结构的核心 意义:

config/agents/<name>/
├── agent.yaml          # 身份 + 绑定
├── tools/*.yaml        # 能力
├── hooks/*.yaml        # 生命周期
├── bench/scenarios.yaml # 质量控制
└── tests/*.yaml        # 工具测试

智能体作为员工

一个有用的比喻:instructions —— 职位说明书(做什么以及如何 回复),tools —— 工作岗位上的技能和工具,hooks —— 反射 和规则(记忆、常用缓存、转交人工),budget —— 权限 限额,通道 —— 通信线路。

优秀智能体的原则

模板 → 进程 → 会话

你描述的是模板(配方)。内核根据第一个请求创建进程 (lazy spawn),而用户的对话就是会话。修改模板——新的 会话会获得新行为;已经启动的进程会保留旧的 系统提示词。


4.2 目录结构

服务器会自行查找智能体:任何 config/agents/ 的子文件夹,只要包含 agent.yaml(或 agent.yml),就会成为模板。tool_files 和 hook_files 相对于智能体文件夹解析。


4.3 agent.yaml —— 参考手册

name: my-agent
welcome: |
  👋 Привет! Чем помочь?
instructions: |
  You are a helpful assistant.
  Use my_tool to answer questions about X.
  Answer in Russian.
tools: [http_fetch]
tool_files:
  - tools/my_tool.yaml
hooks: []                    # имена хуков, зарегистрированных при старте
hook_files:
  - hooks/faq_cache.yaml
route_files:                # HTTP-маршруты, которые добавляет агент (http_route / static_route)
  - routes/api.yaml
max_context_tokens: 32000
max_output_tokens: 8192       # потолок ответа за inference (перекрывает kernel.max_output_tokens)
model: ""                   # именованная модель из server.yaml → models:
output_schema: null         # JSON Schema структурированного завершения
budget:                     # опциональное перекрытие бюджета
  refill_per_sec: 30
  max_bucket: 10000
  max_total: 200000
budget_group: my-shared
budget_messages:
  session_exhausted: "Лимит диалога исчерпан. Начните новый."
  user_exhausted: "Лимит на сегодня исчерпан."
  group_exhausted: "Общий лимит исчерпан. Попробуйте позже."
  rate_limited: "Слишком много запросов. Подождите."
inherit_budget: false
durable: false              # true = сессия переживает рестарт (opt-in)
supervision: null           # политика авто-рестарта (opt-in), см. ниже
parent: null
attachments:                 # что виджет принимает от пользователя (файлы/скриншоты/камера)
  enabled: true              # false = виджет скрывает кнопку вложений
  file: true                 # загрузка файлов (по умолч. true)
  screenshot: true           # снимок экрана (по умолч. true)
  camera: true               # фото с камеры (по умолч. true)
  accept: "image/*,.pdf"     # маска accept для файлового инпута (опционально)
  max_size: 10485760         # макс. размер файла в байтах (опционально)
login:                        # вход в виджете (см. §19.1)
  enabled: false             # показывать кнопку «Войти»
  required: false            # требовать вход (анонимные отклоняются, 401)
  providers: [yandex]        # OIDC-провайдеры (пусто = все)
  allow_users: false         # разрешить вход логином/паролем из auth.users
  prompt: "Войти через Яндекс"
widget:                       # оформление виджета (см. §19.1)
  show_close: true           # кнопка «закрыть» (×); false — для инлайн-встраивания
  starters: []               # вопросы-кнопки под приветствием нового диалога
  persist: true              # сохранять диалог при закрытии/перезагрузке (по умолч. true)
  history_limit: 40          # сколько завершённых обменов хранить в истории
  tool_visuals: final        # визуалы тулов: final|always|never (см. §19)
cron: []                    # задания по расписанию этого агента
telegram:
  token_env: MY_TG_BOT_TOKEN
  allowed_user_ids: []
  show_typing: true
  show_tool_calls: true
email:
  imap_host: imap.example.com
  imap_port: 993
  imap_username: support@example.com
  imap_password_env: EMAIL_PASSWORD
  allowed_senders: []
  smtp:
    host: smtp.example.com
    username: support@example.com
    password_env: SMTP_PASSWORD
    from: support@example.com
  notify: []
  poll_secs: 60
vk:
  token_env: VK_GROUP_TOKEN
  group_id: 123456789
  allowed_user_ids: []
  show_typing: true
  show_tool_calls: true
  poll_wait: 25

字段参考

字段 必填 默认值 描述
name 是 — 模板的唯一名称
instructions 是 — 系统提示词
welcome 否 — widget 的欢迎语
tools 否 [] 内置工具(http_fetch、fs_read、job_done)
tool_files 否 [] YAML 工具的路径(相对于智能体文件夹)
hooks 否 [] 钩子名称(在启动时注册)
hook_files 否 [] YAML 钩子的路径(相对于智能体文件夹)
route_files 否 [] YAML HTTP 路由 http_route/static_route 的路径(相对于智能体文件夹;见 §21)
max_context_tokens 否 32000 上下文窗口限制
max_output_tokens 否 全局 kernel.max_output_tokens(4096) 单次 inference 的回复长度上限。覆盖全局值,并在需要时提升进程的 per-turn 预算
model 否 空 来自 server.yaml → models: 的命名模型(空 = 默认 provider)
output_schema 否 — 完成项的 JSON Schema:替换 job_done 参数
budget 否 — {refill_per_sec, max_bucket, max_total}
budget_group 否 — 共享预算组的名称
budget_messages 否 — 耗尽消息(4 个级别)
inherit_budget 否 false 子级继承父级的组
durable 否 false 会话在核心重启后仍然存活:进程、预算组和会话在回合边界保存,并在 boot 时恢复(需要 logging.durable_db_path;见 §27.2)
supervision 否 null 崩溃时的自动重启策略(见下文)
sandbox 否 null per-agent 沙箱策略(限额/文件系统根),覆盖全局 server.yaml → sandbox(见 §3.2)
parent 否 — 父级名称(进程树)
attachments 否 — 来自用户的附件:{enabled, file, screenshot, camera, accept, max_size} —— 在 widget 中启用附件按钮,并接收来自通道的附件(email/telegram/vk,§19)
login 否 — widget 中的登录:{enabled, required, providers, allow_users, prompt} —— 强制 OIDC 授权和/或 auth.users(§19.1)
admin_only 否 false 仅限管理员:对 agent 的聊天请求必须携带有效的 admin 凭据(bearer token 或 admin login-cookie),否则返回 403。关闭认证时该门禁开放。适用于高权限 agent(例如 coder,它会编辑文件并执行命令)
cron 否 [] 按计划触发该 agent 的定时任务
telegram 否 — {token_env, allowed_user_ids, show_typing, show_tool_calls}
email 否 — 入站 email 通道(见下文)
vk 否 — 入站 VK 社区机器人(见下文)
yandex_messenger 否 — 入站 Yandex Messenger 机器人(Bot API,轮询)

监督(自动重启)

可选:agent 在崩溃时重启(非瞬态推理错误或意外退出且 exit≠0)。

supervision:
  restart: on_failure        # never | on_failure | always
  max_restarts: 3            # бюджет перезапусков в окне
  window_secs: 300
  backoff_secs: 5            # задержка перед рестартом
  resume: from_template      # from_template | from_checkpoint
  replay_input: false        # opt-in: переиграть вход, на котором упало
  on_exhausted: terminate    # terminate | quarantine
  poison_threshold: 3        # одинаковых ошибок до карантина (0 = выкл)

输入/输出通道

模板可以绑定通道——将消息路由到该 agent 的入站来源: widget、Telegram 机器人、VK 机器人、Yandex Messenger 机器人、 email、HTTP API、webhook。在 agent.yaml 中,这些是字段 telegram、vk、 yandex_messenger 和 email。所有通道的完整解析(widget 嵌入、 Telegram/VK/Yandex Messenger/email 配置、sink)——见 §19。

结构化输出(output_schema)

设置 output_schema,让 agent 返回固定形式而非 自由文本。内核会用该 schema 替换参数 job_done(如果 tools: 中没有 job_done,则添加它),并且模型必须以调用 job_done(...) 并传入合适的值来结束:

output_schema:
  type: object
  properties:
    needs_action: { type: boolean }
    summary: { type: string }
  required: [needs_action, summary]

job_done 的参数在结束前会被校验;无效调用 会被拒绝(模型看到错误,并可在同一循环中修正)。 校验后的值可供调用方使用(例如 subagent_tool 会将其放入 结果的 data 中)。


4.4 分步操作

第 1 步 —— 骨架

mkdir -p config/agents/mybot/tools config/agents/mybot/hooks config/agents/mybot/bench config/agents/mybot/tests

编写 config/agents/mybot/agent.yaml(见 §4.3)。

第 2 步 —— 工具

创建 config/agents/mybot/tools/my_tool.yaml(见 工具参考),并在 tool_files: 中列出它。 无需服务器即可验证:

agent-os tool config/agents/mybot/tools/my_tool.yaml '{"query":"..."}'

第 3 步 —— 钩子(可选)

创建 config/agents/mybot/hooks/<hook>.yaml(见 06-hooks-reference), 并在 hook_files: 中列出它。无需服务器即可验证:

agent-os hook config/agents/mybot/hooks/<hook>.yaml user "Привет"

第 4 步 —— 预算组

将组添加到 config/budgets.yaml 并绑定模板:

budget_groups:
  - name: mybot-shared
    refill_per_sec: 30
    max_bucket: 30000
    max_total: 3000000
agents:
  - template: my-agent
    group: mybot-shared

第 5 步 —— 基准测试场景

创建 config/agents/mybot/bench/scenarios.yaml:

name: "MyBot"
model: "my-agent"

scenarios:
  - name: "Простой вопрос"
    turns:
      - user: "Как мне ...?"
        assert:
          contains: [ожидаемое, слово]
          min_links: 1
          valid_urls: true

检查类型:contains(子串,不区分大小写)、min_links / min_urls(markdown 链接数量)、valid_urls(所有链接均为 http/https)。

运行(见 09-operations):

agent-os bench --port=3001 --agent=my-agent --max=3

第 6 步 —— 工具测试(可选)

config/agents/mybot/tests/my_tool_tests.yaml 中的 fixture 测试(无网络):

kind: site_tool_tests
tool: tools/my_tool.yaml
tests:
  - name: "извлекает заголовок"
    input: { url: "https://example.com/p/1" }
    fixture: { productData: { title: "Widget", price: 99 } }
    assert:
      - { path: /title, equals: "Widget" }
      - { path: /price, equals: 99 }

Live 测试(真实 HTTP:kind: site_tool_live_tests、 archive_tool_live_tests、rss_tool_live_tests)通过设置 fetch: true 来替代 fixture:。如何运行它们——见 09-operations。


4.5 指令实践


4.6 热重载

在 live_reload: true(默认)下,可以实时修改 agent.yaml、工具 和钩子:新会话采用新指令,重新注册的 工具/钩子从下一次调用起生效。已启动的进程 保留原始系统提示词。