smith-agents 0.12.4 → 0.12.5
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/AGENTS.md +11 -1
- data/docs/ERRORS_AND_RETRIES.md +3 -2
- data/lib/smith/agent/attempt_chat.rb +44 -0
- data/lib/smith/agent/provider_completion.rb +1 -1
- data/lib/smith/agent/provider_streaming.rb +4 -0
- data/lib/smith/version.rb +1 -1
- 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: 663a12c4a26041ded1da57a8bd899213cc28d330eae1d42a9dc3e4fdbda4dd87
|
|
4
|
+
data.tar.gz: c997927bbcea376c0db21a92aacdc21e510ac3c8e664d4ebb1eb0f8282cd2b32
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27cc5f8437f8325afde26cd1816f8ca007a1c0f43b0ea02cc14cdcd49c17504cc5b2a22934c8b26ddcac132f5d2dc28f40e9e6fcde8976e01b3d4438523235b2
|
|
7
|
+
data.tar.gz: d621799d104e1b3b1ba97f14974afb6fa01c99cd179f8817471a55148d0587c9b2e333d47a0ab016855e48b8ab7739badb5db72c22e6730294f23da4947bc96d
|
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.5] - 2026-10-13
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- A streamed step's requests are each sent once: its chat takes a copy of
|
|
12
|
+
the agent's config (its context, or RubyLLM's) with RubyLLM's HTTP retries
|
|
13
|
+
(`max_retries`) off, its model resolved again as RubyLLM resolves a chat
|
|
14
|
+
built with a context (the same id and provider, and the agent's own
|
|
15
|
+
`assume_model_exists`, so a model RubyLLM lists keeps its output limit and
|
|
16
|
+
thinking options). Retrying a stream read the next response's error into
|
|
17
|
+
the buffer the first had filled, so the provider's words were lost: a
|
|
18
|
+
refusal for want of credit reached Smith as a bare "Invalid request"
|
|
19
|
+
(`RubyLLM::BadRequestError`) and was never classified as
|
|
20
|
+
`RubyLLM::PaymentRequiredError`, and a spend-cap 429 as a plain rate limit.
|
|
21
|
+
A stream that failed part way was sent again, its text streamed twice.
|
|
22
|
+
Every provider failure of a streamed request, a transient one at connect
|
|
23
|
+
time included, now goes straight to the step's fallbacks and retries, so
|
|
24
|
+
a streamed step that should ride out an outage declares them. Calls that
|
|
25
|
+
do not stream, and requests OpenAI's Responses route sends whole, keep
|
|
26
|
+
RubyLLM's retries. The global config and the agent's are never changed.
|
|
27
|
+
|
|
7
28
|
## [0.12.4] - 2026-10-13
|
|
8
29
|
|
|
9
30
|
### Changed
|
data/README.md
CHANGED
data/docs/AGENTS.md
CHANGED
|
@@ -155,7 +155,17 @@ reason and message id, with `mode: :stream`), and a replayed step streams again
|
|
|
155
155
|
(unless a response memo kept its reply: then its sink gets the text at once).
|
|
156
156
|
Graph inspection shows the stream, and runtime readiness reports a missing sink
|
|
157
157
|
(`:stream_sink_missing`) and an agent whose calls go to the Responses API
|
|
158
|
-
(`:stream_whole_replies`).
|
|
158
|
+
(`:stream_whole_replies`). A streamed step's requests are each sent once:
|
|
159
|
+
its chat takes a copy of the agent's config with RubyLLM's HTTP retries
|
|
160
|
+
(`max_retries`) off and its model resolved again as RubyLLM resolves a chat
|
|
161
|
+
built with a context (a model RubyLLM lists keeps its limits), since a
|
|
162
|
+
retried stream loses the first response's error body (a refusal for want of
|
|
163
|
+
credit would reach Smith as a bare "Invalid request") or streams its text
|
|
164
|
+
again. Every provider failure of a streamed request, a transient one at
|
|
165
|
+
connect time included, takes the step's fallbacks and retries directly, so
|
|
166
|
+
declare them (`fallback_models`, `retry_on Smith::AgentError`) on a streamed
|
|
167
|
+
step that should ride out an outage. A request OpenAI's Responses route sends
|
|
168
|
+
whole keeps the retries.
|
|
159
169
|
|
|
160
170
|
## Batch mode
|
|
161
171
|
|
data/docs/ERRORS_AND_RETRIES.md
CHANGED
|
@@ -113,8 +113,9 @@ naming `RubyLLM::PaymentRequiredError` makes a step that meets it wait for
|
|
|
113
113
|
credit instead of failing, even when a fallback then fails some other way.
|
|
114
114
|
`Smith::Providers::Anthropic::Batches#submit` raises it too. Any other error
|
|
115
115
|
stays as it is: a rate limit whose message mentions a quota (Gemini's) is a
|
|
116
|
-
rate limit. RubyLLM's own HTTP retries (`max_retries`) may repeat a 429
|
|
117
|
-
|
|
116
|
+
rate limit. RubyLLM's own HTTP retries (`max_retries`) may repeat a 429 of a
|
|
117
|
+
call that does not stream before Smith sees it (a refused request is not
|
|
118
|
+
charged); a streamed call is sent once, so its refusal reaches Smith whole.
|
|
118
119
|
|
|
119
120
|
Name only errors that mean your own systems failed. A provider's own failure
|
|
120
121
|
(`RubyLLM::Error`, or its HTTP client's `Faraday::Error`) is the provider's
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Agent
|
|
5
|
+
module AttemptChat
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
# An attempt's chat. A streamed step's sends each request it streams
|
|
9
|
+
# over the wire once: RubyLLM's HTTP retries would send it again on an
|
|
10
|
+
# error, its error body already read into the first request's buffer
|
|
11
|
+
# (so a refusal for want of credit reaches Smith as a bare "Invalid
|
|
12
|
+
# request", never classified) or its text already streamed (the
|
|
13
|
+
# retry's text would join it). Smith's fallbacks and the step's
|
|
14
|
+
# retries take their place. Its chat takes a copy of the agent's config
|
|
15
|
+
# (its context, or RubyLLM's) without the retries, and resolves its
|
|
16
|
+
# model again as RubyLLM builds a chat with a context (the same id and
|
|
17
|
+
# provider, and the agent's own assume_model_exists, so a model RubyLLM
|
|
18
|
+
# knows keeps its limits), before any hold attaches to its provider. A
|
|
19
|
+
# request OpenAI's Responses route sends answers whole, unstreamed,
|
|
20
|
+
# and keeps the retries.
|
|
21
|
+
def attempt_chat(agent_class, model_reference)
|
|
22
|
+
chat = agent_class.chat(**model_reference.chat_options, **bridge_workflow_inputs(agent_class))
|
|
23
|
+
return chat unless @step_stream && streams_over_the_wire?(chat)
|
|
24
|
+
|
|
25
|
+
sent_once(chat, agent_class)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def sent_once(chat, agent_class)
|
|
29
|
+
model = chat.model
|
|
30
|
+
config = (agent_class.context&.config || RubyLLM.config).dup
|
|
31
|
+
config.max_retries = 0
|
|
32
|
+
chat.with_context(RubyLLM::Context.new(config))
|
|
33
|
+
chat.with_model(model.id, provider: model.provider,
|
|
34
|
+
assume_exists: agent_class.chat_kwargs.fetch(:assume_model_exists, false))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def streams_over_the_wire?(chat)
|
|
38
|
+
return false unless chat.respond_to?(:with_context) && chat.respond_to?(:with_model)
|
|
39
|
+
|
|
40
|
+
(chat.params[:openai_api_mode] || chat.params["openai_api_mode"]).to_s != "responses"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -147,7 +147,7 @@ module Smith
|
|
|
147
147
|
# rubocop:enable Metrics/AbcSize
|
|
148
148
|
|
|
149
149
|
def prepared_attempt_chat(agent_class, prepared_input, model_reference, shape)
|
|
150
|
-
chat = agent_class
|
|
150
|
+
chat = attempt_chat(agent_class, model_reference)
|
|
151
151
|
add_prepared_input(chat, prepared_input, observed_model_reference(chat, fallback: model_reference).provider)
|
|
152
152
|
continue_paused_loop(chat)
|
|
153
153
|
shaped_chat(chat, model_reference, shape)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "attempt_chat"
|
|
4
|
+
|
|
3
5
|
module Smith
|
|
4
6
|
class Agent
|
|
5
7
|
# The provider calls of a streamed step (Smith::Streaming): before a
|
|
@@ -8,6 +10,8 @@ module Smith
|
|
|
8
10
|
# reason is read from the stream (Providers::StreamFacts), as a
|
|
9
11
|
# streamed reply keeps no raw body.
|
|
10
12
|
module ProviderStreaming
|
|
13
|
+
include AttemptChat
|
|
14
|
+
|
|
11
15
|
# Room for what a pause adds besides its messages (its retry, the
|
|
12
16
|
# attempt's usage).
|
|
13
17
|
PAUSE_MARGIN = 4096
|
data/lib/smith/version.rb
CHANGED
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.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Ralak
|
|
@@ -161,6 +161,7 @@ files:
|
|
|
161
161
|
- lib/generators/smith/install/templates/workflow_state.rb.tt
|
|
162
162
|
- lib/smith.rb
|
|
163
163
|
- lib/smith/agent.rb
|
|
164
|
+
- lib/smith/agent/attempt_chat.rb
|
|
164
165
|
- lib/smith/agent/chat_construction.rb
|
|
165
166
|
- lib/smith/agent/completion.rb
|
|
166
167
|
- lib/smith/agent/completion_usage_recording.rb
|