riffer 0.48.0 → 0.50.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/.claude/rules/comments.md +2 -4
- data/.claude/rules/rbs-inline.md +2 -12
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +32 -0
- data/docs/AGENTS.md +3 -5
- data/docs/AGENT_LIFECYCLE.md +13 -16
- data/docs/CONFIGURATION.md +22 -33
- data/docs/STREAM_EVENTS.md +3 -3
- data/docs/TOOL_ADVANCED.md +1 -3
- data/docs/TRACING.md +1 -1
- data/docs/providers/AMAZON_BEDROCK.md +31 -0
- data/docs/providers/ANTHROPIC.md +10 -0
- data/docs/providers/AZURE_OPENAI.md +2 -0
- data/docs/providers/OPENAI.md +22 -0
- data/docs/providers/OPENROUTER.md +18 -1
- data/docs-site/build.rb +0 -6
- data/docs-site/check.rb +1 -5
- data/lib/riffer/agent/config.rb +4 -45
- data/lib/riffer/agent/context.rb +2 -26
- data/lib/riffer/agent/outcome.rb +0 -20
- data/lib/riffer/agent/response.rb +0 -38
- data/lib/riffer/agent/run.rb +11 -43
- data/lib/riffer/agent/serializer.rb +10 -39
- data/lib/riffer/agent/session/repair.rb +3 -15
- data/lib/riffer/agent/session.rb +25 -38
- data/lib/riffer/agent/structured_output/result.rb +0 -8
- data/lib/riffer/agent/structured_output.rb +0 -7
- data/lib/riffer/agent.rb +10 -130
- data/lib/riffer/config/amazon_bedrock.rb +30 -0
- data/lib/riffer/config/anthropic.rb +21 -0
- data/lib/riffer/config/azure_open_ai.rb +30 -0
- data/lib/riffer/config/evals.rb +18 -0
- data/lib/riffer/config/files.rb +61 -0
- data/lib/riffer/config/gemini.rb +21 -0
- data/lib/riffer/config/mcp.rb +31 -0
- data/lib/riffer/config/open_ai.rb +30 -0
- data/lib/riffer/config/open_router.rb +21 -0
- data/lib/riffer/config/pricing/rates.rb +36 -0
- data/lib/riffer/config/pricing.rb +64 -0
- data/lib/riffer/config/skills.rb +38 -0
- data/lib/riffer/config/tracing.rb +43 -0
- data/lib/riffer/config.rb +3 -362
- data/lib/riffer/evals/evaluator.rb +1 -29
- data/lib/riffer/evals/evaluator_runner.rb +0 -15
- data/lib/riffer/evals/judge.rb +0 -8
- data/lib/riffer/evals/result.rb +1 -11
- data/lib/riffer/evals/run_result.rb +0 -12
- data/lib/riffer/evals/scenario_result.rb +0 -19
- data/lib/riffer/files/downloader.rb +2 -3
- data/lib/riffer/files/resolver.rb +2 -7
- data/lib/riffer/guardrail.rb +2 -22
- data/lib/riffer/guardrails/modification.rb +0 -8
- data/lib/riffer/guardrails/result.rb +0 -17
- data/lib/riffer/guardrails/runner.rb +2 -11
- data/lib/riffer/guardrails/tripwire.rb +0 -8
- data/lib/riffer/guardrails.rb +0 -2
- data/lib/riffer/helpers/boolean.rb +0 -4
- data/lib/riffer/helpers/call_or_value.rb +0 -3
- data/lib/riffer/helpers/deep_dup.rb +3 -8
- data/lib/riffer/helpers/dependencies.rb +0 -4
- data/lib/riffer/helpers/identifier.rb +3 -12
- data/lib/riffer/helpers/validate.rb +42 -0
- data/lib/riffer/mcp/authenticated_tool.rb +4 -12
- data/lib/riffer/mcp/client.rb +0 -7
- data/lib/riffer/mcp/manifest.rb +3 -7
- data/lib/riffer/mcp/registration.rb +0 -10
- data/lib/riffer/mcp/registry.rb +0 -9
- data/lib/riffer/mcp/search_tool.rb +0 -4
- data/lib/riffer/mcp/tool.rb +1 -4
- data/lib/riffer/mcp/tool_factory.rb +3 -6
- data/lib/riffer/mcp.rb +2 -23
- data/lib/riffer/messages/assistant/reasoning_part.rb +4 -21
- data/lib/riffer/messages/assistant/tool_call.rb +1 -9
- data/lib/riffer/messages/assistant.rb +1 -21
- data/lib/riffer/messages/base.rb +0 -12
- data/lib/riffer/messages/system.rb +0 -3
- data/lib/riffer/messages/tool.rb +0 -15
- data/lib/riffer/messages/user/file_part.rb +2 -30
- data/lib/riffer/messages/user.rb +0 -4
- data/lib/riffer/params/boolean.rb +1 -5
- data/lib/riffer/params/param.rb +3 -31
- data/lib/riffer/params.rb +8 -41
- data/lib/riffer/providers/amazon_bedrock.rb +98 -46
- data/lib/riffer/providers/anthropic.rb +65 -65
- data/lib/riffer/providers/azure_open_ai.rb +10 -8
- data/lib/riffer/providers/base.rb +7 -28
- data/lib/riffer/providers/finish_reason.rb +0 -6
- data/lib/riffer/providers/gemini/client.rb +4 -17
- data/lib/riffer/providers/gemini.rb +4 -10
- data/lib/riffer/providers/mock.rb +1 -26
- data/lib/riffer/providers/open_ai.rb +94 -46
- data/lib/riffer/providers/open_router.rb +105 -30
- data/lib/riffer/providers/repository.rb +2 -14
- data/lib/riffer/providers/token_usage.rb +5 -14
- data/lib/riffer/registrable.rb +11 -45
- data/lib/riffer/runner/fibers.rb +1 -6
- data/lib/riffer/runner/sequential.rb +0 -1
- data/lib/riffer/runner/threaded.rb +0 -3
- data/lib/riffer/runner.rb +0 -3
- data/lib/riffer/skills/activate_tool.rb +0 -3
- data/lib/riffer/skills/adapter.rb +0 -8
- data/lib/riffer/skills/backend.rb +2 -7
- data/lib/riffer/skills/config.rb +4 -20
- data/lib/riffer/skills/context.rb +0 -29
- data/lib/riffer/skills/filesystem_backend.rb +0 -7
- data/lib/riffer/skills/frontmatter.rb +2 -16
- data/lib/riffer/skills/markdown_adapter.rb +3 -6
- data/lib/riffer/skills/xml_adapter.rb +0 -3
- data/lib/riffer/stream_events/base.rb +0 -3
- data/lib/riffer/stream_events/finish_reason_done.rb +1 -6
- data/lib/riffer/stream_events/guardrail_modification.rb +0 -10
- data/lib/riffer/stream_events/guardrail_tripwire.rb +0 -10
- data/lib/riffer/stream_events/interrupt.rb +2 -12
- data/lib/riffer/stream_events/reasoning_delta.rb +0 -3
- data/lib/riffer/stream_events/reasoning_done.rb +1 -5
- data/lib/riffer/stream_events/skill_activation.rb +0 -3
- data/lib/riffer/stream_events/text_delta.rb +0 -2
- data/lib/riffer/stream_events/text_done.rb +0 -2
- data/lib/riffer/stream_events/token_usage_done.rb +0 -2
- data/lib/riffer/stream_events/tool_call_delta.rb +1 -5
- data/lib/riffer/stream_events/tool_call_done.rb +0 -5
- data/lib/riffer/stream_events/web_search_done.rb +0 -3
- data/lib/riffer/stream_events/web_search_status.rb +1 -5
- data/lib/riffer/testing/minitest.rb +4 -5
- data/lib/riffer/testing.rb +5 -38
- data/lib/riffer/tool.rb +2 -28
- data/lib/riffer/tools/response.rb +3 -32
- data/lib/riffer/tools/runtime/fibers.rb +0 -6
- data/lib/riffer/tools/runtime/inline.rb +0 -1
- data/lib/riffer/tools/runtime/threaded.rb +0 -6
- data/lib/riffer/tools/runtime.rb +5 -26
- data/lib/riffer/tools/toolable.rb +0 -37
- data/lib/riffer/tracing/capture.rb +3 -5
- data/lib/riffer/tracing/no_op.rb +0 -7
- data/lib/riffer/tracing/otel.rb +7 -16
- data/lib/riffer/tracing/stream_recorder.rb +0 -7
- data/lib/riffer/tracing.rb +4 -27
- data/lib/riffer/version.rb +1 -1
- data/lib/riffer.rb +2 -30
- data/sig/generated/riffer/agent/config.rbs +12 -48
- data/sig/generated/riffer/agent/context.rbs +2 -26
- data/sig/generated/riffer/agent/outcome.rbs +0 -20
- data/sig/generated/riffer/agent/response.rbs +1 -29
- data/sig/generated/riffer/agent/run.rbs +1 -27
- data/sig/generated/riffer/agent/serializer.rbs +2 -27
- data/sig/generated/riffer/agent/session/repair.rbs +2 -10
- data/sig/generated/riffer/agent/session.rbs +10 -36
- data/sig/generated/riffer/agent/structured_output/result.rbs +0 -7
- data/sig/generated/riffer/agent/structured_output.rbs +0 -7
- data/sig/generated/riffer/agent.rbs +5 -121
- data/sig/generated/riffer/config/amazon_bedrock.rbs +21 -0
- data/sig/generated/riffer/config/anthropic.rbs +15 -0
- data/sig/generated/riffer/config/azure_open_ai.rbs +21 -0
- data/sig/generated/riffer/config/evals.rbs +13 -0
- data/sig/generated/riffer/config/files.rbs +43 -0
- data/sig/generated/riffer/config/gemini.rbs +15 -0
- data/sig/generated/riffer/config/mcp.rbs +19 -0
- data/sig/generated/riffer/config/open_ai.rbs +21 -0
- data/sig/generated/riffer/config/open_router.rbs +15 -0
- data/sig/generated/riffer/config/pricing/rates.rbs +19 -0
- data/sig/generated/riffer/config/pricing.rbs +31 -0
- data/sig/generated/riffer/config/skills.rbs +19 -0
- data/sig/generated/riffer/config/tracing.rbs +25 -0
- data/sig/generated/riffer/config.rbs +2 -307
- data/sig/generated/riffer/evals/evaluator.rbs +0 -28
- data/sig/generated/riffer/evals/evaluator_runner.rbs +0 -14
- data/sig/generated/riffer/evals/judge.rbs +0 -7
- data/sig/generated/riffer/evals/result.rbs +1 -11
- data/sig/generated/riffer/evals/run_result.rbs +0 -12
- data/sig/generated/riffer/evals/scenario_result.rbs +0 -14
- data/sig/generated/riffer/files/resolver.rbs +0 -7
- data/sig/generated/riffer/guardrail.rbs +2 -22
- data/sig/generated/riffer/guardrails/modification.rbs +0 -6
- data/sig/generated/riffer/guardrails/result.rbs +0 -15
- data/sig/generated/riffer/guardrails/runner.rbs +0 -11
- data/sig/generated/riffer/guardrails/tripwire.rbs +0 -8
- data/sig/generated/riffer/guardrails.rbs +0 -2
- data/sig/generated/riffer/helpers/boolean.rbs +0 -4
- data/sig/generated/riffer/helpers/call_or_value.rbs +0 -3
- data/sig/generated/riffer/helpers/deep_dup.rbs +0 -8
- data/sig/generated/riffer/helpers/dependencies.rbs +0 -4
- data/sig/generated/riffer/helpers/identifier.rbs +0 -12
- data/sig/generated/riffer/helpers/validate.rbs +21 -0
- data/sig/generated/riffer/mcp/authenticated_tool.rbs +0 -5
- data/sig/generated/riffer/mcp/client.rbs +0 -7
- data/sig/generated/riffer/mcp/manifest.rbs +3 -7
- data/sig/generated/riffer/mcp/registration.rbs +0 -10
- data/sig/generated/riffer/mcp/registry.rbs +0 -9
- data/sig/generated/riffer/mcp/search_tool.rbs +0 -4
- data/sig/generated/riffer/mcp/tool.rbs +0 -4
- data/sig/generated/riffer/mcp/tool_factory.rbs +0 -6
- data/sig/generated/riffer/mcp.rbs +2 -22
- data/sig/generated/riffer/messages/assistant/reasoning_part.rbs +4 -17
- data/sig/generated/riffer/messages/assistant/tool_call.rbs +1 -9
- data/sig/generated/riffer/messages/assistant.rbs +10 -30
- data/sig/generated/riffer/messages/base.rbs +0 -12
- data/sig/generated/riffer/messages/system.rbs +0 -3
- data/sig/generated/riffer/messages/tool.rbs +0 -12
- data/sig/generated/riffer/messages/user/file_part.rbs +0 -30
- data/sig/generated/riffer/messages/user.rbs +0 -4
- data/sig/generated/riffer/params/boolean.rbs +1 -4
- data/sig/generated/riffer/params/param.rbs +0 -31
- data/sig/generated/riffer/params.rbs +4 -40
- data/sig/generated/riffer/providers/amazon_bedrock.rbs +26 -26
- data/sig/generated/riffer/providers/anthropic.rbs +23 -22
- data/sig/generated/riffer/providers/azure_open_ai.rbs +5 -8
- data/sig/generated/riffer/providers/base.rbs +0 -28
- data/sig/generated/riffer/providers/finish_reason.rbs +0 -6
- data/sig/generated/riffer/providers/gemini/client.rbs +4 -17
- data/sig/generated/riffer/providers/gemini.rbs +0 -6
- data/sig/generated/riffer/providers/mock.rbs +0 -26
- data/sig/generated/riffer/providers/open_ai.rbs +36 -19
- data/sig/generated/riffer/providers/open_router.rbs +33 -14
- data/sig/generated/riffer/providers/repository.rbs +2 -14
- data/sig/generated/riffer/providers/token_usage.rbs +5 -14
- data/sig/generated/riffer/registrable.rbs +0 -45
- data/sig/generated/riffer/runner/fibers.rbs +0 -6
- data/sig/generated/riffer/runner/sequential.rbs +0 -1
- data/sig/generated/riffer/runner/threaded.rbs +0 -3
- data/sig/generated/riffer/runner.rbs +0 -3
- data/sig/generated/riffer/skills/activate_tool.rbs +0 -3
- data/sig/generated/riffer/skills/adapter.rbs +0 -8
- data/sig/generated/riffer/skills/backend.rbs +2 -7
- data/sig/generated/riffer/skills/config.rbs +0 -20
- data/sig/generated/riffer/skills/context.rbs +0 -29
- data/sig/generated/riffer/skills/filesystem_backend.rbs +0 -7
- data/sig/generated/riffer/skills/frontmatter.rbs +2 -16
- data/sig/generated/riffer/skills/markdown_adapter.rbs +0 -6
- data/sig/generated/riffer/skills/xml_adapter.rbs +0 -3
- data/sig/generated/riffer/stream_events/base.rbs +0 -3
- data/sig/generated/riffer/stream_events/finish_reason_done.rbs +1 -6
- data/sig/generated/riffer/stream_events/guardrail_modification.rbs +0 -10
- data/sig/generated/riffer/stream_events/guardrail_tripwire.rbs +0 -10
- data/sig/generated/riffer/stream_events/interrupt.rbs +2 -10
- data/sig/generated/riffer/stream_events/reasoning_delta.rbs +0 -3
- data/sig/generated/riffer/stream_events/reasoning_done.rbs +1 -5
- data/sig/generated/riffer/stream_events/skill_activation.rbs +0 -3
- data/sig/generated/riffer/stream_events/text_delta.rbs +0 -2
- data/sig/generated/riffer/stream_events/text_done.rbs +0 -2
- data/sig/generated/riffer/stream_events/token_usage_done.rbs +0 -2
- data/sig/generated/riffer/stream_events/tool_call_delta.rbs +1 -5
- data/sig/generated/riffer/stream_events/tool_call_done.rbs +0 -5
- data/sig/generated/riffer/stream_events/web_search_done.rbs +0 -3
- data/sig/generated/riffer/stream_events/web_search_status.rbs +1 -5
- data/sig/generated/riffer/testing.rbs +0 -38
- data/sig/generated/riffer/tool.rbs +0 -27
- data/sig/generated/riffer/tools/response.rbs +3 -29
- data/sig/generated/riffer/tools/runtime/fibers.rbs +0 -5
- data/sig/generated/riffer/tools/runtime/inline.rbs +0 -1
- data/sig/generated/riffer/tools/runtime/threaded.rbs +0 -5
- data/sig/generated/riffer/tools/runtime.rbs +2 -24
- data/sig/generated/riffer/tools/toolable.rbs +0 -37
- data/sig/generated/riffer/tracing/capture.rbs +2 -5
- data/sig/generated/riffer/tracing/no_op.rbs +0 -7
- data/sig/generated/riffer/tracing/otel.rbs +7 -16
- data/sig/generated/riffer/tracing/stream_recorder.rbs +0 -2
- data/sig/generated/riffer/tracing.rbs +4 -23
- data/sig/generated/riffer.rbs +2 -29
- data/sig/manual/riffer/helpers/validate.rbs +5 -0
- metadata +30 -1
data/lib/riffer/agent/config.rb
CHANGED
|
@@ -1,46 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Typed configuration object holding every class-level DSL setting on a
|
|
5
|
-
# Riffer::Agent subclass. Procs are stored unresolved and resolved per-instance
|
|
6
|
-
# later.
|
|
7
4
|
class Riffer::Agent::Config
|
|
8
5
|
DEFAULT_MAX_STEPS = 16 #: Integer
|
|
9
6
|
|
|
10
7
|
# @rbs @tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?
|
|
11
8
|
|
|
12
|
-
# The configured agent identifier.
|
|
13
9
|
attr_reader :identifier #: String? # @dynamic identifier
|
|
14
|
-
|
|
15
|
-
# The configured model.
|
|
16
10
|
attr_reader :model #: (String | Proc)? # @dynamic model
|
|
17
|
-
|
|
18
|
-
# The configured instructions.
|
|
19
11
|
attr_reader :instructions #: (String | Proc)? # @dynamic instructions
|
|
20
|
-
|
|
21
|
-
# Options passed to generate_text/stream_text.
|
|
22
12
|
attr_accessor :model_options #: Hash[Symbol, untyped] # @dynamic model_options, model_options=
|
|
23
|
-
|
|
24
|
-
# The configured structured-output schema.
|
|
25
13
|
attr_reader :structured_output #: Riffer::Params? # @dynamic structured_output
|
|
26
|
-
|
|
27
|
-
# The maximum number of LLM call steps in the tool-use loop.
|
|
28
14
|
attr_accessor :max_steps #: Numeric? # @dynamic max_steps, max_steps=
|
|
29
|
-
|
|
30
|
-
# The configured tools.
|
|
31
15
|
attr_accessor :tools_config #: (Array[singleton(Riffer::Tool)] | Proc)? # @dynamic tools_config, tools_config=
|
|
32
|
-
|
|
33
|
-
# The accumulated +use_mcp+ tag configurations.
|
|
34
16
|
attr_reader :mcp_configs #: Array[Hash[Symbol, untyped]] # @dynamic mcp_configs
|
|
35
|
-
|
|
36
|
-
# The configured skills.
|
|
37
17
|
attr_accessor :skills_config #: Riffer::Skills::Config? # @dynamic skills_config, skills_config=
|
|
38
|
-
|
|
39
|
-
# Registered guardrail entries keyed by phase.
|
|
40
18
|
attr_reader :guardrails #: Hash[Symbol, Array[Hash[Symbol, untyped]]] # @dynamic guardrails
|
|
41
19
|
|
|
42
|
-
# Builds a new Config. Raises Riffer::ArgumentError if +model+ or
|
|
43
|
-
# +instructions+ is invalid (e.g. an empty string).
|
|
44
20
|
#--
|
|
45
21
|
#: (
|
|
46
22
|
# ?identifier: String?,
|
|
@@ -81,14 +57,12 @@ class Riffer::Agent::Config
|
|
|
81
57
|
self.tool_runtime = tool_runtime if tool_runtime
|
|
82
58
|
end
|
|
83
59
|
|
|
84
|
-
# Sets +identifier+, coercing the value to String.
|
|
85
60
|
#--
|
|
86
61
|
#: (untyped) -> String?
|
|
87
62
|
def identifier=(value)
|
|
88
63
|
@identifier = value&.to_s
|
|
89
64
|
end
|
|
90
65
|
|
|
91
|
-
# Sets +structured_output+. Raises Riffer::ArgumentError on an invalid value.
|
|
92
66
|
#--
|
|
93
67
|
#: (Riffer::Params?) -> Riffer::Params?
|
|
94
68
|
def structured_output=(value)
|
|
@@ -99,16 +73,14 @@ class Riffer::Agent::Config
|
|
|
99
73
|
@structured_output = value
|
|
100
74
|
end
|
|
101
75
|
|
|
102
|
-
# Returns the declared tool runtime, or +Riffer.config.tool_runtime+ when none
|
|
103
|
-
# was declared. Resolving the global here rather than at construction is what
|
|
104
|
-
# lets a copy tell an inherited runtime from a defaulted one.
|
|
105
76
|
#--
|
|
106
77
|
#: () -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
|
|
107
78
|
def tool_runtime
|
|
79
|
+
# Resolved here rather than at construction so a copy can tell an inherited
|
|
80
|
+
# runtime from a defaulted one.
|
|
108
81
|
@tool_runtime || Riffer.config.tool_runtime
|
|
109
82
|
end
|
|
110
83
|
|
|
111
|
-
# Sets +tool_runtime+. Raises Riffer::ArgumentError on an invalid value.
|
|
112
84
|
#--
|
|
113
85
|
#: ((singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)) -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)
|
|
114
86
|
def tool_runtime=(value)
|
|
@@ -122,8 +94,6 @@ class Riffer::Agent::Config
|
|
|
122
94
|
@tool_runtime = value
|
|
123
95
|
end
|
|
124
96
|
|
|
125
|
-
# Sets +model+. Raises Riffer::ArgumentError on an invalid value (e.g. an
|
|
126
|
-
# empty string).
|
|
127
97
|
#--
|
|
128
98
|
#: ((String | Proc)?) -> (String | Proc)?
|
|
129
99
|
def model=(value)
|
|
@@ -131,8 +101,6 @@ class Riffer::Agent::Config
|
|
|
131
101
|
@model = value
|
|
132
102
|
end
|
|
133
103
|
|
|
134
|
-
# Sets +instructions+. Raises Riffer::ArgumentError on an invalid value (e.g.
|
|
135
|
-
# an empty string).
|
|
136
104
|
#--
|
|
137
105
|
#: ((String | Proc)?) -> (String | Proc)?
|
|
138
106
|
def instructions=(value)
|
|
@@ -140,8 +108,6 @@ class Riffer::Agent::Config
|
|
|
140
108
|
@instructions = value
|
|
141
109
|
end
|
|
142
110
|
|
|
143
|
-
# Appends an MCP tag entry to +mcp_configs+.
|
|
144
|
-
#
|
|
145
111
|
#--
|
|
146
112
|
#: (String | Symbol, ?progressive: bool) -> Array[Hash[Symbol, untyped]]
|
|
147
113
|
def add_mcp(tag, progressive: true)
|
|
@@ -150,9 +116,6 @@ class Riffer::Agent::Config
|
|
|
150
116
|
@mcp_configs << { tags: [tag.to_sym], progressive: progressive }
|
|
151
117
|
end
|
|
152
118
|
|
|
153
|
-
# Appends a guardrail entry to +guardrails+ for the given phase; +:around+
|
|
154
|
-
# appends to both +:before+ and +:after+. Raises Riffer::ArgumentError unless
|
|
155
|
-
# +phase+ is :before, :after, or :around.
|
|
156
119
|
#--
|
|
157
120
|
#: (Symbol, klass: singleton(Riffer::Guardrail), ?options: Hash[Symbol, untyped]) -> void
|
|
158
121
|
def add_guardrail(phase, klass:, options: {})
|
|
@@ -175,8 +138,6 @@ class Riffer::Agent::Config
|
|
|
175
138
|
end
|
|
176
139
|
end
|
|
177
140
|
|
|
178
|
-
# Returns the guardrail entries for the given phase, or +[]+ if none.
|
|
179
|
-
#
|
|
180
141
|
#--
|
|
181
142
|
#: (Symbol) -> Array[Hash[Symbol, untyped]]
|
|
182
143
|
def guardrails_for(phase)
|
|
@@ -185,14 +146,12 @@ class Riffer::Agent::Config
|
|
|
185
146
|
|
|
186
147
|
private
|
|
187
148
|
|
|
188
|
-
# +dup+ would leave the copy sharing every collection with this one, so a
|
|
189
|
-
# declaration on either would reach the other. The nesting runs deeper than one
|
|
190
|
-
# level: an mcp entry holds its own +:tags+ array, a guardrail entry its own
|
|
191
|
-
# +:options+ hash, and +model_options+ is arbitrary.
|
|
192
149
|
#--
|
|
193
150
|
#: (Riffer::Agent::Config) -> void
|
|
194
151
|
def initialize_copy(source)
|
|
195
152
|
super
|
|
153
|
+
# A shallow copy would share collections, letting a declaration on either
|
|
154
|
+
# config reach the other; entries nest (mcp +:tags+, guardrail +:options+).
|
|
196
155
|
@model_options = Riffer::Helpers::DeepDup.call(source.model_options)
|
|
197
156
|
@mcp_configs = Riffer::Helpers::DeepDup.call(source.mcp_configs)
|
|
198
157
|
@guardrails = Riffer::Helpers::DeepDup.call(source.guardrails)
|
data/lib/riffer/agent/context.rb
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Typed value object wrapping the runtime context Hash held by a Riffer::Agent.
|
|
5
|
-
# Exposes typed +skills+, +token_usage+, +mcp_progressive_tools+, and
|
|
6
|
-
# +discovered_tools+ accessors while preserving +#[]+ / +#dig+ for caller-provided keys.
|
|
7
4
|
class Riffer::Agent::Context
|
|
8
5
|
# @rbs @data: Hash[Symbol, untyped]
|
|
9
6
|
|
|
10
7
|
RESERVED_KEYS = %i[skills token_usage mcp_progressive_tools discovered_tools].freeze #: Array[Symbol]
|
|
11
8
|
|
|
12
|
-
#
|
|
13
|
-
# don't leak in. Raises Riffer::ArgumentError if it contains a reserved key.
|
|
9
|
+
# Raises Riffer::ArgumentError if +data+ contains a reserved key.
|
|
14
10
|
#--
|
|
15
11
|
#: (?Hash[Symbol, untyped]) -> void
|
|
16
12
|
def initialize(data = {})
|
|
@@ -27,17 +23,12 @@ class Riffer::Agent::Context
|
|
|
27
23
|
@data[:discovered_tools] = nil
|
|
28
24
|
end
|
|
29
25
|
|
|
30
|
-
# The agent's resolved +Riffer::Skills::Context+, or +nil+ when skills
|
|
31
|
-
# are not configured.
|
|
32
|
-
#
|
|
33
26
|
#--
|
|
34
27
|
#: () -> Riffer::Skills::Context?
|
|
35
28
|
def skills
|
|
36
29
|
@data[:skills]
|
|
37
30
|
end
|
|
38
31
|
|
|
39
|
-
# Sets the resolved skills context. Raises Riffer::ArgumentError on an
|
|
40
|
-
# invalid value.
|
|
41
32
|
#--
|
|
42
33
|
#: (Riffer::Skills::Context?) -> Riffer::Skills::Context?
|
|
43
34
|
def skills=(value)
|
|
@@ -48,17 +39,13 @@ class Riffer::Agent::Context
|
|
|
48
39
|
@data[:skills] = value
|
|
49
40
|
end
|
|
50
41
|
|
|
51
|
-
#
|
|
52
|
-
# or +nil+ before the first response is recorded.
|
|
53
|
-
#
|
|
42
|
+
# Cumulative across every run on this agent.
|
|
54
43
|
#--
|
|
55
44
|
#: () -> Riffer::Providers::TokenUsage?
|
|
56
45
|
def token_usage
|
|
57
46
|
@data[:token_usage]
|
|
58
47
|
end
|
|
59
48
|
|
|
60
|
-
# Sets the cumulative token usage. Raises Riffer::ArgumentError on an invalid
|
|
61
|
-
# value.
|
|
62
49
|
#--
|
|
63
50
|
#: (Riffer::Providers::TokenUsage?) -> Riffer::Providers::TokenUsage?
|
|
64
51
|
def token_usage=(value)
|
|
@@ -69,22 +56,18 @@ class Riffer::Agent::Context
|
|
|
69
56
|
@data[:token_usage] = value
|
|
70
57
|
end
|
|
71
58
|
|
|
72
|
-
# Hash-style read, preserved so tools can pull caller-provided keys via
|
|
73
|
-
# <tt>context[:agent]</tt>.
|
|
74
59
|
#--
|
|
75
60
|
#: (Symbol) -> untyped
|
|
76
61
|
def [](key)
|
|
77
62
|
@data[key]
|
|
78
63
|
end
|
|
79
64
|
|
|
80
|
-
# Auth-wrapped MCP tool classes for progressive discovery, or +nil+.
|
|
81
65
|
#--
|
|
82
66
|
#: () -> Array[singleton(Riffer::Tool)]?
|
|
83
67
|
def mcp_progressive_tools
|
|
84
68
|
@data[:mcp_progressive_tools]
|
|
85
69
|
end
|
|
86
70
|
|
|
87
|
-
# Sets progressive MCP tools. Raises Riffer::ArgumentError on an invalid value.
|
|
88
71
|
#--
|
|
89
72
|
#: (Array[singleton(Riffer::Tool)]?) -> Array[singleton(Riffer::Tool)]?
|
|
90
73
|
def mcp_progressive_tools=(value)
|
|
@@ -99,15 +82,12 @@ class Riffer::Agent::Context
|
|
|
99
82
|
@data[:mcp_progressive_tools] = value
|
|
100
83
|
end
|
|
101
84
|
|
|
102
|
-
# MCP tool classes discovered during progressive search. Accumulates across
|
|
103
|
-
# +generate+ calls and is merged into the active tool list on every LLM call.
|
|
104
85
|
#--
|
|
105
86
|
#: () -> Array[singleton(Riffer::Tool)]?
|
|
106
87
|
def discovered_tools
|
|
107
88
|
@data[:discovered_tools]
|
|
108
89
|
end
|
|
109
90
|
|
|
110
|
-
# Sets the discovered tools array. Raises Riffer::ArgumentError on an invalid value.
|
|
111
91
|
#--
|
|
112
92
|
#: (Array[singleton(Riffer::Tool)]?) -> Array[singleton(Riffer::Tool)]?
|
|
113
93
|
def discovered_tools=(value)
|
|
@@ -122,8 +102,6 @@ class Riffer::Agent::Context
|
|
|
122
102
|
@data[:discovered_tools] = value
|
|
123
103
|
end
|
|
124
104
|
|
|
125
|
-
# Accumulates newly discovered MCP tool classes, deduplicating by name.
|
|
126
|
-
# Each call extends the existing set; calling multiple times is safe.
|
|
127
105
|
#--
|
|
128
106
|
#: (Array[singleton(Riffer::Tool)]) -> Array[singleton(Riffer::Tool)]
|
|
129
107
|
def discover_tools(tools)
|
|
@@ -137,8 +115,6 @@ class Riffer::Agent::Context
|
|
|
137
115
|
@data.dig(*keys)
|
|
138
116
|
end
|
|
139
117
|
|
|
140
|
-
# Returns a copy of the underlying Hash; mutating it does not affect this
|
|
141
|
-
# context.
|
|
142
118
|
#--
|
|
143
119
|
#: () -> Hash[Symbol, untyped]
|
|
144
120
|
def to_h
|
data/lib/riffer/agent/outcome.rb
CHANGED
|
@@ -1,36 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# How a run ended — the single place to read whether the agent completed
|
|
5
|
-
# normally and, if not, why. +detail+ carries the specifics when there are any:
|
|
6
|
-
# the tripwire reason, the interrupt reason, the provider's raw finish value,
|
|
7
|
-
# or the structured output parse/validation error.
|
|
8
|
-
#
|
|
9
|
-
# response = agent.generate("Analyze this")
|
|
10
|
-
# case response.outcome.reason
|
|
11
|
-
# when :completed then puts response.structured_output
|
|
12
|
-
# when :invalid_structured_output then warn response.outcome.detail
|
|
13
|
-
# end
|
|
14
4
|
class Riffer::Agent::Outcome
|
|
15
|
-
# Finish reasons that end a turn normally; every other finish reason means the
|
|
16
|
-
# provider cut the turn short and surfaces as the run's outcome verbatim.
|
|
17
5
|
NORMAL_FINISH_REASONS = %i[stop tool_calls].freeze #: Array[Symbol]
|
|
18
6
|
|
|
19
|
-
# Derived from the provider vocabulary so a new finish reason becomes an
|
|
20
|
-
# outcome without a second list to update.
|
|
21
7
|
PROVIDER_STOP_REASONS = (Riffer::Providers::FinishReason::VALUES - NORMAL_FINISH_REASONS).freeze #: Array[Symbol]
|
|
22
8
|
|
|
23
|
-
# The vocabulary every run ends in.
|
|
24
9
|
VALUES = (%i[completed guardrail_blocked interrupted max_steps invalid_structured_output] +
|
|
25
10
|
PROVIDER_STOP_REASONS).freeze #: Array[Symbol]
|
|
26
11
|
|
|
27
|
-
# Why the run ended.
|
|
28
12
|
attr_reader :reason #: Symbol # @dynamic reason
|
|
29
13
|
|
|
30
|
-
# Human-readable specifics for +reason+, when there are any.
|
|
31
14
|
attr_reader :detail #: String? # @dynamic detail
|
|
32
15
|
|
|
33
|
-
# Raises Riffer::ArgumentError when +reason+ is outside VALUES.
|
|
34
16
|
#--
|
|
35
17
|
#: (reason: Symbol, ?detail: String?) -> void
|
|
36
18
|
def initialize(reason:, detail: nil)
|
|
@@ -42,8 +24,6 @@ class Riffer::Agent::Outcome
|
|
|
42
24
|
@detail = detail
|
|
43
25
|
end
|
|
44
26
|
|
|
45
|
-
# Returns true when the run completed normally.
|
|
46
|
-
#
|
|
47
27
|
#--
|
|
48
28
|
#: () -> bool
|
|
49
29
|
def success?
|
|
@@ -1,50 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# Wraps an agent generation response. +outcome+ says how the run ended; when a
|
|
5
|
-
# guardrail blocks execution, +content+ is empty and +tripwire+ carries the
|
|
6
|
-
# block details.
|
|
7
|
-
#
|
|
8
|
-
# response = agent.generate("Hello")
|
|
9
|
-
# if response.outcome.success?
|
|
10
|
-
# puts response.content
|
|
11
|
-
# else
|
|
12
|
-
# puts "#{response.outcome.reason}: #{response.outcome.detail}"
|
|
13
|
-
# end
|
|
14
4
|
class Riffer::Agent::Response
|
|
15
|
-
# The response content.
|
|
16
5
|
attr_reader :content #: String # @dynamic content
|
|
17
|
-
|
|
18
|
-
# How the run ended.
|
|
19
6
|
attr_reader :outcome #: Riffer::Agent::Outcome # @dynamic outcome
|
|
20
|
-
|
|
21
|
-
# The tripwire if execution was blocked.
|
|
22
7
|
attr_reader :tripwire #: Riffer::Guardrails::Tripwire? # @dynamic tripwire
|
|
23
|
-
|
|
24
|
-
# The modifications made by guardrails during processing.
|
|
25
8
|
attr_reader :modifications #: Array[Riffer::Guardrails::Modification] # @dynamic modifications
|
|
26
|
-
|
|
27
|
-
# The reasoning parts on the final assistant message, if the provider
|
|
28
|
-
# produced any.
|
|
29
9
|
attr_reader :reasoning #: Array[Riffer::Messages::Assistant::ReasoningPart] # @dynamic reasoning
|
|
30
|
-
|
|
31
|
-
# The parsed structured output, if structured output was configured.
|
|
32
10
|
attr_reader :structured_output #: Hash[Symbol, untyped]? # @dynamic structured_output
|
|
33
|
-
|
|
34
|
-
# The aggregate token usage across this run's LLM calls, if any was reported.
|
|
35
11
|
attr_reader :token_usage #: Riffer::Providers::TokenUsage? # @dynamic token_usage
|
|
36
|
-
|
|
37
|
-
# The number of LLM calls made during this run (0 when a before-guardrail
|
|
38
|
-
# blocks before any call). Distinct from the session's cumulative step count.
|
|
39
12
|
attr_reader :steps #: Integer # @dynamic steps
|
|
40
|
-
|
|
41
|
-
# The full message history from the agent conversation.
|
|
42
13
|
attr_reader :messages #: Array[Riffer::Messages::Base] # @dynamic messages
|
|
43
14
|
|
|
44
|
-
# Call ids of tool_use blocks riffer filled with placeholder results this
|
|
45
|
-
# turn (when an interrupt left them unanswered and history healing is on).
|
|
46
|
-
attr_reader :healed_tool_call_ids #: Array[String] # @dynamic healed_tool_call_ids
|
|
47
|
-
|
|
48
15
|
#--
|
|
49
16
|
#: (
|
|
50
17
|
# String,
|
|
@@ -54,7 +21,6 @@ class Riffer::Agent::Response
|
|
|
54
21
|
# ?reasoning: Array[Riffer::Messages::Assistant::ReasoningPart],
|
|
55
22
|
# ?structured_output: Hash[Symbol, untyped]?,
|
|
56
23
|
# ?messages: Array[Riffer::Messages::Base],
|
|
57
|
-
# ?healed_tool_call_ids: Array[String],
|
|
58
24
|
# ?token_usage: Riffer::Providers::TokenUsage?,
|
|
59
25
|
# ?steps: Integer
|
|
60
26
|
# ) -> void
|
|
@@ -66,7 +32,6 @@ class Riffer::Agent::Response
|
|
|
66
32
|
reasoning: [],
|
|
67
33
|
structured_output: nil,
|
|
68
34
|
messages: [],
|
|
69
|
-
healed_tool_call_ids: [],
|
|
70
35
|
token_usage: nil,
|
|
71
36
|
steps: 0
|
|
72
37
|
)
|
|
@@ -77,13 +42,10 @@ class Riffer::Agent::Response
|
|
|
77
42
|
@reasoning = reasoning
|
|
78
43
|
@structured_output = structured_output
|
|
79
44
|
@messages = messages
|
|
80
|
-
@healed_tool_call_ids = healed_tool_call_ids
|
|
81
45
|
@token_usage = token_usage
|
|
82
46
|
@steps = steps
|
|
83
47
|
end
|
|
84
48
|
|
|
85
|
-
# Returns true if any guardrail modified data during processing.
|
|
86
|
-
#
|
|
87
49
|
#--
|
|
88
50
|
#: () -> bool
|
|
89
51
|
def modified?
|
data/lib/riffer/agent/run.rb
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
# rbs_inline: enabled
|
|
3
3
|
|
|
4
|
-
# The generation loop — a pure module of functions over an +agent+, which owns
|
|
5
|
-
# every per-call value; Run just orchestrates.
|
|
6
4
|
module Riffer::Agent::Run
|
|
7
5
|
extend self
|
|
8
6
|
|
|
9
|
-
# Runs the generate loop for the given agent. See Riffer::Agent#generate
|
|
10
|
-
# for prompt/files semantics.
|
|
11
|
-
#
|
|
12
7
|
#--
|
|
13
8
|
#: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::User::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Riffer::Agent::Response
|
|
14
9
|
def generate(agent:, prompt: nil, files: nil, tags: {})
|
|
@@ -16,17 +11,13 @@ module Riffer::Agent::Run
|
|
|
16
11
|
run_loop(agent, tags: tags)
|
|
17
12
|
end
|
|
18
13
|
|
|
19
|
-
# Runs the streaming loop for the given agent. See Riffer::Agent#stream
|
|
20
|
-
# for prompt/files semantics.
|
|
21
|
-
#
|
|
22
14
|
#--
|
|
23
15
|
#: (agent: Riffer::Agent, ?prompt: String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::User::FilePart]?, ?tags: Hash[(String | Symbol), untyped]) -> Enumerator[Riffer::StreamEvents::Base, Riffer::Agent::Response]
|
|
24
16
|
def stream(agent:, prompt: nil, files: nil, tags: {})
|
|
25
17
|
append_user_message(agent, prompt, files: files)
|
|
26
18
|
# The enumerator body runs in its own fiber, where the fiber-local OTEL
|
|
27
19
|
# context is empty — capture here so the run span parents to the caller's
|
|
28
|
-
# trace.
|
|
29
|
-
# cross the fiber boundary without any re-propagation.
|
|
20
|
+
# trace.
|
|
30
21
|
trace_context = Riffer::Tracing.current_context
|
|
31
22
|
Enumerator.new do |stream_yielder|
|
|
32
23
|
Riffer::Tracing.with_context(trace_context) { run_loop(agent, tags: tags, stream_yielder: stream_yielder) }
|
|
@@ -35,12 +26,6 @@ module Riffer::Agent::Run
|
|
|
35
26
|
|
|
36
27
|
private
|
|
37
28
|
|
|
38
|
-
# Both +generate+ and +stream+ funnel here, so this is the single place raw
|
|
39
|
-
# +tags+ are normalized and merged over the default tags (a caller tag wins
|
|
40
|
-
# on a shared key). The clean <tt>String => String</tt> map is then
|
|
41
|
-
# threaded to every span builder in the run as +riffer.tag.*+ and to each
|
|
42
|
-
# provider call (via +merged_model_options+) for native request-metadata
|
|
43
|
-
# mapping.
|
|
44
29
|
#--
|
|
45
30
|
#: (Riffer::Agent, ?tags: Hash[(String | Symbol), untyped]?, ?stream_yielder: Enumerator::Yielder?) -> Riffer::Agent::Response
|
|
46
31
|
def run_loop(agent, tags: {}, stream_yielder: nil)
|
|
@@ -125,20 +110,12 @@ module Riffer::Agent::Run
|
|
|
125
110
|
return final_response(agent, all_modifications, token_usage: run_usage, steps: run_steps)
|
|
126
111
|
end
|
|
127
112
|
|
|
128
|
-
|
|
129
|
-
agent.session.set(new_messages)
|
|
130
|
-
if stream_yielder
|
|
131
|
-
stream_yielder << Riffer::StreamEvents::Interrupt.new(
|
|
132
|
-
reason: reason,
|
|
133
|
-
healed_tool_call_ids: filled,
|
|
134
|
-
)
|
|
135
|
-
end
|
|
113
|
+
stream_yielder << Riffer::StreamEvents::Interrupt.new(reason: reason) if stream_yielder
|
|
136
114
|
final_response(
|
|
137
115
|
agent,
|
|
138
116
|
all_modifications,
|
|
139
117
|
interrupted: true,
|
|
140
118
|
interrupt_reason: reason,
|
|
141
|
-
healed_tool_call_ids: filled,
|
|
142
119
|
token_usage: run_usage,
|
|
143
120
|
steps: run_steps,
|
|
144
121
|
)
|
|
@@ -164,12 +141,12 @@ module Riffer::Agent::Run
|
|
|
164
141
|
|
|
165
142
|
case event
|
|
166
143
|
when Riffer::StreamEvents::TextDelta
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
# owned buffer; replace (not =) on TextDone keeps it that way so a later
|
|
170
|
-
# delta's << can never mutate the string held by a TextDone event.
|
|
144
|
+
# << rather than +=, which copies the whole buffer per delta (O(n^2)
|
|
145
|
+
# over a stream).
|
|
171
146
|
accumulated_content << event.content
|
|
172
147
|
when Riffer::StreamEvents::TextDone
|
|
148
|
+
# replace, not =, so a later delta's << can never mutate the string
|
|
149
|
+
# this event holds.
|
|
173
150
|
accumulated_content.replace(event.content)
|
|
174
151
|
when Riffer::StreamEvents::ToolCallDone
|
|
175
152
|
accumulated_tool_calls << Riffer::Messages::Assistant::ToolCall.new(
|
|
@@ -239,14 +216,12 @@ module Riffer::Agent::Run
|
|
|
239
216
|
)
|
|
240
217
|
end
|
|
241
218
|
|
|
242
|
-
# Checked in the order things happened. The loop being stopped (max_steps or
|
|
243
|
-
# an interrupt) beats the provider's finish reason, which beats riffer's own
|
|
244
|
-
# validation of the content. A truncated response that also fails the schema
|
|
245
|
-
# therefore reports :length, not :invalid_structured_output.
|
|
246
219
|
#--
|
|
247
220
|
#: (Riffer::Messages::Assistant?, Riffer::Agent::StructuredOutput::Result?, interrupted: bool, interrupt_reason: (String | Symbol)?) -> Riffer::Agent::Outcome
|
|
248
221
|
def final_outcome(message, result, interrupted:, interrupt_reason:)
|
|
249
222
|
finish_reason = message&.finish_reason
|
|
223
|
+
# Precedence follows the order things happened: a stopped loop beats the
|
|
224
|
+
# provider's finish reason, which beats schema validation of the content.
|
|
250
225
|
if interrupted && interrupt_reason == Riffer::Agent::INTERRUPT_MAX_STEPS
|
|
251
226
|
Riffer::Agent::Outcome.new(reason: :max_steps)
|
|
252
227
|
elsif interrupted
|
|
@@ -374,20 +349,17 @@ module Riffer::Agent::Run
|
|
|
374
349
|
discovered.empty? ? agent.tools : agent.tools + discovered
|
|
375
350
|
end
|
|
376
351
|
|
|
377
|
-
# +tags+ rides in the options hash as a curated key the providers extract for
|
|
378
|
-
# native request-metadata mapping (alongside +:structured_output+); it never
|
|
379
|
-
# reaches an SDK call verbatim. Span tagging is threaded separately to each
|
|
380
|
-
# builder.
|
|
381
352
|
#--
|
|
382
353
|
#: (Riffer::Agent, ?Hash[String, String]) -> Hash[Symbol, untyped]
|
|
383
354
|
def merged_model_options(agent, tags = {})
|
|
384
355
|
opts = agent.config.model_options.dup
|
|
385
356
|
opts[:structured_output] = agent.structured_output if agent.structured_output
|
|
357
|
+
# Providers extract :tags for native request metadata; it never reaches an
|
|
358
|
+
# SDK call verbatim.
|
|
386
359
|
opts[:tags] = tags unless tags.empty?
|
|
387
360
|
opts
|
|
388
361
|
end
|
|
389
362
|
|
|
390
|
-
# The tags riffer adds to every run, identifying the agent it's on behalf of.
|
|
391
363
|
#--
|
|
392
364
|
#: (Riffer::Agent) -> Hash[String, String]
|
|
393
365
|
def default_tags(agent)
|
|
@@ -403,7 +375,6 @@ module Riffer::Agent::Run
|
|
|
403
375
|
# ?modifications: Array[Riffer::Guardrails::Modification],
|
|
404
376
|
# ?reasoning: Array[Riffer::Messages::Assistant::ReasoningPart],
|
|
405
377
|
# ?structured_output: Hash[Symbol, untyped]?,
|
|
406
|
-
# ?healed_tool_call_ids: Array[String],
|
|
407
378
|
# ?token_usage: Riffer::Providers::TokenUsage?,
|
|
408
379
|
# ?steps: Integer
|
|
409
380
|
# ) -> Riffer::Agent::Response
|
|
@@ -415,7 +386,6 @@ module Riffer::Agent::Run
|
|
|
415
386
|
modifications: [],
|
|
416
387
|
reasoning: [],
|
|
417
388
|
structured_output: nil,
|
|
418
|
-
healed_tool_call_ids: [],
|
|
419
389
|
token_usage: nil,
|
|
420
390
|
steps: 0
|
|
421
391
|
)
|
|
@@ -428,17 +398,15 @@ module Riffer::Agent::Run
|
|
|
428
398
|
reasoning: reasoning,
|
|
429
399
|
structured_output: structured_output,
|
|
430
400
|
messages: messages.frozen? ? messages : messages.dup.freeze,
|
|
431
|
-
healed_tool_call_ids: healed_tool_call_ids,
|
|
432
401
|
token_usage: token_usage,
|
|
433
402
|
steps: steps,
|
|
434
403
|
)
|
|
435
404
|
end
|
|
436
405
|
|
|
437
|
-
# Raises when +files+ are supplied without a +prompt+ — the provider needs
|
|
438
|
-
# text to anchor the attachments.
|
|
439
406
|
#--
|
|
440
407
|
#: (Riffer::Agent, String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::User::FilePart]?) -> void
|
|
441
408
|
def append_user_message(agent, prompt, files: nil)
|
|
409
|
+
# The provider needs text to anchor the attachments.
|
|
442
410
|
raise Riffer::ArgumentError, "files: requires a prompt" if files && !files.empty? && prompt.nil?
|
|
443
411
|
return unless prompt
|
|
444
412
|
|
|
@@ -3,32 +3,20 @@
|
|
|
3
3
|
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
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.
|
|
9
|
-
#
|
|
10
|
-
# hash = Riffer::Agent::Serializer.to_h(agent: agent)
|
|
11
|
-
# rebuilt = Riffer::Agent::Serializer.from_h(hash, context: {tenant: "acme"})
|
|
12
6
|
module Riffer::Agent::Serializer
|
|
13
7
|
extend self
|
|
14
8
|
|
|
15
|
-
#
|
|
16
|
-
# shape; +from_h+ refuses any other version.
|
|
9
|
+
# Bump only on an incompatible change to the hash shape.
|
|
17
10
|
SCHEMA_VERSION = 1 #: Integer
|
|
18
11
|
|
|
19
|
-
# Raised by +from_h+ when the hash's +schema_version+ is unsupported.
|
|
20
12
|
class VersionError < Riffer::ArgumentError; end
|
|
21
13
|
|
|
22
|
-
# The default +tool_resolver+: synthesizes a body-less tool shell from a
|
|
23
|
-
# descriptor. Its +#call+ raises — route shells through a remote runtime.
|
|
24
14
|
DEFAULT_TOOL_RESOLVER = ->(descriptor) { build_tool_shell(descriptor) } #: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool)
|
|
25
15
|
|
|
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.
|
|
29
16
|
#--
|
|
30
17
|
#: (agent: Riffer::Agent) -> Hash[Symbol, untyped]
|
|
31
18
|
def to_h(agent:)
|
|
19
|
+
# Already resolved against the agent's context, so the hash carries plain data, never Procs.
|
|
32
20
|
config = agent.config
|
|
33
21
|
{
|
|
34
22
|
schema_version: SCHEMA_VERSION,
|
|
@@ -43,17 +31,11 @@ module Riffer::Agent::Serializer
|
|
|
43
31
|
}
|
|
44
32
|
end
|
|
45
33
|
|
|
46
|
-
#
|
|
47
|
-
# tool dispatch (not used to re-resolve the already-resolved config);
|
|
48
|
-
# +session+ seeds conversation history (the hash carries the agent definition,
|
|
49
|
-
# not its history). Raises Riffer::Agent::Serializer::VersionError on an
|
|
50
|
-
# unsupported +schema_version+.
|
|
51
|
-
#
|
|
34
|
+
# Raises Riffer::Agent::Serializer::VersionError on an unsupported +schema_version+.
|
|
52
35
|
#--
|
|
53
36
|
#: (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
|
|
54
37
|
def from_h(hash, context: nil, session: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
55
|
-
#
|
|
56
|
-
# decoder) is how a future breaking change keeps older hashes readable.
|
|
38
|
+
# One arm per supported version keeps older hashes decodable after a breaking change.
|
|
57
39
|
case hash[:schema_version]
|
|
58
40
|
when SCHEMA_VERSION
|
|
59
41
|
decode_v1(hash, context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
|
|
@@ -63,15 +45,12 @@ module Riffer::Agent::Serializer
|
|
|
63
45
|
end
|
|
64
46
|
end
|
|
65
47
|
|
|
66
|
-
# Snapshots a resolved agent to a JSON string.
|
|
67
48
|
#--
|
|
68
49
|
#: (agent: Riffer::Agent) -> String
|
|
69
50
|
def to_json(agent:)
|
|
70
51
|
JSON.generate(to_h(agent: agent))
|
|
71
52
|
end
|
|
72
53
|
|
|
73
|
-
# Reconstructs a runnable agent from a JSON string produced by +to_json+. See
|
|
74
|
-
# +from_h+ for the arguments.
|
|
75
54
|
#--
|
|
76
55
|
#: (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
|
|
77
56
|
def from_json(json, context: nil, session: nil, tool_resolver: DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
|
|
@@ -100,14 +79,11 @@ module Riffer::Agent::Serializer
|
|
|
100
79
|
max_steps: decode_max_steps(hash),
|
|
101
80
|
tools_config: tools,
|
|
102
81
|
} #: Hash[Symbol, untyped]
|
|
103
|
-
# tool_runtime= rejects nil
|
|
104
|
-
# Config default (Riffer.config.tool_runtime) applies.
|
|
82
|
+
# Config#tool_runtime= rejects nil.
|
|
105
83
|
config_args[:tool_runtime] = tool_runtime if tool_runtime
|
|
106
84
|
|
|
107
|
-
# +session+ is
|
|
108
|
-
#
|
|
109
|
-
# resume persisted history. The hash never carries history (see "What does
|
|
110
|
-
# not transfer"), so this is the only seam for rehydrating a conversation.
|
|
85
|
+
# The hash never carries history, so +session+ is the only seam for rehydrating a conversation.
|
|
86
|
+
# +context+ feeds tool dispatch only; the config was resolved before serialization.
|
|
111
87
|
Riffer::Agent.new(config: Riffer::Agent::Config.new(**config_args), context: context, session: session)
|
|
112
88
|
end
|
|
113
89
|
|
|
@@ -119,19 +95,17 @@ module Riffer::Agent::Serializer
|
|
|
119
95
|
Riffer::Params.from_json_schema(schema)
|
|
120
96
|
end
|
|
121
97
|
|
|
122
|
-
# Encodes unlimited steps (+nil+ in the DSL) as +-1+ on the wire, where a
|
|
123
|
-
# JSON +null+ is awkward across transports (e.g. proto3).
|
|
124
98
|
#--
|
|
125
99
|
#: (Numeric?) -> Numeric
|
|
126
100
|
def encode_max_steps(value)
|
|
101
|
+
# A JSON null is awkward across transports (e.g. proto3), so unlimited travels as -1.
|
|
127
102
|
value.nil? ? -1 : value
|
|
128
103
|
end
|
|
129
104
|
|
|
130
|
-
# Reverses +encode_max_steps+; a missing key falls back to the default so a
|
|
131
|
-
# partial hash can't become an unbounded loop.
|
|
132
105
|
#--
|
|
133
106
|
#: (Hash[Symbol, untyped]) -> Numeric?
|
|
134
107
|
def decode_max_steps(hash)
|
|
108
|
+
# A partial hash must not become an unbounded loop.
|
|
135
109
|
return Riffer::Agent::Config::DEFAULT_MAX_STEPS unless hash.key?(:max_steps)
|
|
136
110
|
|
|
137
111
|
hash[:max_steps] == -1 ? nil : hash[:max_steps]
|
|
@@ -151,10 +125,7 @@ module Riffer::Agent::Serializer
|
|
|
151
125
|
schema = descriptor[:parameters_schema]
|
|
152
126
|
tool_timeout = descriptor[:timeout]
|
|
153
127
|
|
|
154
|
-
#
|
|
155
|
-
# from data — the tool DSL is class-level, so there is no value-level
|
|
156
|
-
# builder to type against. Same approach as Riffer::Mcp::ToolFactory;
|
|
157
|
-
# steep can't type the dynamic class body, hence the ignore block.
|
|
128
|
+
# The tool DSL is class-level, so there is no value-level builder to synthesize a tool from data.
|
|
158
129
|
Class.new(Riffer::Tool) do
|
|
159
130
|
# steep:ignore:start
|
|
160
131
|
identifier tool_name
|