组件与输入/输出通道

🔵 用户和外部系统如何与智能体通信。这不是软件意义上的「API」,而是通道——智能体从配置中获得的现成入口/出口点。

通道 配置位置 性质
组件(网页) server.yaml → widget + 嵌入 widget.js 可嵌入网站的聊天
Telegram 机器人 agent.yaml → telegram(按智能体) long-polling,私聊
社区 VK 机器人 agent.yaml → vk(按智能体) Long Poll,社区消息
Yandex Messenger agent.yaml → yandex_messenger(按智能体) polling getUpdates,私聊和群聊
Email(IMAP→SMTP) agent.yaml → email(按智能体) 接收邮件 + 回复
Storage(S3/WebDAV/FTP) agent.yaml → storage(按智能体) 来自存储桶/文件夹的新文件 → 智能体
HTTP API(SSE) 无需配置 POST /v1/chat/completions(§8)
传入 webhook server.yaml → inbound POST /v1/inbound/:template(§8.8)
Sink(输出) cron/email/workflow 中的 notify log/webhook/telegram/vk/yandex_messenger/messages_db/agent/email/workflow/storage(§11)

19.1 组件(网页)

可嵌入的聊天界面,通过 /v1/chat/completions(SSE)与智能体通信,并通过 POST /v1/sessions/:id 关闭会话。

嵌入

嵌入到网站任意页面:

<script src="http://127.0.0.1:3000/widget.js"></script>

提供的路由:/widget(页面)、/widget.js(加载器)、/dist/*(资源)。页面 /widget 通过带构建版本的地址引用资源(/dist/<версия>/*):浏览器会永久缓存它们,部署后立即获取新版本。不带版本的地址仍然保留,并在每次请求时重新校验。

打开哪个智能体

组件按以下顺序解析模型(后者优先):

  1. widget.default_model(来自 server.yaml);
  2. window.AGENTOS_MODEL —— 父框架的变量;
  3. URL 中的 ?model=(/widget?model=hello-agent)。

server.yaml 中的配置:

widget:
  default_model: "drom-agent"   # агент по умолчанию

组件从 window.AGENTOS_BASE_URL 获取基础 URL(空 = 同源)——当组件托管在其他域名时需要。

现成的演示页面

URL 内容
/widget?model=hello-agent 带特定智能体的组件
/demo/overlay 覆盖在真实网站上的组件(来自 config/demo.yaml 的标签页)
/demo 组件演示

/demo/overlay 的标签页在 config/demo.yaml 中设置(tabs: [{label, url, model}],实时热重载)。

关闭按钮

默认情况下,组件头部有一个「关闭」按钮(×):它向父框架发送 agentos:close,加载器 widget.js 隐藏弹出聊天。对于内联嵌入(组件直接插入页面,而非作为弹窗打开),不需要该按钮——可见性由页面本身控制。可以在模板级别禁用它:

widget:
  show_close: false   # не показывать кнопку «закрыть» (по умолчанию true)

felix-agent(页面 /ext/felix/)和 admin(页面 /agents/admin)就是这样做的,那里的聊天是内联嵌入的。

对话会保存

默认情况下,对话会一直保留,直到用户自己通过「新对话」按钮(头部中的铅笔图标)开始新对话。关闭组件只是隐藏它,切换演示标签页会返回到所选智能体的对话,而页面重新加载后,已完成的问题和回答(连同表格、地图和图表)会重新绘制——它们存储在浏览器的 localStorage 中,按每个智能体分别保存。

行为可以在模板级别配置:

widget:
  persist: true        # false — закрытие/перезагрузка начинают новый диалог
  history_limit: 40    # сколько завершённых обменов хранить в истории

当 persist: false 时,组件不保存也不恢复历史:关闭或重新加载页面会重新开始对话。history_limit 限制已保存转录的大小——最旧的交换会被挤出。

回答中的链接会在组件背后的网站上打开。当组件占据屏幕大部分时(手机),加载器会关闭它,以便能看到打开的页面;再次点击聊天按钮会继续对话。

对话成本和智能体名称隐藏在头部「$」按钮后面。

提示按钮

在新对话中,可以在问候语下方以按钮形式显示现成的问题——点击发送问题,就像用户自己输入一样。第一条消息后按钮消失。

widget:
  starters:
    - "Подбери игру для компании из шести человек"
    - "Что подарить ребёнку 10 лет?"

2–4 个简短具体、智能体能自信回答的问题效果很好。托管方在自己的 ChatWidget 上直接调用相同功能:widget.showSuggestions([...])。

组件中的视觉块

工具可以返回视觉层(chart 图表、table 表格、文本)——组件会内联渲染它们(Chart.js + 表格)。块的格式和分页——通用字段。

如何显示这些块由 widget.tool_visuals 决定(默认 final):

widget:
  tool_visuals: final   # final | always | never

该规则应用于组件的 SSE 流,因此保存的历史恰好包含用户看到的那些块。

组件中的附件

当模板设置了 attachments.enabled: true(§4.3)时,小组件中会出现一个 📎 按钮,可选择来源:文件(上传)、截图(getDisplayMedia)、照片(相机;在移动端为原生 capture)。文件会进入 POST /v1/uploads,而在 /v1/chat/completions 中通过 attachments 字段传递——智能体收到一条消息,其中包含这些文件在会话 scratch 目录中的名称。

此外,智能体还可以自行通过工具 request_attachment 请求附件:小组件会显示一张带按钮的提示卡片,用户附加文件后,智能体将通过 POST /v1/input-response 进行回复(§8.2.1)。

小组件中的登录

如果模板设置了 login.enabled: true(§4.3)并且配置了 auth.oidc 提供方,小组件顶部会出现**「登录」**按钮。点击后会打开带有 OIDC 登录的弹窗(GET /v1/login/widget/:provider);登录后,callback 会签发一个已签名的 x-user-token(HMAC),小组件将其缓存到 localStorage 中,并在后续请求中通过 x-user-token 请求头发送。此时 browser-id 会自动与 OIDC 身份合并(user-registry 中的 Origin::Oidc)——在所有渠道中都是同一个用户。按钮会变为「退出」(重置本地令牌)。

login:
  enabled: true
  providers: [yandex]           # пусто = все включённые провайдеры
  prompt: "Войти через Яндекс"  # текст кнопки (опционально)

强制登录(login.required)

login.required: true 使登录成为强制要求:匿名用户无法向智能体发送消息。小组件会显示全屏登录界面,而不是输入框,服务器对匿名 POST /v1/chat/completions 返回 401。该要求可通过以下方式之一满足:

login:
  required: true
  providers: [yandex]     # вход через Яндекс
  allow_users: true       # ИЛИ логин/пароль из auth.users
  prompt: "Войдите, чтобы продолжить"
字段 默认值 描述
enabled false 显示「登录」按钮(可选登录)
required false 要求登录;匿名请求会被拒绝(401)
providers [] OIDC 提供方(auth.oidc.providers);留空 = 全部
allow_users false 允许使用 auth.users 中的登录名/密码登录
prompt 留空 登录界面按钮/标题的文本

Android 应用中的登录

Android 客户端支持相同的登录方式。如果所选智能体 login.required,应用会显示登录界面而不是聊天:

获得的令牌存储在应用设置中(x-user-token),并随 x-user-id 一起发送;聊天顶部的「退出」按钮会重置身份。

动态发件人 allowlist

对于消息渠道(Telegram / VK / Yandex Messenger / email),允许的发件人列表(allowed_user_ids / allowed_logins / allowed_senders)在 agent.yaml 中设置,并且需要重新部署。还有一种动态替代方案——logs/allowed_senders.db 中的 allowed_senders 表,可通过 API 修改,无需重启/重新部署。

记录就是渠道身份:

记录绑定到智能体模板(或 * —— 全局)。当表中存在针对该模板的记录(或全局 *)时,该表优先:agent.yaml 中的静态 allowlist 会被忽略。如果表为空——则像以前一样使用静态 allowlist。

# разрешить tg:12345 и ym:ivan агенту my-agent (без редеплоя)
curl -X POST http://127.0.0.1:3000/v1/allowed-senders/my-agent \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{"identity":"tg:12345","note":"Pavel"}'
curl -X POST http://127.0.0.1:3000/v1/allowed-senders/my-agent \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
  -d '{"identity":"ym:ivan"}'

# список
curl http://127.0.0.1:3000/v1/allowed-senders/my-agent -H "Authorization: Bearer $ADMIN_TOKEN"

# удалить
curl -X DELETE http://127.0.0.1:3000/v1/allowed-senders/my-agent/tg:12345 \
  -H "Authorization: Bearer $ADMIN_TOKEN"

端点位于 08-http-api.md(§「Allowed senders」)。


19.2 Telegram

每个模板一个 long-polling 机器人;如果设置了 token_env,则在服务器启动时启动。令牌从 env 读取(绝不放在 YAML 中)。

telegram:
  token_env: MY_TG_BOT_TOKEN      # имя env с токеном бота
  allowed_user_ids: []            # пусто = любой; список = только эти Telegram user id
  show_typing: true               # индикатор «печатает…» во время работы
  show_tool_calls: true           # компактные строки статуса tool-вызовов
字段 默认值 描述
token_env 留空 含令牌的 env 名称;留空 = 机器人关闭
allowed_user_ids [] 按 Telegram user id 限制发件人
show_typing true 显示 typing 指示器
show_tool_calls true 将 tool 调用回显到聊天

Telegram 上的可视化块:图表 → PNG(sendPhoto),表格 → markdown 行。

用户的传入照片和文档(当模板 attachments.enabled: true 时)通过 getFile 下载并保存到会话 scratch 中;智能体收到一条包含文件名的消息。智能体请求 request_attachment 也以同样方式解析:机器人发送提示「请发送文件/照片」,用户带媒体的回复会继续该轮次。


19.3 VK(社区)

基于 Long Poll API 的社区机器人——不需要公共 HTTPS。每个模板一个机器人;如果设置了 token_env,则在服务器启动时启动。社区令牌从 env 读取(绝不放在 YAML 中)。

vk:
  token_env: VK_GROUP_TOKEN     # имя env с group access token
  group_id: 123456789           # id сообщества (для longpoll и сообщений от имени группы)
  allowed_user_ids: []          # пусто = любой; список = только эти VK user id
  show_typing: true             # индикатор «печатает…» (messages.setActivity)
  show_tool_calls: true         # эхо tool-вызовов в чат
  poll_wait: 25                 # wait для longpoll (сек, 1..25)
字段 默认值 描述
token_env 留空 含令牌的 env 名称;留空 = 机器人关闭
group_id 0 社区 id;0 = 从令牌推导
allowed_user_ids [] 按 VK user id 限制发件人
show_typing true 显示 typing 指示器
show_tool_calls true 将 tool 调用回显到聊天
poll_wait 25 longpoll 请求等待间隔(秒)

会话为 vk:{template}:{peer_id}(对于对话为 peer_id = 2000000000 + chat_id)。令牌权限:messages(以及用于上传媒体的 photos/docs)。可视化块:图表 → 通过 photos.getMessagesUploadServer 生成 PNG,表格 → 文本行,文件 → 通过 docs.getMessagesUploadServer 发送文档。 用户的照片和文档(当模板的 attachments.enabled: true 时)会被下载并保存到会话的 scratch 中;agent 会收到一条包含文件名的消息。Agent 请求 request_attachment 的解析方式相同:bot 发送提示,用户带媒体的回复继续该轮次。

作为结果的 sink(用于 cron/email/monitor),VK 通过类型 vk 指定:

notify:
  - type: vk
    peer_id: 41848219        # peer_id получателя (для беседы 2000000000 + chat_id)
    # token_env: VK_GROUP_TOKEN  # опц.; по умолчанию берётся токен из vk-конфига шаблона

社区设置

要让 bot 接收消息,需要在社区中做两件事:

  1. 启用 Long Poll API(管理 → API → Long Poll API → “已启用”)。
  2. 启用事件类型“新消息” —— 默认情况下 VK 会启用 Long Poll,但所有事件类型都是关闭的(message_new: 0),因此 bot 什么 都收不到。在 UI 中 —— Long Poll API 里的“新消息”复选框;或者通过 API:
curl -X POST "https://api.vk.com/method/groups.setLongPollSettings" \
  -d "group_id=<id>&access_token=<token>&v=5.199" \
  -d "message_new=1&message_reply=1&message_edit=1&message_allow=1&message_deny=1&message_typing_state=1"

检查当前设置:groups.getLongPollSettings(字段 events.message_new)。


19.4 Yandex Messenger(Yandex 360)

基于 Yandex Bot API 的 bot —— 使用游标 offset 轮询 bot/v1/messages/getUpdates/(不需要公共 HTTPS)。每个模板一个 bot;如果设置了 token_env,则在服务器启动时启动。bot 的 OAuth token 从 env 读取(绝不放在 YAML 中)。bot 在 Yandex 360 for Business(“Messenger 中的 Bot”)中创建。

yandex_messenger:
  token_env: YANDEX_MESSENGER_BOT_TOKEN  # имя env с OAuth-токеном бота
  allowed_logins: []                     # пусто = любой; список = только эти логины
  show_typing: true                      # индикатор «печатает…» (sendTyping)
  show_tool_calls: false                 # эхо tool-вызовов (по умолчанию выключено)
  poll_secs: 2                           # пауза между опросами getUpdates (сек)
字段 默认值 描述
token_env 空 包含 OAuth token 的 env 名称;空 = bot 已禁用
allowed_logins [] 按 Yandex 登录名限制发送者
show_typing true 显示 typing 指示器
show_tool_calls false 回显 tool 调用(单独消息 —— 很吵,因为 Messenger 无法替换它们)
poll_secs 2 轮询之间的暂停 getUpdates(秒)

会话 —— ym:{template}:user:{login}(个人聊天)或 ym:{template}:chat:{chat_id}(群组/频道)。回复到个人聊天通过 login,到群组/频道通过 chat_id。格式化 —— Yandex 的 markdown 子集 (**жирный**、__курсив__、~~зачёркнутый~~、[ссылка](url)、代码); agent 的完整 Markdown 会通过 comrak 重新渲染。可视化块:图表 → PNG(sendImage),表格 → 文本行,文件 → 文档(sendFile)。

用户的文件和图片(当模板的 attachments.enabled: true 时)通过 getFile 下载并保存到会话的 scratch 中;agent 请求 request_attachment 的解析方式相同。

作为结果的 sink(用于 cron/email/monitor),Yandex Messenger 通过类型 yandex_messenger 指定;字段 chat_id 接受聊天/频道 id(来自 / 的值,例如 0/0/4f24b544-…),或用于个人聊天的用户登录名:

notify:
  - type: yandex_messenger
    chat_id: "0/0/4f24b544-697c-4e18-a9c1-b39432ee9bf9"   # или логин пользователя
    # token_env: YANDEX_MESSENGER_BOT_TOKEN  # опц.; по умолчанию токен из yandex_messenger-конфига

此频道仅涉及 Messenger。通过 Yandex 登录(Web 上的 OAuth 登录) 以及 Yandex Disk / 其他服务的工具 —— 是单独的集成。

如何测试

  1. 在 Yandex 360 → Messenger 中的 Bot 中创建一个 bot 并复制其 OAuth token。

  2. 将 token 放入 .env(参见 .env.example):

    YANDEX_MESSENGER_BOT_TOKEN=At...
    
  3. 在模板的 agent.yaml 中添加 yandex_messenger 部分(drom-agent 已有示例), 重启服务器。日志中会出现 yandex messenger bot authenticated(或关于 token 错误的警告)。

  4. 给 bot 发私信,或将其添加到群聊。

不启动服务器快速检查 token —— smoke 测试:

cargo test -p agent_web_bot --test yandex_messenger_live -- --nocapture

它会调用 self/get 和 getUpdates 并打印 bot 的登录名;没有 token 时测试 会被静默跳过。


19.4 Email

轮询 IMAP 邮箱:每封新邮件都会启动一次 one-shot 轮次并通过 SMTP 回复。邮件之间的上下文 —— 通过长期记忆(hook memory),键 —— 发送者的规范 user id。

email:
  imap_host: imap.example.com
  imap_port: 993
  imap_username: support@example.com
  imap_password_env: EMAIL_PASSWORD   # пароль из env, не в YAML
  allowed_senders: []                 # пусто = любой; "@domain" = весь домен
  smtp:                               # reply-настройки (опусти = только обработка)
    host: smtp.example.com
    username: support@example.com
    password_env: SMTP_PASSWORD
    from: support@example.com
  notify: []                          # доп. sink'и (как у cron)
  poll_secs: 60
字段 默认值 描述
imap_host 空 IMAP 主机;空 = 频道已禁用
imap_port 993 IMAP 端口(implicit TLS)
imap_username 空 登录名(通常是邮箱地址)
imap_password_env 空 包含 IMAP 密码的 env 名称
allowed_senders [] 允许的发送者(精确地址或 @домен)
smtp 否 用于回复的 SMTP:host、username、password_env、from、tls、port
notify [] 结果的其他 sink
poll_secs 60 邮箱轮询间隔

邮件附件(当模板的 attachments.enabled: true 时)会保存到 会话的 scratch 中并传递给 agent(base64/quoted-printable 会被解码)。


19.4 Storage(S3/WebDAV/FTP)

轮询外部存储:前缀下的每个新文件都会启动 agent 的一次 one-shot 轮次(文件文本 —— 在消息中)。类似于 email 频道,但来源是 bucket/文件夹中的文件。

storage:
  storage:                           # подключение (как у storage_tool)
    backend: s3
    endpoint: https://s3.example.com
    bucket: inbox
    region: us-east-1
    access_key_env: S3_ACCESS_KEY
    secret_key_env: S3_SECRET_KEY
  prefix: inbox/                     # папка для опроса
  poll_secs: 60                      # интервал опроса
  include_suffix: ".txt"             # опц.: обрабатывать только *.txt
  move_after: processed/             # опц.: переместить файл после обработки
  # delete_after: true               # или: удалить после обработки
字段 默认值 描述
storage 否 连接(backend:s3/webdav/ftp);空 = 频道已禁用
prefix "" 用于轮询的文件夹/前缀
poll_secs 60 轮询间隔
include_suffix "" 按文件名后缀过滤(空 = 全部)
move_after "" 将已处理的文件移动到此前缀下
delete_after false 删除已处理的文件(与 move_after 互斥)

Dedup —— 在内存中(已处理的键);为了在重启后仍保留,请使用 move_after/delete_after,以免文件被重复处理。

Storage 同步(输出)

将结果投递到存储 —— 在 notify 中同步 storage(与 cron/email 相同):

notify:
  - type: storage
    storage:
      backend: s3
      endpoint: https://s3.example.com
      bucket: reports
      region: us-east-1
      access_key_env: S3_ACCESS_KEY
      secret_key_env: S3_SECRET_KEY
    key: "runs/{source}/{ts}.txt"   # {source} и {ts} подставляются

19.5 程序化频道(指引)