LLM 响应协议和类型定义
定义 Core 工具调用循环所需的响应接口契约。 第三方只需依赖 clj-agent-core 即可实现自定义响应类型。
核心概念:
使用示例:
(require '[im.ttalk.agent.model.response :as resp])
;; 创建响应 (def r (resp/make-response :text "你好" :tool-calls [{:id "1" :name "calc" :args {:x 1}}] :usage {:input_tokens 100 :output_tokens 50}))
;; 使用协议方法 (resp/response-text r) ; => "你好" (resp/has-tool-calls? r) ; => true (resp/response-tool-calls r) ; => [{:id "1" ...}]
LLM 响应协议和类型定义
定义 Core 工具调用循环所需的响应接口契约。
第三方只需依赖 clj-agent-core 即可实现自定义响应类型。
核心概念:
- ILLMResponse: 响应协议,定义 Core 所需的方法
- ChatResponse: 默认实现,使用 record
- make-response: 工厂函数,创建规范化的响应
使用示例:
(require '[im.ttalk.agent.model.response :as resp])
;; 创建响应
(def r (resp/make-response
:text "你好"
:tool-calls [{:id "1" :name "calc" :args {:x 1}}]
:usage {:input_tokens 100 :output_tokens 50}))
;; 使用协议方法
(resp/response-text r) ; => "你好"
(resp/has-tool-calls? r) ; => true
(resp/response-tool-calls r) ; => [{:id "1" ...}](empty-completion? resp)整个补全是不是空的(designs/provider-reliability.md §2,Keelson RetryingChatClient.IsEmptyCompletion)。
只认真正会进历史的几类为「有内容」:非空白文本、非空白推理、工具调用、回放块 (thinking / 服务端工具块)、原始响应里的 audio。usage / finish-reason 不是回答。 收尾原因是厂商结论(content-filter / max-tokens / pause / refusal)的不算空。
整个补全是不是空的(designs/provider-reliability.md §2,Keelson `RetryingChatClient.IsEmptyCompletion`)。 只认真正会进历史的几类为「有内容」:非空白文本、非空白推理、工具调用、回放块 (thinking / 服务端工具块)、原始响应里的 audio。usage / finish-reason 不是回答。 收尾原因是厂商结论(content-filter / max-tokens / pause / refusal)的不算空。
(extract-reasoning raw)从原始 API 响应提取推理/思考内容(与最终答案分离)
兼容两种形态:
返回:推理文本字符串,或 nil(普通模型 / 无推理内容)。
示例: (extract-reasoning {:content [{:type "thinking" :thinking "让我想想..."} {:type "text" :text "答案"}]}) ; => "让我想想..." (extract-reasoning {:choices [{:message {:reasoning_content "推理..." :content "答案"}}]}) ; => "推理..."
从原始 API 响应提取推理/思考内容(与最终答案分离)
兼容两种形态:
- Anthropic / Anthropic 兼容:content 中 type="thinking" 的块,取 :thinking 文本
- OpenAI 兼容(DeepSeek-reasoner 等):choices[0].message.reasoning_content
返回:推理文本字符串,或 nil(普通模型 / 无推理内容)。
示例:
(extract-reasoning {:content [{:type "thinking" :thinking "让我想想..."}
{:type "text" :text "答案"}]})
; => "让我想想..."
(extract-reasoning {:choices [{:message {:reasoning_content "推理..." :content "答案"}}]})
; => "推理..."(has-text? resp)检查响应是否包含非空文本
参数:
返回: boolean
检查响应是否包含非空文本 参数: - resp: ILLMResponse 实例 返回: boolean
LLM 响应统一接口
定义 Core 工具调用循环所需的契约。 所有 LLM 服务返回的响应都应实现此协议。
核心方法(Core 必需):
扩展方法:
LLM 响应统一接口 定义 Core 工具调用循环所需的契约。 所有 LLM 服务返回的响应都应实现此协议。 核心方法(Core 必需): - response-text 获取文本内容 - response-tool-calls 获取工具调用列表 - has-tool-calls? 是否包含工具调用 扩展方法: - response-usage Token 使用情况 - response-finish-reason 完成原因 - response-raw 原始响应
(response-id this)获取响应 ID
返回:字符串或 nil
获取响应 ID 返回:字符串或 nil
(response-model this)获取模型名称
返回:字符串或 nil
获取模型名称 返回:字符串或 nil
(response-raw this)获取原始 API 响应
返回:原始响应数据
获取原始 API 响应 返回:原始响应数据
(response-tool-calls this)获取工具调用列表
返回:工具调用列表 [{:id "..." :name "字符串" :args {...}}] 或 nil
获取工具调用列表
返回:工具调用列表 [{:id "..." :name "字符串" :args {...}}] 或 nil(response-usage this)获取 Token 使用情况
返回:{:input-tokens n :output-tokens m :total-tokens t}
获取 Token 使用情况
返回:{:input-tokens n :output-tokens m :total-tokens t}(response-finish-reason this)获取完成原因
返回::stop | :tool-use | :max-tokens | :content-filter | :unknown
获取完成原因 返回::stop | :tool-use | :max-tokens | :content-filter | :unknown
(response-provider this)获取 Provider 类型
返回:关键字 :openai | :anthropic | ...
获取 Provider 类型 返回:关键字 :openai | :anthropic | ...
(response-text this)获取文本内容
返回:字符串或 nil
获取文本内容 返回:字符串或 nil
(has-tool-calls? this)是否包含工具调用
返回:boolean
是否包含工具调用 返回:boolean
(make-response &
{:keys [text reasoning tool-calls usage finish-reason id model
provider raw-response replay-blocks]})创建 LLM 响应
自动归一化 usage 和 finish-reason。
参数(关键字参数):
返回: ChatResponse record
示例: (make-response :text "你好" :tool-calls nil :usage {:prompt_tokens 100 :completion_tokens 50} :finish-reason "stop")
创建 LLM 响应
自动归一化 usage 和 finish-reason。
参数(关键字参数):
- :text 文本内容
- :tool-calls 工具调用列表
- :usage Token 使用情况(自动归一化)
- :finish-reason 完成原因(自动归一化)
- :id 响应 ID
- :model 模型名称
- :provider Provider 类型
- :raw-response 原始响应
- :replay-blocks 需原样回放的不透明载荷 {:format kw :data ...}(可选,见
im.ttalk.agent.model/IReplayableResponse)
返回:
ChatResponse record
示例:
(make-response
:text "你好"
:tool-calls nil
:usage {:prompt_tokens 100 :completion_tokens 50}
:finish-reason "stop")(normalize-finish-reason reason)归一化完成原因
将不同 Provider 的完成原因统一为标准关键字。
参数:
返回: 标准化关键字 :stop | :tool-use | :max-tokens | :content-filter | :unknown
示例: (normalize-finish-reason "tool_calls") ; => :tool-use (normalize-finish-reason "end_turn") ; => :stop (normalize-finish-reason :tool_use) ; => :tool-use
归一化完成原因 将不同 Provider 的完成原因统一为标准关键字。 参数: - reason: 原始完成原因(字符串或关键字) 返回: 标准化关键字 :stop | :tool-use | :max-tokens | :content-filter | :unknown 示例: (normalize-finish-reason "tool_calls") ; => :tool-use (normalize-finish-reason "end_turn") ; => :stop (normalize-finish-reason :tool_use) ; => :tool-use
(normalize-usage usage)归一化 Token 使用情况
将 OpenAI/Anthropic 的不同命名统一为标准格式。
参数:
返回: 统一格式 {:input-tokens n :output-tokens m :total-tokens t :cache-read-tokens r :cache-write-tokens w} (cache 字段仅在原始 usage 含相应数据时出现,向后兼容)
示例: ;; OpenAI 格式 (normalize-usage {:prompt_tokens 100 :completion_tokens 50 :total_tokens 150}) ; => {:input-tokens 100 :output-tokens 50 :total-tokens 150}
;; Anthropic 格式(含 prompt caching) (normalize-usage {:input_tokens 100 :output_tokens 50 :cache_read_input_tokens 800 :cache_creation_input_tokens 200}) ; => {:input-tokens 100 :output-tokens 50 :total-tokens 150 ; :cache-read-tokens 800 :cache-write-tokens 200}
归一化 Token 使用情况
将 OpenAI/Anthropic 的不同命名统一为标准格式。
参数:
- usage: 原始 usage map(可能来自不同 Provider)
返回:
统一格式 {:input-tokens n :output-tokens m :total-tokens t
:cache-read-tokens r :cache-write-tokens w}
(cache 字段仅在原始 usage 含相应数据时出现,向后兼容)
示例:
;; OpenAI 格式
(normalize-usage {:prompt_tokens 100 :completion_tokens 50 :total_tokens 150})
; => {:input-tokens 100 :output-tokens 50 :total-tokens 150}
;; Anthropic 格式(含 prompt caching)
(normalize-usage {:input_tokens 100 :output_tokens 50
:cache_read_input_tokens 800 :cache_creation_input_tokens 200})
; => {:input-tokens 100 :output-tokens 50 :total-tokens 150
; :cache-read-tokens 800 :cache-write-tokens 200}(pause-turn-resumable? resp)(pause-turn-resumable? resp last-iteration?)pause_turn 能不能原样续跑(Keelson PauseTurn.CanResume):
last-iteration? 为真时不续,以 :pause 正常收尾)。续跑做法(react 侧):把这条 assistant 消息(含 :blocks)原样追加到历史末尾,不加 user 消息, 再请求一次;计入迭代预算。见 designs/provider-reliability.md §10.4。
pause_turn 能不能原样续跑(Keelson `PauseTurn.CanResume`): - finish-reason 是 :pause; - 没有要本地执行的工具调用(有的话走正常工具循环); - 有回放块(server_tool_use 等不透明块)——没有块就没有可原样接回历史的东西,续不了; - 不是最后一轮(`last-iteration?` 为真时不续,以 :pause 正常收尾)。 续跑做法(react 侧):把这条 assistant 消息(含 :blocks)原样追加到历史末尾,**不加 user 消息**, 再请求一次;计入迭代预算。见 designs/provider-reliability.md §10.4。
(pause-turn? resp)这一轮是不是被服务端暂停了(Anthropic stop_reason pause_turn → :pause)。
这一轮是不是被服务端暂停了(Anthropic stop_reason pause_turn → :pause)。
(response-attempts resp)这条响应经 ChatModel 重试层一共试了几次(含第一次);没经过(FnChatModel 等)→ nil。
这条响应经 ChatModel 重试层一共试了几次(含第一次);没经过(FnChatModel 等)→ nil。
(response-rate-limit resp)这次响应抄下来的限流快照(OpenAI x-ratelimit-* / Anthropic anthropic-ratelimit-* 折成同一形状): {:requests-limit :requests-remaining :requests-reset :tokens-limit :tokens-remaining :tokens-reset}, 头缺哪格就没有哪格;provider 没给任何限流头 → nil。
这次响应抄下来的限流快照(OpenAI x-ratelimit-* / Anthropic anthropic-ratelimit-* 折成同一形状):
{:requests-limit :requests-remaining :requests-reset :tokens-limit :tokens-remaining :tokens-reset},
头缺哪格就没有哪格;provider 没给任何限流头 → nil。(response-reasoning resp)获取推理/思考内容(reasoning_content / thinking)
返回:字符串或 nil。普通模型为 nil;推理模型(deepseek-reasoner、 Claude thinking、MiniMax-M 系列等)返回独立于最终答案的推理过程文本。
获取推理/思考内容(reasoning_content / thinking) 返回:字符串或 nil。普通模型为 nil;推理模型(deepseek-reasoner、 Claude thinking、MiniMax-M 系列等)返回独立于最终答案的推理过程文本。
(response-replay-blocks resp)获取「必须原样带回下一轮」的不透明载荷 {:format kw :data ...},无则 nil。
由 chat-model 归一化时经可选协议 im.ttalk.agent.model/IReplayableResponse 抽取; provider 不实现该协议即恒为 nil(原路径不变)。 下游:filter/memory 的 response->neutral 把它挂到中立消息的 :blocks。
获取「必须原样带回下一轮」的不透明载荷 {:format kw :data ...},无则 nil。
由 chat-model 归一化时经可选协议 im.ttalk.agent.model/IReplayableResponse 抽取;
provider 不实现该协议即恒为 nil(原路径不变)。
下游:filter/memory 的 response->neutral 把它挂到中立消息的 :blocks。(response? x)检查是否为 ILLMResponse 实现
参数:
返回: boolean
检查是否为 ILLMResponse 实现 参数: - x: 任意值 返回: boolean
(stopped-by-max-tokens? resp)检查是否因达到最大 token 而停止
检查是否因达到最大 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 |