组件与输入/输出通道
🔵 用户和外部系统如何与智能体通信。这不是软件意义上的「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/<версия>/*):浏览器会永久缓存它们,部署后立即获取新版本。不带版本的地址仍然保留,并在每次请求时重新校验。
打开哪个智能体
组件按以下顺序解析模型(后者优先):
widget.default_model(来自server.yaml);window.AGENTOS_MODEL—— 父框架的变量;- 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
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。该要求可通过以下方式之一满足:
- OIDC —— 通过
login.providers中的提供方登录(留空 = 任意已启用的提供方,例如 Yandex); - 来自配置的用户库 —— 当
login.allow_users: true时,使用auth.users中的登录名/密码登录(POST /v1/login/widget/password)。
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,应用会显示登录界面而不是聊天:
- OIDC —— 「通过
{provider}登录」按钮会在内置 WebView 中打开GET /v1/login/widget/:provider;登录后,callback 页面会调用原生桥接window.AndroidAuth.onLogin(...),应用获得已签名的x-user-token; - 登录名/密码 —— 当
login.allow_users: true时,登录表单会发送POST /v1/login/widget/password并保存获得的{user_id, token}对。
获得的令牌存储在应用设置中(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 修改,无需重启/重新部署。
记录就是渠道身份:
tg:<user_id>—— Telegram;vk:<user_id>—— VK;ym:<login>—— Yandex Messenger;email:<address>—— email 发件人(小写);user:<canonical_id>—— 规范用户(任意渠道)。
记录绑定到智能体模板(或 * —— 全局)。当表中存在针对该模板的记录(或全局 *)时,该表优先: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 接收消息,需要在社区中做两件事:
- 启用 Long Poll API(管理 → API → Long Poll API → “已启用”)。
- 启用事件类型“新消息” —— 默认情况下 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 / 其他服务的工具 —— 是单独的集成。
如何测试
-
在 Yandex 360 → Messenger 中的 Bot 中创建一个 bot 并复制其 OAuth token。
-
将 token 放入
.env(参见.env.example):YANDEX_MESSENGER_BOT_TOKEN=At... -
在模板的
agent.yaml中添加yandex_messenger部分(drom-agent已有示例), 重启服务器。日志中会出现yandex messenger bot authenticated(或关于 token 错误的警告)。 -
给 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 程序化频道(指引)
- 通过 HTTP 聊天(SSE) ——
POST /v1/chat/completions,字段和事件 —— §8.2。 - 传入 webhook ——
POST /v1/inbound/:template(同步响应)—— §8.8。 - Webhook 工作流 ——
POST /v1/workflows/<name>/webhook—— §12.5。 - 结果的 sink(将回复投递到哪里)—— cron 中的
notify—— §11。