SimpleAgent - 统一 Agent(合并原 kernel-agent / process-agent)
对话历史由 ChatClient 的 ChatMemory store 按 conversation-id 自管(Memory Filter)。 Agent 只持 conversation-id + 轻量控制状态。
Callback 体系(独立于 chat-client filter): 通过 :callbacks map 注册 9 个回调,用于监控和控制 agent 执行过程: :on-turn-start (fn [metadata]) 新 turn 开始 :on-turn-end (fn [metadata]) turn 正常完成 :on-turn-error (fn [error metadata]) turn 出错 :on-llm-call (fn [messages metadata]) 每次 LLM 调用前(在 react 层触发) :on-llm-result (fn [response metadata]) 每次 LLM 返回后(在 react 层触发) :on-tool-call (fn [tool-name args]) tool 调用前;返回 {:interrupt reason} 触发中断 :on-tool-result (fn [tool-name result]) tool 执行后(在 react 层触发) :on-interrupt (fn [interrupt-info metadata]) 进入中断状态时 :on-resume (fn [interrupt-info metadata]) 从中断状态恢复时
pause/resume 为可选能力:配置 callbacks :on-tool-call 即启用。
线程安全:单个 agent 实例不可被多线程并发 chat/resume。每个 agent 应绑定 单一对话线程;并发请按会话各建一个 agent(共享底层持久 store + 各自 :conversation-id 即可隔离)。
使用示例:
;; 简单同步 (def a (create-agent {:provider p :model "glm-4.7" :tools [#'get-weather]})) (chat a "北京天气?") ;; => {:status :completed :text "..." :tool-calls-made [...]}
;; 带 callbacks 的可观测 agent (def a (create-agent {:provider p :tools [#'my-tool] :callbacks {:on-turn-start (fn [m] (println "Turn" (:turn-count m))) :on-tool-result (fn [n r] (println n "=>" r)) :on-tool-call (fn [n a] (when (risky? n) {:interrupt :need-approval}))}}))
;; 工具审批:on-tool-call 返回 {:interrupt reason} 触发暂停 (def a (create-agent {:provider p :tools [#'delete-file] :callbacks {:on-tool-call (fn [n _] (when (= :delete-file n) {:interrupt "需要审批"})) :on-interrupt (fn [info _] (println "等待审批" (:reason info)))}})) (let [r (chat a "删除 /tmp/x")] (when (= :paused (:status r)) (resume a "approved")))
SimpleAgent - 统一 Agent(合并原 kernel-agent / process-agent)
对话历史由 ChatClient 的 ChatMemory store 按 conversation-id 自管(Memory Filter)。
Agent 只持 conversation-id + 轻量控制状态。
**Callback 体系**(独立于 chat-client filter):
通过 :callbacks map 注册 9 个回调,用于监控和控制 agent 执行过程:
:on-turn-start (fn [metadata]) 新 turn 开始
:on-turn-end (fn [metadata]) turn 正常完成
:on-turn-error (fn [error metadata]) turn 出错
:on-llm-call (fn [messages metadata]) 每次 LLM 调用前(在 react 层触发)
:on-llm-result (fn [response metadata]) 每次 LLM 返回后(在 react 层触发)
:on-tool-call (fn [tool-name args]) tool 调用前;返回 {:interrupt reason} 触发中断
:on-tool-result (fn [tool-name result]) tool 执行后(在 react 层触发)
:on-interrupt (fn [interrupt-info metadata]) 进入中断状态时
:on-resume (fn [interrupt-info metadata]) 从中断状态恢复时
pause/resume 为可选能力:**配置 callbacks :on-tool-call 即启用**。
线程安全:单个 agent 实例不可被多线程并发 chat/resume。每个 agent 应绑定
单一对话线程;并发请按会话各建一个 agent(共享底层持久 store + 各自
:conversation-id 即可隔离)。
使用示例:
;; 简单同步
(def a (create-agent {:provider p :model "glm-4.7" :tools [#'get-weather]}))
(chat a "北京天气?") ;; => {:status :completed :text "..." :tool-calls-made [...]}
;; 带 callbacks 的可观测 agent
(def a (create-agent {:provider p :tools [#'my-tool]
:callbacks {:on-turn-start (fn [m] (println "Turn" (:turn-count m)))
:on-tool-result (fn [n r] (println n "=>" r))
:on-tool-call (fn [n a] (when (risky? n) {:interrupt :need-approval}))}}))
;; 工具审批:on-tool-call 返回 {:interrupt reason} 触发暂停
(def a (create-agent {:provider p :tools [#'delete-file]
:callbacks {:on-tool-call (fn [n _] (when (= :delete-file n) {:interrupt "需要审批"}))
:on-interrupt (fn [info _] (println "等待审批" (:reason info)))}}))
(let [r (chat a "删除 /tmp/x")]
(when (= :paused (:status r))
(resume a "approved")))(chat agent message)(chat agent message opts)对话。返回 {:status :completed :text ...} 或 {:status :paused ...}
对话。返回 {:status :completed :text ...} 或 {:status :paused ...}
(chat-async agent message)(chat-async agent message opts)同 chat,但立刻返回 CompletableFuture,整个 turn 跑在虚拟线程上。
给 Ring / Luminus 异步 handler 用:
(defn handler [request respond raise]
(-> (agent/chat-async a (get-in request [:body-params :message]))
(flt/fmap (fn [r] {:status 200 :body {:text (:text r)}}))
(async/on-complete respond raise)))
返回值形状与 chat 逐字相同(:completed / :paused / :error),只是被
deferred 包了一层:async/join 阻塞取、flt/fmap 组合、async/on-complete
接回调。turn 级回调在工作线程上触发(见 run-loop-async)。
opts 与 chat-stream 同源:传 :on-token 即异步流式,传 :cancel-token
即可从别的线程取消(见 loop-passthrough-keys)。
⚠️ agent 的 state-atom 是单会话状态机:同一个 agent 上不要并发多个
chat-async(start-turn! 会互相踩 run-id / 暂停态)。web 场景下每个会话
一个 agent 实例,或自行串行化。
同 `chat`,但**立刻返回 `CompletableFuture`**,整个 turn 跑在虚拟线程上。
给 Ring / Luminus 异步 handler 用:
```clojure
(defn handler [request respond raise]
(-> (agent/chat-async a (get-in request [:body-params :message]))
(flt/fmap (fn [r] {:status 200 :body {:text (:text r)}}))
(async/on-complete respond raise)))
```
返回值形状与 `chat` 逐字相同(`:completed` / `:paused` / `:error`),只是被
deferred 包了一层:`async/join` 阻塞取、`flt/fmap` 组合、`async/on-complete`
接回调。turn 级回调在工作线程上触发(见 `run-loop-async`)。
**opts 与 `chat-stream` 同源**:传 `:on-token` 即异步流式,传 `:cancel-token`
即可从别的线程取消(见 `loop-passthrough-keys`)。
⚠️ agent 的 state-atom 是单会话状态机:**同一个 agent 上不要并发多个
chat-async**(`start-turn!` 会互相踩 run-id / 暂停态)。web 场景下每个会话
一个 agent 实例,或自行串行化。(chat-stream agent message on-token)(chat-stream agent message on-token opts)流式对话。on-token 接收 {:token / :reasoning-token ...}(增量 token,需全文请自行累积)。
返回最终结果(与 chat 同形)。取消:opts 传 :cancel-token。
chat-async 也吃 :on-token(opts 透传,见 loop-passthrough-keys)——
要「异步 + 流式 + 可取消」用 (chat-async a msg {:on-token f :cancel-token t})。
流式对话。`on-token` 接收 {:token / :reasoning-token ...}(增量 token,需全文请自行累积)。
返回最终结果(与 chat 同形)。取消:opts 传 `:cancel-token`。
**`chat-async` 也吃 `:on-token`**(opts 透传,见 `loop-passthrough-keys`)——
要「异步 + 流式 + 可取消」用 `(chat-async a msg {:on-token f :cancel-token t})`。(create-agent opts)创建 Agent
参数 opts:
deftool {:sensitive true} 的工具在执行
前自动暂停,等 resume。暂停发生时本回调被触发。两条 pause 启用路径,可并存(缺省两条都不配 = 永不暂停,chat 只会返回
:completed/:error,无人值守调用方不会收到意外的暂停态):
:on-pause —— 声明式,{:sensitive true} 的工具一律暂停;:callbacks :on-tool-call —— 命令式,返回 {:interrupt reason} 即暂停。
两条都配时先问回调;回调放行的,若工具是敏感工具仍暂停——:sensitive
是工具作者立的下限,不该被一个泛泛的回调放行掉。:chat-client 与 :memory 可独立同时指定,store 解析规则:
Agent 层不暴露 chat-client filter(传入 :filters 会被忽略并警告); 需要 filter 请用 chat-client/build-chat-client 自建后经 :chat-client 传入。
返回 Agent map
创建 Agent
参数 opts:
- :chat-client 预构建 ChatClient(提供则跳过构建)
- :provider ILLMProvider 实例
- :model 模型名(默认 "glm-4")
- :max-tokens 最大 token(默认 4096)
- :temperature 温度
- :system-prompt 系统提示词
- :tools tool var / inline-tool map 列表
- :memory ChatMemory store(可选,默认 in-memory;false → 无记忆)
- :pause-store PauseStore(可选,见 im.ttalk.agent.pause):暂停快照自动
持久化,进程重启后同 conversation-id + 同 store 重建 agent
即可 resume(跨重启 HITL;对话历史请配 SQLite ChatMemory)
- :conversation-id 会话 ID(可选)
- :max-iterations 最大工具循环次数(默认 10)
- :tool-context 装配期塞进 ToolContext 的种子 map(可选,见返回值注释)
- :on-pause (fn [{:keys [pending-tool reason]}])(可选):**配置即启用
pause/resume**——声明 `deftool {:sensitive true}` 的工具在执行
前自动暂停,等 `resume`。暂停发生时本回调被触发。
- :callbacks 回调 map(:on-turn-start/:on-turn-end/:on-turn-error/:on-llm-call/
:on-llm-result/:on-tool-call/:on-tool-result/:on-interrupt/:on-resume;
:on-event 收生命周期事件,见 im.ttalk.agent.events)
- :approval 声明式审批(designs/runtime-borrow.md §5 §6,可选):
{:policy 策略(im.ttalk.agent.approval 的 rule-policy / hint-policy …)
:cache 会话缓存(缺省每个 agent 一份;false = 不缓存)
:timeout-ms 暂停超过这么久再 resume → 按「拒绝:审批超时」处理
:breaker 拒绝熔断参数 {:max-consecutive 3 …}(resume 的拒绝计数)}
配置即启用暂停;与 :on-pause / :on-tool-call 并存时三路取最严
**两条 pause 启用路径,可并存**(缺省两条都不配 = 永不暂停,`chat` 只会返回
`:completed`/`:error`,无人值守调用方不会收到意外的暂停态):
- `:on-pause` —— 声明式,`{:sensitive true}` 的工具一律暂停;
- `:callbacks :on-tool-call` —— 命令式,返回 `{:interrupt reason}` 即暂停。
两条都配时先问回调;回调放行的,若工具是敏感工具**仍暂停**——`:sensitive`
是工具作者立的下限,不该被一个泛泛的回调放行掉。
:chat-client 与 :memory 可独立同时指定,store 解析规则:
- :memory store → 用它(预构建 chat-client 上的 memory-filter 会被重挂到该 store)
- :memory false → 无记忆(预构建 chat-client 上的 memory-filter 会被移除)
- :memory 缺省 → 复用 chat-client memory-filter 的 store;都没有则默认 in-memory
Agent 层不暴露 chat-client filter(传入 :filters 会被忽略并警告);
需要 filter 请用 chat-client/build-chat-client 自建后经 :chat-client 传入。
返回 Agent map工具循环的缺省上限。提成常量是为了装配方能如实上报——/info 的
capabilities.execution.maxIterations 要报这个数,抄一份魔数过去迟早对不上。
工具循环的缺省上限。提成常量是为了**装配方能如实上报**——`/info` 的 `capabilities.execution.maxIterations` 要报这个数,抄一份魔数过去迟早对不上。
创建取消令牌,传给 chat-stream 的 opts :cancel-token。见 im.ttalk.agent.streaming。
创建取消令牌,传给 chat-stream 的 opts :cancel-token。见 im.ttalk.agent.streaming。
(paused? agent)是否处于暂停态(本进程 state-atom 或 PauseStore 中的持久化快照)。
是否处于暂停态(本进程 state-atom 或 PauseStore 中的持久化快照)。
请求取消 chat-stream(取消上游 + 停止循环)。见 im.ttalk.agent.streaming。
请求取消 chat-stream(取消上游 + 停止循环)。见 im.ttalk.agent.streaming。
(resume agent decision)(resume agent decision payload)(resume agent decision payload opts)恢复暂停的 Agent(本进程暂停或跨重启的持久化暂停均可)。
审批暂停:
环境类暂停(:env-retry):"retry"/:retry 或 "approved"/:approved 表示 环境已修复、重跑失败工具;其余 → 错误结果交给模型(不支持 :reply)。
resume 的 ToolContext 恢复自暂停快照(各轮 :writes 的累积折叠结果保留)。
4-arity 的 opts 与 chat 侧同义,尤其是 :on-token / :cancel-token:
HITL 第二段(审批之后的续跑)往往正是最终答案那段,不给这两个键,前端就只能
干等若干秒再一次性收到整段文字,期间「停止」也按不动。
payload 是用户答复的载荷(:args / :message),传输选项走 opts
——两件事不挤一个槽。
恢复暂停的 Agent(本进程暂停或跨重启的持久化暂停均可)。
审批暂停:
- "approved"/:approved 批准;payload 可带 {:args 新参数}(编辑后批准,
pending 工具以替换后的参数执行)
- "reply"/:reply 答复即结果(ask-user 语义):payload 必带 {:message 答复},
pending 工具不执行、答复直接作为其结果回模型
- 其余 → 拒绝;payload 可带 {:message 理由}(结果「已拒绝执行:<理由>」,
模型直接拿到原因,省一轮干猜)
环境类暂停(:env-retry):"retry"/:retry 或 "approved"/:approved 表示
环境已修复、重跑失败工具;其余 → 错误结果交给模型(不支持 :reply)。
resume 的 ToolContext 恢复自暂停快照(各轮 :writes 的累积折叠结果保留)。
4-arity 的 `opts` 与 `chat` 侧同义,**尤其是 `:on-token` / `:cancel-token`**:
HITL 第二段(审批之后的续跑)往往正是最终答案那段,不给这两个键,前端就只能
干等若干秒再一次性收到整段文字,期间「停止」也按不动。
`payload` 是**用户答复**的载荷(`:args` / `:message`),传输选项走 `opts`
——两件事不挤一个槽。(resume-async agent decision)(resume-async agent decision payload)(resume-async agent decision payload opts)同 resume,但立刻返回 CompletableFuture,延续跑在虚拟线程上。
HITL 的第二段(人答复之后接着跑)与第一段一样长——首段用了 chat-async,
这段还阻塞 HTTP 线程就白费了。返回值形状同 resume;4-arity 的 opts
见 resume(:on-token / :cancel-token 在这里同样成立)。
同 `resume`,但立刻返回 `CompletableFuture`,延续跑在虚拟线程上。 HITL 的第二段(人答复之后接着跑)与第一段一样长——首段用了 `chat-async`, 这段还阻塞 HTTP 线程就白费了。返回值形状同 `resume`;4-arity 的 `opts` 见 `resume`(`:on-token` / `:cancel-token` 在这里同样成立)。
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 |