AG-UI 前端工具(client-side tool / CopilotKit useCopilotAction)。
零框架改动——三样都是现成的(docs/agent-runtime-design.md §7.2):
| AG-UI 要什么 | 我们已有的 |
|---|---|
| 前端 action 的 schema 进工具列表 | 内联工具 map::handler 之外的键原样就是发给模型的 schema(chat-client/build-chat-client) |
| 模型发出 tool call 后挂起、等前端 | gate 判 :pause → 暂停快照自动落 PauseStore |
| 前端执行完把结果送回 | resume-run! :reply {:message 结果}——「pending 工具不执行、载荷即其结果」正是 ask-user 语义 |
于是「AG-UI 前端工具」不是一套新机制,只是既有 HITL 词汇的一个用法。
前端标记走 metadata 而不是 map 里的键:那个 map 去掉 :handler 之后
整个发给模型,多一个 :agui/frontend 就是往 wire 上塞私货;metadata 不序列化。
AG-UI 前端工具(client-side tool / CopilotKit `useCopilotAction`)。
**零框架改动**——三样都是现成的(docs/agent-runtime-design.md §7.2):
| AG-UI 要什么 | 我们已有的 |
|---|---|
| 前端 action 的 schema 进工具列表 | **内联工具 map**:`:handler` 之外的键**原样就是**发给模型的 schema(`chat-client/build-chat-client`) |
| 模型发出 tool call 后挂起、等前端 | gate 判 `:pause` → 暂停快照自动落 PauseStore |
| 前端执行完把结果送回 | `resume-run! :reply {:message 结果}`——「pending 工具不执行、载荷即其结果」正是 ask-user 语义 |
于是「AG-UI 前端工具」不是一套新机制,只是**既有 HITL 词汇的一个用法**。
**前端标记走 metadata 而不是 map 里的键**:那个 map 去掉 `:handler` 之后
整个发给模型,多一个 `:agui/frontend` 就是往 wire 上塞私货;metadata 不序列化。(agent-fn spec)把一份 create-agent 配置包成 runtime 要的 :agent-fn,并自动接好前端工具。
(rt/runtime {:agent-fn (tools/agent-fn {:provider p :memory store :pause-store ps
:tools [#'my-server-tool]})})
;; 每个 run 的前端 action:
(rt/start-run! rt "c1" "帮我改主题"
{:tools (map tools/frontend-tool (:tools agui-input))})
每 run 现建一个 agent/ChatClient——装配期就定好的工具集容不下每请求变化的 前端 action(§6.6)。runtime 随后会把会话级 state-atom 换上去,所以暂停态 不会因为重建而丢。
把一份 `create-agent` 配置包成 runtime 要的 `:agent-fn`,并自动接好前端工具。
```clojure
(rt/runtime {:agent-fn (tools/agent-fn {:provider p :memory store :pause-store ps
:tools [#'my-server-tool]})})
;; 每个 run 的前端 action:
(rt/start-run! rt "c1" "帮我改主题"
{:tools (map tools/frontend-tool (:tools agui-input))})
```
每 run 现建一个 agent/ChatClient——**装配期就定好的工具集容不下每请求变化的
前端 action**(§6.6)。runtime 随后会把会话级 state-atom 换上去,所以暂停态
不会因为重建而丢。(frontend-tool {:keys [name description parameters] :as agui-tool})AG-UI tool({:name :description :parameters},parameters 是 JSON Schema)
→ 内联工具 map。
:handler 正常路径下永远不会被调用(gate 先把这一轮暂停了)。留一个会
抛的 handler 而不是 (constantly nil):真跑到了说明 gate 没接上,那时候
静默返回 nil 比抛出去更难查。
AG-UI tool(`{:name :description :parameters}`,parameters 是 JSON Schema)
→ 内联工具 map。
`:handler` 正常路径下**永远不会被调用**(gate 先把这一轮暂停了)。留一个会
抛的 handler 而不是 `(constantly nil)`:真跑到了说明 gate 没接上,那时候
静默返回 nil 比抛出去更难查。(interrupt-callback names):on-tool-call 回调:前端工具一律暂停。
返回 {:interrupt ...} 即触发 gate 的 :pause(simple-agent/gate-of)。
注意 tool-name 在回调里是字符串——拿 keyword 去比会永不相等,
callbacks.clj 的 docstring 记着这个踩过的坑。
`:on-tool-call` 回调:前端工具一律暂停。
返回 `{:interrupt ...}` 即触发 gate 的 `:pause`(`simple-agent/gate-of`)。
注意 `tool-name` 在回调里是**字符串**——拿 keyword 去比会永不相等,
`callbacks.clj` 的 docstring 记着这个踩过的坑。(state-tools em)给模型两把写共享状态的工具,闭包在本 run 的发射器上。
名字照抄上游(CopilotKit/packages/runtime/src/agent/index.ts 的
AGUISendStateSnapshot / AGUISendStateDelta)——第三种叫法只会逼每个前端
再写一层适配。
为什么必须是内联工具而不是 deftool 的 var:它们要往这一条 run 的
发射器上发事件,而 var 是进程级的。闭包是唯一能把 run 绑进去的形状
(同 delegate-tool 的 :observer)。
读面(/threads/:id/state)本来就有;缺的一直是这半边——没有写的入口,
那条链永远不启动。
给模型两把**写共享状态**的工具,闭包在本 run 的发射器上。 名字照抄上游(`CopilotKit/packages/runtime/src/agent/index.ts` 的 `AGUISendStateSnapshot` / `AGUISendStateDelta`)——第三种叫法只会逼每个前端 再写一层适配。 为什么必须是**内联工具**而不是 `deftool` 的 var:它们要往**这一条 run 的 发射器**上发事件,而 var 是进程级的。闭包是唯一能把 run 绑进去的形状 (同 `delegate-tool` 的 `:observer`)。 读面(`/threads/:id/state`)本来就有;缺的一直是这半边——没有写的入口, 那条链永远不启动。
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |