生产级数字人
让 Halo 数字人稳定地无人值守运行:按步骤写系统提示词,用记忆避免重复,用浏览器脚本自动化网站,处理登录,并给不可撤销的操作设限。
一个在演示里跑通的数字人,每天早上自己运行时仍可能出问题。本页汇总让它稳定运行的做法:哪里交给 AI 判断、哪里交给脚本执行,怎么写指令和记忆,怎么用浏览器脚本自动化网站,以及上线前要检查什么。
原则:AI 做判断,脚本做执行
让 AI 在网站上一步步点击,探索阶段没问题,放到每天的运行里就撑不住:
- 成本:每一步都要重新读取页面快照,消耗大量 token。
- 速度:每一步都要等模型推理。
- 稳定性:模型每次可能走不同的路径。
所以要分工。AI 读取配置和记忆、决定做什么、调用脚本、读结果、再决定下一步。所有重复、精确的操作,比如调用网站的接口、填写表单、提取表格,都放进每次做法都一样的脚本里:
数字人
├── system_prompt → 计划:编号步骤和确切的工具调用
├── 浏览器脚本 → 重复性操作,放在技能文件夹里
└── memory_schema → 两次运行之间要记住的内容把系统提示词写成操作流程
系统提示词是数字人了解要做什么的唯一来源。每次都用同样的结构:
- 角色:用一句话说明它负责什么。
- 配置:它用到
config_schema里的哪些设置、怎么用。每次运行都会拿到当前的值。 - 禁止事项:绝不能做的事,以及哪些事要先等人决定。
- 编号步骤:每一步写明调用哪个工具或脚本、传什么参数,以及每种结果怎么处理。
- 最后一步:更新记忆,然后用
report_to_user汇报。
report_to_user 是一次运行告诉用户发生了什么的方式。请有意识地选择类型:
| 类型 | 用于 |
|---|---|
run_complete | 运行结束,不需要用户做什么。出错时也用它,说清哪里出了问题 |
run_skipped | 这次没有要做的事 |
milestone | 发生了重要的事,运行继续 |
escalation | 需要人来决定。运行会等待回答 |
output | 产出了文件或报告,说明放在哪里 |
桌面通知按数字人的 消息与通知 › 系统通知 级别发送,默认只在里程碑、提问和产出时通知。外部通知(邮件、企业微信、飞书、钉钉、Webhook)由 AI 决定何时发送,经由 设置 › 消息通道 里配置的通道发出。想让它一定发送,就把它写成一个步骤,例如「订单被拒绝时,通知销售通道」。
记住做过的事
每位数字人都有自己的记忆文件,每次运行开始时载入。用 memory_schema 描述要记什么;这些描述是给 AI 的指引,不会被校验:
memory_schema:
processed_ids:
type: array
description: 已经处理过的条目 id,遇到就跳过;只保留最近 500 个。
last_run_at:
type: date
description: 上次运行结束的时间。
consecutive_failures:
type: number
description: 连续失败的次数。成功一次后归零。- 避免重复:用已处理 id 的列表(简单,但要限制长度),或者用「上次看到的时间」(紧凑,但要求条目有可靠的时间字段)。
- 记录失败次数,连续失败几次后就提问,而不是每天悄悄失败。
- 把「更新记忆」单独作为一个编号步骤,放在汇报之前,这样中途失败的运行不会记下没做完的事。
用户可以查看这个文件,也可以在数字人页面用 清除记忆 清空。清空不会影响工作区里的文件。
浏览器脚本(browser_run)
浏览器脚本是一个 JavaScript 文件,数字人用 browser_run 工具在 Halo 内置浏览器的页面里运行它。重复的网页任务靠它变得又快又稳定。
什么时候该写脚本
- 同样的步骤会反复执行,每天一次或者每个条目一次。
- 任务需要真正的流程控制:遍历数量不定的行、按页面内容分支、重试。
- 你希望一次调用就完成读取、计算并返回结构化数据,而不是让 AI 去读页面快照。
只做一次的简单操作,用交互工具(browser_navigate、browser_snapshot、browser_click、browser_fill)更简单,而且每一步之后都会重新检查页面。
文件格式
async (params) => {
const rows = document.querySelectorAll(params.rowSelector)
return Array.from(rows).map((row) => row.innerText.trim())
}- 文件里只有一个箭头函数,写成
async。Halo 会用你的params调用它。不要自己调用它,右花括号}之后也不要再写任何内容。 params是一个 JSON 对象,不能携带页面快照里的元素:请传选择器或文字,在脚本里自己找到元素。- 脚本在页面里运行,和浏览器开发者工具控制台里的代码一样:可以用
document、window、fetch和页面自己的 cookie,不能用require、import和 Node.js 模块。 - 返回普通的 JSON。DOM 元素和函数会变成
{}。抛出的错误会以Script execution failed: <message>返回。 - 返回清楚的结构,例如
{ success: true, items: [...] }或{ success: false, error: "not logged in" },这样提示词可以写明每种情况怎么处理。
脚本可以用 fetch 调用网站自己的接口;因为在页面里运行,请求会带上用户登录后的会话。网站没有可用的接口时,就直接读写页面(DOM)。
文件放在哪里
browser_run 的参数是 { file, params, timeout },只运行以下位置的文件:
- 数字人所在工作区文件夹里的任何位置,包括它的
.claude/skills/<技能>/文件夹(为这个工作区安装的技能就放在这里); - 工作区上级文件夹里的
.claude/skills/,或者你个人主目录下的.claude/skills/。
其他位置的文件都会被拒绝,提示 Path not allowed。使用 Halo 默认的配置文件夹时,为所有工作区安装的技能也在此列。file 用相对路径时从工作区文件夹算起,而且文件名必须以 .js 结尾。
要让脚本随数字人一起分发,就把它们放进一个技能,并声明为随包附带,这样它会被安装到同一个工作区:
requires:
skills:
- id: order-tools
bundled: true
files: [SKILL.md, list-orders.js, approve-order.js]在包里,这些文件放在 skills/order-tools/ 下。见 DHP 协议。技能在工作区里的文件夹以它 SKILL.md 中的 name 命名,所以 name、skills/ 下的文件夹名和 requires.skills 里的 id 请用同一个小写英文名:提示词就是按这个路径调用脚本的。
超时
timeout 以毫秒为单位:默认 60000,最小 1000,最大 120000。它只限制工具等待多久。超时之后脚本仍在页面里继续运行,所以请给脚本里自己的 fetch 设置超时;万一卡住,下一步之前先重新打开页面。
开发和调试脚本
- 先在打开的页面上用一次性的
browser_evaluate调用把逻辑跑通。它的运行方式相同,只是不用来回读写文件。 - 把跑通的函数体保存成允许位置下的
.js文件。 - 用一小组安全的
params调用browser_run,把结果和第 1 步对比。 - 只有写成文件后才失败时,检查格式:漏了
async、}后面还有内容,或者params的键名对不上。 - 报错信息说明不了原因时,立即运行
browser_inspect,不要先跳转页面:target: "console"显示页面记录的错误,target: "network"配合resourceTypes: ["fetch", "xhr"]显示请求和响应。
需要登录的网站
- 在
browser_login里声明每个网站。Halo 会在数字人的 工作动态 上方显示提示和 打开并登录 按钮,能力与设置 里的 所需登录 也会列出这些网站。用户在那里登录,数字人永远拿不到密码。 - 这台电脑上,所有数字人和主对话共用同一个浏览器配置。在任何地方登录,大家都能用;在任何地方退出,大家都会退出。设置 › 系统 › 清除浏览器数据 会一次退出所有网站。
- Halo 不会检查用户是否真的登录了,也无法知道网站何时结束了会话。请让脚本识别登录页并返回
{ success: false, error: "not logged in" },并让提示词在这种情况下提问,而不是反复重试。 - 有些网站会把会话和浏览器类型绑定。数字人切换到移动版视图(
device: "h5")后登录失效的话,请在它实际运行的视图里重新登录。 - 登录状态不会跟着换电脑:把 Halo 搬到新电脑后,要在那里重新登录。
给不可撤销的操作加上保护
对于会审批、预订、发送、付款或删除的数字人:
- 在
config_schema里加一个开关,例如auto_approve(boolean,默认false)。开关关闭时,提示词只报告它打算做什么。 - 在提示词里列出禁止事项,配置没有允许的事一律要用
escalation提问。 - 用
max_actions_per_run这样的设置限制每次运行的操作数量。 - 过期的回答会造成损害时,设置
escalation.timeout_hours。不设置时,提问会一直等到有人回答。 escalation.enabled没有效果,数字人随时都可以提问。它能自主做多少,完全取决于提示词允许多少。
测试和观察
- 在数字人页面点 立即运行,然后在 工作动态 里查看这次运行:每次运行都会列出它的汇报和提问。
- 想通过 API 查看一次运行的每一步,用
GET /api/apps/:appId/runs/:runId/session。 - 先打开「只报告」开关运行几次,对照你自己会怎么做来检查结果,再让它真正执行。
上线前检查
- [ ] 提示词的每一步都写明了工具或脚本,并处理了失败。
- [ ] 重复的网页操作是随包技能里的脚本,而不是一步步点击。
- [ ] 记忆能防止重复,并在汇报之前更新。
- [ ] 需要登录的网站都写进了
browser_login,脚本能识别会话过期。 - [ ] 不可撤销的操作放在默认关闭的开关后面,并限制了每次运行的数量。
- [ ] 触发方式已经设置:
schedule、带path的file,或带secret的webhook。见 spec 参考。 - [ ] spec、设置默认值和随包的文件里都没有 API Key、密码或 webhook secret。发布前的检查只在系统提示词里找几种常见的密钥格式(见发布预检),别指望它兜底;导出不做任何检查。
相关页面
- 数字人 spec 参考
- 教程:做一个浏览器自动化数字人
- 编写技能
- AI 浏览器:用户视角下的内置浏览器