跳转到内容

Artifact 是什么

在 Hashee 里,“小程序”和”小游戏”都通过同一个机制实现:Artifact。 Agent 推一段结构化数据进对话,客户端用对应 renderer 把它渲染成可交互卡片, 用户操作通过 artifact_response 反向传给 Agent,Agent 用 artifact_update 推回新状态。

这一页讲 Artifact 的概念、与”web 应用”的对比、生命周期、以及为什么这套 设计能同时支持简单卡片(投票)和复杂应用(游戏面板)。

与 Web 应用的对比

维度Web 应用Hashee Artifact
渲染层浏览器 / WebView原生客户端 React Native + 内置 / 自定义 renderer
通信层HTTP / WebSocket / RPCE2EE 加密 + Hashee 协议
状态层客户端 + 服务器Agent 持有真源 + 客户端只是”当前帧”
部署独立站点 / 路径嵌入在聊天内,作为消息的一种
加密TLS(线传)端到端(payload 内容平台不可见)
离线缓存 / Service Worker自动(Hashee 客户端已缓存最近消息)
推送Service Worker push内建(Agent 主动 send)
生命周期用户主动打开/关闭跟随消息,TTL 默认 30 天

简言之:Artifact 把”应用”内嵌到聊天对话里,并把通信层换成端到端加密。 对开发者来说,最大好处是不用维护”另一个前端”——直接发数据,客户端帮你渲染。

subtype 全枚举

subtype内置 renderer适合
infoInfoCard一次性提示
replyReplyCard结构化回复
appAppRenderer (按 payload.ui 路由)完全自定义 UI(小程序 / 小游戏)
formFormCard表单输入
responseResponseCard表单回响
tool_call(内部)Capability Manifest 工具调用
tool_response(内部)工具结果回流
approvalApprovalCard危险操作确认
progressProgressCard长任务进度
mediaMediaCard多媒体聚合

详细参考 SDK / Artifact 入门。 本章节聚焦 subtype: "app"——它最灵活,是实现”小程序”和”小游戏”的关键。

“app” subtype 是怎么工作的

Agent 发送
{
subtype: "app",
payload: {
ui: "vote_card", ← 客户端按这个字段选 React 组件
question: "...",
options: [...]
}
}
客户端 ArtifactAppRenderer
按 ui 字段路由到对应组件:
- "vote_card" → <VoteCard payload={payload} onAction={...} />
- "tic_tac_toe" → <TicTacToe ... />
- "2048" → <Game2048 ... />
- "<unknown>" → <UnknownCard payload={payload} />(fallback:JSON 显示)
组件 onAction 回调把用户操作打包成 artifact_response 发回 Agent

V1 客户端内置 renderer 列表:

payload.ui渲染组件用途
vote_cardVoteCard投票 / 单选 / 多选
progress_cardProgressCard(也可走 subtype:"progress"进度条
tic_tac_toeTicTacToe井字棋
2048Game20482048
guess_numberGuessNumber猜数字
text_formTextForm(可走 subtype:"form"简易文本表单
其他UnknownCard显示 JSON + 一个 “Action” 按钮

V2 路线允许 Agent 上传自定义 renderer 包(沙盒 React Native 组件, 通过 capability_manifest 声明),但 V1 仅内置列表。

生命周期

Agent 调用 sendArtifact()
后端写 messages 表 (subtype="artifact_app", revision=0, expires_at=now+30d)
推送给会话所有成员的客户端
客户端 renderer 渲染初始状态
┌─ 用户操作 → artifact_response → Agent 处理 → updateArtifact (revision++)
│ ↓
│ 客户端按 ref_artifact + revision 替换渲染
循环直到:
- 用户主动关闭(V1 没有"关闭"概念,artifact 跟消息走)
- Agent 调 expireArtifact() 主动结束
- expires_at 到达
- 100 次 update 上限触发自动 expire

为什么这套设计能同时支持简单和复杂

简单(投票卡)

  • payload 是几十字节 JSON
  • Agent 端无状态机(在内存的 Map / Set 就够)
  • 一次创建 + 几次 update

复杂(2048 游戏)

  • payload 是 16 个格子 + 分数 + 历史
  • Agent 端有完整状态机(可放 Redis / Postgres)
  • 每次 user action 触发 update

两者协议层面没有差别,只在 payload 大小与 Agent 端状态管理复杂度上不同。

为什么不直接给 Agent 一个 WebView

可以这么做(artifact 里放 url),但有几个问题:

问题WebViewArtifact
E2EE失效(WebView 内容可能上 CDN)维持(payload 端到端加密)
性能WebView 启动 ~500msrenderer ~10ms
内存每个 WebView ~50MB共享 React Native runtime
一致性不同平台 WebView 行为不同统一 React Native 渲染
离线需自己 cache自动
通信需要桥(postMessage)Agent 直接 send/update

V1 不提供”WebView artifact”。如果你确实要嵌外部 web 应用, 建议用 subtype: "info" + 一个 actions: [{ url, label }] 跳到外部浏览器。

客户端 renderer 的安全沙盒

V1 内置 renderer 是 静态编译进客户端的 React Native 组件,没有 “agent 上传 renderer 代码”的能力(V2 路线)。所以:

  • ✅ 你能自由设计 payload schema 来驱动现有 renderer
  • ✅ 你能 fork 客户端添加自定义 renderer(自托管场景)
  • ❌ 你不能让别人客户端”动态下载”你写的 renderer

V2 路线引入”renderer 包”(沙盒 + capability 声明 + 显式 install) 后将开放,但 V1 这是底线。

性能预算

操作预期时长
sendArtifact (Agent 端)5-10ms 加密管线 + 网络
客户端首次渲染30-50ms(含解密 + React 挂载)
updateArtifact5-10ms 加密 + 网络
客户端 update 渲染16ms(一帧)
artifact_response 往返100-200ms(典型 RTT)

游戏类应用建议每秒 update 不超过 5 次(200ms 一次),太频繁用户感知不到差别 但消耗带宽 / 后端配额。

限制

上限
单 artifact JSON64 KB
同对话同时 alive artifact100
单 artifact update 次数100
TTL 默认30 天
payload.ui 字符串长度64 字符

下一步