Liking cljdoc? Tell your friends :D

im.ttalk.agent.model.error

LLM 错误处理模块

提供统一的错误类型、分类和处理机制。

错误类型:

  • :network-error 网络连接错误(可重试)
  • :timeout-error 请求超时(可重试)
  • :rate-limit-error 速率限制(可重试)
  • :auth-error 认证失败(不可重试)
  • :validation-error 参数验证失败(不可重试)
  • :parse-error 响应解析失败(不可重试)
  • :provider-error Provider 特定错误
  • :quota-exhausted-error 欠费 / 额度用尽 / 402(不可重试;不是限流,可切备用模型)
  • :context-overflow-error 请求超出上下文窗口(不可重试、不切备用,留给压缩恢复)
  • :empty-completion-error HTTP 200 但无有效内容(可重试,但只走 ChatModel 的独立预算)

厂商业务码(智谱 1309、OpenAI insufficient_quota@429 …)的细分在 provider 边界做 (im.ttalk.agent.provider.common.business-codes),core 只认状态码能下的结论(402)。

使用示例:

(require '[im.ttalk.agent.model.error :as errors])

;; 创建错误 (errors/error :timeout-error "请求超时")

;; 检查是否可重试(直接关键字访问) (:retryable? err) ; => true

;; 安全执行 (errors/with-error-handling #(api-call))

LLM 错误处理模块

提供统一的错误类型、分类和处理机制。

错误类型:
- :network-error    网络连接错误(可重试)
- :timeout-error    请求超时(可重试)
- :rate-limit-error 速率限制(可重试)
- :auth-error       认证失败(不可重试)
- :validation-error 参数验证失败(不可重试)
- :parse-error      响应解析失败(不可重试)
- :provider-error   Provider 特定错误
- :quota-exhausted-error  欠费 / 额度用尽 / 402(不可重试;**不是限流**,可切备用模型)
- :context-overflow-error 请求超出上下文窗口(不可重试、不切备用,留给压缩恢复)
- :empty-completion-error HTTP 200 但无有效内容(可重试,但只走 ChatModel 的独立预算)

厂商业务码(智谱 1309、OpenAI insufficient_quota@429 …)的细分在 provider 边界做
(`im.ttalk.agent.provider.common.business-codes`),core 只认状态码能下的结论(402)。

使用示例:

(require '[im.ttalk.agent.model.error :as errors])

;; 创建错误
(errors/error :timeout-error "请求超时")

;; 检查是否可重试(直接关键字访问)
(:retryable? err) ; => true

;; 安全执行
(errors/with-error-handling #(api-call))
raw docstring

auth-error?clj

(auth-error? err)

检查是否为认证错误(401/403 或 :auth-error 类型)

检查是否为认证错误(401/403 或 :auth-error 类型)
sourceraw docstring

classify-exceptionclj

(classify-exception e)

把工具执行抛出的异常分类为故障类别,决定屏障处的路由:

  • :semantic (缺省)模型造成、只有模型能修 → 序列化为结果回给模型
  • :transient 重试同一调用有意义(超时/限流/网络抖动)→ 工具级自动重试 (仅当工具声明 :retry;幂等前提)
  • :environment 模型修不了、重试无用(认证失效/配额/磁盘满)→ 屏障处暂停等人
  • :subagent-suspended 不是失败:委派出去的子 agent 停下来等人答复了 (subagent/delegate 抛的)→ 屏障处暂停,resume 时续跑那个子 agent 而不是重跑这次调用。单列一类是因为措辞会到用户眼前 (RUN_FINISHED.outcome.interrupts[].reason),借 :environment 会写成 「环境类错误」——而这压根不是错误

判定顺序:

  1. ex-data 显式 :error-class(工具作者标注,最高优先级)
  2. canonical error(D5 词汇)::retryable? true → :transient; :auth-error → :environment;其余 → :semantic
  3. 常见网络异常(SocketTimeoutException/ConnectException/HttpTimeoutException) → :transient
  4. 其余 → :semantic
把工具执行抛出的异常分类为故障类别,决定屏障处的路由:

- :semantic    (缺省)模型造成、只有模型能修 → 序列化为结果回给模型
- :transient   重试同一调用有意义(超时/限流/网络抖动)→ 工具级自动重试
               (仅当工具声明 :retry;幂等前提)
- :environment 模型修不了、重试无用(认证失效/配额/磁盘满)→ 屏障处暂停等人
- :subagent-suspended **不是失败**:委派出去的子 agent 停下来等人答复了
  (`subagent/delegate` 抛的)→ 屏障处暂停,resume 时**续跑那个子 agent**
  而不是重跑这次调用。单列一类是因为措辞会到用户眼前
  (`RUN_FINISHED.outcome.interrupts[].reason`),借 `:environment` 会写成
  「环境类错误」——而这压根不是错误

判定顺序:
1. ex-data 显式 :error-class(工具作者标注,最高优先级)
2. canonical error(D5 词汇)::retryable? true → :transient;
   :auth-error → :environment;其余 → :semantic
3. 常见网络异常(SocketTimeoutException/ConnectException/HttpTimeoutException)
   → :transient
4. 其余 → :semantic
sourceraw docstring

contain-throwableclj

(contain-throwable t)

catch-Throwable 三元组(fatal 检查 / nil-message 回退 / 分类)的统一入口。

致命的(OOM 等)原样上抛(fatal-throwable? 判据);非致命的返回 {:message m :class c}——调用方据此组装自己的错误形状(terminal map / success-false map / invoke-one 的 :value+:error)。

DRY 4 处手抄:tool/invoke、chat-client/invoke-tool 两个 terminal、 react/invoke-one 此前各自重复 fatal-check + nil-message + classify-exception 三步,三元组漂移即出 bug。

catch-Throwable 三元组(fatal 检查 / nil-message 回退 / 分类)的统一入口。

致命的(OOM 等)原样上抛(`fatal-throwable?` 判据);非致命的返回
`{:message m :class c}`——调用方据此组装自己的错误形状(terminal map /
success-false map / invoke-one 的 :value+:error)。

**DRY 4 处手抄**:`tool/invoke`、`chat-client/invoke-tool` 两个 terminal、
`react/invoke-one` 此前各自重复 fatal-check + nil-message + classify-exception
三步,三元组漂移即出 bug。
sourceraw docstring

context-overflow?clj

(context-overflow? err)

请求超出上下文窗口(:context-overflow-error)。不重试、不切备用,交压缩恢复。

请求超出上下文窗口(:context-overflow-error)。不重试、不切备用,交压缩恢复。
sourceraw docstring

empty-completion?clj

(empty-completion? err)

空补全(:empty-completion-error)。

空补全(:empty-completion-error)。
sourceraw docstring

errorclj

(error type message)
(error type message opts)

创建错误 map

参数:

  • type: 错误类型(关键字)
  • message: 错误消息
  • opts: 可选参数 map
    • :cause 原因异常
    • :status HTTP 状态码
    • :provider Provider 名称
    • :context 上下文信息
    • :code 厂商业务码 / 细分码(如 "insufficient_quota"、:model-mismatch),可选

返回: 错误 map {:type :xxx :message "..." :retryable? bool}

示例: (error :timeout-error "请求超时" {:status 504})

创建错误 map

参数:
- type:    错误类型(关键字)
- message: 错误消息
- opts:    可选参数 map
  - :cause      原因异常
  - :status     HTTP 状态码
  - :provider   Provider 名称
  - :context    上下文信息
  - :code       厂商业务码 / 细分码(如 "insufficient_quota"、:model-mismatch),可选

返回:
错误 map {:type :xxx :message "..." :retryable? bool}

示例:
(error :timeout-error "请求超时" {:status 504})
sourceraw docstring

error?clj

(error? x)

检查是否为错误 map

参数:

  • x: 任意值

返回: boolean

检查是否为错误 map

参数:
- x: 任意值

返回:
boolean
sourceraw docstring

exception->errorclj

(exception->error e)
(exception->error e context)

将异常转换为错误 map

参数:

  • e: 异常对象
  • context: 上下文信息(可选)

返回: 错误 map

示例: (exception->error (IOException. "连接失败"))

将异常转换为错误 map

参数:
- e:       异常对象
- context: 上下文信息(可选)

返回:
错误 map

示例:
(exception->error (IOException. "连接失败"))
sourceraw docstring

fatal-throwable?clj

(fatal-throwable? t)

该 Throwable 是否不该被收敛为工具错误,必须原样上抛。

背景:工具执行的各层此前一律 catch Exception,于是 Error 全部逃逸—— 一个工具的深递归 StackOverflowError 会打死整个 agent 循环,而分层错误路由 (:semantic/:transient/:environment)的全部意义就是「一个工具坏了不牵连别人」。 但也不能无差别 catch Throwable:吞掉 OOM 只会掩盖真因,且收敛动作本身还要 分配内存,多半当场再炸。故要一条判据。

致命(放行):VirtualMachineError 中除 StackOverflowError 之外的那些 (OutOfMemoryError / InternalError / UnknownError——JVM 自身已坏, 继续跑工具没有意义)、ThreadDeath。

不致命(收敛):其余一切,尤其是

  • StackOverflowError——工具自己的递归 bug,栈一退就恢复,正是最该被 收敛成「这一个工具失败」的那类。这是我们与 Scala NonFatal 的有意分歧: 它把整个 VirtualMachineError 划为致命,但那是通用库的保守取舍;这里的 Throwable 来自用户工具函数体,栈溢出是它最常见的自伤方式。
  • AssertionError(工具里的 assert)、LinkageError / NoClassDefFoundError (该工具缺可选依赖)——都只说明这个工具不可用,不该牵连整轮。

收敛后经 classify-exception 归类(缺省 :semantic:工具 bug 重试无意义)。

该 Throwable 是否**不该被收敛为工具错误**,必须原样上抛。

背景:工具执行的各层此前一律 `catch Exception`,于是 `Error` 全部逃逸——
一个工具的深递归 `StackOverflowError` 会打死整个 agent 循环,而分层错误路由
(:semantic/:transient/:environment)的全部意义就是「一个工具坏了不牵连别人」。
但也不能无差别 `catch Throwable`:吞掉 OOM 只会掩盖真因,且收敛动作本身还要
分配内存,多半当场再炸。故要一条判据。

**致命(放行)**:`VirtualMachineError` 中除 StackOverflowError 之外的那些
(`OutOfMemoryError` / `InternalError` / `UnknownError`——JVM 自身已坏,
继续跑工具没有意义)、`ThreadDeath`。

**不致命(收敛)**:其余一切,尤其是
- `StackOverflowError`——**工具自己的递归 bug**,栈一退就恢复,正是最该被
  收敛成「这一个工具失败」的那类。这是我们与 Scala `NonFatal` 的**有意分歧**:
  它把整个 `VirtualMachineError` 划为致命,但那是通用库的保守取舍;这里的
  Throwable 来自**用户工具函数体**,栈溢出是它最常见的自伤方式。
- `AssertionError`(工具里的 `assert`)、`LinkageError` / `NoClassDefFoundError`
  (该工具缺可选依赖)——都只说明**这个工具**不可用,不该牵连整轮。

收敛后经 `classify-exception` 归类(缺省 :semantic:工具 bug 重试无意义)。
sourceraw docstring

format-errorclj

(format-error err)

格式化错误为可读字符串

参数:

  • err: 错误 map

返回: 格式化的字符串

示例: (format-error {:type :timeout-error :message "请求超时" :status 504}) ; => "[TIMEOUT-ERROR] 请求超时 (HTTP 504)"

格式化错误为可读字符串

参数:
- err: 错误 map

返回:
格式化的字符串

示例:
(format-error {:type :timeout-error :message "请求超时" :status 504})
; => "[TIMEOUT-ERROR] 请求超时 (HTTP 504)"
sourceraw docstring

http-error?clj

(http-error? err)

检查是否为 HTTP 相关错误(网络/超时/有状态码)

检查是否为 HTTP 相关错误(网络/超时/有状态码)
sourceraw docstring

http-response->errorclj

(http-response->error response provider)

将 HTTP 错误响应转换为错误 map

参数:

  • response: HTTP 响应 {:status n :body ...}
  • provider: Provider 名称

返回: 错误 map

示例: (http-response->error {:status 401 :body "Unauthorized"} :openai)

将 HTTP 错误响应转换为错误 map

参数:
- response: HTTP 响应 {:status n :body ...}
- provider: Provider 名称

返回:
错误 map

示例:
(http-response->error {:status 401 :body "Unauthorized"} :openai)
sourceraw docstring

quota-exhausted?clj

(quota-exhausted? err)

欠费 / 额度用尽(:quota-exhausted-error)。不可重试,但可切备用模型。

欠费 / 额度用尽(:quota-exhausted-error)。不可重试,但可切备用模型。
sourceraw docstring

rate-limit-error?clj

(rate-limit-error? err)

检查是否为速率限制错误(:rate-limit-error 类型,或 429 且不是欠费)。

OpenAI 兼容端点欠费也回 429(insufficient_quota)——那不是限流, provider 边界已把它细分为 :quota-exhausted-error,这里不能再认回去。

检查是否为速率限制错误(:rate-limit-error 类型,或 429 且**不是欠费**)。

OpenAI 兼容端点欠费也回 429(insufficient_quota)——那不是限流,
provider 边界已把它细分为 :quota-exhausted-error,这里不能再认回去。
sourceraw docstring

throw!clj

(throw! err)

将错误转换为异常并抛出

参数:

  • err: 错误 map

抛出: ExceptionInfo

将错误转换为异常并抛出

参数:
- err: 错误 map

抛出:
ExceptionInfo
sourceraw docstring

with-error-handlingclj

(with-error-handling f)
(with-error-handling f context)

执行函数并捕获异常转换为错误

参数:

  • f: 要执行的函数
  • context: 上下文信息(可选)

返回: [:ok result] 或 [:error error-map]

示例: (with-error-handling #(api-call) {:operation "call-llm"})

执行函数并捕获异常转换为错误

参数:
- f:       要执行的函数
- context: 上下文信息(可选)

返回:
[:ok result] 或 [:error error-map]

示例:
(with-error-handling #(api-call) {:operation "call-llm"})
sourceraw docstring

with-ex-dataclj

(with-ex-data t extra)

返回一个追加了 extra 键的等价异常(重试的 :attempts、主备切换的 :fallback 报告用)。

ex-info 的 data 不可变,只能换一个实例:消息、cause、栈逐字保留,data = (merge 原 data extra) ——调用方拿到的仍是同一个 canonical error(:type/:status/:retryable? 不丢),只是多了几个键。 非 ExceptionInfo(IOException 等)先 exception->error 归一,原异常作为 cause—— 保证 D5「ex-data 即 canonical error」不破。

返回一个**追加了 extra 键**的等价异常(重试的 :attempts、主备切换的 :fallback 报告用)。

ex-info 的 data 不可变,只能换一个实例:消息、cause、栈逐字保留,data = (merge 原 data extra)
——调用方拿到的仍是同一个 canonical error(:type/:status/:retryable? 不丢),只是多了几个键。
非 ExceptionInfo(IOException 等)先 `exception->error` 归一,原异常作为 cause——
保证 D5「ex-data 即 canonical error」不破。
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