Liking cljdoc? Tell your friends :D

im.ttalk.agent.async

异步适配层:CompletionStage ⇄ 链结果组合子 + 虚拟线程入口 + 两回调 sink。

filter.clj 只定义协议 IChainResult(契约见 docs/filter-chain-design.md §2.6.4,C5「协议进 core,实现不进」);本 ns 是它的第一个 deferred 实现 ——CompletionStage 是 JDK 自带、零依赖,故可以住在 core,require 即生效。 manifold / core.async 的适配层照抄本 ns 的形状自写即可,两者都不必进 core。

为什么是 CompletableFuture 而不是 Flux

turn 结果是单值(一个 TurnResult),不是流——Flux 那一套控制反转在这里 买不到东西。真正的流是 token 流,而 token 流走 :token-xform transducer (token-stream-filter-design.md),与本 ns 无关。

为什么是虚拟线程

LLM 调用与工具执行本身是阻塞 IO。Loom 下把它们跑在虚拟线程上,调用线程 立刻拿到 CompletableFuture 返回,而阻塞代码一行不用改写成回调——这正是 §2.6.2 说的「around 形状在 async 下不用变」。用 Thread/startVirtualThread 而非 clojure.core/future(send-off 平台线程池)的理由同 tool.clj 的 call-with-timeout:几 KB 栈、阻塞 IO 可被 interrupt 真正打断。

典型用法(Ring / Luminus 异步 handler)

(defn chat-handler [request respond raise]
  (-> (agent/chat-async my-agent (get-in request [:body-params :message]))
      (flt/fmap (fn [r] {:status 200 :body {:text (:text r)}}))
      (async/on-complete respond raise)))

respond / raise 正是 Ring 3 异步 handler 的两个回调;on-complete 只是 把链结果接到这一对回调上——同步值也接(此时 respond 在当前线程直接调用), 所以同一个 handler 对同步与异步入口都成立。

异步适配层:CompletionStage ⇄ 链结果组合子 + 虚拟线程入口 + 两回调 sink。

`filter.clj` 只定义协议 `IChainResult`(契约见 docs/filter-chain-design.md
§2.6.4,C5「协议进 core,实现不进」);本 ns 是它的**第一个 deferred 实现**
——CompletionStage 是 JDK 自带、零依赖,故可以住在 core,`require` 即生效。
manifold / core.async 的适配层照抄本 ns 的形状自写即可,两者都不必进 core。

## 为什么是 CompletableFuture 而不是 Flux

turn 结果是**单值**(一个 TurnResult),不是流——Flux 那一套控制反转在这里
买不到东西。真正的流是 token 流,而 token 流走 `:token-xform` transducer
(`token-stream-filter-design.md`),与本 ns 无关。

## 为什么是虚拟线程

LLM 调用与工具执行本身是**阻塞 IO**。Loom 下把它们跑在虚拟线程上,调用线程
立刻拿到 CompletableFuture 返回,而阻塞代码一行不用改写成回调——这正是
§2.6.2 说的「around 形状在 async 下不用变」。用 `Thread/startVirtualThread`
而非 `clojure.core/future`(send-off 平台线程池)的理由同 `tool.clj`
的 `call-with-timeout`:几 KB 栈、阻塞 IO 可被 interrupt 真正打断。

## 典型用法(Ring / Luminus 异步 handler)

```clojure
(defn chat-handler [request respond raise]
  (-> (agent/chat-async my-agent (get-in request [:body-params :message]))
      (flt/fmap (fn [r] {:status 200 :body {:text (:text r)}}))
      (async/on-complete respond raise)))
```

`respond` / `raise` 正是 Ring 3 异步 handler 的两个回调;`on-complete` 只是
把链结果接到这一对回调上——**同步值也接**(此时 respond 在当前线程直接调用),
所以同一个 handler 对同步与异步入口都成立。
raw docstring

completedclj

(completed v)

已完成的 deferred。

已完成的 deferred。
sourceraw docstring

deferred?clj

(deferred? x)

x 是否为 deferred(本适配层认 CompletionStage)。

x 是否为 deferred(本适配层认 `CompletionStage`)。
sourceraw docstring

delayedclj

(delayed ms)

ms 毫秒后完成的 deferred(值为 nil)。不占线程——JDK 的 delayedExecutor 用共享调度器,到点才把任务丢出去。异步路径上的「等一会儿」一律用它, 绝不 Thread/sleep(那会睡掉传输层 executor 的线程)。

ms 毫秒后完成的 deferred(值为 nil)。**不占线程**——JDK 的 delayedExecutor
用共享调度器,到点才把任务丢出去。异步路径上的「等一会儿」一律用它,
绝不 `Thread/sleep`(那会睡掉传输层 executor 的线程)。
sourceraw docstring

failedclj

(failed t)

已异常完成的 deferred。

已异常完成的 deferred。
sourceraw docstring

inlineclj

(inline thunk)

同步「跑法」:当场执行 thunk,返回普通值。

与 vthread 同签名,专供「一份代码两条路径」的入口函数—— react/invoke 传它、react/invoke-async 传 vthread,其余逐字相同。

同步「跑法」:当场执行 thunk,返回普通值。

与 `vthread` 同签名,专供「一份代码两条路径」的入口函数——
`react/invoke` 传它、`react/invoke-async` 传 `vthread`,其余逐字相同。
sourceraw docstring

joinclj

(join x)
(join x timeout-ms)

阻塞取值:deferred 等它完成,同步值原样返回;失败时抛解包后的原异常。

给调用方(脚本、测试、同步 API 边界)用。filter 内部禁止调用—— 契约 C2「永不阻塞」,filter 里想拿值请用 flt/fmap。

阻塞取值:deferred 等它完成,同步值原样返回;失败时抛**解包后**的原异常。

给调用方(脚本、测试、同步 API 边界)用。**filter 内部禁止调用**——
契约 C2「永不阻塞」,filter 里想拿值请用 `flt/fmap`。
sourceraw docstring

on-completeclj

(on-complete x success failure)

把链结果接到一对回调上:成功调 success,失败调 failure(拿到的是 解包后的原异常)。返回 nil。

同步值(含同步抛出)也照常接——所以 Ring handler 不必关心入口是否异步。 正是 Ring 异步 handler 的 (fn [request respond raise]) 那对回调。

把链结果接到一对回调上:成功调 `success`,失败调 `failure`(拿到的是
**解包后**的原异常)。返回 nil。

同步值(含同步抛出)也照常接——所以 Ring handler 不必关心入口是否异步。
正是 Ring 异步 handler 的 `(fn [request respond raise])` 那对回调。
sourceraw docstring

unwrap-causeclj

(unwrap-cause t)

剥掉 CompletionException / ExecutionException 这层 JDK 包装,露出原异常。

为什么必须剥:契约 C3 要求「异常语义不变」——filter 作者写 (flt/fcatch (chain req) handler),handler 在同步与异步两条路径上应当拿到 同一个异常对象。不剥的话异步路径会多出一层 CompletionException, (ex-data t) / instance? 判断全部失效,这类 filter 就得为两条路径各写一遍。

剥掉 `CompletionException` / `ExecutionException` 这层 JDK 包装,露出原异常。

**为什么必须剥**:契约 C3 要求「异常语义不变」——filter 作者写
`(flt/fcatch (chain req) handler)`,handler 在同步与异步两条路径上应当拿到
**同一个异常对象**。不剥的话异步路径会多出一层 CompletionException,
`(ex-data t)` / `instance?` 判断全部失效,这类 filter 就得为两条路径各写一遍。
sourceraw docstring

vthreadclj

(vthread thunk)

在虚拟线程上跑 thunk,立刻返回 CompletableFuture——调用线程不阻塞。

thunk 抛出的任何 Throwable 落进 deferred 的 error channel(不吞、不打印)。

bound-fn* 包装:调用方的动态绑定帧带进工作线程(*register-cancel* 等靠动态 var 穿链的机制因此照常可见)。与 clojure.core/future、 react/run-on-executor 的传导语义一致——引擎不改变「跑的是什么」。

在虚拟线程上跑 thunk,立刻返回 `CompletableFuture`——调用线程不阻塞。

thunk 抛出的任何 `Throwable` 落进 deferred 的 error channel(不吞、不打印)。

`bound-fn*` 包装:调用方的**动态绑定帧**带进工作线程(`*register-cancel*`
等靠动态 var 穿链的机制因此照常可见)。与 `clojure.core/future`、
`react/run-on-executor` 的传导语义一致——引擎不改变「跑的是什么」。
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close