activeagent 1.7.0 → 1.7.2
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 +98 -0
- data/README.md +3 -0
- data/lib/active_agent/evals/model_spec.rb +4 -0
- data/lib/active_agent/evals/runner.rb +2 -2
- data/lib/active_agent/evals/scenario.rb +3 -3
- data/lib/active_agent/evals/scenario_parser.rb +3 -3
- data/lib/active_agent/evals/suite.rb +8 -8
- data/lib/active_agent/evals.rb +1 -1
- data/lib/active_agent/providers/_base_provider.rb +12 -3
- data/lib/active_agent/providers/ruby_llm/tool_proxy.rb +5 -0
- data/lib/active_agent/providers/ruby_llm_provider.rb +80 -10
- data/lib/active_agent/version.rb +1 -1
- metadata +10 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 881ca4e27488bd719dda01c80e6241bf6d17082876039033c22f9265d1e559aa
|
|
4
|
+
data.tar.gz: 62526259e8a48d5243a480cf3ac6f795c1672b20e8569f7804fcf13af4d25ec4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d135cc35623442cdf38df2d588e323fec82f7e16c7d9c8fcf90d2add91bbb55476e07fe548c70ddb2b22cc709e684ab8ccc53dd337c0650800f9d6dd268f547
|
|
7
|
+
data.tar.gz: 76a0b25ef5cf125e99399ff61cef131d2fe6ae6dade18173601f73f0ec7ecedde6bfc0738af99ac490444b1a067cd136dc2caac34cc1f1cd6c01996cbdddf841
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,104 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.7.2] - 2026-09-29
|
|
11
|
+
|
|
12
|
+
Releases `activeagent` and `actionagent` 1.7.2 from one tag. A patch on 1.7.1:
|
|
13
|
+
the RubyLLM provider runs on ruby_llm 1.16 and 2.x, where 1.7.1 required 1.x.
|
|
14
|
+
Apps on the RubyLLM provider need ruby_llm 1.16 or later; earlier 1.x
|
|
15
|
+
releases lack the APIs the provider calls. No migrations.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **The RubyLLM provider supports ruby_llm 1.16 and 2.x** (`activeagent`).
|
|
20
|
+
The adapter handles 2.x's tool interface, token limits, embedding model
|
|
21
|
+
objects, usage and finish reasons while keeping the 1.16 API working.
|
|
22
|
+
Requiring `>= 1.16, < 3` prevents Bundler from selecting an older 1.x
|
|
23
|
+
release without the APIs the adapter calls. Rails main can now resolve
|
|
24
|
+
RubyLLM 2.x alongside Active Storage's Marcel 2 dependency. CI also runs
|
|
25
|
+
the full suite with RubyLLM 1.16 to retain coverage of that version.
|
|
26
|
+
Unsupported-version errors name the loaded version and both bounds (#508).
|
|
27
|
+
|
|
28
|
+
## [1.7.1] - 2026-09-29
|
|
29
|
+
|
|
30
|
+
Releases `activeagent` and `actionagent` 1.7.1 from one tag. A patch on 1.7.0:
|
|
31
|
+
the Evaluations page picks the judge and compared models from the provider
|
|
32
|
+
catalogs, `GET /api/provider_models` lists the host's RubyLLM registry, and
|
|
33
|
+
the RubyLLM provider requires ruby_llm 1.x and sends tool calls and structured
|
|
34
|
+
output in the shape ruby_llm reads. No migrations.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- **Model pickers on the Evaluations page** (`actionagent`). The judge model
|
|
39
|
+
and the models to compare, on the new-evaluation form and on a scenario
|
|
40
|
+
suite, are chosen from type-ahead suggestions, and a model the suggestions
|
|
41
|
+
lack can still be typed. The compare fields hold one removable chip per
|
|
42
|
+
model and still submit the same comma-separated list.
|
|
43
|
+
- With scenarios, the suggestions are the catalogs the agent builder uses,
|
|
44
|
+
for the providers the owner's runs have credentials for. A model whose
|
|
45
|
+
name alone would run elsewhere is offered with its provider in front, as
|
|
46
|
+
`ModelSpec.parse` reads it: `ollama/llama3.2`, or
|
|
47
|
+
`openrouter/anthropic/claude-sonnet-4.5` for OpenRouter's copy of an
|
|
48
|
+
Anthropic model.
|
|
49
|
+
- Without scenarios, a compared model selects the generations recorded
|
|
50
|
+
under its name, usually the provider's dated id, so the suggestions are
|
|
51
|
+
the names the agent's generations were recorded under, from the new
|
|
52
|
+
`GET /api/agents/:id/recorded_models`. Adding or clearing the scenarios
|
|
53
|
+
renames the catalog models already chosen to match.
|
|
54
|
+
- The judge field suggests the models of the provider the judge runs on
|
|
55
|
+
and says which provider that is, or that the credentials deciding it
|
|
56
|
+
could not be read.
|
|
57
|
+
|
|
58
|
+
`GET /api/evaluations` reports that provider as `judge_provider` and the
|
|
59
|
+
providers runs can use as `model_providers`
|
|
60
|
+
(`AgentExecutionService.available_providers`). Runs and their judge use
|
|
61
|
+
the evaluated agent's owner's credentials, so both fields describe that
|
|
62
|
+
owner's when `agent_id` scopes the list, and the signed-in owner's
|
|
63
|
+
otherwise. `judge_provider` is null when no provider has credentials, and
|
|
64
|
+
also when reading them raised, which `judge_provider_error: true` marks.
|
|
65
|
+
`model_providers` leaves out a provider whose credentials cannot be read.
|
|
66
|
+
Either way the list still loads.
|
|
67
|
+
- **`GET /api/provider_models` lists the host's RubyLLM registry**
|
|
68
|
+
(`actionagent`). When the host app loads RubyLLM, the chat models its
|
|
69
|
+
registry lists for the provider that take and return text (RubyLLM's
|
|
70
|
+
bundled catalog, or the host's own model table) follow the live or curated
|
|
71
|
+
list, each once, so the builder's preselected default is unchanged. That
|
|
72
|
+
leaves out the speech, transcription, moderation and completion-only
|
|
73
|
+
models RubyLLM counts as chat models, which its registry lists with no
|
|
74
|
+
modalities or as taking audio, and with them the few chat models it lists
|
|
75
|
+
with no modalities, which can still be typed. A registry that raises is
|
|
76
|
+
logged and leaves the list as it was.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- **The RubyLLM provider requires ruby_llm 1.x** (`activeagent`). ruby_llm
|
|
81
|
+
2.0 renamed the APIs `RubyLLMProvider` calls, and the open `>= 1.0`
|
|
82
|
+
requirement let `bundle update` install it. The provider now requires
|
|
83
|
+
`~> 1.0` until it supports 2.0 (#502). Loading it with an unsupported
|
|
84
|
+
version names the supported range and the loaded version, instead of
|
|
85
|
+
asking for a gem that is already in the Gemfile. Pin
|
|
86
|
+
`gem "ruby_llm", "~> 1.0"` if your bundle resolved 2.0.
|
|
87
|
+
|
|
88
|
+
### Fixed
|
|
89
|
+
|
|
90
|
+
- **Tool calls sent back through the RubyLLM provider** (`activeagent`).
|
|
91
|
+
After a tool ran, the follow-up request repeated the model's tool call
|
|
92
|
+
with its arguments as a JSON string where ruby_llm expects a Hash: OpenAI
|
|
93
|
+
received them JSON-encoded twice, and Anthropic received a string for
|
|
94
|
+
`tool_use.input`, which its API requires to be an object. The same
|
|
95
|
+
happened when a stored conversation containing a tool call was replayed.
|
|
96
|
+
The provider now hands ruby_llm the parsed arguments (#501).
|
|
97
|
+
- **Structured output through the RubyLLM provider** (`activeagent`). A
|
|
98
|
+
`json_schema` response_format reached ruby_llm unchanged, but ruby_llm
|
|
99
|
+
reads `{ name:, schema:, strict: }`, so OpenAI received a schema with a
|
|
100
|
+
null name and body, and the Anthropic request raised inside ruby_llm
|
|
101
|
+
before it was sent. The provider now converts it, naming the schema
|
|
102
|
+
`response` and making it strict unless the format says otherwise, as
|
|
103
|
+
ruby_llm's own `with_schema` does. A `text` format asks for plain text;
|
|
104
|
+
`json_object`, which ruby_llm has no mode for, and a `json_schema`
|
|
105
|
+
without a schema now raise `ArgumentError` instead of sending a request
|
|
106
|
+
the API rejects (#501).
|
|
107
|
+
|
|
10
108
|
## [1.7.0] - 2026-09-24
|
|
11
109
|
|
|
12
110
|
Releases `activeagent` and `actionagent` 1.7.0 from one tag. A minor release:
|
data/README.md
CHANGED
|
@@ -20,6 +20,10 @@ module ActiveAgent
|
|
|
20
20
|
class ModelSpec
|
|
21
21
|
DEFAULT_PROVIDERS = %w[openai anthropic ollama openrouter].freeze
|
|
22
22
|
|
|
23
|
+
# The dashboard's model pickers resolve names with a JavaScript copy of
|
|
24
|
+
# these rules and of .parse (actionagent/frontend/utils/modelOptions.mjs).
|
|
25
|
+
# actionagent/test/fixtures/model_spec_cases.json holds the cases both
|
|
26
|
+
# are tested against, so a change here needs the same change there.
|
|
23
27
|
DEFAULT_INFERENCE_RULES = [
|
|
24
28
|
[ /\Aclaude/i, "anthropic" ],
|
|
25
29
|
[ /\A(gpt-|o\d|chatgpt|text-embedding)/i, "openai" ],
|
|
@@ -16,10 +16,10 @@ module ActiveAgent
|
|
|
16
16
|
# faults in `refine_faults` (up to `judge_limit` calls per run).
|
|
17
17
|
#
|
|
18
18
|
# Runner.new(
|
|
19
|
-
# scenarios: suite.scenarios(groups: %w[
|
|
19
|
+
# scenarios: suite.scenarios(groups: %w[history]),
|
|
20
20
|
# models: ModelSpec.parse_all(%w[gpt-5-mini claude-haiku-4-5], default_provider: "openai"),
|
|
21
21
|
# criteria: [{ "key" => "response_present", "type" => "response_present" }],
|
|
22
|
-
# available_tools: { "
|
|
22
|
+
# available_tools: { "lookup_order" => "Find an order by number" },
|
|
23
23
|
# instructions: agent.instructions,
|
|
24
24
|
# judge: Judge.new(label: "claude-opus-5") { |instructions:, prompt:| ... },
|
|
25
25
|
# replay: ->(scenario, spec) { ... },
|
|
@@ -7,11 +7,11 @@ module ActiveAgent
|
|
|
7
7
|
# answer is expected to do.
|
|
8
8
|
#
|
|
9
9
|
# @!attribute key
|
|
10
|
-
# @return [String] stable within a suite ("
|
|
10
|
+
# @return [String] stable within a suite ("history_3"), so results line up across runs
|
|
11
11
|
# @!attribute group
|
|
12
|
-
# @return [String, nil] the group key ("
|
|
12
|
+
# @return [String, nil] the group key ("history")
|
|
13
13
|
# @!attribute group_name
|
|
14
|
-
# @return [String, nil] the group's display name ("
|
|
14
|
+
# @return [String, nil] the group's display name ("History / audit")
|
|
15
15
|
# @!attribute expected_tools
|
|
16
16
|
# @return [Array<String>] tool names a passing answer calls (any one of them)
|
|
17
17
|
# @!attribute expected_patterns
|
|
@@ -9,8 +9,8 @@ module ActiveAgent
|
|
|
9
9
|
# - `# Heading` or `**Heading**` lines, which start a group; so does a
|
|
10
10
|
# short unmarked line ending in a colon (`Find records:`)
|
|
11
11
|
# - a message in backticks at the start of the line, followed by notes,
|
|
12
|
-
# as in an issue
|
|
13
|
-
# `` 3. `Show me all
|
|
12
|
+
# as in a list copied from an issue:
|
|
13
|
+
# `` 3. `Show me all tickets with no assignee` — 12 in the sample data ``
|
|
14
14
|
# - trailing ` | tools: a, b | contains: x | not_contains: y | key: k`
|
|
15
15
|
# options on a line
|
|
16
16
|
# - a JSON array of strings, or of objects with `prompt` (or `message`),
|
|
@@ -19,7 +19,7 @@ module ActiveAgent
|
|
|
19
19
|
# group names, stable keys, notes and production-only flags
|
|
20
20
|
#
|
|
21
21
|
# Every scenario gets a key unique within the paste, derived from its group
|
|
22
|
-
# and position ("
|
|
22
|
+
# and position ("history_3"), unless the line names one. The result is an
|
|
23
23
|
# array of string-keyed hashes; `Scenario.from_hash` builds the structs.
|
|
24
24
|
class ScenarioParser
|
|
25
25
|
class ParseError < ArgumentError; end
|
|
@@ -8,17 +8,17 @@ module ActiveAgent
|
|
|
8
8
|
# suite. That is how an app keeps a shared suite and lets a deployment add or
|
|
9
9
|
# reword questions.
|
|
10
10
|
#
|
|
11
|
-
# suite:
|
|
12
|
-
# description:
|
|
11
|
+
# suite: support_desk
|
|
12
|
+
# description: Questions the support team asks every week
|
|
13
13
|
# groups:
|
|
14
|
-
# - key:
|
|
15
|
-
# name:
|
|
14
|
+
# - key: open_tickets
|
|
15
|
+
# name: Open tickets
|
|
16
16
|
# scenarios:
|
|
17
|
-
# - key:
|
|
18
|
-
# prompt: Which
|
|
17
|
+
# - key: open_tickets_1
|
|
18
|
+
# prompt: Which open tickets mention a refund?
|
|
19
19
|
# expect:
|
|
20
|
-
# tools: [
|
|
21
|
-
# notes:
|
|
20
|
+
# tools: [find_tickets, count_tickets]
|
|
21
|
+
# notes: The sample data has three.
|
|
22
22
|
# production_only: false
|
|
23
23
|
class Suite
|
|
24
24
|
class NotFound < StandardError; end
|
data/lib/active_agent/evals.rb
CHANGED
|
@@ -50,7 +50,7 @@ require_relative "evals/publisher"
|
|
|
50
50
|
# report = ActiveAgent::Evals::Runner.new(
|
|
51
51
|
# scenarios: scenarios,
|
|
52
52
|
# models: models,
|
|
53
|
-
# available_tools: { "
|
|
53
|
+
# available_tools: { "lookup_order" => "Find an order by number" },
|
|
54
54
|
# replay: ->(scenario, spec) { my_agent.run(scenario.prompt, model: spec.model, provider: spec.provider) }
|
|
55
55
|
# ).call
|
|
56
56
|
#
|
|
@@ -10,7 +10,8 @@ require_relative "concerns/tool_choice_clearing"
|
|
|
10
10
|
GEM_LOADERS = {
|
|
11
11
|
anthropic: [ "anthropic", "~> 1.12", "anthropic" ],
|
|
12
12
|
openai: [ "openai", "~> 0.34", "openai" ],
|
|
13
|
-
|
|
13
|
+
# Keep a tested floor: 1.2 lacks the provider APIs this adapter uses.
|
|
14
|
+
ruby_llm: [ "ruby_llm", [ ">= 1.16", "< 3" ], "ruby_llm" ]
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
# Requires a provider's gem dependency.
|
|
@@ -18,15 +19,23 @@ GEM_LOADERS = {
|
|
|
18
19
|
# @param type [Symbol] provider type (:anthropic, :openai)
|
|
19
20
|
# @param file_name [String] for error context
|
|
20
21
|
# @return [void]
|
|
21
|
-
# @raise [LoadError] when
|
|
22
|
+
# @raise [LoadError] when the gem is not installed, or when the loaded
|
|
23
|
+
# version is outside the supported range
|
|
22
24
|
def require_gem!(type, file_name)
|
|
23
25
|
gem_name, requirement, package_name = GEM_LOADERS.fetch(type)
|
|
26
|
+
requirements = Array(requirement)
|
|
24
27
|
provider_name = file_name.split("/").last.delete_suffix(".rb").camelize
|
|
25
28
|
|
|
26
29
|
begin
|
|
27
|
-
gem(gem_name,
|
|
30
|
+
gem(gem_name, *requirements)
|
|
28
31
|
require(package_name)
|
|
29
32
|
rescue LoadError
|
|
33
|
+
loaded = Gem.loaded_specs[gem_name]
|
|
34
|
+
if loaded && !Gem::Requirement.new(requirement).satisfied_by?(loaded.version)
|
|
35
|
+
raise LoadError, "#{provider_name} supports the '#{gem_name}' gem #{requirements.join(', ')}, but #{loaded.version} is loaded. " \
|
|
36
|
+
"Add `gem \"#{gem_name}\", #{requirements.map(&:inspect).join(', ')}` to your Gemfile and run `bundle update #{gem_name}`."
|
|
37
|
+
end
|
|
38
|
+
|
|
30
39
|
raise LoadError, "The '#{gem_name}' gem is required for #{provider_name}. Please add it to your Gemfile and run `bundle install`."
|
|
31
40
|
end
|
|
32
41
|
end
|
|
@@ -27,6 +27,11 @@ module ActiveAgent
|
|
|
27
27
|
{}
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# RubyLLM 2 names for the same tool interface.
|
|
31
|
+
alias_method :parameters_schema, :params_schema
|
|
32
|
+
alias_method :declared_parameters, :parameters
|
|
33
|
+
alias_method :provider_options, :provider_params
|
|
34
|
+
|
|
30
35
|
private
|
|
31
36
|
|
|
32
37
|
def deep_stringify(obj)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "_base_provider"
|
|
4
4
|
|
|
5
|
-
require_gem!(:ruby_llm, __FILE__)
|
|
5
|
+
require_gem!(:ruby_llm, __FILE__)
|
|
6
6
|
|
|
7
7
|
require_relative "ruby_llm/_types"
|
|
8
8
|
require_relative "ruby_llm/tool_proxy"
|
|
@@ -59,16 +59,22 @@ module ActiveAgent
|
|
|
59
59
|
tools: tools || {},
|
|
60
60
|
temperature: parameters[:temperature]
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
schema = ruby_llm_schema(parameters[:response_format])
|
|
63
|
+
kwargs[:schema] = schema if schema
|
|
63
64
|
|
|
64
|
-
#
|
|
65
|
+
# RubyLLM 2 renamed params: and exposes a provider-neutral token limit.
|
|
65
66
|
max_tokens = parameters[:max_tokens] || options.max_tokens
|
|
66
67
|
if max_tokens
|
|
67
|
-
|
|
68
|
+
if ruby_llm_v2?
|
|
69
|
+
kwargs[:max_output_tokens] = max_tokens
|
|
70
|
+
else
|
|
71
|
+
kwargs[:params] = { max_tokens: max_tokens }
|
|
72
|
+
end
|
|
68
73
|
end
|
|
69
74
|
|
|
70
75
|
if parameters[:stream]
|
|
71
76
|
stream_proc = parameters[:stream]
|
|
77
|
+
@stream_tool_calls = {}
|
|
72
78
|
|
|
73
79
|
# For streaming, pass a block that forwards chunks
|
|
74
80
|
@ruby_llm_provider.complete(messages, **kwargs) do |chunk|
|
|
@@ -94,7 +100,8 @@ module ActiveAgent
|
|
|
94
100
|
inputs = input.is_a?(Array) ? input : [ input ]
|
|
95
101
|
|
|
96
102
|
data = inputs.map.with_index do |text, index|
|
|
97
|
-
|
|
103
|
+
embedding_model = ruby_llm_v2? ? @ruby_llm_model : model_id
|
|
104
|
+
embedding = @ruby_llm_provider.embed(text, model: embedding_model, dimensions: parameters[:dimensions])
|
|
98
105
|
|
|
99
106
|
{
|
|
100
107
|
object: "embedding",
|
|
@@ -137,12 +144,15 @@ module ActiveAgent
|
|
|
137
144
|
# Handle tool calls in chunk
|
|
138
145
|
if chunk.tool_calls&.any?
|
|
139
146
|
message[:tool_calls] ||= []
|
|
140
|
-
chunk.tool_calls.each do |
|
|
141
|
-
|
|
147
|
+
chunk.tool_calls.each do |key, tool_call|
|
|
148
|
+
# RubyLLM 2 keys OpenAI deltas by index; only the first delta
|
|
149
|
+
# includes the call ID and name. Keep each index tied to its call.
|
|
150
|
+
existing = message[:tool_calls].find { |tc| tool_call.id && tc[:id] == tool_call.id }
|
|
151
|
+
existing ||= @stream_tool_calls[key]
|
|
142
152
|
if existing
|
|
143
153
|
existing[:function][:arguments] += tool_call.arguments.to_s if tool_call.arguments
|
|
144
154
|
else
|
|
145
|
-
|
|
155
|
+
existing = {
|
|
146
156
|
id: tool_call.id,
|
|
147
157
|
type: "function",
|
|
148
158
|
function: {
|
|
@@ -150,7 +160,9 @@ module ActiveAgent
|
|
|
150
160
|
arguments: tool_call.arguments.to_s
|
|
151
161
|
}
|
|
152
162
|
}
|
|
163
|
+
message[:tool_calls] << existing
|
|
153
164
|
end
|
|
165
|
+
@stream_tool_calls[key] = existing
|
|
154
166
|
end
|
|
155
167
|
end
|
|
156
168
|
|
|
@@ -253,6 +265,10 @@ module ActiveAgent
|
|
|
253
265
|
|
|
254
266
|
private
|
|
255
267
|
|
|
268
|
+
def ruby_llm_v2?
|
|
269
|
+
Gem.loaded_specs.fetch("ruby_llm").version.segments.first >= 2
|
|
270
|
+
end
|
|
271
|
+
|
|
256
272
|
# Resolves and caches the RubyLLM provider for the given model.
|
|
257
273
|
#
|
|
258
274
|
# Reuses the cached provider if the model hasn't changed (e.g., during
|
|
@@ -345,12 +361,61 @@ module ActiveAgent
|
|
|
345
361
|
call = ::RubyLLM::ToolCall.new(
|
|
346
362
|
id: id,
|
|
347
363
|
name: tc.dig(:function, :name) || tc[:name],
|
|
348
|
-
arguments: tc.dig(:function, :arguments) || tc[:input]
|
|
364
|
+
arguments: ruby_llm_tool_arguments(tc.dig(:function, :arguments) || tc[:input])
|
|
349
365
|
)
|
|
350
366
|
hash[id] = call
|
|
351
367
|
end
|
|
352
368
|
end
|
|
353
369
|
|
|
370
|
+
# ActiveAgent keeps a tool call's arguments as the JSON string the
|
|
371
|
+
# model sent; RubyLLM takes them as a Hash and renders them itself
|
|
372
|
+
# (JSON-encoded for OpenAI, as the input object for Anthropic).
|
|
373
|
+
#
|
|
374
|
+
# A string that is not valid JSON, such as arguments a model cut off
|
|
375
|
+
# mid-way in a stored conversation, is passed through unchanged.
|
|
376
|
+
#
|
|
377
|
+
# @param arguments [String, Hash, nil]
|
|
378
|
+
# @return [Hash, String]
|
|
379
|
+
def ruby_llm_tool_arguments(arguments)
|
|
380
|
+
case arguments
|
|
381
|
+
when Hash
|
|
382
|
+
arguments.deep_stringify_keys
|
|
383
|
+
when String
|
|
384
|
+
arguments.blank? ? {} : JSON.parse(arguments)
|
|
385
|
+
else
|
|
386
|
+
{}
|
|
387
|
+
end
|
|
388
|
+
rescue JSON::ParserError
|
|
389
|
+
arguments
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# Converts ActiveAgent's response_format to the schema RubyLLM's
|
|
393
|
+
# complete takes, { name:, schema:, strict: }, filling the name and
|
|
394
|
+
# strict flag in the way RubyLLM::Chat#with_schema does.
|
|
395
|
+
#
|
|
396
|
+
# @param response_format [Hash, Symbol, String, nil] ActiveAgent common format
|
|
397
|
+
# @return [Hash, nil] nil when the response is plain text
|
|
398
|
+
# @raise [ArgumentError] for json_schema without a schema, and for any
|
|
399
|
+
# other type, including json_object, which RubyLLM has no mode for
|
|
400
|
+
def ruby_llm_schema(response_format)
|
|
401
|
+
return nil if response_format.nil?
|
|
402
|
+
|
|
403
|
+
format = response_format.is_a?(Hash) ? response_format : { type: response_format.to_s }
|
|
404
|
+
|
|
405
|
+
case format[:type].to_s
|
|
406
|
+
when "text"
|
|
407
|
+
nil
|
|
408
|
+
when "json_schema"
|
|
409
|
+
json_schema = format[:json_schema] || {}
|
|
410
|
+
raise ArgumentError, "RubyLLMProvider needs a schema for a json_schema response_format" unless json_schema[:schema]
|
|
411
|
+
|
|
412
|
+
{ name: json_schema[:name] || "response", schema: json_schema[:schema], strict: json_schema[:strict] != false }
|
|
413
|
+
else
|
|
414
|
+
raise ArgumentError, "RubyLLMProvider supports a json_schema or text response_format, not #{format[:type].inspect}; " \
|
|
415
|
+
"ruby_llm has no JSON object mode, so give json_schema a schema instead"
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
354
419
|
# Converts ActiveAgent tool definitions to RubyLLM ToolProxy objects.
|
|
355
420
|
#
|
|
356
421
|
# @param tools [Array<Hash>, nil] ActiveAgent tool definitions
|
|
@@ -397,6 +462,8 @@ module ActiveAgent
|
|
|
397
462
|
# Add stop_reason if available
|
|
398
463
|
if response.respond_to?(:stop_reason) && response.stop_reason
|
|
399
464
|
hash[:stop_reason] = response.stop_reason
|
|
465
|
+
elsif response.respond_to?(:finish_reason) && response.finish_reason
|
|
466
|
+
hash[:stop_reason] = { stop: "end_turn", tool_calls: "tool_use" }.fetch(response.finish_reason, response.finish_reason.to_s)
|
|
400
467
|
elsif response.tool_calls&.any?
|
|
401
468
|
hash[:stop_reason] = "tool_use"
|
|
402
469
|
else
|
|
@@ -404,7 +471,10 @@ module ActiveAgent
|
|
|
404
471
|
end
|
|
405
472
|
|
|
406
473
|
# Add usage info if available
|
|
407
|
-
if response.respond_to?(:
|
|
474
|
+
if response.respond_to?(:tokens)
|
|
475
|
+
tokens = response.tokens
|
|
476
|
+
hash[:usage] = { input_tokens: tokens.input, output_tokens: tokens.output } if tokens.input
|
|
477
|
+
elsif response.respond_to?(:input_tokens) && response.input_tokens
|
|
408
478
|
hash[:usage] = {
|
|
409
479
|
input_tokens: response.input_tokens,
|
|
410
480
|
output_tokens: response.output_tokens
|
data/lib/active_agent/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activeagent
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.7.
|
|
4
|
+
version: 1.7.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Bowen
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: actionpack
|
|
@@ -206,14 +206,20 @@ dependencies:
|
|
|
206
206
|
requirements:
|
|
207
207
|
- - ">="
|
|
208
208
|
- !ruby/object:Gem::Version
|
|
209
|
-
version: '1.
|
|
209
|
+
version: '1.16'
|
|
210
|
+
- - "<"
|
|
211
|
+
- !ruby/object:Gem::Version
|
|
212
|
+
version: '3'
|
|
210
213
|
type: :development
|
|
211
214
|
prerelease: false
|
|
212
215
|
version_requirements: !ruby/object:Gem::Requirement
|
|
213
216
|
requirements:
|
|
214
217
|
- - ">="
|
|
215
218
|
- !ruby/object:Gem::Version
|
|
216
|
-
version: '1.
|
|
219
|
+
version: '1.16'
|
|
220
|
+
- - "<"
|
|
221
|
+
- !ruby/object:Gem::Version
|
|
222
|
+
version: '3'
|
|
217
223
|
- !ruby/object:Gem::Dependency
|
|
218
224
|
name: capybara
|
|
219
225
|
requirement: !ruby/object:Gem::Requirement
|