Liking cljdoc? Tell your friends :D

im.ttalk.agent.chat-model

ChatModel —— 一次 LLM 调用的抽象

对标 Spring AI 的 ChatModel:Provider 只管「底层怎么发这一个请求」, ChatModel 管「这一次逻辑调用」——选项解析、响应归一化、重试,都在这里。 分工的判据是「换一家 provider 要不要重写」:wire 格式要,退避策略不要。

ILLMProvider   一次 HTTP 往返,认识厂商 wire 格式
     ↑
 ChatModel     一次逻辑调用:选项合并 → 重试 → 响应归一化
     ↑
ChatClient     filter 洋葱链 + 工具
     ↑
SimpleAgent    工具循环 + 记忆 + HITL

重试在这里,位于整个 filter 栈之下(与 beamai beamai_chat_model 同一 取舍,理由见 im.ttalk.agent.retry 的 ns 文档):filter 看到的是「一次逻辑 调用」,重试重入碰不到任何 filter,memory/计时的副作用每轮只发生一次。 call 重试、stream 只在首个 token 之前重试——token 已经投递给 sink 后重跑会让下游看到 重复内容;流式要容错请在 turn 层重跑整轮。

两个实现:

  • DefaultChatModel:包一个 ILLMProvider,日常用的就是它
  • FnChatModel:包 {:chat-fn :stream-fn} 两个闭包——历史写法与测试 stub 照旧可用(as-chat-model 在装配期归一化),也是接入任意 LLM 的最短路径

使用示例:

(require '[im.ttalk.agent.chat-model :as chat-model])

(def cm (chat-model/create-chat-model my-provider {:model "gpt-4" :max-tokens 4096 :retry {:max-retries 3}})) ;; 缺省 2 次;:retry false 关闭

(chat-model/call cm (req/chat-request messages {:tools [...]}))

ChatModel —— 一次 LLM 调用的抽象

对标 Spring AI 的 `ChatModel`:**Provider 只管「底层怎么发这一个请求」,
ChatModel 管「这一次逻辑调用」**——选项解析、响应归一化、重试,都在这里。
分工的判据是「换一家 provider 要不要重写」:wire 格式要,退避策略不要。

    ILLMProvider   一次 HTTP 往返,认识厂商 wire 格式
         ↑
     ChatModel     一次逻辑调用:选项合并 → 重试 → 响应归一化
         ↑
    ChatClient     filter 洋葱链 + 工具
         ↑
    SimpleAgent    工具循环 + 记忆 + HITL

**重试在这里,位于整个 filter 栈之下**(与 beamai `beamai_chat_model` 同一
取舍,理由见 `im.ttalk.agent.retry` 的 ns 文档):filter 看到的是「一次逻辑
调用」,重试重入碰不到任何 filter,memory/计时的副作用每轮只发生一次。
**`call` 重试、`stream` 只在首个 token 之前重试**——token 已经投递给 sink 后重跑会让下游看到
重复内容;流式要容错请在 turn 层重跑整轮。

两个实现:
- `DefaultChatModel`:包一个 ILLMProvider,日常用的就是它
- `FnChatModel`:包 `{:chat-fn :stream-fn}` 两个闭包——历史写法与测试 stub
  照旧可用(`as-chat-model` 在装配期归一化),也是接入任意 LLM 的最短路径

使用示例:

(require '[im.ttalk.agent.chat-model :as chat-model])

(def cm (chat-model/create-chat-model my-provider
          {:model "gpt-4" :max-tokens 4096
           :retry {:max-retries 3}}))       ;; 缺省 2 次;:retry false 关闭

(chat-model/call cm (req/chat-request messages {:tools [...]}))
raw docstring

as-chat-modelclj

(as-chat-model x)

归一化成 ChatModel:已实现 IChatModel 则原样返回,{:chat-fn :stream-fn} map 包成 FnChatModel。

build-chat-client 对 :chat-model 调用本函数——所以用户侧传 record、 传裸 map、传自定义 IChatModel 实现三种形态等价(与 filter/as-filter 同一套手法)。

归一化成 ChatModel:已实现 IChatModel 则原样返回,`{:chat-fn :stream-fn}`
map 包成 `FnChatModel`。

`build-chat-client` 对 `:chat-model` 调用本函数——所以用户侧传 record、
传裸 map、传自定义 IChatModel 实现三种形态等价(与 `filter/as-filter`
同一套手法)。
sourceraw docstring

call-async*clj

(call-async* chat-model request)

任何 ChatModel 的异步调用入口 → deferred<ChatResponse>。

实现了 IAsyncChatModel 就用原生异步;否则 async/vthread 包 call。 两条路径的语义(重试、归一化、异常)逐字相同。

任何 ChatModel 的异步调用入口 → deferred<ChatResponse>。

实现了 `IAsyncChatModel` 就用原生异步;否则 `async/vthread` 包 `call`。
两条路径的语义(重试、归一化、异常)逐字相同。
sourceraw docstring

chat-model?clj

(chat-model? x)

是否已是 ChatModel(实现 IChatModel 的任意类型)。

是否已是 ChatModel(实现 IChatModel 的任意类型)。
sourceraw docstring

create-chat-modelclj

(create-chat-model provider config)

从 ILLMProvider 创建 ChatModel。

参数:

  • provider: ILLMProvider 实例
  • config: 模型配置 {:model "..." :max-tokens n :system-prompt "..."} 另可带 :retry —— nil/true 用默认(2 次),false 关闭, map 作为配置({:max-retries 3 :base-delay 1000 ...})

返回:DefaultChatModel(实现 IChatModel)

  • call 同步调用,内建重试(判据为 canonical error 的 :retryable?)
  • stream-call 流式调用,只在首个 token 前重试(token 已投递后重跑即重复)

说明:历史的 :build-result-msgs 已移除——对话历史由 filter/memory 的 response->neutral 用中立消息统一构建,ChatModel 无需再提供该函数。

从 ILLMProvider 创建 ChatModel。

参数:
- provider: ILLMProvider 实例
- config:   模型配置 {:model "..." :max-tokens n :system-prompt "..."}
            另可带 :retry —— nil/true 用默认(2 次),false 关闭,
            map 作为配置({:max-retries 3 :base-delay 1000 ...})

返回:`DefaultChatModel`(实现 IChatModel)

- `call`        同步调用,**内建重试**(判据为 canonical error 的 :retryable?)
- `stream-call` 流式调用,只在首个 token 前重试(token 已投递后重跑即重复)

说明:历史的 :build-result-msgs 已移除——对话历史由 filter/memory 的
response->neutral 用中立消息统一构建,ChatModel 无需再提供该函数。
sourceraw docstring

IAsyncChatModelcljprotocol

可选协议:ChatModel 能异步调用时实现它。语义与 IChatModel 逐字相同 (含重试策略:call-async 重试、stream-call-async 只在首个 token 前重试),只是不阻塞调用线程。

不实现也能异步:call-async* / stream-call-async* 探测不到会用虚拟线程包 同步调用兜底——调用方永远拿得到 deferred。实现本协议只省掉那根线程。

不得 extend-type Object 兜底(satisfies? 会恒真,探测机制当场失效)。

**可选**协议:ChatModel 能异步调用时实现它。语义与 `IChatModel` 逐字相同
(含重试策略:`call-async` 重试、`stream-call-async` 只在首个 token 前重试),只是不阻塞调用线程。

**不实现也能异步**:`call-async*` / `stream-call-async*` 探测不到会用虚拟线程包
同步调用兜底——调用方永远拿得到 deferred。实现本协议只省掉那根线程。

**不得 `extend-type Object` 兜底**(`satisfies?` 会恒真,探测机制当场失效)。

call-asyncclj

(call-async this request)

-> deferred<ChatResponse>。语义同 call(含重试)。

-> deferred<ChatResponse>。语义同 `call`(含重试)。

stream-call-asyncclj

(stream-call-async this request on-token)

-> deferred<ChatResponse>。语义同 stream-call(只在首个 token 前重试)。

on-token 的调用线程不保证——原生异步实现里 token 在传输层 executor 上派发。

-> deferred<ChatResponse>。语义同 `stream-call`(**只在首个 token 前重试**)。

on-token 的调用线程不保证——原生异步实现里 token 在传输层 executor 上派发。
sourceraw docstring

IChatModelcljprotocol

一次 LLM 调用的契约。对标 Spring AI 的 ChatModel 接口。

call 内建重试;stream-call 只在首个 token 之前重试(token 已出门之后 重跑即重复投递——这条是流式语义的硬约束,designs/provider-reliability.md §2)。

一次 LLM 调用的契约。对标 Spring AI 的 `ChatModel` 接口。

`call` 内建重试;`stream-call` 只在**首个 token 之前**重试(token 已出门之后
重跑即重复投递——这条是流式语义的硬约束,designs/provider-reliability.md §2)。

callclj

(call this request)

同步调用。

参数: request —— ChatRequest(或可归一化为它的 map) 返回: ChatResponse(实现 ILLMResponse) 抛出: ex-info,data 即 canonical error(重试耗尽后原样重抛)

同步调用。

参数: request —— `ChatRequest`(或可归一化为它的 map)
返回: `ChatResponse`(实现 ILLMResponse)
抛出: ex-info,data 即 canonical error(重试耗尽后原样重抛)

model-optionsclj

(model-options this)

该 ChatModel 的缺省调用选项({:model ... :max-tokens ...})。 单次请求的 :options 覆盖它。

该 ChatModel 的缺省调用选项({:model ... :max-tokens ...})。
单次请求的 `:options` 覆盖它。

stream-callclj

(stream-call this request on-token)

流式调用。on-token 收 {:token ...} / {:reasoning-token ...} 增量。

返回: 流结束时的最终 ChatResponse(与 call 同形)。 provider 不支持流式时回退同步,并把全文作为单个 token emit——保证 chat-stream 对任何 provider 都可用。

只在首个 token 之前重试:见本 ns 文档。

流式调用。on-token 收 {:token ...} / {:reasoning-token ...} 增量。

返回: 流结束时的最终 `ChatResponse`(与 `call` 同形)。
provider 不支持流式时回退同步,并把全文作为单个 token emit——保证
chat-stream 对任何 provider 都可用。

**只在首个 token 之前重试**:见本 ns 文档。
sourceraw docstring

stream-call-async*clj

(stream-call-async* chat-model request on-token)

call-async* 的流式版 → deferred<ChatResponse>。只在首个 token 前重试(同 stream-call)。

⚠️ on-token 的调用线程不保证:原生异步路径上 token 在传输层 executor 上派发, 虚拟线程兜底路径上则在那根虚拟线程上。sink 自行保证线程安全。

`call-async*` 的流式版 → deferred<ChatResponse>。**只在首个 token 前重试**(同 `stream-call`)。

⚠️ on-token 的调用线程不保证:原生异步路径上 token 在传输层 executor 上派发,
虚拟线程兜底路径上则在那根虚拟线程上。sink 自行保证线程安全。
sourceraw docstring

with-admissionclj

(with-admission chat-model pool)
(with-admission chat-model pool {:keys [key key-fn]})

给 ChatModel 套上在途票据池(im.ttalk.agent.admission)。

  • chat-model 是 DefaultChatModel → 返回同一种 DefaultChatModel,config 挂 :admission: 每次尝试(流式为整条流)取一张票、尝试结束即还,退避等待不占票。
  • 其它 ChatModel(FnChatModel / FallbackChatModel / 自定义)→ AdmittingChatModel 装饰器, 票覆盖整次调用(它看不见内层的重试)。

opts:

  • :key-fn (fn [request] key) 按请求算池的键;缺省 DefaultChatModel 为 "<provider>/<model>", 其它为 "default"
  • :key 固定键(与 :key-fn 二选一)
给 ChatModel 套上在途票据池(`im.ttalk.agent.admission`)。

- `chat-model` 是 DefaultChatModel → 返回同一种 DefaultChatModel,config 挂 `:admission`:
  **每次尝试**(流式为整条流)取一张票、尝试结束即还,**退避等待不占票**。
- 其它 ChatModel(FnChatModel / FallbackChatModel / 自定义)→ `AdmittingChatModel` 装饰器,
  票覆盖**整次调用**(它看不见内层的重试)。

opts:
- :key-fn (fn [request] key)  按请求算池的键;缺省 DefaultChatModel 为 "<provider>/<model>",
                               其它为 "default"
- :key    固定键(与 :key-fn 二选一)
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