Anthropic Provider 实现
实现 ILLMProvider 协议,提供 Anthropic Claude API 的完整访问。
支持功能:
调用 config 支持的 Anthropic 专属能力(均「存在才发送」,详见 build-params):
prompt caching 策略(:cache-strategy):
响应限流:同步调用成功时,响应体附加 :rate-limit {:requests-limit :requests-remaining :requests-reset :tokens-limit :tokens-remaining :tokens-reset :retry-after}(源自 anthropic-ratelimit-* 头)。
使用示例:
(require '[im.ttalk.agent.provider.anthropic :as anthropic] '[im.ttalk.agent.provider.schema.anthropic :as schema])
(def provider (anthropic/create-provider))
;; 同步调用 (anthropic/call-anthropic config messages tools)
;; 流式调用 (anthropic/call-anthropic-stream config messages tools on-token)
;; web_search + 缓存 + 服务层级 (anthropic/call-anthropic {:model "claude-opus-4-8" :max-tokens 4096 :service-tier "auto" :cache-strategy :system-and-tools :cache-ttl "1h"} messages [(schema/web-search-tool {:max-uses 5})])
;; Citations:可引用文档 -> 响应带引用 (let [resp (anthropic/call-anthropic {:model "claude-opus-4-8" :max-tokens 1024} [{:role "user" :content [(schema/text-document "地球绕太阳公转。" {:title "天文"}) {:type "text" :text "地球绕什么转?"}]}] [])] (anthropic/extract-citations resp))
;; Skills(beta):需开 :beta 头 + code_execution 工具 (anthropic/call-anthropic {:model "claude-opus-4-8" :max-tokens 4096 :beta schema/default-skills-beta :container (schema/skills-container [(schema/skill "xlsx")])} messages [(schema/code-execution-tool)])
Anthropic Provider 实现
实现 ILLMProvider 协议,提供 Anthropic Claude API 的完整访问。
支持功能:
- 同步调用 / 流式调用(同步阻塞)/ 异步调用 / 异步流式调用
- 工具调用(Function Calling)+ 结构化输出
- 服务端内置工具:web_search(见 schema.anthropic/web-search-tool)
- Citations 引用:可引用文档块(schema.anthropic/text-document)+ 响应引用提取(extract-citations)
- Skills(beta):技能容器 + code_execution 工具(schema.anthropic/skill 等,配合 config :beta)
- prompt caching 策略层(见 common.cache)
- 响应限流头解析(parse-rate-limit)
调用 config 支持的 Anthropic 专属能力(均「存在才发送」,详见 build-params):
- 采样::temperature :top-p :top-k :max-tokens :stop(=> stop_sequences)
- 推理::thinking(如 {:type "adaptive"} / {:type "enabled" :budget_tokens 2048})
- :metadata、:service-tier("auto" | "standard_only",容量路由)
- 工具::tools(含 web_search 等 wire 工具)、:tool-choice
- 缓存::cache-strategy + :cache-ttl(见下;可按断点给档 {:system "1h" :conversation nil},
用到 "1h" 自动并入 beta extended-cache-ttl-2025-04-11)
- 宿主钩子::configure-request((fn [payload] payload'),发出前最后改一次请求体,
见 common.request-hook)
prompt caching 策略(:cache-strategy):
- :none | :system | :tools | :system-and-tools | :conversation
- :tool-results 缓存到最后一个 tool_result(多轮工具循环跨轮复用)
- :system-and-conversation system + 对话历史双断点
命中/创建 token 归一化到响应 usage 的 :cache-read-tokens / :cache-write-tokens。
响应限流:同步调用成功时,响应体附加 :rate-limit
{:requests-limit :requests-remaining :requests-reset
:tokens-limit :tokens-remaining :tokens-reset :retry-after}(源自 anthropic-ratelimit-* 头)。
使用示例:
(require '[im.ttalk.agent.provider.anthropic :as anthropic]
'[im.ttalk.agent.provider.schema.anthropic :as schema])
(def provider (anthropic/create-provider))
;; 同步调用
(anthropic/call-anthropic config messages tools)
;; 流式调用
(anthropic/call-anthropic-stream config messages tools on-token)
;; web_search + 缓存 + 服务层级
(anthropic/call-anthropic
{:model "claude-opus-4-8" :max-tokens 4096
:service-tier "auto"
:cache-strategy :system-and-tools :cache-ttl "1h"}
messages
[(schema/web-search-tool {:max-uses 5})])
;; Citations:可引用文档 -> 响应带引用
(let [resp (anthropic/call-anthropic
{:model "claude-opus-4-8" :max-tokens 1024}
[{:role "user"
:content [(schema/text-document "地球绕太阳公转。" {:title "天文"})
{:type "text" :text "地球绕什么转?"}]}]
[])]
(anthropic/extract-citations resp))
;; Skills(beta):需开 :beta 头 + code_execution 工具
(anthropic/call-anthropic
{:model "claude-opus-4-8" :max-tokens 4096
:beta schema/default-skills-beta
:container (schema/skills-container [(schema/skill "xlsx")])}
messages
[(schema/code-execution-tool)])Anthropic 服务端工具块的「看得懂」的视图(designs/provider-reliability.md §9;
Keelson Providers/AnthropicServerTools.cs 的 Describe)。
服务端工具块(server_tool_use、web_search_tool_result、code_execution_tool_result …)在
中立层始终是不透明回放载荷(:blocks,原样回放、pause_turn 续跑都认它),这一点不变。
本 ns 只给界面 / 日志一个纯数据视图(查询词、搜到的网址、执行输出),不进历史。
(server-tools/describe-all (:content raw-anthropic-response))
;=> [{:kind :web-search-call :id "srvtoolu_1" :queries ["clojure"]}
; {:kind :web-search-result :tool-use-id "srvtoolu_1" :results [{:url .. :title ..}]}]
Anthropic 服务端工具块的「看得懂」的视图(designs/provider-reliability.md §9;
Keelson `Providers/AnthropicServerTools.cs` 的 `Describe`)。
服务端工具块(`server_tool_use`、`web_search_tool_result`、`code_execution_tool_result` …)在
中立层始终是不透明回放载荷(`:blocks`,原样回放、pause_turn 续跑都认它),这一点不变。
本 ns 只给界面 / 日志一个纯数据视图(查询词、搜到的网址、执行输出),**不进历史**。
```clojure
(server-tools/describe-all (:content raw-anthropic-response))
;=> [{:kind :web-search-call :id "srvtoolu_1" :queries ["clojure"]}
; {:kind :web-search-result :tool-use-id "srvtoolu_1" :results [{:url .. :title ..}]}]
```LLM 模块统一 API 入口
提供所有 LLM 功能的统一访问入口。
主要功能:
使用示例:
(require '[im.ttalk.agent.provider.api :as llm])
;; 创建 Provider (def provider (llm/create-provider :openai))
;; 同步调用 (llm/call provider config messages tools)
;; 流式调用 (llm/call-stream provider config messages tools on-token)
;; 提取结果 (llm/extract-text provider response) (llm/extract-tool-calls provider response)
LLM 模块统一 API 入口 提供所有 LLM 功能的统一访问入口。 主要功能: - Provider 创建和管理 - LLM 调用(同步、流式、异步) - 响应解析 - 错误处理 使用示例: (require '[im.ttalk.agent.provider.api :as llm]) ;; 创建 Provider (def provider (llm/create-provider :openai)) ;; 同步调用 (llm/call provider config messages tools) ;; 流式调用 (llm/call-stream provider config messages tools on-token) ;; 提取结果 (llm/extract-text provider response) (llm/extract-tool-calls provider response)
OpenAI 兼容 Provider 基础模块
为所有 OpenAI 兼容的 LLM Provider 提供统一的基础实现。 支持: OpenAI, ZhiPu, Ollama, Gemini, Mistral 等。
主要功能:
使用示例:
(require '[im.ttalk.agent.provider.common.base :as base])
;; 创建配置 (def config (base/make-config :openai "https://api.openai.com/v1" "OPENAI_API_KEY"))
;; 创建 Provider (def provider (base/create-provider config))
OpenAI 兼容 Provider 基础模块
为所有 OpenAI 兼容的 LLM Provider 提供统一的基础实现。
支持: OpenAI, ZhiPu, Ollama, Gemini, Mistral 等。
主要功能:
- 统一的 API 调用包装
- 通用的 Provider Record 实现
- 配置管理
使用示例:
(require '[im.ttalk.agent.provider.common.base :as base])
;; 创建配置
(def config (base/make-config
:openai
"https://api.openai.com/v1"
"OPENAI_API_KEY"))
;; 创建 Provider
(def provider (base/create-provider config))厂商业务码 → canonical 错误类型的码表(designs/provider-reliability.md §1;
照搬 Keelson Providers/ProviderBusinessCodes.cs + ChatProviderException.RefineKind)。
为什么要有这张表:限流和欠费在 HTTP 上常常是同一个 429(OpenAI 兼容的
insufficient_quota 就这么干),光看状态码会把「再等一会就有」当成「再等一个月也没有」,
ChatModel 于是对欠费反复退避重试。反过来,欠费被回成 400 时只看状态码会判成参数错。
状态码只给初判,业务码说了算。
判定次序(refine-error,只会「收紧」):
error.type / error.code / 顶层 code / MiniMax base_resp.status_code)→ 定;
provider 专属行优先于通用行;码表是数据(不是 if 链):补新样本往 rules 加一行即可,判定次序不用动。
厂商业务码 → canonical 错误类型的码表(designs/provider-reliability.md §1; 照搬 Keelson `Providers/ProviderBusinessCodes.cs` + `ChatProviderException.RefineKind`)。 **为什么要有这张表**:限流和欠费在 HTTP 上常常是同一个 429(OpenAI 兼容的 `insufficient_quota` 就这么干),光看状态码会把「再等一会就有」当成「再等一个月也没有」, ChatModel 于是对欠费反复退避重试。反过来,欠费被回成 400 时只看状态码会判成参数错。 **状态码只给初判,业务码说了算。** 判定次序(`refine-error`,只会「收紧」): 1. 码表命中(`error.type` / `error.code` / 顶层 `code` / MiniMax `base_resp.status_code`)→ 定; provider 专属行优先于通用行; 2. 402 → 欠费; 3. 原判是 :validation-error / :provider-error 且文案像「上下文超长」→ :context-overflow-error; 4. 原判是 auth / validation / rate-limit → 维持(**文案绝不凭空造出限流,也不把限流改成欠费**); 5. 其余(5xx / provider)文案像欠费 → :quota-exhausted-error。 码表是数据(不是 if 链):补新样本往 `rules` 加一行即可,判定次序不用动。
Anthropic Prompt Caching 策略层(对标 Spring AI AnthropicCacheStrategy)
Anthropic 通过在内容块上挂 cache_control 来缓存请求前缀,命中后该段
输入 token 仅按约 0.1x 计费。要点(以官方为准):
cache_control 标记的是"断点"
(breakpoint),断点之前的全部内容作为可缓存前缀。:none 不缓存(默认) :system 仅缓存 system :tools 仅缓存工具定义 :system-and-tools 缓存 system + tools(两个 breakpoint) :conversation 缓存到"当前用户问题之前"的对话历史 (标记最后一条消息的最后一个内容块) :tool-results 缓存到最后一个 tool_result(多轮工具循环里跨轮复用工具结果, 标记 messages 中最后一个 type=tool_result 的内容块) :system-and-conversation 同时缓存 system 与对话历史(两个 breakpoint)
ttl(:cache-ttl)两种形状:
服务端规则:长 TTL 的断点必须排在短 TTL 之前(渲染顺序 tools → system → messages)。
apply-anthropic-cache 在注入前校验,违者抛 ex-info(:type :invalid-cache-ttl),
不等服务端 400。任一断点为 "1h" 时 anthropic provider 自动并入 beta
extended-cache-ttl-2025-04-11(兼容端点如 MiniMax /anthropic 少了它会静默退回 5m)。
用法见 apply-anthropic-cache。
Anthropic Prompt Caching 策略层(对标 Spring AI AnthropicCacheStrategy)
========================================
背景
========================================
Anthropic 通过在内容块上挂 `cache_control` 来缓存请求前缀,命中后该段
输入 token 仅按约 0.1x 计费。要点(以官方为准):
- 渲染顺序:tools -> system -> messages。`cache_control` 标记的是"断点"
(breakpoint),断点之前的全部内容作为可缓存前缀。
- 单请求最多 4 个 breakpoint。
- 最小可缓存前缀与模型相关(Opus/Haiku4.5 = 4096 token,Sonnet4.x = 1024/2048);
过短不会缓存(无报错,cache_creation_input_tokens=0)。
- 任意字节变化都会使该位置之后的缓存失效 —— 稳定内容靠前,易变内容靠后。
========================================
策略(cache-strategy)
========================================
:none 不缓存(默认)
:system 仅缓存 system
:tools 仅缓存工具定义
:system-and-tools 缓存 system + tools(两个 breakpoint)
:conversation 缓存到"当前用户问题之前"的对话历史
(标记最后一条消息的最后一个内容块)
:tool-results 缓存到最后一个 tool_result(多轮工具循环里跨轮复用工具结果,
标记 messages 中最后一个 type=tool_result 的内容块)
:system-and-conversation 同时缓存 system 与对话历史(两个 breakpoint)
ttl(:cache-ttl)两种形状:
- 标量:nil -> 默认 5 分钟;"1h" -> 1 小时;本策略的**所有**断点同一档(老用法)。
- 按断点给档的 map:键 :tools / :system / :conversation / :tool-results,
缺键或 nil = 5 分钟(cache_control 不带 ttl 字段)。典型:
{:cache-strategy :system-and-conversation
:cache-ttl {:system "1h" :conversation nil}}
⇒ 前缀(tools+system)1h、对话尾巴 5m——尾巴每轮被取代,付 1h 的 2× 写入费纯亏
(feedbacks/2026-11-10-provider-request-hook-and-two-tier-cache-ttl.md)。
服务端规则:**长 TTL 的断点必须排在短 TTL 之前**(渲染顺序 tools → system → messages)。
apply-anthropic-cache 在注入前校验,违者抛 ex-info(:type :invalid-cache-ttl),
不等服务端 400。任一断点为 "1h" 时 anthropic provider 自动并入 beta
`extended-cache-ttl-2025-04-11`(兼容端点如 MiniMax /anthropic 少了它会静默退回 5m)。
用法见 apply-anthropic-cache。Embedding HTTP 调用与响应归一(两种线上形态)
OpenAI 兼容 POST {base}/embeddings
body {:model .. :input ["a" "b"] :dimensions n? :encoding_format ..}
resp {:data [{:index 0 :embedding [..]} ..] :model .. :usage {:prompt_tokens ..}}
覆盖:OpenAI / 智谱 / SiliconFlow / Ollama / Gemini(兼容端点) / Mistral / xAI …
DashScope 原生 POST .../services/embeddings/text-embedding/text-embedding
body {:model .. :input {:texts [..]} :parameters {:dimension n :text_type ..}}
resp {:output {:embeddings [{:text_index 0 :embedding [..]}]} :usage {:total_tokens n}}
两条路的公共约定:
:batch-size),usage 逐片累加Embedding HTTP 调用与响应归一(两种线上形态)
1. **OpenAI 兼容** `POST {base}/embeddings`
body {:model .. :input ["a" "b"] :dimensions n? :encoding_format ..}
resp {:data [{:index 0 :embedding [..]} ..] :model .. :usage {:prompt_tokens ..}}
覆盖:OpenAI / 智谱 / SiliconFlow / Ollama / Gemini(兼容端点) / Mistral / xAI …
2. **DashScope 原生** `POST .../services/embeddings/text-embedding/text-embedding`
body {:model .. :input {:texts [..]} :parameters {:dimension n :text_type ..}}
resp {:output {:embeddings [{:text_index 0 :embedding [..]}]} :usage {:total_tokens n}}
两条路的公共约定:
- 返回向量与入参 texts **同序等长**(按服务端返回的 index 重排,不假定顺序)
- 超批自动切片(`:batch-size`),usage 逐片累加
- 失败一律抛规范错误(D5),与 chat 路径同一套词汇轻量有界缓存工具。
与 clojure.core/memoize 的区别:容量有界(超限整体清空重建), 不会随参数多样性无限增长——适合做库代码里的转换缓存。
轻量有界缓存工具。 与 clojure.core/memoize 的区别:容量有界(超限整体清空重建), 不会随参数多样性无限增长——适合做库代码里的转换缓存。
OpenAI 兼容 API 调用
提供 OpenAI 风格 API 的通用调用代码,供多个 Provider 复用:
使用示例:
(require '[im.ttalk.agent.provider.common.openai-compat :as compat])
;; 同步调用 (compat/call-api api-url api-key config messages tools)
;; 流式调用 (compat/call-api-stream api-url api-key config messages tools on-token)
OpenAI 兼容 API 调用 提供 OpenAI 风格 API 的通用调用代码,供多个 Provider 复用: - OpenAI - 智谱 AI (ZhiPu) - Ollama - 其他 OpenAI 兼容 API 使用示例: (require '[im.ttalk.agent.provider.common.openai-compat :as compat]) ;; 同步调用 (compat/call-api api-url api-key config messages tools) ;; 流式调用 (compat/call-api-stream api-url api-key config messages tools on-token)
统一限流快照(designs/provider-reliability.md §6;Keelson Providers/RateLimitInfo.cs)。
OpenAI 的 x-ratelimit-{limit,remaining,reset}-{requests,tokens} 与 Anthropic 的
anthropic-ratelimit-{requests,tokens}-{limit,remaining,reset} 折成同一形状:
{:requests-limit :requests-remaining :requests-reset :tokens-limit :tokens-remaining :tokens-reset}
头缺哪格就没有哪格;全空 → nil(不挂)。有 retry-after 头时另带 :retry-after
(Anthropic 旧 parse-rate-limit 形状的超集,向后兼容)。
头值可以是字符串,也可以是 java.net.http 给的 List<String>(http.client 的
http-headers->map 曾经这么存)——取第一个值(im.ttalk.http/header)。
统一限流快照(designs/provider-reliability.md §6;Keelson `Providers/RateLimitInfo.cs`)。
OpenAI 的 `x-ratelimit-{limit,remaining,reset}-{requests,tokens}` 与 Anthropic 的
`anthropic-ratelimit-{requests,tokens}-{limit,remaining,reset}` 折成同一形状:
{:requests-limit :requests-remaining :requests-reset
:tokens-limit :tokens-remaining :tokens-reset}
头缺哪格就没有哪格;全空 → nil(不挂)。有 `retry-after` 头时另带 `:retry-after`
(Anthropic 旧 `parse-rate-limit` 形状的超集,向后兼容)。
头值可以是字符串,也可以是 `java.net.http` 给的 `List<String>`(`http.client` 的
`http-headers->map` 曾经这么存)——取第一个值(`im.ttalk.http/header`)。宿主对最终请求体的最后一改:config :configure-request。
形状:(fn [payload] payload),payload 是本库组好的完整请求体(wire 形态的
map,键为 snake_case 关键字)——思考字段(common.thinking 生成的)、:extra-body、
缓存断点(Anthropic)、流式的 :stream true 都已在场,钩子看得见、删得掉、
改写得了;返回值原样作为请求体发出。
为什么要有(feedbacks/2026-11-10-provider-request-hook-and-two-tier-cache-ttl.md):
:extra-body 只能 merge,删不掉库生成的键。qwen 收到 thinking /
reasoning_effort 回 400(它要 enable_thinking)、kimi-k2 收到 temperature
回 400——宿主手里只有 config,算不出最终请求体,于是绕不过去。
对标 Keelson.Agent 的 ConfigureRequest(Action<JsonObject>)。
约定:
ex-info(:type :invalid-request-hook),
不静默发出半截请求体。宿主对**最终请求体**的最后一改:config `:configure-request`。 形状:`(fn [payload] payload)`,payload 是本库组好的完整请求体(wire 形态的 map,键为 snake_case 关键字)——思考字段(common.thinking 生成的)、`:extra-body`、 缓存断点(Anthropic)、流式的 `:stream true` **都已在场**,钩子看得见、删得掉、 改写得了;返回值原样作为请求体发出。 为什么要有(feedbacks/2026-11-10-provider-request-hook-and-two-tier-cache-ttl.md): `:extra-body` 只能 merge,删不掉库生成的键。qwen 收到 `thinking` / `reasoning_effort` 回 400(它要 `enable_thinking`)、kimi-k2 收到 `temperature` 回 400——宿主手里只有 config,算不出最终请求体,于是绕不过去。 对标 Keelson.Agent 的 `ConfigureRequest`(`Action<JsonObject>`)。 约定: - 同步 / 流式 / deferred 各条路径**同一处**调用,发出之前最后一步。 - 返回非 map 视为宿主 bug,当场抛 `ex-info`(`:type :invalid-request-hook`), 不静默发出半截请求体。 - 钩子抛出的异常原样上抛(不包装、不吞)。
响应解析模块
提供统一的 LLM API 响应解析功能,支持 OpenAI 格式的响应。 将 OpenAI 原始响应转换为统一的内部格式。
使用示例:
(require '[im.ttalk.agent.provider.common.response-parser :as parser])
;; 提取工具调用 (parser/extract-tool-calls response)
;; 提取文本 (parser/extract-text response)
;; 标准化响应为统一格式 (parser/normalize-response response)
响应解析模块 提供统一的 LLM API 响应解析功能,支持 OpenAI 格式的响应。 将 OpenAI 原始响应转换为统一的内部格式。 使用示例: (require '[im.ttalk.agent.provider.common.response-parser :as parser]) ;; 提取工具调用 (parser/extract-tool-calls response) ;; 提取文本 (parser/extract-text response) ;; 标准化响应为统一格式 (parser/normalize-response response)
thinking 档位统一(designs/provider-reliability.md §7;Keelson Providers/ThinkingKeys.cs、
OpenAICompatibleChatClient.ApplyThinkingCall、AnthropicChatClient.ApplyThinkingCall)。
上层只说一句「档位 / 开关」,这里按各家风格生成 wire:
| config 键 | 含义 |
|---|---|
:thinking-effort | 档位名(字符串 / 关键字),原样上 wire(DeepSeek 的 "max" 也行);有档位即开 |
:thinking-enabled | 只开 / 关,不带档位(effort 在场时被忽略) |
:thinking-style | OpenAI 兼容线的写法::plain / :nested / :top-level / :qwen / :none;缺省按 provider 预设 |
:thinking-budget-tokens | Anthropic 旧式预算(压过档位与「只开」,只有「关」压得过它) |
:thinking-always-on? | Anthropic:关不掉思考的模型,「关」时省略字段 |
不破坏现有透传:config 里显式给了 :thinking(或 OpenAI 的 :reasoning-effort、Responses 的
:reasoning)→ 对应字段不由本 ns 生成,显式值原样发。两个新键都没给 → 什么都不生成。
thinking 档位统一(designs/provider-reliability.md §7;Keelson `Providers/ThinkingKeys.cs`、 `OpenAICompatibleChatClient.ApplyThinkingCall`、`AnthropicChatClient.ApplyThinkingCall`)。 上层只说一句「档位 / 开关」,这里按各家风格生成 wire: | config 键 | 含义 | |---|---| | `:thinking-effort` | 档位名(字符串 / 关键字),**原样上 wire**(DeepSeek 的 "max" 也行);有档位即开 | | `:thinking-enabled` | 只开 / 关,不带档位(effort 在场时被忽略) | | `:thinking-style` | OpenAI 兼容线的写法:`:plain` / `:nested` / `:top-level` / `:qwen` / `:none`;缺省按 provider 预设 | | `:thinking-budget-tokens` | Anthropic 旧式预算(压过档位与「只开」,只有「关」压得过它) | | `:thinking-always-on?` | Anthropic:关不掉思考的模型,「关」时省略字段 | **不破坏现有透传**:config 里显式给了 `:thinking`(或 OpenAI 的 `:reasoning-effort`、Responses 的 `:reasoning`)→ 对应字段不由本 ns 生成,显式值原样发。两个新键都没给 → 什么都不生成。
阿里云 DashScope Provider 实现
实现 ILLMProvider 协议,使用原生 DashScope API。
API 文档:https://help.aliyun.com/zh/model-studio/dashscope-api-reference/
支持的模型:
使用示例:
(require '[im.ttalk.agent.provider.dashscope :as dashscope])
(def provider (dashscope/create-provider))
;; 同步调用 (dashscope/call-dashscope config messages tools)
阿里云 DashScope Provider 实现 实现 ILLMProvider 协议,使用原生 DashScope API。 API 文档:https://help.aliyun.com/zh/model-studio/dashscope-api-reference/ 支持的模型: - Qwen Max 系列: qwen3-max, qwen-max, qwen-max-latest - Qwen Plus 系列: qwen-plus, qwen-plus-latest - Qwen Turbo 系列: qwen-turbo, qwen-turbo-latest - Qwen Coder 系列: qwen3-coder-plus, qwen-coder-plus - QwQ 系列: qwq-plus 使用示例: (require '[im.ttalk.agent.provider.dashscope :as dashscope]) (def provider (dashscope/create-provider)) ;; 同步调用 (dashscope/call-dashscope config messages tools)
DeepSeek Provider —— OpenAI 兼容实现(统一由 base/defprovider 生成)。
需要 DEEPSEEK_API_KEY 或显式 :api-key。 模型:deepseek-chat、deepseek-reasoner。
DeepSeek 专属差异(均已支持):
作为 OpenAI 兼容实现,同样继承 common.openai-compat 的通用能力: :parallel-tool-calls、:response-format(json_object / json_schema+strict 结构化输出)、 :stream-options、:extra-body 等(:reasoning-effort/:verbosity 为 OpenAI 系参数, DeepSeek 当前忽略,但传入安全)。
(require '[im.ttalk.agent.provider.deepseek :as deepseek]) (def provider (deepseek/create-provider {:api-key "sk-..."}))
;; 前缀续写:让模型续写 "春天的风" (deepseek/call-prefix-completion {:model "deepseek-chat" :max-tokens 256} [{:role "user" :content "写一句诗"} {:role "assistant" :content "春天的风"}] nil)
DeepSeek Provider —— OpenAI 兼容实现(统一由 base/defprovider 生成)。
需要 DEEPSEEK_API_KEY 或显式 :api-key。
模型:deepseek-chat、deepseek-reasoner。
DeepSeek 专属差异(均已支持):
- reasoning_content:deepseek-reasoner 的思维链(同步 message.reasoning_content /
流式 delta.reasoning_content),统一归一化到 :reasoning 字段,:text 为干净答案;
流式推理 token 经回调 :reasoning-token 单独下发
- 前缀续写(beta):call-prefix-completion / call-prefix-completion-stream,
最后一条 assistant 消息标记 prefix:true,模型从该前缀继续生成
(自动走 https://api.deepseek.com/beta 路径),常与 :stop 搭配
- SSE 末块 usage:流式最后一个 chunk 原生携带 usage
(含 prompt_cache_hit_tokens / prompt_cache_miss_tokens),已捕获并归一化为
:cache-read-tokens / :cache-miss-tokens
- 注意:deepseek-reasoner 不支持 logprobs/top_logprobs(设置会报错),
temperature/top_p/presence_penalty/frequency_penalty 会被忽略 ——
本实现「存在才发送」,不传即安全
作为 OpenAI 兼容实现,同样继承 common.openai-compat 的通用能力:
:parallel-tool-calls、:response-format(json_object / json_schema+strict 结构化输出)、
:stream-options、:extra-body 等(:reasoning-effort/:verbosity 为 OpenAI 系参数,
DeepSeek 当前忽略,但传入安全)。
(require '[im.ttalk.agent.provider.deepseek :as deepseek])
(def provider (deepseek/create-provider {:api-key "sk-..."}))
;; 前缀续写:让模型续写 "春天的风"
(deepseek/call-prefix-completion
{:model "deepseek-chat" :max-tokens 256}
[{:role "user" :content "写一句诗"}
{:role "assistant" :content "春天的风"}]
nil)Embedding Provider 实现与工厂
与对话 provider 分开:embedding 模型是另一种模型(另一个端点、另一套参数、
另一份计费),所以它是独立实例而不是给 ILLMProvider 加方法——对齐 Vercel AI SDK
的 openai.embedding(...) 与 openai.chat(...) 两条线。好处是能力探测不撒谎:
(emb/embedding-provider? p) 为真,就是真能 embed;Anthropic 这种没有 embedding
服务的厂商,这里根本没有条目。
内置(create-provider 第一参数):
:openai / :zhipu / :siliconflow / :ollama / :gemini / :mistral / :dashscope /
:openai-compat(任意兼容端点,:base-url 必填)/ :mock(确定性假向量,测试用)
(require '[im.ttalk.agent.provider.embeddings :as emb])
(def e (emb/create-provider :openai)) ;; 取 OPENAI_API_KEY
(emb/embed e {} ["你好" "世界"])
;; => {:embeddings [[...] [...]] :model "text-embedding-3-small"
;; :usage {:input-tokens 8 :total-tokens 8} :provider :openai}
(emb/embed-one e {:dimensions 256} "只要一条")
;; 自建 / 私有兼容端点
(emb/create-provider :openai-compat
{:base-url "https://my-gw.internal/v1" :api-key "..." :model "bge-m3"})
调用时的 config 覆盖实例 config:{:model :dimensions :batch-size :timeout :retry :encoding-format :user :extra-headers :extra-body}(DashScope 另有
:text-type)。
Embedding Provider 实现与工厂
**与对话 provider 分开**:embedding 模型是另一种模型(另一个端点、另一套参数、
另一份计费),所以它是独立实例而不是给 `ILLMProvider` 加方法——对齐 Vercel AI SDK
的 `openai.embedding(...)` 与 `openai.chat(...)` 两条线。好处是**能力探测不撒谎**:
`(emb/embedding-provider? p)` 为真,就是真能 embed;Anthropic 这种没有 embedding
服务的厂商,这里根本没有条目。
内置(`create-provider` 第一参数):
:openai / :zhipu / :siliconflow / :ollama / :gemini / :mistral / :dashscope /
:openai-compat(任意兼容端点,:base-url 必填)/ :mock(确定性假向量,测试用)
```clojure
(require '[im.ttalk.agent.provider.embeddings :as emb])
(def e (emb/create-provider :openai)) ;; 取 OPENAI_API_KEY
(emb/embed e {} ["你好" "世界"])
;; => {:embeddings [[...] [...]] :model "text-embedding-3-small"
;; :usage {:input-tokens 8 :total-tokens 8} :provider :openai}
(emb/embed-one e {:dimensions 256} "只要一条")
;; 自建 / 私有兼容端点
(emb/create-provider :openai-compat
{:base-url "https://my-gw.internal/v1" :api-key "..." :model "bge-m3"})
```
调用时的 config 覆盖实例 config:`{:model :dimensions :batch-size :timeout
:retry :encoding-format :user :extra-headers :extra-body}`(DashScope 另有
:text-type)。Provider 构建器:根据配置创建 Provider 实例
持有内置 provider 的延迟注册逻辑(ensure-providers-registered!), 并在此基础上提供 create-provider / create-provider-auto 等编排函数。
Provider 构建器:根据配置创建 Provider 实例 持有内置 provider 的延迟注册逻辑(ensure-providers-registered!), 并在此基础上提供 create-provider / create-provider-auto 等编排函数。
Provider 配置管理:加载、验证、合并
机制(环境变量读取/校验/三级合并)与 provider 专属数据 (环境变量前缀、默认配置、免 api-key 列表)都在此 model 空间内。
配置优先级(从高到低):
Provider 配置管理:加载、验证、合并 机制(环境变量读取/校验/三级合并)与 provider 专属数据 (环境变量前缀、默认配置、免 api-key 列表)都在此 model 空间内。 配置优先级(从高到低): 1. 用户提供的 opts 2. 环境变量 3. 默认配置
LLM 提供商注册表
管理 LLM Provider 的注册、注销和查询。
本命名空间与具体 Provider 实现无关:它只维护一个 {:provider-type factory-fn} 的注册表,工厂函数由上层模块 (如 clj-agent-model)在初始化时注入。
使用示例:
(require '[im.ttalk.agent.provider.factory.registry :as registry])
;; 注册新的 Provider (registry/register-provider! :custom (fn [] (->CustomProvider)))
;; 检查 Provider 是否存在 (registry/provider-exists? :openai) ; => true
;; 获取支持的 Provider 列表 (registry/supported-providers) ; => [:anthropic :openai :zhipu ...]
LLM 提供商注册表
管理 LLM Provider 的注册、注销和查询。
本命名空间与具体 Provider 实现无关:它只维护一个
{:provider-type factory-fn} 的注册表,工厂函数由上层模块
(如 clj-agent-model)在初始化时注入。
使用示例:
(require '[im.ttalk.agent.provider.factory.registry :as registry])
;; 注册新的 Provider
(registry/register-provider! :custom (fn [] (->CustomProvider)))
;; 检查 Provider 是否存在
(registry/provider-exists? :openai) ; => true
;; 获取支持的 Provider 列表
(registry/supported-providers) ; => [:anthropic :openai :zhipu ...]Google Gemini Provider —— 经 OpenAI 兼容端点(统一由 base/defprovider 生成)。
需要 GOOGLE_API_KEY 或显式 :api-key。
(require '[im.ttalk.agent.provider.gemini :as gemini]) (def provider (gemini/create-provider {:api-key "AIza..."}))
Google Gemini Provider —— 经 OpenAI 兼容端点(统一由 base/defprovider 生成)。
需要 GOOGLE_API_KEY 或显式 :api-key。
(require '[im.ttalk.agent.provider.gemini :as gemini])
(def provider (gemini/create-provider {:api-key "AIza..."}))provider 的 HTTP 门面——仅非流式请求/响应。传输在 im.ttalk.http(clj-http,
designs/http-module.md);本 ns 只留 provider 的约定:JSON 编解码、{:status 0 :error …}(IO 失败不抛)、
:success?、canonical error 映射(response->error / success-body / check-model!)。
公开函数的签名与返回形状自 0.4.0 起未变。
流式(SSE)已迁移到 im.ttalk.agent.provider.http.stream-client(java.net.http 真增量,
见 docs/streaming-async-design.md);本模块不再提供流式 API。
用法: (require '[im.ttalk.agent.provider.http.client :as http]) (http/get "https://api.example.com/users") (http/post "https://api.example.com/users" :body {:name "张三"}) (http/post-deferred url :body {...}) ; 异步:返回 deferred<响应 map> (http/post-async url callback :body {...}) ; 异步回调(低层,形状同上) (http/request :get url :async? true :callback (fn [resp] ...))
provider 的 HTTP 门面——**仅非流式**请求/响应。传输在 `im.ttalk.http`(clj-http,
designs/http-module.md);本 ns 只留 provider 的约定:JSON 编解码、`{:status 0 :error …}`(IO 失败不抛)、
`:success?`、canonical error 映射(`response->error` / `success-body` / `check-model!`)。
公开函数的签名与返回形状自 0.4.0 起未变。
流式(SSE)已迁移到 `im.ttalk.agent.provider.http.stream-client`(java.net.http 真增量,
见 docs/streaming-async-design.md);本模块不再提供流式 API。
用法:
(require '[im.ttalk.agent.provider.http.client :as http])
(http/get "https://api.example.com/users")
(http/post "https://api.example.com/users" :body {:name "张三"})
(http/post-deferred url :body {...}) ; 异步:返回 deferred<响应 map>
(http/post-async url callback :body {...}) ; 异步回调(低层,形状同上)
(http/request :get url :async? true :callback (fn [resp] ...))HTTP 重试与错误分类(对标 Spring AI RetryUtils)
可重试(transient):
不可重试(non-transient):
(require '[im.ttalk.agent.provider.http.retry :as retry])
;; 包裹一个返回 http response map 的函数 (retry/with-retry #(http/post url :headers h :body b) {:max-retries 3})
;; 关闭重试(max-retries 0)即等价于直接调用
HTTP 重试与错误分类(对标 Spring AI RetryUtils)
========================================
设计目标
========================================
- 错误二分类:transient(可重试)vs non-transient(不可重试),对标
Spring AI 的 TransientAiException / NonTransientAiException。
- 指数退避 + 满抖动(full jitter),避免重试风暴。
- 尊重服务端的 Retry-After 响应头(429 / 503 常见)。
- opt-in:仅当调用方在 config 里传入 :retry 时启用,不改变现有默认行为。
========================================
错误分类规则
========================================
可重试(transient):
- 网络层错误(连接失败、超时)—— HTTP 客户端返回 {:error ...}
- HTTP 408 / 409 / 425 / 429 / 500 / 502 / 503 / 504 / 529
不可重试(non-transient):
- 其余 4xx(400 参数错误、401 鉴权、403、404 等)—— 重试无意义
========================================
基本用法
========================================
(require '[im.ttalk.agent.provider.http.retry :as retry])
;; 包裹一个返回 http response map 的函数
(retry/with-retry
#(http/post url :headers h :body b)
{:max-retries 3})
;; 关闭重试(max-retries 0)即等价于直接调用
provider 的真流式 —— 传输是 im.ttalk.http/stream-lines(clj-http,designs/http-module.md;
最初为替代 http-kit 的伪流式而写,修 BUG2)。本 ns 只留 provider 语义:parse-fn / process-fn、canonical error
(超时 / 非 2xx / IO)、模型名核对、限流头、重试与取消登记。
与 http-kit 客户端的根本区别:http-kit 的 :as :stream 等整段响应收完才兑现
promise(伪流式);stream-lines 用 BodyHandlers/fromLineSubscriber(响应式 Flow.Subscriber),
每行 SSE 到达即回调——真增量、非阻塞、可取消、原生背压。
与现有 stream 处理器无缝衔接:parse-fn / process-fn 复用 stream.anthropic / stream.openai 的纯函数(parse-sse-line / process-event),传输层只负责把行喂进来。
回调契约(与 http-kit 版 post-stream-async 一致):
返回 {:future CompletableFuture :cancel (fn [])}:
provider 的真流式 —— 传输是 `im.ttalk.http/stream-lines`(clj-http,designs/http-module.md;
最初为替代 http-kit 的伪流式而写,修 BUG2)。本 ns 只留 provider 语义:parse-fn / process-fn、canonical error
(超时 / 非 2xx / IO)、模型名核对、限流头、重试与取消登记。
与 http-kit 客户端的根本区别:http-kit 的 `:as :stream` 等整段响应收完才兑现
promise(伪流式);`stream-lines` 用 `BodyHandlers/fromLineSubscriber`(响应式 Flow.Subscriber),
**每行 SSE 到达即回调**——真增量、非阻塞、可取消、原生背压。
与现有 stream 处理器无缝衔接:parse-fn / process-fn 复用 stream.anthropic / stream.openai
的纯函数(parse-sse-line / process-event),传输层只负责把行喂进来。
回调契约(与 http-kit 版 post-stream-async 一致):
- :on-token (fn [token-data] ...) 每个 token/事件产出
- :on-complete (fn [final-state] ...) 流正常结束
- :on-error (fn [{:keys [error cause status]}] ...) 连接/解析/非 2xx 失败
返回 {:future CompletableFuture :cancel (fn [])}:
- future 在流结束(或失败)时完成;
- cancel 取消上游 HTTP(客户端断连/超时/用户停止时调用,不再消耗 token)。MiniMax Provider —— Anthropic 兼容实现(MiniMax 官方推荐使用 Anthropic 格式)。
端点:https://api.minimaxi.com/anthropic/v1/messages 鉴权:Authorization: Bearer <MINIMAX_API_KEY>(无需 anthropic-version 头)
通过复用 anthropic provider 的端点抽象,完整继承其请求/响应/流式/工具调用/ prompt-cache 策略/重试机制 —— MiniMax 只是换了 base-url + 路径 + Bearer 鉴权。
模型(M 系列推理模型,content 可能含推理过程,建议给足 :max-tokens): MiniMax-M3(最新,1M 上下文)、MiniMax-M2.7、MiniMax-M2.7-highspeed、 MiniMax-M2.5、MiniMax-M2.1、MiniMax-M2 等。
需要 MINIMAX_API_KEY 或显式 :api-key。
(require '[im.ttalk.agent.provider.minimax :as minimax] '[im.ttalk.agent.model :as model]) (def p (minimax/create-provider {:api-key "..."})) (model/call-llm p {:model "MiniMax-M2.7" :max-tokens 2048} [{:role :user :content "你好"}] nil)
MiniMax Provider —— Anthropic 兼容实现(MiniMax 官方推荐使用 Anthropic 格式)。
端点:https://api.minimaxi.com/anthropic/v1/messages
鉴权:Authorization: Bearer <MINIMAX_API_KEY>(无需 anthropic-version 头)
通过复用 anthropic provider 的端点抽象,完整继承其请求/响应/流式/工具调用/
prompt-cache 策略/重试机制 —— MiniMax 只是换了 base-url + 路径 + Bearer 鉴权。
模型(M 系列推理模型,content 可能含推理过程,建议给足 :max-tokens):
MiniMax-M3(最新,1M 上下文)、MiniMax-M2.7、MiniMax-M2.7-highspeed、
MiniMax-M2.5、MiniMax-M2.1、MiniMax-M2 等。
需要 MINIMAX_API_KEY 或显式 :api-key。
(require '[im.ttalk.agent.provider.minimax :as minimax]
'[im.ttalk.agent.model :as model])
(def p (minimax/create-provider {:api-key "..."}))
(model/call-llm p {:model "MiniMax-M2.7" :max-tokens 2048}
[{:role :user :content "你好"}] nil)Mistral AI Provider —— OpenAI 兼容实现(统一由 base/defprovider 生成)。
需要 MISTRAL_API_KEY 或显式 :api-key。
(require '[im.ttalk.agent.provider.mistral :as mistral]) (def provider (mistral/create-provider {:api-key "..."}))
Mistral AI Provider —— OpenAI 兼容实现(统一由 base/defprovider 生成)。
需要 MISTRAL_API_KEY 或显式 :api-key。
(require '[im.ttalk.agent.provider.mistral :as mistral])
(def provider (mistral/create-provider {:api-key "..."}))Mock LLM Provider - 用于测试和开发
提供无需 API key 的 Mock LLM,支持:
使用示例:
(require '[im.ttalk.agent.provider.mock :as mock])
(def provider (mock/create-mock-provider))
;; 设置自定义响应 (mock/set-mock-response provider "Hello from Mock!")
Mock LLM Provider - 用于测试和开发 提供无需 API key 的 Mock LLM,支持: - 返回预设响应 - 记录调用历史 - 预设测试场景 使用示例: (require '[im.ttalk.agent.provider.mock :as mock]) (def provider (mock/create-mock-provider)) ;; 设置自定义响应 (mock/set-mock-response provider "Hello from Mock!")
Moonshot(Kimi)Provider —— OpenAI 兼容实现(统一由 base/defprovider 生成)。
端点:https://api.moonshot.cn/v1/chat/completions(Bearer 鉴权,国内站)。
国际站请显式覆盖 base-url:
(create-provider {:base-url "https://api.moonshot.ai/v1"})
或设环境变量 MOONSHOT_BASE_URL(经 factory/create-provider-auto 生效)。
需要 MOONSHOT_API_KEY 或显式 :api-key。 模型:kimi-k2.5、kimi-k2.6、kimi-k2.7-code、kimi-k3、moonshot-v1-{8k,32k,128k} 等。
Kimi 专属(经 config 透传,存在才发送——见 common.openai-compat/build-params):
(require '[im.ttalk.agent.provider.moonshot :as moonshot]) (def provider (moonshot/create-provider {:api-key "sk-..."}))
Moonshot(Kimi)Provider —— OpenAI 兼容实现(统一由 base/defprovider 生成)。
端点:https://api.moonshot.cn/v1/chat/completions(Bearer 鉴权,国内站)。
国际站请显式覆盖 base-url:
`(create-provider {:base-url "https://api.moonshot.ai/v1"})`
或设环境变量 MOONSHOT_BASE_URL(经 factory/create-provider-auto 生效)。
需要 MOONSHOT_API_KEY 或显式 :api-key。
模型:kimi-k2.5、kimi-k2.6、kimi-k2.7-code、kimi-k3、moonshot-v1-{8k,32k,128k} 等。
Kimi 专属(经 config 透传,存在才发送——见 common.openai-compat/build-params):
- :thinking {:type "enabled"|"disabled" :budget_tokens n}(K2.5+ 思考开关)
- :reasoning-effort(K3 目前只支持 "max")
- 其余私有字段走 :extra-body
(require '[im.ttalk.agent.provider.moonshot :as moonshot])
(def provider (moonshot/create-provider {:api-key "sk-..."}))Ollama Provider —— 本地模型,OpenAI 兼容端点(统一由 base/defprovider 生成)。
(require '[im.ttalk.agent.provider.ollama :as ollama]) (def provider (ollama/create-provider {:model "llama2"}))
Ollama Provider —— 本地模型,OpenAI 兼容端点(统一由 base/defprovider 生成)。
- :base-url 默认 http://localhost:11434/v1
- 不需要 API key(预置假 key "ollama")
- create-provider 必须提供 :model
(require '[im.ttalk.agent.provider.ollama :as ollama])
(def provider (ollama/create-provider {:model "llama2"}))OpenAI Provider —— OpenAI 兼容实现(统一由 base/defprovider 生成)。
生成:default-config、call-openai{,-stream,-async,-stream-async}、create-provider。 同步(单次 HTTP)与流式(SSE)调用均支持;流式回调 (fn [{:keys [token ...]}] ...)。
调用 config 支持的 OpenAI 专属能力(均「存在才发送」,详见 common.openai-compat/build-params):
:stream-options {:include_usage true};
显式 false / {} 关闭;非流式恒不发 stream_options工具定义:deftool 生成的 schema(:input_schema 形态)与 OpenAI 风格 (:parameters 形态)均被 schema.openai/tool->schema 正确识别,参数不丢失。
(require '[im.ttalk.agent.provider.openai :as openai]) (def provider (openai/create-provider {:api-key "sk-..."}))
OpenAI Provider —— OpenAI 兼容实现(统一由 base/defprovider 生成)。
生成:default-config、call-openai{,-stream,-async,-stream-async}、create-provider。
同步(单次 HTTP)与流式(SSE)调用均支持;流式回调 (fn [{:keys [token ...]}] ...)。
调用 config 支持的 OpenAI 专属能力(均「存在才发送」,详见
common.openai-compat/build-params):
- 工具调用::tool-choice、:parallel-tool-calls(并行工具调用开关,精确透传 false)
- 推理控制::reasoning-effort(o 系列 / GPT-5:low|medium|high)
:verbosity(GPT-5 输出冗长度:low|medium|high)
- 多模态输出::modalities(如 ["text" "audio"]):audio({:voice X :format Y},gpt-4o-audio)
- 结构化输出::response-format
{:type "json_object"} 或
{:type "json_schema" :json_schema {:name "X" :strict true :schema {...}}}
(可用 core 的 converter.json-schema/to-openai-response-format 从 schema 生成)
- 采样::temperature :top-p :max-tokens :stop :seed :n
:frequency-penalty :presence-penalty :logprobs :top-logprobs
- 流式 usage:流式缺省 `:stream-options {:include_usage true}`;
显式 `false` / `{}` 关闭;非流式恒不发 `stream_options`
- 私有字段逃生通道::extra-body(直接 merge 进请求体)
- prompt caching:OpenAI 自动生效,命中 token 归一化到响应 usage 的
:cache-read-tokens(源自 prompt_tokens_details.cached_tokens)
工具定义:deftool 生成的 schema(:input_schema 形态)与 OpenAI 风格
(:parameters 形态)均被 schema.openai/tool->schema 正确识别,参数不丢失。
(require '[im.ttalk.agent.provider.openai :as openai])
(def provider (openai/create-provider {:api-key "sk-..."}))通用 OpenAI 兼容 Provider —— 面向 vLLM / LocalAI / LM Studio / 自建网关等 任意暴露 OpenAI Chat Completions 协议的后端。
与具体厂商 provider 不同,这里没有内置 base-url::base-url 必填, 端点默认 "/chat/completions"(可经 :endpoint 覆盖)。
用法: (require '[im.ttalk.agent.provider.openai-compat-provider :as oc]) (oc/create-provider {:base-url "http://localhost:8000/v1" :api-key "key"})
也可经 factory:(factory/create-provider :openai-compat {:base-url ... :api-key ...})
通用 OpenAI 兼容 Provider —— 面向 vLLM / LocalAI / LM Studio / 自建网关等
任意暴露 OpenAI Chat Completions 协议的后端。
与具体厂商 provider 不同,这里没有内置 base-url:**:base-url 必填**,
端点默认 "/chat/completions"(可经 :endpoint 覆盖)。
用法:
(require '[im.ttalk.agent.provider.openai-compat-provider :as oc])
(oc/create-provider {:base-url "http://localhost:8000/v1" :api-key "key"})
也可经 factory:(factory/create-provider :openai-compat {:base-url ... :api-key ...})OpenAI Responses API provider。
这是继 OpenAI Chat Completions 与 Anthropic Messages 之后的第三个内置协议栈。 上层仍使用同一个 ILLMProvider / 中立消息边界;provider 只把协议形状翻译成 Chat Completions 兼容的内部响应骨架,便于复用统一 usage / finish reason 归一化。
OpenAI Responses API provider。 这是继 OpenAI Chat Completions 与 Anthropic Messages 之后的第三个内置协议栈。 上层仍使用同一个 ILLMProvider / 中立消息边界;provider 只把协议形状翻译成 Chat Completions 兼容的内部响应骨架,便于复用统一 usage / finish reason 归一化。
OpenRouter Provider —— 多厂商聚合网关,OpenAI 兼容(由 base/defprovider 生成)。
端点:https://openrouter.ai/api/v1/chat/completions(Bearer 鉴权) 需要 OPENROUTER_API_KEY 或显式 :api-key。 模型 id 带厂商前缀:"openai/gpt-4o-mini"、"anthropic/claude-sonnet-4.5"、 "deepseek/deepseek-chat"、"google/gemini-2.5-pro" …
OpenRouter 专属(都走既有通道,不新增字段):
{:extra-headers {"HTTP-Referer" "https://your.app" "X-Title" "Your App"}}{:extra-body {:provider {:order ["anthropic"]} :models ["openai/gpt-4o"]}}(require '[im.ttalk.agent.provider.openrouter :as openrouter]) (def provider (openrouter/create-provider {:api-key "sk-or-..."}))
OpenRouter Provider —— 多厂商聚合网关,OpenAI 兼容(由 base/defprovider 生成)。
端点:https://openrouter.ai/api/v1/chat/completions(Bearer 鉴权)
需要 OPENROUTER_API_KEY 或显式 :api-key。
模型 id 带厂商前缀:"openai/gpt-4o-mini"、"anthropic/claude-sonnet-4.5"、
"deepseek/deepseek-chat"、"google/gemini-2.5-pro" …
OpenRouter 专属(都走既有通道,不新增字段):
- 排行榜署名头:`{:extra-headers {"HTTP-Referer" "https://your.app"
"X-Title" "Your App"}}`
- 路由/兜底等私有字段:`{:extra-body {:provider {:order ["anthropic"]}
:models ["openai/gpt-4o"]}}`
(require '[im.ttalk.agent.provider.openrouter :as openrouter])
(def provider (openrouter/create-provider {:api-key "sk-or-..."}))Anthropic Schema 转换
将工具定义转换为 Anthropic API 格式。
Anthropic 的工具格式与 OpenAI 不同:
使用示例:
(require '[im.ttalk.agent.provider.schema.anthropic :as schema])
;; 转换单个工具 (schema/tool->schema {:name :calculator :description "计算器" :parameters {...}})
;; 批量转换 (schema/tools->schemas tools)
Anthropic Schema 转换
将工具定义转换为 Anthropic API 格式。
Anthropic 的工具格式与 OpenAI 不同:
- 使用 input_schema 而非 parameters
- 工具调用使用 content_block 机制
使用示例:
(require '[im.ttalk.agent.provider.schema.anthropic :as schema])
;; 转换单个工具
(schema/tool->schema {:name :calculator
:description "计算器"
:parameters {...}})
;; 批量转换
(schema/tools->schemas tools)OpenAI Schema 转换
将工具定义转换为 OpenAI Function Call 格式。
使用示例:
(require '[im.ttalk.agent.provider.schema.openai :as schema])
;; 转换单个工具 (schema/tool->schema {:name :calculator :description "计算器" :parameters {...}})
;; 批量转换 (schema/tools->schemas tools)
OpenAI Schema 转换
将工具定义转换为 OpenAI Function Call 格式。
使用示例:
(require '[im.ttalk.agent.provider.schema.openai :as schema])
;; 转换单个工具
(schema/tool->schema {:name :calculator
:description "计算器"
:parameters {...}})
;; 批量转换
(schema/tools->schemas tools)SiliconFlow(硅基流动)Provider —— OpenAI 兼容实现(由 base/defprovider 生成)。
端点:https://api.siliconflow.cn/v1/chat/completions(Bearer 鉴权) 需要 SILICONFLOW_API_KEY 或显式 :api-key。 模型 id 带组织前缀:"Qwen/Qwen3-8B"、"deepseek-ai/DeepSeek-V3"、 "THUDM/GLM-4-9B-0414" …(国际站 api.siliconflow.com 请覆盖 :base-url)
embedding 走独立实例(provider/embeddings,默认 BAAI/bge-m3):
(embeddings/create-provider :siliconflow)
(require '[im.ttalk.agent.provider.siliconflow :as siliconflow]) (def provider (siliconflow/create-provider {:api-key "sk-..."}))
SiliconFlow(硅基流动)Provider —— OpenAI 兼容实现(由 base/defprovider 生成)。
端点:https://api.siliconflow.cn/v1/chat/completions(Bearer 鉴权)
需要 SILICONFLOW_API_KEY 或显式 :api-key。
模型 id 带组织前缀:"Qwen/Qwen3-8B"、"deepseek-ai/DeepSeek-V3"、
"THUDM/GLM-4-9B-0414" …(国际站 api.siliconflow.com 请覆盖 :base-url)
embedding 走独立实例(provider/embeddings,默认 BAAI/bge-m3):
`(embeddings/create-provider :siliconflow)`
(require '[im.ttalk.agent.provider.siliconflow :as siliconflow])
(def provider (siliconflow/create-provider {:api-key "sk-..."}))Anthropic 流式响应处理
处理 Anthropic API 的 SSE 流式响应。 构建的响应格式与非流式调用兼容,并支持统一响应转换。
Anthropic 的流式事件类型:
使用示例:
(require '[im.ttalk.agent.provider.stream.anthropic :as stream])
;; 处理流式响应块 (let [[new-state token-data] (stream/process-event event state)] (when token-data (print (:token token-data))))
;; 构建统一格式响应 (stream/normalize-response final-state)
Anthropic 流式响应处理
处理 Anthropic API 的 SSE 流式响应。
构建的响应格式与非流式调用兼容,并支持统一响应转换。
Anthropic 的流式事件类型:
- message_start: 消息开始
- content_block_start: 内容块开始
- content_block_delta: 内容块增量(文本或工具输入)
- content_block_stop: 内容块结束
- message_delta: 消息增量
- message_stop: 消息结束
使用示例:
(require '[im.ttalk.agent.provider.stream.anthropic :as stream])
;; 处理流式响应块
(let [[new-state token-data] (stream/process-event event state)]
(when token-data
(print (:token token-data))))
;; 构建统一格式响应
(stream/normalize-response final-state)DashScope 原生流式响应处理(SSE)。
DashScope 流式:请求头 X-DashScope-SSE: enable + parameters.incremental_output true。 响应是 SSE,每个事件含 id:/event:result/:HTTP_STATUS/data: 几行,data 为 JSON: {:output {:choices [{:finish_reason fr :message {:role :content delta}}]} :usage {...} :request_id ...} incremental_output=true 时 content 是增量(delta)。finish_reason 生成中为 "null"/null, 结束为 "stop"/"length" 等。
产出与同步 parse-response 同形的 OpenAI 兼容响应(extract-text 读 choices[0].message.content)。
DashScope 原生流式响应处理(SSE)。
DashScope 流式:请求头 X-DashScope-SSE: enable + parameters.incremental_output true。
响应是 SSE,每个事件含 id:/event:result/:HTTP_STATUS/data: 几行,data 为 JSON:
{:output {:choices [{:finish_reason fr :message {:role :content delta}}]}
:usage {...} :request_id ...}
incremental_output=true 时 content 是增量(delta)。finish_reason 生成中为 "null"/null,
结束为 "stop"/"length" 等。
产出与同步 parse-response 同形的 OpenAI 兼容响应(extract-text 读 choices[0].message.content)。OpenAI 流式响应处理
处理 OpenAI API 的 SSE 流式响应,累积文本和工具调用。 构建的响应格式与非流式调用兼容,并支持统一响应转换。
使用示例:
(require '[im.ttalk.agent.provider.stream.openai :as stream])
;; 处理流式响应块 (let [[new-state token-data] (stream/process-chunk chunk state)] (when token-data (print (:token token-data))))
;; 构建最终响应(支持统一格式) (stream/build-response final-state :id "xxx" :model "gpt-4")
OpenAI 流式响应处理
处理 OpenAI API 的 SSE 流式响应,累积文本和工具调用。
构建的响应格式与非流式调用兼容,并支持统一响应转换。
使用示例:
(require '[im.ttalk.agent.provider.stream.openai :as stream])
;; 处理流式响应块
(let [[new-state token-data] (stream/process-chunk chunk state)]
(when token-data
(print (:token token-data))))
;; 构建最终响应(支持统一格式)
(stream/build-response final-state :id "xxx" :model "gpt-4")OpenAI Responses SSE 流式处理。
流式只负责实时发射 delta;最终响应优先使用 response.completed 携带的完整 对象,避免自行重建 reasoning/tool call 的不透明字段。
OpenAI Responses SSE 流式处理。 流式只负责实时发射 delta;最终响应优先使用 response.completed 携带的完整 对象,避免自行重建 reasoning/tool call 的不透明字段。
中立消息 ↔ Anthropic wire 格式 转换
neutral->wire: 中立消息列表 → {:system .. :messages ..} response->neutral: Anthropic 响应 → 中立 assistant 消息
Anthropic wire 形态:
多模态:user 消息的 :content 为中立部件向量(见 model/content)时翻译成 Anthropic 内容块——image/* → image 块(内联 base64 source / url source)、 application/pdf → document 块。Anthropic 不收音频,遇到即抛 :validation-error (不静默丢内容)。:type 为字符串的元素视为 Anthropic 原生块(citations 的 document、cache_control 断点等),原样透传。
assistant 消息带 :blocks {:format :anthropic-content :data [...]} 时原样吐回 (thinking 块 + signature 必须逐字回传);格式认不出或没有 :blocks 就走 text + tool_use 重建。见 docs/provider-variant-design.md。
中立消息 ↔ Anthropic wire 格式 转换
neutral->wire: 中立消息列表 → {:system .. :messages ..}
response->neutral: Anthropic 响应 → 中立 assistant 消息
Anthropic wire 形态:
- system 是顶层参数(不在 messages 里)
- user: {:role "user" :content "..."}
- assistant: {:role "assistant" :content "..."}
- assistant+tools: {:role "assistant"
:content [{:type "text" :text ..}?
{:type "tool_use" :id .. :name .. :input {..}}]}
- tool result: {:role "user"
:content [{:type "tool_result" :tool_use_id .. :content ..}]}
连续的 tool 结果合并进同一条 user 消息的 content 数组。
**多模态**:user 消息的 :content 为中立部件向量(见 model/content)时翻译成
Anthropic 内容块——image/* → image 块(内联 base64 source / url source)、
application/pdf → document 块。Anthropic 不收音频,遇到即抛 :validation-error
(不静默丢内容)。:type 为**字符串**的元素视为 Anthropic 原生块(citations 的
document、cache_control 断点等),原样透传。
assistant 消息带 :blocks {:format :anthropic-content :data [...]} 时**原样吐回**
(thinking 块 + signature 必须逐字回传);格式认不出或没有 :blocks 就走
text + tool_use 重建。见 docs/provider-variant-design.md。中立消息 ↔ OpenAI wire 格式 转换
neutral->wire: 中立消息列表 → OpenAI chat messages response->neutral: OpenAI 响应 → 中立 assistant 消息
OpenAI wire 形态:
多模态:user 消息的 :content 为中立部件向量(见 model/content)时, 按 media type 顶层类别翻译——image/* → image_url(内联走 data URI)、 audio/* → input_audio(仅 wav/mp3,OpenAI 不收 URL 音频)、 application/pdf → file.file_data。其余类型不静默丢,抛 :validation-error。 :type 为字符串的元素视为 OpenAI 原生块,原样透传。
中立消息 ↔ OpenAI wire 格式 转换
neutral->wire: 中立消息列表 → OpenAI chat messages
response->neutral: OpenAI 响应 → 中立 assistant 消息
OpenAI wire 形态:
- system/user: {:role "system"/"user" :content "..."}
- assistant: {:role "assistant" :content "..."}
- assistant+tools: {:role "assistant" :content nil
:tool_calls [{:id .. :type "function"
:function {:name .. :arguments "{json}"}}]}
- tool result: {:role "tool" :tool_call_id .. :content "..."}
**多模态**:user 消息的 :content 为中立部件向量(见 model/content)时,
按 media type 顶层类别翻译——image/* → image_url(内联走 data URI)、
audio/* → input_audio(仅 wav/mp3,OpenAI 不收 URL 音频)、
application/pdf → file.file_data。其余类型不静默丢,抛 :validation-error。
:type 为**字符串**的元素视为 OpenAI 原生块,原样透传。中立消息 ↔ OpenAI Responses API wire 格式转换。
OpenAI Responses 与 Chat Completions 的差别在协议形状,不在语义: system -> instructions;消息 -> input items;工具定义扁平化; function_call / function_call_output 是顶层 item。
中立消息 ↔ OpenAI Responses API wire 格式转换。 OpenAI Responses 与 Chat Completions 的差别在协议形状,不在语义: system -> instructions;消息 -> input items;工具定义扁平化; function_call / function_call_output 是顶层 item。
xAI (Grok) Provider —— OpenAI 兼容实现(统一由 base/defprovider 生成)。
端点:https://api.x.ai/v1/chat/completions(Bearer 鉴权) 需要 XAI_API_KEY 或显式 :api-key。 模型:grok-4.5、grok-4.3、grok-4.20-reasoning、grok-4.20-non-reasoning、grok-latest 等。
继承 common.openai-compat 的全部通用能力(工具调用 / 流式 / 结构化输出 / :reasoning-effort / :extra-body 逃生通道);grok 的推理内容经 message.reasoning_content 返回,已由统一响应层归一化到 :reasoning。
(require '[im.ttalk.agent.provider.xai :as xai]) (def provider (xai/create-provider {:api-key "xai-..."}))
xAI (Grok) Provider —— OpenAI 兼容实现(统一由 base/defprovider 生成)。
端点:https://api.x.ai/v1/chat/completions(Bearer 鉴权)
需要 XAI_API_KEY 或显式 :api-key。
模型:grok-4.5、grok-4.3、grok-4.20-reasoning、grok-4.20-non-reasoning、grok-latest 等。
继承 common.openai-compat 的全部通用能力(工具调用 / 流式 / 结构化输出 /
:reasoning-effort / :extra-body 逃生通道);grok 的推理内容经
message.reasoning_content 返回,已由统一响应层归一化到 :reasoning。
(require '[im.ttalk.agent.provider.xai :as xai])
(def provider (xai/create-provider {:api-key "xai-..."}))智谱 GLM Provider —— 双协议支持。
OpenAI 兼容(默认,create-provider): https://open.bigmodel.cn/api/paas/v4/chat/completions(Bearer 鉴权)
Anthropic 兼容(create-anthropic-provider,官方推荐 coding/agent 场景, Claude Code 同款端点): https://open.bigmodel.cn/api/anthropic/v1/messages(Bearer 鉴权)
异步任务(GLM 对话补全异步方案): submit-async(POST /async/chat/completions)→ query-async-result / await-async-result(GET /async-result/{id},PROCESSING|SUCCESS|FAIL)
对话补全文档字段(经 config 透传,存在才发送): :temperature :top-p :max-tokens :stop :thinking :do-sample :tool-stream :request-id :user-id :response-format :tool-choice; tools 支持简单定义(自动包装为 function)与预置类型透传 ({:type "web_search"/"retrieval"/"mcp" ...})。 finish_reason 的 sensitive/network_error/model_context_window_exceeded 已归一化(:content-filter/:error/:context-window-exceeded)。
模型:glm-5.1、glm-5-turbo、glm-5、glm-4.7、glm-4.7-flash、glm-4.6 等。 需要 ZHIPU_API_KEY 或显式 :api-key。
(require '[im.ttalk.agent.provider.zhipu :as zhipu]) (def p (zhipu/create-provider {:api-key "..."})) ;; OpenAI 协议 (def pa (zhipu/create-anthropic-provider {:api-key "..."})) ;; Anthropic 协议 ;; 异步: (let [{:keys [id]} (zhipu/submit-async {:model "glm-4.7"} msgs nil)] (zhipu/await-async-result {} id))
智谱 GLM Provider —— 双协议支持。
1. OpenAI 兼容(默认,create-provider):
https://open.bigmodel.cn/api/paas/v4/chat/completions(Bearer 鉴权)
- 思考开关:config 传 :thinking {:type "enabled"|"disabled"
:clear_thinking true}(GLM-4.5+ 思考系列)
- 思维链经 message.reasoning_content 返回,已由统一响应层归一化到
:reasoning 字段;流式推理 token 走回调 :reasoning-token
2. Anthropic 兼容(create-anthropic-provider,官方推荐 coding/agent 场景,
Claude Code 同款端点):
https://open.bigmodel.cn/api/anthropic/v1/messages(Bearer 鉴权)
- 完整复用 anthropic provider 的请求/响应/流式/工具调用/缓存/重试机制
- 思考内容为 thinking 内容块,自动归一化到 :reasoning
3. 异步任务(GLM 对话补全异步方案):
submit-async(POST /async/chat/completions)→ query-async-result /
await-async-result(GET /async-result/{id},PROCESSING|SUCCESS|FAIL)
对话补全文档字段(经 config 透传,存在才发送):
:temperature :top-p :max-tokens :stop :thinking :do-sample :tool-stream
:request-id :user-id :response-format :tool-choice;
tools 支持简单定义(自动包装为 function)与预置类型透传
({:type "web_search"/"retrieval"/"mcp" ...})。
finish_reason 的 sensitive/network_error/model_context_window_exceeded
已归一化(:content-filter/:error/:context-window-exceeded)。
模型:glm-5.1、glm-5-turbo、glm-5、glm-4.7、glm-4.7-flash、glm-4.6 等。
需要 ZHIPU_API_KEY 或显式 :api-key。
(require '[im.ttalk.agent.provider.zhipu :as zhipu])
(def p (zhipu/create-provider {:api-key "..."})) ;; OpenAI 协议
(def pa (zhipu/create-anthropic-provider {:api-key "..."})) ;; Anthropic 协议
;; 异步:
(let [{:keys [id]} (zhipu/submit-async {:model "glm-4.7"} msgs nil)]
(zhipu/await-async-result {} id))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 |