smith-agents 0.12.5 → 0.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +1 -1
- data/docs/ERRORS_AND_RETRIES.md +9 -0
- data/lib/smith/agent/provider_call_timing.rb +4 -2
- data/lib/smith/provider_errors.rb +98 -0
- data/lib/smith/version.rb +1 -1
- data/lib/smith.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 97813bcba79ce652b05eb860da067e3c566b8ba3450344d66940c9f65d23a1dc
|
|
4
|
+
data.tar.gz: ea20e0a30b7517aab44a43b2668cb5e59fde6b98a829056e8e73ba585338f216
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 37d47a1f6268dc96cd05ce69f4138998d1c5a9649bbb41921882b6ae93a8f5d030586c273f37231501c121dd43492e1a37034cefe88981a5f89138cbf1ccd128
|
|
7
|
+
data.tar.gz: 40eb2c1a4ac979411860e4971b4814e0c2cdaebc39f1a827e956f7fcb2a5ca33c3f34d18aad339edfa1460a8fce07188f505aa174a2e59233d735c50bab258d3
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@ All notable changes to Smith are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Smith is pre-1.0 and under active development; expect occasional contract tightening between minor versions until 1.0.
|
|
6
6
|
|
|
7
|
+
## [0.12.6] - 2026-10-13
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- An error of a streamed request is what the provider said it was
|
|
12
|
+
(`Smith::ProviderErrors`). RubyLLM builds a streamed request's error
|
|
13
|
+
response itself, guessing its status from the error event's type:
|
|
14
|
+
Anthropic's adapter says 500 unless overloaded, OpenAI's 400 unless a
|
|
15
|
+
server error or a rate limit. With 0.12.5 sending a streamed request
|
|
16
|
+
once, an Anthropic bad request, wrong key or unknown model looked like a
|
|
17
|
+
passing outage (fallbacks and retries instead of failing or moving to
|
|
18
|
+
another account), and an OpenAI wrong key, unknown model, regional refusal
|
|
19
|
+
or rate limit like a bad request (no fallback). Before, RubyLLM's retry
|
|
20
|
+
hid this by garbling the error into one of the response's real status.
|
|
21
|
+
Smith reads the type the provider gave in the body (Anthropic's
|
|
22
|
+
`error.type`, OpenAI's `error.code`, else its `error.type`) and, when it
|
|
23
|
+
names another status, raises what RubyLLM raises for that status, through
|
|
24
|
+
RubyLLM's own mapping (context length included), with the provider's
|
|
25
|
+
message and the response's headers, its cause the error RubyLLM raised.
|
|
26
|
+
An error the provider sent whole keeps its own status.
|
|
27
|
+
|
|
7
28
|
## [0.12.5] - 2026-10-13
|
|
8
29
|
|
|
9
30
|
### Fixed
|
data/README.md
CHANGED
data/docs/ERRORS_AND_RETRIES.md
CHANGED
|
@@ -117,6 +117,15 @@ rate limit. RubyLLM's own HTTP retries (`max_retries`) may repeat a 429 of a
|
|
|
117
117
|
call that does not stream before Smith sees it (a refused request is not
|
|
118
118
|
charged); a streamed call is sent once, so its refusal reaches Smith whole.
|
|
119
119
|
|
|
120
|
+
RubyLLM guesses the status of a streamed request's error from the error
|
|
121
|
+
event's type (Anthropic's adapter says 500 unless overloaded, OpenAI's 400
|
|
122
|
+
unless a server error or a rate limit). Smith takes it as what the provider
|
|
123
|
+
said it was, from the type in the body (Anthropic's `error.type`, OpenAI's
|
|
124
|
+
`error.code`, else its `error.type`), through RubyLLM's own mapping, with the
|
|
125
|
+
response's headers: a streamed bad request fails, a wrong key is the
|
|
126
|
+
account's, an unknown model moves to the next candidate, an overload is a
|
|
127
|
+
passing outage. An error the provider sent whole keeps its own status.
|
|
128
|
+
|
|
120
129
|
Name only errors that mean your own systems failed. A provider's own failure
|
|
121
130
|
(`RubyLLM::Error`, or its HTTP client's `Faraday::Error`) is the provider's
|
|
122
131
|
whatever it was raised from, so naming a low-level class such as
|
|
@@ -123,9 +123,11 @@ module Smith
|
|
|
123
123
|
# have their join target; the error then propagates unchanged. A
|
|
124
124
|
# provider failure's own reported usage is accounted beside the
|
|
125
125
|
# completed prefix, so its trace totals both.
|
|
126
|
-
#
|
|
126
|
+
# An error is recorded as what the provider said it was
|
|
127
|
+
# (ProviderErrors), a billing refusal as the provider account's
|
|
128
|
+
# (BillingRefusals).
|
|
127
129
|
def failed_provider_attempt(error, observed_reference, timer, facts, prefix_usages)
|
|
128
|
-
error = BillingRefusals.classified(error)
|
|
130
|
+
error = BillingRefusals.classified(ProviderErrors.classified(error))
|
|
129
131
|
aborted = !provider_failure?(error)
|
|
130
132
|
usages = aborted ? prefix_usages : [*prefix_usages, ProviderUsage.from_message(error)].compact
|
|
131
133
|
attempt = ProviderAttempt.failure(
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "ruby_llm"
|
|
5
|
+
|
|
6
|
+
module Smith
|
|
7
|
+
# An error of a streamed request as the provider said it was. RubyLLM
|
|
8
|
+
# builds a streamed request's error response itself, its status a guess
|
|
9
|
+
# from the error event's type (Anthropic's adapter says 500 unless
|
|
10
|
+
# overloaded, OpenAI's 400 unless a server error or a rate limit), so a
|
|
11
|
+
# wrong key, a model the provider does not serve or a refusal by region
|
|
12
|
+
# could look like a passing outage or a bad request. Smith reads the type
|
|
13
|
+
# the provider gave in the body (Anthropic's `error.type`, OpenAI's
|
|
14
|
+
# `error.code`, else its `error.type`) and, when it names another status,
|
|
15
|
+
# raises what RubyLLM raises for that status (its own mapping, context
|
|
16
|
+
# length included), with the provider's message, the response's headers
|
|
17
|
+
# kept, its cause the error RubyLLM raised. An error from a response the
|
|
18
|
+
# provider sent whole (its status the provider's own), or whose body
|
|
19
|
+
# names nothing it knows, stays as it is.
|
|
20
|
+
module ProviderErrors
|
|
21
|
+
STATUSES = {
|
|
22
|
+
"invalid_request_error" => 400,
|
|
23
|
+
"authentication_error" => 401,
|
|
24
|
+
"invalid_api_key" => 401,
|
|
25
|
+
"billing_error" => 402,
|
|
26
|
+
"permission_error" => 403,
|
|
27
|
+
"unsupported_country_region_territory" => 403,
|
|
28
|
+
"not_found_error" => 404,
|
|
29
|
+
"model_not_found" => 404,
|
|
30
|
+
"request_too_large" => 413,
|
|
31
|
+
"rate_limit_error" => 429,
|
|
32
|
+
"rate_limit_exceeded" => 429,
|
|
33
|
+
"timeout_error" => 504
|
|
34
|
+
}.freeze
|
|
35
|
+
private_constant :STATUSES
|
|
36
|
+
|
|
37
|
+
Response = Struct.new(:status, :body)
|
|
38
|
+
Said = Struct.new(:message) do
|
|
39
|
+
def parse_error(_response) = message
|
|
40
|
+
end
|
|
41
|
+
private_constant :Response, :Said
|
|
42
|
+
|
|
43
|
+
module_function
|
|
44
|
+
|
|
45
|
+
# The error as Smith takes it: a streamed request's error whose body
|
|
46
|
+
# names another status as the error RubyLLM raises for that status,
|
|
47
|
+
# anything else unchanged.
|
|
48
|
+
def classified(error)
|
|
49
|
+
return error unless error.is_a?(RubyLLM::Error) && streamed?(error)
|
|
50
|
+
|
|
51
|
+
said = said_of(body_of(error))
|
|
52
|
+
status = named_status(said)
|
|
53
|
+
return error if status.nil? || status == error.response.status.to_i
|
|
54
|
+
|
|
55
|
+
reclassify(error, with_status(error.response, status), said["message"])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def named_status(said) = said && (STATUSES[said["code"].to_s] || STATUSES[said["type"].to_s])
|
|
59
|
+
|
|
60
|
+
# A response RubyLLM built for a streamed request's error, never one the
|
|
61
|
+
# provider sent whole (a Faraday::Response).
|
|
62
|
+
def streamed?(error)
|
|
63
|
+
response = error.response
|
|
64
|
+
response.respond_to?(:status) && response.respond_to?(:body) && !response.is_a?(Faraday::Response)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def reclassify(error, response, message)
|
|
68
|
+
named = begin
|
|
69
|
+
RubyLLM::ErrorMiddleware.parse_error(provider: Said.new(message.is_a?(String) ? message : nil), response:)
|
|
70
|
+
rescue RubyLLM::Error => e
|
|
71
|
+
e
|
|
72
|
+
end
|
|
73
|
+
return error unless named.is_a?(RubyLLM::Error)
|
|
74
|
+
|
|
75
|
+
begin
|
|
76
|
+
raise named.class.new(response, named.message), cause: error
|
|
77
|
+
rescue RubyLLM::Error => e
|
|
78
|
+
e
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def with_status(response, status)
|
|
83
|
+
response.respond_to?(:merge) ? response.merge(status:) : Response.new(status, response.body)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def said_of(body)
|
|
87
|
+
said = body.is_a?(Hash) ? body["error"] : nil
|
|
88
|
+
said.is_a?(Hash) ? said : nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def body_of(error)
|
|
92
|
+
body = error.response.body
|
|
93
|
+
body.is_a?(String) ? JSON.parse(body) : body
|
|
94
|
+
rescue JSON::ParserError
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
data/lib/smith/version.rb
CHANGED
data/lib/smith.rb
CHANGED
|
@@ -212,6 +212,7 @@ require_relative "smith/tool_failure_notification_failed"
|
|
|
212
212
|
require_relative "smith/persistence_payload_conflict"
|
|
213
213
|
require_relative "smith/exponential_backoff"
|
|
214
214
|
require_relative "smith/interruptions"
|
|
215
|
+
require_relative "smith/provider_errors"
|
|
215
216
|
require_relative "smith/billing_refusals"
|
|
216
217
|
|
|
217
218
|
# Event system (depends on Types)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smith-agents
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.12.
|
|
4
|
+
version: 0.12.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Ralak
|
|
@@ -298,6 +298,7 @@ files:
|
|
|
298
298
|
- lib/smith/pricing.rb
|
|
299
299
|
- lib/smith/pricing/catalog_validation.rb
|
|
300
300
|
- lib/smith/pricing_configuration_error.rb
|
|
301
|
+
- lib/smith/provider_errors.rb
|
|
301
302
|
- lib/smith/provider_permanent_failure.rb
|
|
302
303
|
- lib/smith/providers/anthropic/batches.rb
|
|
303
304
|
- lib/smith/providers/batch_exchange.rb
|