riffer 0.31.0 → 0.32.0
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/.agents/code-style.md +63 -4
- data/.agents/rbs-inline.md +1 -6
- data/.release-please-manifest.json +1 -1
- data/AGENTS.md +1 -2
- data/CHANGELOG.md +18 -0
- data/docs/08_MESSAGES.md +1 -1
- data/docs/14_MCP.md +50 -5
- data/docs/providers/02_AMAZON_BEDROCK.md +14 -0
- data/lib/riffer/agent/config.rb +42 -47
- data/lib/riffer/agent/context.rb +70 -50
- data/lib/riffer/agent/response.rb +4 -20
- data/lib/riffer/agent/run.rb +28 -67
- data/lib/riffer/agent/serializer.rb +22 -81
- data/lib/riffer/agent/session/repair.rb +14 -40
- data/lib/riffer/agent/session.rb +25 -67
- data/lib/riffer/agent/structured_output/result.rb +3 -11
- data/lib/riffer/agent/structured_output.rb +5 -13
- data/lib/riffer/agent.rb +74 -192
- data/lib/riffer/config.rb +34 -101
- data/lib/riffer/evals/evaluator.rb +7 -27
- data/lib/riffer/evals/evaluator_runner.rb +11 -19
- data/lib/riffer/evals/judge.rb +4 -25
- data/lib/riffer/evals/result.rb +1 -18
- data/lib/riffer/evals/run_result.rb +0 -11
- data/lib/riffer/evals/scenario_result.rb +0 -14
- data/lib/riffer/evals.rb +0 -6
- data/lib/riffer/guardrail.rb +4 -27
- data/lib/riffer/guardrails/modification.rb +0 -10
- data/lib/riffer/guardrails/result.rb +3 -30
- data/lib/riffer/guardrails/runner.rb +5 -22
- data/lib/riffer/guardrails/tripwire.rb +1 -19
- data/lib/riffer/guardrails.rb +2 -4
- data/lib/riffer/helpers/call_or_value.rb +4 -3
- data/lib/riffer/helpers/class_name_converter.rb +3 -1
- data/lib/riffer/helpers/dependencies.rb +5 -7
- data/lib/riffer/helpers.rb +0 -5
- data/lib/riffer/mcp/authenticated_tool.rb +9 -9
- data/lib/riffer/mcp/client.rb +12 -17
- data/lib/riffer/mcp/manifest.rb +13 -10
- data/lib/riffer/mcp/registration.rb +2 -11
- data/lib/riffer/mcp/registry.rb +44 -52
- data/lib/riffer/mcp/search_tool.rb +53 -0
- data/lib/riffer/mcp/tool_factory.rb +13 -18
- data/lib/riffer/mcp.rb +12 -17
- data/lib/riffer/messages/assistant.rb +2 -9
- data/lib/riffer/messages/base.rb +46 -16
- data/lib/riffer/messages/file_part.rb +32 -24
- data/lib/riffer/messages/system.rb +0 -5
- data/lib/riffer/messages/tool.rb +0 -10
- data/lib/riffer/messages/user.rb +0 -10
- data/lib/riffer/messages.rb +0 -7
- data/lib/riffer/params/boolean.rb +2 -4
- data/lib/riffer/params/param.rb +28 -39
- data/lib/riffer/params.rb +9 -21
- data/lib/riffer/providers/amazon_bedrock.rb +42 -28
- data/lib/riffer/providers/anthropic.rb +4 -9
- data/lib/riffer/providers/azure_open_ai.rb +3 -19
- data/lib/riffer/providers/base.rb +13 -26
- data/lib/riffer/providers/gemini.rb +4 -4
- data/lib/riffer/providers/mock.rb +6 -26
- data/lib/riffer/providers/open_ai.rb +6 -8
- data/lib/riffer/providers/open_router.rb +4 -10
- data/lib/riffer/providers/repository.rb +4 -3
- data/lib/riffer/providers/token_usage.rb +9 -20
- data/lib/riffer/providers.rb +0 -8
- data/lib/riffer/runner/fibers.rb +10 -16
- data/lib/riffer/runner/sequential.rb +1 -4
- data/lib/riffer/runner/threaded.rb +3 -14
- data/lib/riffer/runner.rb +2 -15
- data/lib/riffer/skills/activate_tool.rb +2 -11
- data/lib/riffer/skills/adapter.rb +4 -22
- data/lib/riffer/skills/backend.rb +7 -21
- data/lib/riffer/skills/config.rb +10 -31
- data/lib/riffer/skills/context.rb +5 -20
- data/lib/riffer/skills/filesystem_backend.rb +7 -25
- data/lib/riffer/skills/frontmatter.rb +10 -28
- data/lib/riffer/skills/markdown_adapter.rb +2 -9
- data/lib/riffer/skills/xml_adapter.rb +2 -8
- data/lib/riffer/stream_events/base.rb +1 -6
- data/lib/riffer/stream_events/guardrail_modification.rb +1 -8
- data/lib/riffer/stream_events/guardrail_tripwire.rb +1 -8
- data/lib/riffer/stream_events/interrupt.rb +4 -7
- data/lib/riffer/stream_events/reasoning_delta.rb +2 -4
- data/lib/riffer/stream_events/reasoning_done.rb +2 -4
- data/lib/riffer/stream_events/skill_activation.rb +2 -4
- data/lib/riffer/stream_events/text_delta.rb +0 -2
- data/lib/riffer/stream_events/text_done.rb +1 -3
- data/lib/riffer/stream_events/token_usage_done.rb +1 -8
- data/lib/riffer/stream_events/tool_call_delta.rb +2 -3
- data/lib/riffer/stream_events/tool_call_done.rb +1 -3
- data/lib/riffer/stream_events/web_search_done.rb +1 -3
- data/lib/riffer/stream_events/web_search_status.rb +2 -3
- data/lib/riffer/stream_events.rb +0 -10
- data/lib/riffer/tool.rb +6 -13
- data/lib/riffer/tools/response.rb +8 -4
- data/lib/riffer/tools/runtime/fibers.rb +0 -3
- data/lib/riffer/tools/runtime/inline.rb +1 -4
- data/lib/riffer/tools/runtime/threaded.rb +0 -2
- data/lib/riffer/tools/runtime.rb +5 -38
- data/lib/riffer/tools/toolable.rb +5 -16
- data/lib/riffer/tools.rb +0 -4
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +7 -8
- data/sig/generated/riffer/agent/config.rbs +29 -46
- data/sig/generated/riffer/agent/context.rbs +40 -48
- data/sig/generated/riffer/agent/response.rbs +4 -20
- data/sig/generated/riffer/agent/run.rbs +12 -61
- data/sig/generated/riffer/agent/serializer.rbs +21 -80
- data/sig/generated/riffer/agent/session/repair.rbs +12 -40
- data/sig/generated/riffer/agent/session.rbs +25 -67
- data/sig/generated/riffer/agent/structured_output/result.rbs +2 -10
- data/sig/generated/riffer/agent/structured_output.rbs +5 -12
- data/sig/generated/riffer/agent.rbs +57 -186
- data/sig/generated/riffer/config.rbs +34 -100
- data/sig/generated/riffer/evals/evaluator.rbs +7 -27
- data/sig/generated/riffer/evals/evaluator_runner.rbs +9 -19
- data/sig/generated/riffer/evals/judge.rbs +4 -24
- data/sig/generated/riffer/evals/result.rbs +1 -17
- data/sig/generated/riffer/evals/run_result.rbs +0 -10
- data/sig/generated/riffer/evals/scenario_result.rbs +0 -13
- data/sig/generated/riffer/evals.rbs +0 -6
- data/sig/generated/riffer/guardrail.rbs +4 -27
- data/sig/generated/riffer/guardrails/modification.rbs +0 -10
- data/sig/generated/riffer/guardrails/result.rbs +3 -30
- data/sig/generated/riffer/guardrails/runner.rbs +5 -22
- data/sig/generated/riffer/guardrails/tripwire.rbs +1 -19
- data/sig/generated/riffer/guardrails.rbs +2 -4
- data/sig/generated/riffer/helpers/call_or_value.rbs +4 -3
- data/sig/generated/riffer/helpers/class_name_converter.rbs +1 -1
- data/sig/generated/riffer/helpers/dependencies.rbs +3 -7
- data/sig/generated/riffer/helpers.rbs +0 -5
- data/sig/generated/riffer/mcp/authenticated_tool.rbs +5 -4
- data/sig/generated/riffer/mcp/client.rbs +10 -16
- data/sig/generated/riffer/mcp/manifest.rbs +9 -9
- data/sig/generated/riffer/mcp/registration.rbs +2 -10
- data/sig/generated/riffer/mcp/registry.rbs +11 -18
- data/sig/generated/riffer/mcp/search_tool.rbs +26 -0
- data/sig/generated/riffer/mcp/tool_factory.rbs +10 -15
- data/sig/generated/riffer/mcp.rbs +10 -17
- data/sig/generated/riffer/messages/assistant.rbs +2 -8
- data/sig/generated/riffer/messages/base.rbs +11 -16
- data/sig/generated/riffer/messages/file_part.rbs +13 -23
- data/sig/generated/riffer/messages/system.rbs +0 -4
- data/sig/generated/riffer/messages/tool.rbs +0 -9
- data/sig/generated/riffer/messages/user.rbs +0 -9
- data/sig/generated/riffer/messages.rbs +0 -7
- data/sig/generated/riffer/params/boolean.rbs +2 -4
- data/sig/generated/riffer/params/param.rbs +21 -39
- data/sig/generated/riffer/params.rbs +9 -21
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +21 -25
- data/sig/generated/riffer/providers/anthropic.rbs +2 -7
- data/sig/generated/riffer/providers/azure_open_ai.rbs +3 -18
- data/sig/generated/riffer/providers/base.rbs +9 -25
- data/sig/generated/riffer/providers/gemini.rbs +0 -2
- data/sig/generated/riffer/providers/mock.rbs +6 -26
- data/sig/generated/riffer/providers/open_ai.rbs +1 -5
- data/sig/generated/riffer/providers/open_router.rbs +4 -10
- data/sig/generated/riffer/providers/repository.rbs +2 -3
- data/sig/generated/riffer/providers/token_usage.rbs +6 -16
- data/sig/generated/riffer/providers.rbs +0 -8
- data/sig/generated/riffer/runner/fibers.rbs +8 -15
- data/sig/generated/riffer/runner/sequential.rbs +1 -3
- data/sig/generated/riffer/runner/threaded.rbs +3 -13
- data/sig/generated/riffer/runner.rbs +2 -14
- data/sig/generated/riffer/skills/activate_tool.rbs +2 -11
- data/sig/generated/riffer/skills/adapter.rbs +4 -22
- data/sig/generated/riffer/skills/backend.rbs +7 -21
- data/sig/generated/riffer/skills/config.rbs +10 -31
- data/sig/generated/riffer/skills/context.rbs +5 -20
- data/sig/generated/riffer/skills/filesystem_backend.rbs +7 -24
- data/sig/generated/riffer/skills/frontmatter.rbs +10 -27
- data/sig/generated/riffer/skills/markdown_adapter.rbs +2 -9
- data/sig/generated/riffer/skills/xml_adapter.rbs +2 -8
- data/sig/generated/riffer/stream_events/base.rbs +1 -6
- data/sig/generated/riffer/stream_events/guardrail_modification.rbs +1 -8
- data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +1 -8
- data/sig/generated/riffer/stream_events/interrupt.rbs +4 -7
- data/sig/generated/riffer/stream_events/reasoning_delta.rbs +2 -4
- data/sig/generated/riffer/stream_events/reasoning_done.rbs +2 -4
- data/sig/generated/riffer/stream_events/skill_activation.rbs +2 -4
- data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
- data/sig/generated/riffer/stream_events/text_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/token_usage_done.rbs +1 -7
- data/sig/generated/riffer/stream_events/tool_call_delta.rbs +2 -3
- data/sig/generated/riffer/stream_events/tool_call_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/web_search_done.rbs +1 -3
- data/sig/generated/riffer/stream_events/web_search_status.rbs +2 -3
- data/sig/generated/riffer/stream_events.rbs +0 -10
- data/sig/generated/riffer/tool.rbs +5 -12
- data/sig/generated/riffer/tools/response.rbs +6 -4
- data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -3
- data/sig/generated/riffer/tools/runtime/inline.rbs +1 -3
- data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -2
- data/sig/generated/riffer/tools/runtime.rbs +5 -37
- data/sig/generated/riffer/tools/toolable.rbs +4 -14
- data/sig/generated/riffer/tools.rbs +0 -4
- data/sig/generated/riffer.rbs +5 -4
- data/sig/manual/riffer/agent/session/repair.rbs +5 -0
- data/sig/manual/riffer/evals/evaluator_runner.rbs +5 -0
- data/sig/manual/riffer/helpers/class_name_converter.rbs +5 -0
- data/sig/manual/riffer/helpers/dependencies.rbs +5 -0
- data/sig/manual/riffer/mcp/authenticated_tool.rbs +5 -0
- data/sig/manual/riffer/mcp/registry.rbs +5 -0
- data/sig/manual/riffer/mcp/tool_factory.rbs +5 -0
- data/sig/manual/riffer/mcp.rbs +5 -0
- data/sig/manual/riffer/providers/repository.rbs +5 -0
- data/sig/manual/riffer.rbs +5 -0
- metadata +17 -9
- data/.agents/rdoc.md +0 -69
- data/lib/riffer/messages/converter.rb +0 -90
- data/sig/generated/riffer/messages/converter.rbs +0 -33
- data/sig/manual/riffer/tools/toolable.rbs +0 -6
data/lib/riffer/agent/run.rb
CHANGED
|
@@ -1,23 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# runtime, structured output, session, context); Run just orchestrates.
|
|
7
|
-
#
|
|
8
|
-
# Tools and user code see the agent's +context+ (a +Riffer::Agent::Context+)
|
|
9
|
-
# unchanged through the loop, so downstream tool runtimes can read
|
|
10
|
-
# caller-provided keys via <tt>context[:agent]</tt> /
|
|
11
|
-
# <tt>context.dig(:key)</tt>, or the framework built-ins via
|
|
12
|
-
# +context.skills+. Cumulative token usage is updated into
|
|
13
|
-
# +agent.context.token_usage+ as the loop progresses.
|
|
14
|
-
#
|
|
15
|
-
# Riffer::Agent::Run.generate(agent: my_agent, prompt: "Hello")
|
|
16
|
-
# Riffer::Agent::Run.stream(agent: my_agent, prompt: "Hello")
|
|
17
|
-
#
|
|
4
|
+
# The generation loop — a pure module of functions over an +agent+, which owns
|
|
5
|
+
# every per-call value; Run just orchestrates.
|
|
18
6
|
module Riffer::Agent::Run
|
|
19
7
|
extend self
|
|
20
|
-
include Riffer::Messages::Converter
|
|
21
8
|
|
|
22
9
|
# Runs the generate loop for the given agent. See Riffer::Agent#generate
|
|
23
10
|
# for prompt/files semantics.
|
|
@@ -41,13 +28,6 @@ module Riffer::Agent::Run
|
|
|
41
28
|
|
|
42
29
|
private
|
|
43
30
|
|
|
44
|
-
# The generation loop. When +stream_yielder+ is provided, per-step events are
|
|
45
|
-
# pushed to it (and +stream+ discards the return value). When +stream_yielder+
|
|
46
|
-
# is +nil+, no events are emitted and +generate+ returns the Response
|
|
47
|
-
# directly. The two modes share every step of the loop — the only
|
|
48
|
-
# divergences are the LLM call shape (atomic vs. accumulated stream)
|
|
49
|
-
# and whether per-step events are emitted.
|
|
50
|
-
#
|
|
51
31
|
#--
|
|
52
32
|
#: (Riffer::Agent, ?stream_yielder: Enumerator::Yielder?) -> Riffer::Agent::Response
|
|
53
33
|
def run_loop(agent, stream_yielder: nil)
|
|
@@ -86,17 +66,12 @@ module Riffer::Agent::Run
|
|
|
86
66
|
return final_response(agent, all_modifications)
|
|
87
67
|
end
|
|
88
68
|
|
|
89
|
-
# catch returns the thrown value when throw :riffer_interrupt fires;
|
|
90
|
-
# the return above exits on the successful (non-interrupted) path.
|
|
91
69
|
new_messages, filled = Riffer::Agent::Session::Repair.fill_orphans(agent.session.messages)
|
|
92
70
|
agent.session.set(new_messages)
|
|
93
71
|
stream_yielder << Riffer::StreamEvents::Interrupt.new(reason: reason, healed_tool_call_ids: filled) if stream_yielder
|
|
94
72
|
final_response(agent, all_modifications, interrupted: true, interrupt_reason: reason, healed_tool_call_ids: filled)
|
|
95
73
|
end
|
|
96
74
|
|
|
97
|
-
# Consumes one provider stream, forwarding every event to +stream_yielder+
|
|
98
|
-
# and folding it into an +Assistant+ message.
|
|
99
|
-
#
|
|
100
75
|
#--
|
|
101
76
|
#: (Riffer::Agent, Enumerator::Yielder) -> Riffer::Messages::Assistant
|
|
102
77
|
def accumulate_streamed_response(agent, stream_yielder)
|
|
@@ -130,9 +105,6 @@ module Riffer::Agent::Run
|
|
|
130
105
|
)
|
|
131
106
|
end
|
|
132
107
|
|
|
133
|
-
# Appends +new_modifications+ to +all_modifications+ and emits a
|
|
134
|
-
# +GuardrailModification+ event for each one when streaming.
|
|
135
|
-
#
|
|
136
108
|
#--
|
|
137
109
|
#: (Enumerator::Yielder?, Array[Riffer::Guardrails::Modification], Array[Riffer::Guardrails::Modification]) -> void
|
|
138
110
|
def record_modifications!(stream_yielder, all_modifications, new_modifications)
|
|
@@ -140,9 +112,6 @@ module Riffer::Agent::Run
|
|
|
140
112
|
new_modifications.each { |m| stream_yielder << Riffer::StreamEvents::GuardrailModification.new(m) } if stream_yielder
|
|
141
113
|
end
|
|
142
114
|
|
|
143
|
-
# Emits a +GuardrailTripwire+ event when streaming and returns the
|
|
144
|
-
# short-circuit +Response+ for a tripped guardrail.
|
|
145
|
-
#
|
|
146
115
|
#--
|
|
147
116
|
#: (Riffer::Agent, Enumerator::Yielder?, Riffer::Guardrails::Tripwire, Array[Riffer::Guardrails::Modification]) -> Riffer::Agent::Response
|
|
148
117
|
def tripwire_response(agent, stream_yielder, tripwire, all_modifications)
|
|
@@ -150,11 +119,6 @@ module Riffer::Agent::Run
|
|
|
150
119
|
build_response(agent, "", tripwire: tripwire, modifications: all_modifications)
|
|
151
120
|
end
|
|
152
121
|
|
|
153
|
-
# Builds the final +Response+ from the session's last assistant
|
|
154
|
-
# message, validating structured output when configured. +extra+
|
|
155
|
-
# carries the interrupt-only fields (+interrupted:+, +interrupt_reason:+,
|
|
156
|
-
# +healed_tool_call_ids:+) on the interrupt exit path.
|
|
157
|
-
#
|
|
158
122
|
#--
|
|
159
123
|
#: (Riffer::Agent, Array[Riffer::Guardrails::Modification], **untyped) -> Riffer::Agent::Response
|
|
160
124
|
def final_response(agent, all_modifications, **extra)
|
|
@@ -168,7 +132,7 @@ module Riffer::Agent::Run
|
|
|
168
132
|
agent.provider.generate_text(
|
|
169
133
|
messages: agent.session.messages,
|
|
170
134
|
model: agent.model_name,
|
|
171
|
-
tools: agent
|
|
135
|
+
tools: effective_tools(agent),
|
|
172
136
|
**merged_model_options(agent)
|
|
173
137
|
)
|
|
174
138
|
end
|
|
@@ -179,7 +143,7 @@ module Riffer::Agent::Run
|
|
|
179
143
|
agent.provider.stream_text(
|
|
180
144
|
messages: agent.session.messages,
|
|
181
145
|
model: agent.model_name,
|
|
182
|
-
tools: agent
|
|
146
|
+
tools: effective_tools(agent),
|
|
183
147
|
**merged_model_options(agent)
|
|
184
148
|
)
|
|
185
149
|
end
|
|
@@ -189,7 +153,9 @@ module Riffer::Agent::Run
|
|
|
189
153
|
def execute_tool_calls(agent, assistant_message, tool_calls: assistant_message.tool_calls)
|
|
190
154
|
return if tool_calls.empty?
|
|
191
155
|
|
|
192
|
-
results = agent.tool_runtime.execute(tool_calls, tools: agent
|
|
156
|
+
results = agent.tool_runtime.execute(tool_calls, tools: effective_tools(agent), context: agent.context, assistant_message: assistant_message)
|
|
157
|
+
|
|
158
|
+
inject_discovered_tools(agent, results)
|
|
193
159
|
|
|
194
160
|
results.each do |tool_call, result|
|
|
195
161
|
agent.session.add(Riffer::Messages::Tool.new(
|
|
@@ -202,12 +168,17 @@ module Riffer::Agent::Run
|
|
|
202
168
|
end
|
|
203
169
|
end
|
|
204
170
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
171
|
+
#--
|
|
172
|
+
#: (Riffer::Agent, Array[[Riffer::Messages::Assistant::ToolCall, Riffer::Tools::Response]]) -> void
|
|
173
|
+
def inject_discovered_tools(agent, results)
|
|
174
|
+
to_inject = results.flat_map { |_, result|
|
|
175
|
+
result.is_a?(Riffer::Mcp::SearchTool::Result) ? result.discovered_tools : [] #: Array[singleton(Riffer::Tool)]
|
|
176
|
+
}
|
|
177
|
+
return if to_inject.empty?
|
|
178
|
+
|
|
179
|
+
agent.context.discover_tools(to_inject)
|
|
180
|
+
end
|
|
181
|
+
|
|
211
182
|
#--
|
|
212
183
|
#: (Riffer::Agent) -> void
|
|
213
184
|
def execute_pending_tool_calls(agent)
|
|
@@ -215,11 +186,6 @@ module Riffer::Agent::Run
|
|
|
215
186
|
execute_tool_calls(agent, assistant_message, tool_calls: pending) if assistant_message
|
|
216
187
|
end
|
|
217
188
|
|
|
218
|
-
# Runs the +:before+ guardrail phase. Records any modifications into
|
|
219
|
-
# +all_modifications+ (and emits them when streaming). When a tripwire
|
|
220
|
-
# fires, yields the short-circuit +Response+ — the caller's block is
|
|
221
|
-
# expected to +return+ it from +run_loop+.
|
|
222
|
-
#
|
|
223
189
|
#--
|
|
224
190
|
#: (Riffer::Agent, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Agent::Response) -> void } -> void
|
|
225
191
|
def run_before_guardrails(agent, stream_yielder, all_modifications)
|
|
@@ -233,12 +199,6 @@ module Riffer::Agent::Run
|
|
|
233
199
|
yield tripwire_response(agent, stream_yielder, tripwire, all_modifications) if tripwire
|
|
234
200
|
end
|
|
235
201
|
|
|
236
|
-
# Runs the +:after+ guardrail phase against the assistant +response+.
|
|
237
|
-
# Records any modifications into +all_modifications+ (and emits them
|
|
238
|
-
# when streaming). When a tripwire fires, yields the short-circuit
|
|
239
|
-
# +Response+ — the caller's block is expected to +return+ it from
|
|
240
|
-
# +run_loop+. Otherwise returns the post-guardrails assistant message.
|
|
241
|
-
#
|
|
242
202
|
#--
|
|
243
203
|
#: (Riffer::Agent, Riffer::Messages::Assistant, Enumerator::Yielder?, Array[Riffer::Guardrails::Modification]) { (Riffer::Agent::Response) -> void } -> untyped
|
|
244
204
|
def run_after_guardrails(agent, response, stream_yielder, all_modifications)
|
|
@@ -265,6 +225,13 @@ module Riffer::Agent::Run
|
|
|
265
225
|
agent.structured_output.parse_and_validate(response.content).object
|
|
266
226
|
end
|
|
267
227
|
|
|
228
|
+
#--
|
|
229
|
+
#: (Riffer::Agent) -> Array[singleton(Riffer::Tool)]
|
|
230
|
+
def effective_tools(agent)
|
|
231
|
+
discovered = agent.context.discovered_tools || []
|
|
232
|
+
discovered.empty? ? agent.tools : agent.tools + discovered
|
|
233
|
+
end
|
|
234
|
+
|
|
268
235
|
#--
|
|
269
236
|
#: (Riffer::Agent) -> Hash[Symbol, untyped]
|
|
270
237
|
def merged_model_options(agent)
|
|
@@ -280,24 +247,18 @@ module Riffer::Agent::Run
|
|
|
280
247
|
Riffer::Agent::Response.new(content, tripwire: tripwire, modifications: modifications, interrupted: interrupted, interrupt_reason: interrupt_reason, structured_output: structured_output, messages: messages.frozen? ? messages : messages.dup.freeze, healed_tool_call_ids: healed_tool_call_ids)
|
|
281
248
|
end
|
|
282
249
|
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
# +files+ are supplied without a +prompt+ — the provider needs text to
|
|
286
|
-
# anchor the attachments.
|
|
287
|
-
#
|
|
250
|
+
# Raises when +files+ are supplied without a +prompt+ — the provider needs
|
|
251
|
+
# text to anchor the attachments.
|
|
288
252
|
#--
|
|
289
253
|
#: (Riffer::Agent, String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?) -> void
|
|
290
254
|
def append_user_message(agent, prompt, files: nil)
|
|
291
255
|
raise Riffer::ArgumentError, "files: requires a prompt" if files && !files.empty? && prompt.nil?
|
|
292
256
|
return unless prompt
|
|
293
257
|
|
|
294
|
-
file_parts = (files || []).map { |f|
|
|
258
|
+
file_parts = (files || []).map { |f| Riffer::Messages::FilePart.from_hash(f) }
|
|
295
259
|
agent.session.add(Riffer::Messages::User.new(prompt, files: file_parts), silent: true)
|
|
296
260
|
end
|
|
297
261
|
|
|
298
|
-
# Accumulates token usage into +agent.context.token_usage+. Updates the
|
|
299
|
-
# context so cumulative usage persists across every run on the agent.
|
|
300
|
-
#
|
|
301
262
|
#--
|
|
302
263
|
#: (Riffer::Agent, Riffer::Providers::TokenUsage?) -> void
|
|
303
264
|
def track_token_usage(agent, usage)
|
|
@@ -3,37 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# +Riffer::Agent#to_h+ / +Riffer::Agent.from_h+ delegators.
|
|
6
|
+
# Turns a resolved agent into a self-contained, provider-neutral data hash and
|
|
7
|
+
# back into a runnable agent, behind the +Riffer::Agent#to_h+ /
|
|
8
|
+
# +Riffer::Agent.from_h+ delegators.
|
|
10
9
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# - <b>In-process</b> (a monolith persisting agent definitions): pass a
|
|
15
|
-
# +tool_resolver+ that looks tool descriptors up in a local registry and
|
|
16
|
-
# returns the real, body-bearing classes. They run on the default runtime.
|
|
17
|
-
# - <b>Distributed</b> (a receiver holding only the Riffer gem): the default
|
|
18
|
-
# resolver synthesizes body-less tool shells; inject a remote
|
|
19
|
-
# +Riffer::Tools::Runtime+ to forward each call back to the origin.
|
|
20
|
-
#
|
|
21
|
-
# data = Riffer::Agent::Serializer.to_h(agent: agent)
|
|
22
|
-
# rebuilt = Riffer::Agent::Serializer.from_h(data, context: {tenant: "acme"})
|
|
23
|
-
#
|
|
24
|
-
# == What does not transfer
|
|
25
|
-
#
|
|
26
|
-
# Guardrails and the skills subsystem (backend/adapter/catalog) are not
|
|
27
|
-
# serialized; a rebuilt agent enforces no guardrails and renders no skills
|
|
28
|
-
# catalog (the +skill_activate+ tool, if present, crosses as an ordinary
|
|
29
|
-
# tool). Secrets must not be placed in +provider_options+/+model_options+:
|
|
30
|
-
# both ride on the wire as plain data.
|
|
10
|
+
# hash = Riffer::Agent::Serializer.to_h(agent: agent)
|
|
11
|
+
# rebuilt = Riffer::Agent::Serializer.from_h(hash, context: {tenant: "acme"})
|
|
31
12
|
module Riffer::Agent::Serializer
|
|
32
13
|
extend self
|
|
33
14
|
|
|
34
|
-
# The wire format version
|
|
35
|
-
#
|
|
36
|
-
# dispatch seam that carries back-compat decoders.
|
|
15
|
+
# The wire format version, bumped only on an incompatible change to the hash
|
|
16
|
+
# shape; +from_h+ refuses any other version.
|
|
37
17
|
SCHEMA_VERSION = 1 #: Integer
|
|
38
18
|
|
|
39
19
|
# Raised by +from_h+ when the hash's +schema_version+ is unsupported.
|
|
@@ -43,16 +23,9 @@ module Riffer::Agent::Serializer
|
|
|
43
23
|
# descriptor. Its +#call+ raises — route shells through a remote runtime.
|
|
44
24
|
DEFAULT_TOOL_RESOLVER = ->(descriptor) { build_tool_shell(descriptor) } #: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool)
|
|
45
25
|
|
|
46
|
-
# Snapshots a resolved agent into a self-contained wire hash.
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# already been evaluated against the agent's own context, so the hash
|
|
50
|
-
# carries plain strings/data, never Procs. Tools are emitted as
|
|
51
|
-
# +{name, description, parameters_schema, timeout}+ descriptors (the
|
|
52
|
-
# resolved +agent.tools+, including MCP tools and +skill_activate+).
|
|
53
|
-
#
|
|
54
|
-
# [agent] a resolved Riffer::Agent instance.
|
|
55
|
-
#
|
|
26
|
+
# Snapshots a resolved agent into a self-contained wire hash. Proc-based
|
|
27
|
+
# settings are already evaluated against the agent's context, so the hash
|
|
28
|
+
# carries plain data, never Procs.
|
|
56
29
|
#--
|
|
57
30
|
#: (agent: Riffer::Agent) -> Hash[Symbol, untyped]
|
|
58
31
|
def to_h(agent:)
|
|
@@ -71,35 +44,17 @@ module Riffer::Agent::Serializer
|
|
|
71
44
|
}
|
|
72
45
|
end
|
|
73
46
|
|
|
74
|
-
# Reconstructs a runnable agent from a wire hash.
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
# config (the hash is already resolved); it is threaded into tool dispatch
|
|
80
|
-
# and read by tools/runtimes at call time (e.g. a remote runtime keying off
|
|
81
|
-
# <tt>context[:tenant]</tt>). Defaults to an empty context.
|
|
82
|
-
# [session] an optional Riffer::Agent::Session to seed conversation history,
|
|
83
|
-
# forwarded verbatim to +Agent.new(session:)+. The hash carries the agent
|
|
84
|
-
# *definition*, not its history (see "What does not transfer"); pass a
|
|
85
|
-
# session here to resume a persisted conversation. Used as-is — the caller
|
|
86
|
-
# owns its contents, including the system instruction message. When omitted,
|
|
87
|
-
# a fresh session seeded with the hash's instructions is built.
|
|
88
|
-
# [tool_resolver] maps a tool descriptor to a Riffer::Tool class. Defaults
|
|
89
|
-
# to DEFAULT_TOOL_RESOLVER (body-less shells). Pass a registry lookup to
|
|
90
|
-
# rebuild real, in-process tools.
|
|
91
|
-
# [tool_runtime] an optional Riffer::Tools::Runtime to inject (e.g. a
|
|
92
|
-
# remote runtime for shells). When omitted, the agent uses the configured
|
|
93
|
-
# default (+Riffer.config.tool_runtime+).
|
|
94
|
-
#
|
|
95
|
-
# Raises Riffer::Agent::Serializer::VersionError on an unsupported
|
|
96
|
-
# +schema_version+, and Riffer::ArgumentError on a malformed hash.
|
|
47
|
+
# Reconstructs a runnable agent from a wire hash. +context+ is threaded into
|
|
48
|
+
# tool dispatch (not used to re-resolve the already-resolved config);
|
|
49
|
+
# +session+ seeds conversation history (the hash carries the agent definition,
|
|
50
|
+
# not its history). Raises Riffer::Agent::Serializer::VersionError on an
|
|
51
|
+
# unsupported +schema_version+.
|
|
97
52
|
#
|
|
98
53
|
#--
|
|
99
54
|
#: (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
100
55
|
def from_h(hash, context: nil, session: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
101
56
|
# Version -> decoder dispatch. Adding a +when 2+ arm (a backwards-compatible
|
|
102
|
-
# decoder) is how a future breaking change keeps older
|
|
57
|
+
# decoder) is how a future breaking change keeps older hashes readable.
|
|
103
58
|
case hash[:schema_version]
|
|
104
59
|
when SCHEMA_VERSION
|
|
105
60
|
decode_v1(hash, context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
@@ -108,19 +63,15 @@ module Riffer::Agent::Serializer
|
|
|
108
63
|
end
|
|
109
64
|
end
|
|
110
65
|
|
|
111
|
-
# Snapshots a resolved agent to a JSON string.
|
|
112
|
-
# <tt>JSON.generate(to_h(agent:))</tt>.
|
|
113
|
-
#
|
|
66
|
+
# Snapshots a resolved agent to a JSON string.
|
|
114
67
|
#--
|
|
115
68
|
#: (agent: Riffer::Agent) -> String
|
|
116
69
|
def to_json(agent:)
|
|
117
70
|
JSON.generate(to_h(agent: agent))
|
|
118
71
|
end
|
|
119
72
|
|
|
120
|
-
# Reconstructs a runnable agent from a JSON string produced by +to_json+.
|
|
121
|
-
# Handles the JSON parse (with symbol keys) so callers don't have to. See
|
|
73
|
+
# Reconstructs a runnable agent from a JSON string produced by +to_json+. See
|
|
122
74
|
# +from_h+ for the arguments.
|
|
123
|
-
#
|
|
124
75
|
#--
|
|
125
76
|
#: (String, ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent
|
|
126
77
|
def from_json(json, context: nil, session: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
@@ -162,20 +113,16 @@ module Riffer::Agent::Serializer
|
|
|
162
113
|
Riffer::Params.from_json_schema(schema)
|
|
163
114
|
end
|
|
164
115
|
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
# awkward (e.g. proto3, which can't tell null from an absent field). The
|
|
168
|
-
# magic value lives only on the wire — +encode_max_steps+/+decode_max_steps+
|
|
169
|
-
# translate at the boundary so neither the DSL nor consumers see it.
|
|
116
|
+
# Encodes unlimited steps (+nil+ in the DSL) as +-1+ on the wire, where a
|
|
117
|
+
# JSON +null+ is awkward across transports (e.g. proto3).
|
|
170
118
|
#--
|
|
171
119
|
#: (Numeric?) -> Numeric
|
|
172
120
|
def encode_max_steps(value)
|
|
173
121
|
value.nil? ? -1 : value
|
|
174
122
|
end
|
|
175
123
|
|
|
176
|
-
# Reverses +encode_max_steps
|
|
177
|
-
#
|
|
178
|
-
# become an unbounded loop.
|
|
124
|
+
# Reverses +encode_max_steps+; a missing key falls back to the default so a
|
|
125
|
+
# partial hash can't become an unbounded loop.
|
|
179
126
|
#--
|
|
180
127
|
#: (Hash[Symbol, untyped]) -> Numeric?
|
|
181
128
|
def decode_max_steps(hash)
|
|
@@ -189,12 +136,6 @@ module Riffer::Agent::Serializer
|
|
|
189
136
|
tool_class.to_tool_schema(strict: false).merge(timeout: tool_class.timeout)
|
|
190
137
|
end
|
|
191
138
|
|
|
192
|
-
# Builds an anonymous, body-less Riffer::Tool subclass that advertises the
|
|
193
|
-
# descriptor's schema to the LLM. Its +#call+ raises — a shell only has
|
|
194
|
-
# identity, not behavior; route its calls through a remote runtime.
|
|
195
|
-
#
|
|
196
|
-
# Returns +untyped+: steep can't see that +Class.new(Riffer::Tool)+ is a
|
|
197
|
-
# +singleton(Riffer::Tool)+ (cf. Riffer::Mcp::ToolFactory#build_tool_class).
|
|
198
139
|
#--
|
|
199
140
|
#: (Hash[Symbol, untyped]) -> untyped
|
|
200
141
|
def build_tool_shell(descriptor)
|
|
@@ -1,39 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# +Riffer.config.experimental_history_healing+: when the flag is off the
|
|
8
|
-
# function returns its input unchanged.
|
|
9
|
-
#
|
|
10
|
-
# Two seams:
|
|
11
|
-
#
|
|
12
|
-
# - +fill_orphans+ — fills orphan +tool_use+ blocks with placeholder
|
|
13
|
-
# results. Used on interrupt (caller-issued or +max_steps+).
|
|
14
|
-
# - +prune_orphans+ — drops orphan +tool_use+ blocks and parentless Tool
|
|
15
|
-
# messages from a caller-provided seed so it is well-formed before the
|
|
16
|
-
# next inference call. Used at construction time when
|
|
17
|
-
# +Riffer::Agent.new(session:)+ receives a session.
|
|
4
|
+
# Pure, stateless transformations keeping the +tool_use+ ↔ +tool_result+
|
|
5
|
+
# invariant on a message array. Each entry point no-ops when
|
|
6
|
+
# +Riffer.config.experimental_history_healing+ is off.
|
|
18
7
|
module Riffer::Agent::Session::Repair
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
# Placeholder response filled in for an orphaned +tool_use+ on interrupt.
|
|
21
11
|
ORPHAN_PLACEHOLDER = ->(_tool_call) {
|
|
22
12
|
Riffer::Tools::Response.error("Tool call interrupted before completion.", type: :interrupted)
|
|
23
13
|
} #: ^(Riffer::Messages::Assistant::ToolCall) -> Riffer::Tools::Response
|
|
24
14
|
|
|
25
|
-
# Fills
|
|
26
|
-
#
|
|
27
|
-
# inserted immediately after its parent assistant message. Returns
|
|
28
|
-
# +[new_messages, filled_call_ids]+; +filled_call_ids+ is empty when
|
|
29
|
-
# there are no orphans.
|
|
30
|
-
#
|
|
31
|
-
# No-op when +Riffer.config.experimental_history_healing+ is off:
|
|
32
|
-
# returns +[messages, []]+ with the same array reference.
|
|
33
|
-
#
|
|
15
|
+
# Fills each orphaned +tool_use+ in +messages+ with an +ORPHAN_PLACEHOLDER+
|
|
16
|
+
# result inserted after its parent. Returns +[new_messages, filled_call_ids]+.
|
|
34
17
|
#--
|
|
35
18
|
#: (Array[Riffer::Messages::Base]) -> [Array[Riffer::Messages::Base], Array[String]]
|
|
36
|
-
def
|
|
19
|
+
def fill_orphans(messages)
|
|
37
20
|
return [messages, []] unless Riffer.config.experimental_history_healing
|
|
38
21
|
|
|
39
22
|
result_ids = messages.filter_map { |m| m.tool_call_id if m.is_a?(Riffer::Messages::Tool) }
|
|
@@ -62,22 +45,13 @@ module Riffer::Agent::Session::Repair
|
|
|
62
45
|
[new_messages, filled]
|
|
63
46
|
end
|
|
64
47
|
|
|
65
|
-
# Prunes a seeded message array
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# Pending tool_calls on the resume boundary — the last assistant whose
|
|
71
|
-
# tail is purely Tool results (or empty) — are preserved. They get
|
|
72
|
-
# swept up by +execute_pending_tool_calls+ at the start of the next
|
|
73
|
-
# generate/stream call.
|
|
74
|
-
#
|
|
75
|
-
# No-op when +Riffer.config.experimental_history_healing+ is off:
|
|
76
|
-
# returns +messages+ unchanged.
|
|
77
|
-
#
|
|
48
|
+
# Prunes a seeded message array to the invariant — dropping orphaned tool
|
|
49
|
+
# exchanges and parentless Tool messages, but preserving the pending
|
|
50
|
+
# tool_calls on the resume boundary (the last assistant) for
|
|
51
|
+
# +execute_pending_tool_calls+. Returns a new array.
|
|
78
52
|
#--
|
|
79
53
|
#: (Array[Riffer::Messages::Base]) -> Array[Riffer::Messages::Base]
|
|
80
|
-
def
|
|
54
|
+
def prune_orphans(messages)
|
|
81
55
|
return messages unless Riffer.config.experimental_history_healing
|
|
82
56
|
|
|
83
57
|
resume_boundary = (messages.length - 1).downto(0).find { |idx|
|
data/lib/riffer/agent/session.rb
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# Access via +agent.session+. Sessions are constructed by +Riffer::Agent+
|
|
9
|
-
# and live for the lifetime of the agent.
|
|
4
|
+
# Owns the conversation handle for an agent: the message array, the
|
|
5
|
+
# +on_message+ callbacks, and the +tool_use+ ↔ +tool_result+ invariant that
|
|
6
|
+
# keeps tool calls and their results consistent.
|
|
10
7
|
#
|
|
11
8
|
# agent.session.add(msg) # append + fire callbacks
|
|
12
9
|
# agent.session.set([msg1, msg2]) # bulk replace (silent)
|
|
@@ -31,12 +28,6 @@ class Riffer::Agent::Session
|
|
|
31
28
|
end
|
|
32
29
|
|
|
33
30
|
# Registers a callback invoked once per message appended via +#add+.
|
|
34
|
-
#
|
|
35
|
-
# Callbacks do NOT fire for +#set+, +#unset+, +#remove+, or +#update+.
|
|
36
|
-
# Returns +self+ to allow chaining.
|
|
37
|
-
#
|
|
38
|
-
# Raises Riffer::ArgumentError if no block is given.
|
|
39
|
-
#
|
|
40
31
|
#--
|
|
41
32
|
#: () { (Riffer::Messages::Base) -> void } -> self
|
|
42
33
|
def on_message(&block)
|
|
@@ -45,13 +36,9 @@ class Riffer::Agent::Session
|
|
|
45
36
|
self
|
|
46
37
|
end
|
|
47
38
|
|
|
48
|
-
# Appends +message+ and fires every registered callback once with it.
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
# non-inference inputs like user messages, which subscribers don't
|
|
52
|
-
# expect to observe through the callback channel. Inference-produced
|
|
53
|
-
# messages (Assistant, Tool) always go through +add+ without +silent+.
|
|
54
|
-
#
|
|
39
|
+
# Appends +message+ and fires every registered callback once with it. Pass
|
|
40
|
+
# +silent: true+ to skip callbacks — used for non-inference inputs like user
|
|
41
|
+
# messages that subscribers don't expect on the callback channel.
|
|
55
42
|
#--
|
|
56
43
|
#: (Riffer::Messages::Base, ?silent: bool) -> Riffer::Messages::Base
|
|
57
44
|
def add(message, silent: false)
|
|
@@ -60,13 +47,7 @@ class Riffer::Agent::Session
|
|
|
60
47
|
message
|
|
61
48
|
end
|
|
62
49
|
|
|
63
|
-
# Replaces the message history wholesale
|
|
64
|
-
# callbacks; registered callbacks persist across the swap.
|
|
65
|
-
#
|
|
66
|
-
# Used for seeding, guardrail rewrites, and history healing — cases
|
|
67
|
-
# where firing callbacks would double-emit messages that subscribers
|
|
68
|
-
# have already observed (or never produced).
|
|
69
|
-
#
|
|
50
|
+
# Replaces the message history wholesale
|
|
70
51
|
#--
|
|
71
52
|
#: (Array[Riffer::Messages::Base]) -> self
|
|
72
53
|
def set(messages)
|
|
@@ -74,9 +55,7 @@ class Riffer::Agent::Session
|
|
|
74
55
|
self
|
|
75
56
|
end
|
|
76
57
|
|
|
77
|
-
# Clears the session.
|
|
78
|
-
# callbacks persist.
|
|
79
|
-
#
|
|
58
|
+
# Clears the session.
|
|
80
59
|
#--
|
|
81
60
|
#: () -> self
|
|
82
61
|
def unset
|
|
@@ -84,18 +63,10 @@ class Riffer::Agent::Session
|
|
|
84
63
|
self
|
|
85
64
|
end
|
|
86
65
|
|
|
87
|
-
# Removes a message by id
|
|
88
|
-
#
|
|
89
|
-
# +
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
# Raises Riffer::ArgumentError when called on a +Riffer::Messages::Tool+
|
|
93
|
-
# message — that would orphan the parent's +tool_use+. Use
|
|
94
|
-
# +#update+ to rewrite a tool result instead.
|
|
95
|
-
#
|
|
96
|
-
# Returns the removed message, or +nil+ when no message has the given id
|
|
97
|
-
# (idempotent).
|
|
98
|
-
#
|
|
66
|
+
# Removes a message by id, cascading to drop the +Tool+ results of a removed
|
|
67
|
+
# assistant's +tool_calls+ so the +tool_use+ ↔ +tool_result+ invariant holds.
|
|
68
|
+
# Raises on a +Tool+ message — that would orphan its parent; use +#update+
|
|
69
|
+
# instead. Returns +nil+ if no message matches.
|
|
99
70
|
#--
|
|
100
71
|
#: (id: String) -> Riffer::Messages::Base?
|
|
101
72
|
def remove(id:)
|
|
@@ -118,19 +89,10 @@ class Riffer::Agent::Session
|
|
|
118
89
|
target
|
|
119
90
|
end
|
|
120
91
|
|
|
121
|
-
# Partial in-place update
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
# When the target is an assistant message and the update drops one or more
|
|
127
|
-
# entries from +tool_calls+, every +Riffer::Messages::Tool+ result whose
|
|
128
|
-
# +tool_call_id+ matches a dropped call is removed atomically — keeping the
|
|
129
|
-
# +tool_use+ ↔ +tool_result+ invariant intact.
|
|
130
|
-
#
|
|
131
|
-
# Raises Riffer::ArgumentError when neither or both lookup keys are
|
|
132
|
-
# provided, or when no message matches.
|
|
133
|
-
#
|
|
92
|
+
# Partial in-place update: looks up a message by +id:+ or +tool_call_id:+
|
|
93
|
+
# (exactly one), overlays +attrs+ onto a same-type replacement, and swaps it
|
|
94
|
+
# in. Dropping +tool_calls+ from an assistant cascades to remove their +Tool+
|
|
95
|
+
# results, preserving the invariant. Raises on neither/both keys or no match.
|
|
134
96
|
#--
|
|
135
97
|
#: (?id: String?, ?tool_call_id: String?, **untyped) -> Riffer::Messages::Base
|
|
136
98
|
def update(id: nil, tool_call_id: nil, **attrs)
|
|
@@ -155,12 +117,9 @@ class Riffer::Agent::Session
|
|
|
155
117
|
replacement
|
|
156
118
|
end
|
|
157
119
|
|
|
158
|
-
# Returns the call_ids of every +tool_call+
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
# Zero-cost validation hook for callers that want to check the
|
|
162
|
-
# +tool_use+ ↔ +tool_result+ invariant before mutating or persisting.
|
|
163
|
-
#
|
|
120
|
+
# Returns the call_ids of every +tool_call+ with no matching
|
|
121
|
+
# +Riffer::Messages::Tool+ result anywhere in history — a hook for checking
|
|
122
|
+
# the +tool_use+ ↔ +tool_result+ invariant before mutating or persisting.
|
|
164
123
|
#--
|
|
165
124
|
#: () -> Array[String]
|
|
166
125
|
def orphaned_tool_call_ids
|
|
@@ -171,10 +130,8 @@ class Riffer::Agent::Session
|
|
|
171
130
|
}
|
|
172
131
|
end
|
|
173
132
|
|
|
174
|
-
# Returns +[
|
|
175
|
-
#
|
|
176
|
-
# element is an empty array.
|
|
177
|
-
#
|
|
133
|
+
# Returns +[last_assistant, pending_tool_calls]+; the second element is empty
|
|
134
|
+
# when there's no assistant message or no pending calls.
|
|
178
135
|
#--
|
|
179
136
|
#: () -> [Riffer::Messages::Assistant?, Array[Riffer::Messages::Assistant::ToolCall]]
|
|
180
137
|
def pending_tool_calls
|
|
@@ -191,6 +148,7 @@ class Riffer::Agent::Session
|
|
|
191
148
|
[assistant, assistant.tool_calls.reject { |tc| executed_ids.include?(tc.call_id) }]
|
|
192
149
|
end
|
|
193
150
|
|
|
151
|
+
# Yields each message in order, or returns an Enumerator without a block.
|
|
194
152
|
#--
|
|
195
153
|
#: () -> Enumerator[Riffer::Messages::Base, self]
|
|
196
154
|
#: () { (Riffer::Messages::Base) -> void } -> untyped
|
|
@@ -199,10 +157,8 @@ class Riffer::Agent::Session
|
|
|
199
157
|
@messages.each(&block)
|
|
200
158
|
end
|
|
201
159
|
|
|
202
|
-
# The number of LLM steps completed
|
|
203
|
-
# count of assistant messages. Used by the agent loop to enforce
|
|
160
|
+
# The number of LLM steps completed, used by the agent loop to enforce
|
|
204
161
|
# +max_steps+ on resume.
|
|
205
|
-
#
|
|
206
162
|
#--
|
|
207
163
|
#: () -> Integer
|
|
208
164
|
def steps
|
|
@@ -223,6 +179,7 @@ class Riffer::Agent::Session
|
|
|
223
179
|
|
|
224
180
|
private
|
|
225
181
|
|
|
182
|
+
#--
|
|
226
183
|
#: (Riffer::Messages::Base, Riffer::Messages::Base) -> void
|
|
227
184
|
def cascade_dropped_tool_calls(old, replacement)
|
|
228
185
|
return unless old.is_a?(Riffer::Messages::Assistant)
|
|
@@ -234,6 +191,7 @@ class Riffer::Agent::Session
|
|
|
234
191
|
@messages.reject! { |m| m.is_a?(Riffer::Messages::Tool) && removed_ids.include?(m.tool_call_id) }
|
|
235
192
|
end
|
|
236
193
|
|
|
194
|
+
#--
|
|
237
195
|
#: (Riffer::Messages::Base, Hash[Symbol, untyped]) -> Riffer::Messages::Base
|
|
238
196
|
def rebuild_message(old, attrs)
|
|
239
197
|
case old
|
|
@@ -2,19 +2,11 @@
|
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
4
|
# Wraps the result of structured output parsing and validation.
|
|
5
|
-
#
|
|
6
|
-
# On success, +object+ contains the validated Hash and +error+ is nil.
|
|
7
|
-
# On failure, +error+ contains the error message and +object+ is nil.
|
|
8
|
-
#
|
|
9
|
-
# result = structured_output.parse_and_validate(json_string)
|
|
10
|
-
# if result.success?
|
|
11
|
-
# result.object #=> {sentiment: "positive", score: 0.9}
|
|
12
|
-
# else
|
|
13
|
-
# result.error #=> "JSON parse error: ..."
|
|
14
|
-
# end
|
|
15
|
-
#
|
|
16
5
|
class Riffer::Agent::StructuredOutput::Result
|
|
6
|
+
# The validated object, or +nil+ on failure.
|
|
17
7
|
attr_reader :object #: Hash[Symbol, untyped]?
|
|
8
|
+
|
|
9
|
+
# The error message, or +nil+ on success.
|
|
18
10
|
attr_reader :error #: String?
|
|
19
11
|
|
|
20
12
|
#--
|