swarm_sdk 2.4.3 → 2.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +6 -1
- data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +2 -2
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +48 -0
- data/lib/swarm_sdk/config.rb +2 -1
- data/lib/swarm_sdk/configuration/parser.rb +22 -2
- data/lib/swarm_sdk/configuration.rb +13 -4
- data/lib/swarm_sdk/models.json +2 -2
- data/lib/swarm_sdk/models.rb +43 -2
- data/lib/swarm_sdk/result.rb +52 -0
- data/lib/swarm_sdk/swarm/hook_triggers.rb +1 -0
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +1 -0
- data/lib/swarm_sdk/swarm.rb +64 -0
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk.rb +20 -4
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0fcbfaece7a208cfd4ef7afedecda776309b3368ffea0f0800cce3fb1c2ba0f
|
|
4
|
+
data.tar.gz: f41125b70b3e4d83f21132a2231e1ae56f4b2dc35c00c28ef16a51e756fb93b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6bc58ad13914191b180912d6ad8d7df187a43a696a5ef6db8f627cd213ab847ac3370d02a4e40e26df9b6bfa882ad3c5aa4adaccdc6c7eab1f3c10179768b97b
|
|
7
|
+
data.tar.gz: 0dcbe3bdbbf6a1443ab33268fd7968c50f874f7de688ca4edce8f7adb53ff3a00ab1910ae363f2b6c07a35401020f8602f3ca3f3ac81f95b6087fd76862e4914
|
|
@@ -170,11 +170,16 @@ module SwarmSDK
|
|
|
170
170
|
|
|
171
171
|
# Fetch real model info for accurate context tracking
|
|
172
172
|
#
|
|
173
|
+
# Uses SwarmSDK::Models for model lookup (reads from models.json).
|
|
174
|
+
# Falls back to RubyLLM.models if not found in SwarmSDK.
|
|
175
|
+
#
|
|
173
176
|
# @param model_id [String] Model ID to lookup
|
|
174
177
|
def fetch_real_model_info(model_id)
|
|
175
178
|
@model_lookup_error = nil
|
|
176
179
|
@real_model_info = begin
|
|
177
|
-
|
|
180
|
+
# Try SwarmSDK::Models first (reads from local models.json)
|
|
181
|
+
# Returns ModelInfo object with method access (context_window, etc.)
|
|
182
|
+
SwarmSDK::Models.find(model_id) || RubyLLM.models.find(model_id)
|
|
178
183
|
rescue StandardError => e
|
|
179
184
|
suggestions = suggest_similar_models(model_id)
|
|
180
185
|
@model_lookup_error = {
|
|
@@ -74,8 +74,8 @@ module SwarmSDK
|
|
|
74
74
|
model_info = SwarmSDK::Models.find(message.model_id)
|
|
75
75
|
return zero_cost unless model_info
|
|
76
76
|
|
|
77
|
-
# Extract pricing from SwarmSDK's
|
|
78
|
-
pricing = model_info
|
|
77
|
+
# Extract pricing from SwarmSDK's ModelInfo (method access for top-level, Hash for nested)
|
|
78
|
+
pricing = model_info.pricing
|
|
79
79
|
return zero_cost unless pricing
|
|
80
80
|
|
|
81
81
|
text_pricing = pricing["text_tokens"] || pricing[:text_tokens]
|
|
@@ -84,6 +84,35 @@ module SwarmSDK
|
|
|
84
84
|
limit - cumulative_total_tokens
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
# Calculate cumulative input cost based on tokens and model pricing
|
|
88
|
+
#
|
|
89
|
+
# @return [Float] Total input cost in dollars
|
|
90
|
+
def cumulative_input_cost
|
|
91
|
+
pricing = model_pricing
|
|
92
|
+
return 0.0 unless pricing
|
|
93
|
+
|
|
94
|
+
input_price = pricing["input_per_million"] || pricing[:input_per_million] || 0.0
|
|
95
|
+
(cumulative_input_tokens / 1_000_000.0) * input_price
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Calculate cumulative output cost based on tokens and model pricing
|
|
99
|
+
#
|
|
100
|
+
# @return [Float] Total output cost in dollars
|
|
101
|
+
def cumulative_output_cost
|
|
102
|
+
pricing = model_pricing
|
|
103
|
+
return 0.0 unless pricing
|
|
104
|
+
|
|
105
|
+
output_price = pricing["output_per_million"] || pricing[:output_per_million] || 0.0
|
|
106
|
+
(cumulative_output_tokens / 1_000_000.0) * output_price
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Calculate cumulative total cost (input + output)
|
|
110
|
+
#
|
|
111
|
+
# @return [Float] Total cost in dollars
|
|
112
|
+
def cumulative_total_cost
|
|
113
|
+
cumulative_input_cost + cumulative_output_cost
|
|
114
|
+
end
|
|
115
|
+
|
|
87
116
|
# Compact the conversation history to reduce token usage
|
|
88
117
|
#
|
|
89
118
|
# @param options [Hash] Compression options
|
|
@@ -92,6 +121,25 @@ module SwarmSDK
|
|
|
92
121
|
compactor = ContextCompactor.new(self, options)
|
|
93
122
|
compactor.compact
|
|
94
123
|
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
# Get pricing info for the current model
|
|
128
|
+
#
|
|
129
|
+
# Extracts standard text token pricing from model info.
|
|
130
|
+
#
|
|
131
|
+
# @return [Hash, nil] Pricing hash with input_per_million and output_per_million
|
|
132
|
+
def model_pricing
|
|
133
|
+
return unless @real_model_info&.pricing
|
|
134
|
+
|
|
135
|
+
pricing = @real_model_info.pricing
|
|
136
|
+
text_pricing = pricing["text_tokens"] || pricing[:text_tokens]
|
|
137
|
+
return unless text_pricing
|
|
138
|
+
|
|
139
|
+
text_pricing["standard"] || text_pricing[:standard]
|
|
140
|
+
rescue StandardError
|
|
141
|
+
nil
|
|
142
|
+
end
|
|
95
143
|
end
|
|
96
144
|
end
|
|
97
145
|
end
|
data/lib/swarm_sdk/config.rb
CHANGED
|
@@ -91,6 +91,7 @@ module SwarmSDK
|
|
|
91
91
|
webfetch_base_url: ["SWARM_SDK_WEBFETCH_BASE_URL", nil],
|
|
92
92
|
webfetch_max_tokens: ["SWARM_SDK_WEBFETCH_MAX_TOKENS", 4096],
|
|
93
93
|
allow_filesystem_tools: ["SWARM_SDK_ALLOW_FILESYSTEM_TOOLS", true],
|
|
94
|
+
env_interpolation: ["SWARM_SDK_ENV_INTERPOLATION", true],
|
|
94
95
|
}.freeze
|
|
95
96
|
|
|
96
97
|
class << self
|
|
@@ -279,7 +280,7 @@ module SwarmSDK
|
|
|
279
280
|
# @return [Integer, Float, Boolean, String] The parsed value
|
|
280
281
|
def parse_env_value(value, key)
|
|
281
282
|
case key
|
|
282
|
-
when :allow_filesystem_tools
|
|
283
|
+
when :allow_filesystem_tools, :env_interpolation
|
|
283
284
|
# Convert string to boolean
|
|
284
285
|
case value.to_s.downcase
|
|
285
286
|
when "true", "yes", "1", "on", "enabled"
|
|
@@ -30,9 +30,18 @@ module SwarmSDK
|
|
|
30
30
|
:nodes,
|
|
31
31
|
:external_swarms
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
# Initialize parser with YAML content and options
|
|
34
|
+
#
|
|
35
|
+
# @param yaml_content [String] YAML configuration content
|
|
36
|
+
# @param base_dir [String, Pathname] Base directory for resolving paths
|
|
37
|
+
# @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
|
|
38
|
+
# When nil, uses the global SwarmSDK.config.env_interpolation setting.
|
|
39
|
+
# When true, interpolates ${VAR} and ${VAR:=default} patterns.
|
|
40
|
+
# When false, skips interpolation entirely.
|
|
41
|
+
def initialize(yaml_content, base_dir:, env_interpolation: nil)
|
|
34
42
|
@yaml_content = yaml_content
|
|
35
43
|
@base_dir = Pathname.new(base_dir).expand_path
|
|
44
|
+
@env_interpolation = env_interpolation
|
|
36
45
|
@config_type = nil
|
|
37
46
|
@swarm_id = nil
|
|
38
47
|
@swarm_name = nil
|
|
@@ -55,7 +64,7 @@ module SwarmSDK
|
|
|
55
64
|
end
|
|
56
65
|
|
|
57
66
|
@config = Utils.symbolize_keys(@config)
|
|
58
|
-
interpolate_env_vars!(@config)
|
|
67
|
+
interpolate_env_vars!(@config) if env_interpolation_enabled?
|
|
59
68
|
|
|
60
69
|
validate_version
|
|
61
70
|
detect_and_validate_type
|
|
@@ -86,6 +95,17 @@ module SwarmSDK
|
|
|
86
95
|
|
|
87
96
|
private
|
|
88
97
|
|
|
98
|
+
# Check if environment variable interpolation is enabled
|
|
99
|
+
#
|
|
100
|
+
# Uses the local setting if explicitly set, otherwise falls back to global config.
|
|
101
|
+
#
|
|
102
|
+
# @return [Boolean] true if interpolation should be performed
|
|
103
|
+
def env_interpolation_enabled?
|
|
104
|
+
return @env_interpolation unless @env_interpolation.nil?
|
|
105
|
+
|
|
106
|
+
SwarmSDK.config.env_interpolation
|
|
107
|
+
end
|
|
108
|
+
|
|
89
109
|
def validate_version
|
|
90
110
|
version = @config[:version]
|
|
91
111
|
raise ConfigurationError, "Missing 'version' field in configuration" unless version
|
|
@@ -38,9 +38,13 @@ module SwarmSDK
|
|
|
38
38
|
# Load configuration from YAML file
|
|
39
39
|
#
|
|
40
40
|
# @param path [String, Pathname] Path to YAML configuration file
|
|
41
|
+
# @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
|
|
42
|
+
# When nil, uses the global SwarmSDK.config.env_interpolation setting.
|
|
43
|
+
# When true, interpolates ${VAR} and ${VAR:=default} patterns.
|
|
44
|
+
# When false, skips interpolation entirely.
|
|
41
45
|
# @return [Configuration] Validated configuration instance
|
|
42
46
|
# @raise [ConfigurationError] If file not found or invalid
|
|
43
|
-
def load_file(path)
|
|
47
|
+
def load_file(path, env_interpolation: nil)
|
|
44
48
|
path = Pathname.new(path).expand_path
|
|
45
49
|
|
|
46
50
|
unless path.exist?
|
|
@@ -50,7 +54,7 @@ module SwarmSDK
|
|
|
50
54
|
yaml_content = File.read(path)
|
|
51
55
|
base_dir = path.dirname
|
|
52
56
|
|
|
53
|
-
new(yaml_content, base_dir: base_dir).tap(&:load_and_validate)
|
|
57
|
+
new(yaml_content, base_dir: base_dir, env_interpolation: env_interpolation).tap(&:load_and_validate)
|
|
54
58
|
rescue Errno::ENOENT
|
|
55
59
|
raise ConfigurationError, "Configuration file not found: #{path}"
|
|
56
60
|
end
|
|
@@ -60,12 +64,17 @@ module SwarmSDK
|
|
|
60
64
|
#
|
|
61
65
|
# @param yaml_content [String] YAML configuration content
|
|
62
66
|
# @param base_dir [String, Pathname] Base directory for resolving agent file paths (default: Dir.pwd)
|
|
63
|
-
|
|
67
|
+
# @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
|
|
68
|
+
# When nil, uses the global SwarmSDK.config.env_interpolation setting.
|
|
69
|
+
# When true, interpolates ${VAR} and ${VAR:=default} patterns.
|
|
70
|
+
# When false, skips interpolation entirely.
|
|
71
|
+
def initialize(yaml_content, base_dir: Dir.pwd, env_interpolation: nil)
|
|
64
72
|
raise ArgumentError, "yaml_content cannot be nil" if yaml_content.nil?
|
|
65
73
|
raise ArgumentError, "base_dir cannot be nil" if base_dir.nil?
|
|
66
74
|
|
|
67
75
|
@yaml_content = yaml_content
|
|
68
76
|
@base_dir = Pathname.new(base_dir).expand_path
|
|
77
|
+
@env_interpolation = env_interpolation
|
|
69
78
|
@parser = nil
|
|
70
79
|
@translator = nil
|
|
71
80
|
end
|
|
@@ -77,7 +86,7 @@ module SwarmSDK
|
|
|
77
86
|
#
|
|
78
87
|
# @return [self]
|
|
79
88
|
def load_and_validate
|
|
80
|
-
@parser = Parser.new(@yaml_content, base_dir: @base_dir)
|
|
89
|
+
@parser = Parser.new(@yaml_content, base_dir: @base_dir, env_interpolation: @env_interpolation)
|
|
81
90
|
@parser.parse
|
|
82
91
|
|
|
83
92
|
# Sync parsed data to instance variables for backward compatibility
|
data/lib/swarm_sdk/models.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"provider": "anthropic",
|
|
6
6
|
"family": "claude-haiku-4-5",
|
|
7
7
|
"created_at": null,
|
|
8
|
-
"context_window":
|
|
8
|
+
"context_window": 200000,
|
|
9
9
|
"max_output_tokens": 64000,
|
|
10
10
|
"knowledge_cutoff": null,
|
|
11
11
|
"modalities": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"provider": "anthropic",
|
|
37
37
|
"family": "claude-haiku-4-5",
|
|
38
38
|
"created_at": null,
|
|
39
|
-
"context_window":
|
|
39
|
+
"context_window": 200000,
|
|
40
40
|
"max_output_tokens": 64000,
|
|
41
41
|
"knowledge_cutoff": null,
|
|
42
42
|
"modalities": {
|
data/lib/swarm_sdk/models.rb
CHANGED
|
@@ -18,16 +18,57 @@ module SwarmSDK
|
|
|
18
18
|
MODELS_JSON_PATH = File.expand_path("models.json", __dir__)
|
|
19
19
|
ALIASES_JSON_PATH = File.expand_path("model_aliases.json", __dir__)
|
|
20
20
|
|
|
21
|
+
# Model information wrapper providing method access to model data
|
|
22
|
+
#
|
|
23
|
+
# Wraps the raw Hash from models.json to provide RubyLLM::Model::Info-like
|
|
24
|
+
# interface for compatibility with code expecting method access.
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# model = SwarmSDK::Models.find("claude-sonnet-4-5-20250929")
|
|
28
|
+
# model.context_window #=> 200000
|
|
29
|
+
# model.id #=> "claude-sonnet-4-5-20250929"
|
|
30
|
+
class ModelInfo
|
|
31
|
+
attr_reader :id,
|
|
32
|
+
:name,
|
|
33
|
+
:provider,
|
|
34
|
+
:family,
|
|
35
|
+
:context_window,
|
|
36
|
+
:max_output_tokens,
|
|
37
|
+
:knowledge_cutoff,
|
|
38
|
+
:modalities,
|
|
39
|
+
:capabilities,
|
|
40
|
+
:pricing,
|
|
41
|
+
:metadata
|
|
42
|
+
|
|
43
|
+
# Create a ModelInfo from a Hash
|
|
44
|
+
#
|
|
45
|
+
# @param data [Hash] Model data from models.json
|
|
46
|
+
def initialize(data)
|
|
47
|
+
@id = data["id"] || data[:id]
|
|
48
|
+
@name = data["name"] || data[:name]
|
|
49
|
+
@provider = data["provider"] || data[:provider]
|
|
50
|
+
@family = data["family"] || data[:family]
|
|
51
|
+
@context_window = data["context_window"] || data[:context_window]
|
|
52
|
+
@max_output_tokens = data["max_output_tokens"] || data[:max_output_tokens]
|
|
53
|
+
@knowledge_cutoff = data["knowledge_cutoff"] || data[:knowledge_cutoff]
|
|
54
|
+
@modalities = data["modalities"] || data[:modalities]
|
|
55
|
+
@capabilities = data["capabilities"] || data[:capabilities]
|
|
56
|
+
@pricing = data["pricing"] || data[:pricing]
|
|
57
|
+
@metadata = data["metadata"] || data[:metadata]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
21
61
|
class << self
|
|
22
62
|
# Find a model by ID or alias
|
|
23
63
|
#
|
|
24
64
|
# @param model_id [String] Model ID or alias to find
|
|
25
|
-
# @return [
|
|
65
|
+
# @return [ModelInfo, nil] Model info or nil if not found
|
|
26
66
|
def find(model_id)
|
|
27
67
|
# Check if it's an alias first
|
|
28
68
|
resolved_id = resolve_alias(model_id)
|
|
29
69
|
|
|
30
|
-
all.find { |m| m["id"] == resolved_id || m[:id] == resolved_id }
|
|
70
|
+
model_hash = all.find { |m| m["id"] == resolved_id || m[:id] == resolved_id }
|
|
71
|
+
model_hash ? ModelInfo.new(model_hash) : nil
|
|
31
72
|
end
|
|
32
73
|
|
|
33
74
|
# Resolve a model alias to full model ID
|
data/lib/swarm_sdk/result.rb
CHANGED
|
@@ -109,6 +109,58 @@ module SwarmSDK
|
|
|
109
109
|
@logs.map { |entry| entry[:agent] }.compact.uniq.map(&:to_sym)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
+
# Get per-agent usage breakdown from logs
|
|
113
|
+
#
|
|
114
|
+
# Aggregates context usage, tokens, and cost for each agent from their
|
|
115
|
+
# final agent_stop or agent_step events. Each agent's entry includes:
|
|
116
|
+
# - input_tokens, output_tokens, total_tokens
|
|
117
|
+
# - context_limit, usage_percentage, tokens_remaining
|
|
118
|
+
# - input_cost, output_cost, total_cost
|
|
119
|
+
#
|
|
120
|
+
# @return [Hash{Symbol => Hash}] Per-agent usage breakdown
|
|
121
|
+
#
|
|
122
|
+
# @example
|
|
123
|
+
# result.per_agent_usage[:backend]
|
|
124
|
+
# # => {
|
|
125
|
+
# # input_tokens: 15000,
|
|
126
|
+
# # output_tokens: 5000,
|
|
127
|
+
# # total_tokens: 20000,
|
|
128
|
+
# # context_limit: 200000,
|
|
129
|
+
# # usage_percentage: "10.0%",
|
|
130
|
+
# # tokens_remaining: 180000,
|
|
131
|
+
# # input_cost: 0.045,
|
|
132
|
+
# # output_cost: 0.075,
|
|
133
|
+
# # total_cost: 0.12
|
|
134
|
+
# # }
|
|
135
|
+
def per_agent_usage
|
|
136
|
+
# Find the last usage entry for each agent
|
|
137
|
+
agent_entries = {}
|
|
138
|
+
|
|
139
|
+
@logs.each do |entry|
|
|
140
|
+
next unless entry[:usage] && entry[:agent]
|
|
141
|
+
next unless entry[:type] == "agent_step" || entry[:type] == "agent_stop"
|
|
142
|
+
|
|
143
|
+
agent_name = entry[:agent].to_sym
|
|
144
|
+
agent_entries[agent_name] = entry[:usage]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Build breakdown from final usage entries
|
|
148
|
+
agent_entries.transform_values do |usage|
|
|
149
|
+
{
|
|
150
|
+
input_tokens: usage[:cumulative_input_tokens] || 0,
|
|
151
|
+
output_tokens: usage[:cumulative_output_tokens] || 0,
|
|
152
|
+
total_tokens: usage[:cumulative_total_tokens] || 0,
|
|
153
|
+
cached_tokens: usage[:cumulative_cached_tokens] || 0,
|
|
154
|
+
context_limit: usage[:context_limit],
|
|
155
|
+
usage_percentage: usage[:tokens_used_percentage],
|
|
156
|
+
tokens_remaining: usage[:tokens_remaining],
|
|
157
|
+
input_cost: usage[:input_cost] || 0.0,
|
|
158
|
+
output_cost: usage[:output_cost] || 0.0,
|
|
159
|
+
total_cost: usage[:total_cost] || 0.0,
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
112
164
|
# Count total LLM requests made
|
|
113
165
|
# Each LLM API call produces either agent_step (tool calls) or agent_stop (final answer)
|
|
114
166
|
def llm_requests
|
|
@@ -208,6 +208,7 @@ module SwarmSDK
|
|
|
208
208
|
total_cost: context.metadata[:total_cost],
|
|
209
209
|
total_tokens: context.metadata[:total_tokens],
|
|
210
210
|
agents_involved: context.metadata[:agents_involved],
|
|
211
|
+
per_agent_usage: context.metadata[:per_agent_usage],
|
|
211
212
|
timestamp: context.metadata[:timestamp],
|
|
212
213
|
)
|
|
213
214
|
end
|
data/lib/swarm_sdk/swarm.rb
CHANGED
|
@@ -366,6 +366,47 @@ module SwarmSDK
|
|
|
366
366
|
@agent_definitions.keys
|
|
367
367
|
end
|
|
368
368
|
|
|
369
|
+
# Get context usage breakdown for all agents
|
|
370
|
+
#
|
|
371
|
+
# Returns per-agent context statistics including tokens used, context limit,
|
|
372
|
+
# usage percentage, and cost. Useful for monitoring context window consumption
|
|
373
|
+
# across the swarm.
|
|
374
|
+
#
|
|
375
|
+
# @return [Hash{Symbol => Hash}] Per-agent context breakdown
|
|
376
|
+
#
|
|
377
|
+
# @example
|
|
378
|
+
# breakdown = swarm.context_breakdown
|
|
379
|
+
# breakdown[:backend]
|
|
380
|
+
# # => {
|
|
381
|
+
# # input_tokens: 15000,
|
|
382
|
+
# # output_tokens: 5000,
|
|
383
|
+
# # total_tokens: 20000,
|
|
384
|
+
# # cached_tokens: 2000,
|
|
385
|
+
# # context_limit: 200000,
|
|
386
|
+
# # usage_percentage: 10.0,
|
|
387
|
+
# # tokens_remaining: 180000,
|
|
388
|
+
# # input_cost: 0.045,
|
|
389
|
+
# # output_cost: 0.075,
|
|
390
|
+
# # total_cost: 0.12
|
|
391
|
+
# # }
|
|
392
|
+
def context_breakdown
|
|
393
|
+
initialize_agents unless @agents_initialized
|
|
394
|
+
|
|
395
|
+
breakdown = {}
|
|
396
|
+
|
|
397
|
+
# Include primary agents
|
|
398
|
+
@agents.each do |name, chat|
|
|
399
|
+
breakdown[name] = build_agent_context_info(chat)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# Include delegation instances
|
|
403
|
+
@delegation_instances.each do |instance_name, chat|
|
|
404
|
+
breakdown[instance_name.to_sym] = build_agent_context_info(chat)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
breakdown
|
|
408
|
+
end
|
|
409
|
+
|
|
369
410
|
# Implement Snapshotable interface
|
|
370
411
|
def primary_agents
|
|
371
412
|
@agents
|
|
@@ -546,6 +587,29 @@ module SwarmSDK
|
|
|
546
587
|
end
|
|
547
588
|
end
|
|
548
589
|
|
|
590
|
+
# Build context info hash for an agent chat instance
|
|
591
|
+
#
|
|
592
|
+
# @param chat [Agent::Chat] Agent chat instance with TokenTracking
|
|
593
|
+
# @return [Hash] Context usage information
|
|
594
|
+
def build_agent_context_info(chat)
|
|
595
|
+
return {} unless chat.respond_to?(:cumulative_input_tokens)
|
|
596
|
+
|
|
597
|
+
{
|
|
598
|
+
input_tokens: chat.cumulative_input_tokens,
|
|
599
|
+
output_tokens: chat.cumulative_output_tokens,
|
|
600
|
+
total_tokens: chat.cumulative_total_tokens,
|
|
601
|
+
cached_tokens: chat.cumulative_cached_tokens,
|
|
602
|
+
cache_creation_tokens: chat.cumulative_cache_creation_tokens,
|
|
603
|
+
effective_input_tokens: chat.effective_input_tokens,
|
|
604
|
+
context_limit: chat.context_limit,
|
|
605
|
+
usage_percentage: chat.context_usage_percentage,
|
|
606
|
+
tokens_remaining: chat.tokens_remaining,
|
|
607
|
+
input_cost: chat.cumulative_input_cost,
|
|
608
|
+
output_cost: chat.cumulative_output_cost,
|
|
609
|
+
total_cost: chat.cumulative_total_cost,
|
|
610
|
+
}
|
|
611
|
+
end
|
|
612
|
+
|
|
549
613
|
# Validate that observer agent exists
|
|
550
614
|
#
|
|
551
615
|
# @param agent_name [Symbol] Name of the observer agent
|
data/lib/swarm_sdk/version.rb
CHANGED
data/lib/swarm_sdk.rb
CHANGED
|
@@ -275,6 +275,11 @@ module SwarmSDK
|
|
|
275
275
|
#
|
|
276
276
|
# @param yaml_content [String] YAML configuration content
|
|
277
277
|
# @param base_dir [String, Pathname] Base directory for resolving agent file paths (default: Dir.pwd)
|
|
278
|
+
# @param allow_filesystem_tools [Boolean, nil] Whether to allow filesystem tools (nil uses global setting)
|
|
279
|
+
# @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
|
|
280
|
+
# When nil, uses the global SwarmSDK.config.env_interpolation setting.
|
|
281
|
+
# When true, interpolates ${VAR} and ${VAR:=default} patterns.
|
|
282
|
+
# When false, skips interpolation entirely.
|
|
278
283
|
# @return [Swarm, Workflow] Configured swarm or workflow instance
|
|
279
284
|
# @raise [ConfigurationError] If YAML is invalid or configuration is incorrect
|
|
280
285
|
#
|
|
@@ -297,8 +302,11 @@ module SwarmSDK
|
|
|
297
302
|
# @example Load with default base_dir (Dir.pwd)
|
|
298
303
|
# yaml = File.read("config.yml")
|
|
299
304
|
# swarm = SwarmSDK.load(yaml) # base_dir defaults to Dir.pwd
|
|
300
|
-
|
|
301
|
-
|
|
305
|
+
#
|
|
306
|
+
# @example Load without environment variable interpolation
|
|
307
|
+
# swarm = SwarmSDK.load(yaml, env_interpolation: false)
|
|
308
|
+
def load(yaml_content, base_dir: Dir.pwd, allow_filesystem_tools: nil, env_interpolation: nil)
|
|
309
|
+
config = Configuration.new(yaml_content, base_dir: base_dir, env_interpolation: env_interpolation)
|
|
302
310
|
config.load_and_validate
|
|
303
311
|
swarm = config.to_swarm(allow_filesystem_tools: allow_filesystem_tools)
|
|
304
312
|
|
|
@@ -320,6 +328,11 @@ module SwarmSDK
|
|
|
320
328
|
# loading swarms from configuration files.
|
|
321
329
|
#
|
|
322
330
|
# @param path [String, Pathname] Path to YAML configuration file
|
|
331
|
+
# @param allow_filesystem_tools [Boolean, nil] Whether to allow filesystem tools (nil uses global setting)
|
|
332
|
+
# @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
|
|
333
|
+
# When nil, uses the global SwarmSDK.config.env_interpolation setting.
|
|
334
|
+
# When true, interpolates ${VAR} and ${VAR:=default} patterns.
|
|
335
|
+
# When false, skips interpolation entirely.
|
|
323
336
|
# @return [Swarm, Workflow] Configured swarm or workflow instance
|
|
324
337
|
# @raise [ConfigurationError] If file not found or configuration invalid
|
|
325
338
|
#
|
|
@@ -329,8 +342,11 @@ module SwarmSDK
|
|
|
329
342
|
#
|
|
330
343
|
# @example With absolute path
|
|
331
344
|
# swarm = SwarmSDK.load_file("/absolute/path/config.yml")
|
|
332
|
-
|
|
333
|
-
|
|
345
|
+
#
|
|
346
|
+
# @example Load without environment variable interpolation
|
|
347
|
+
# swarm = SwarmSDK.load_file("config.yml", env_interpolation: false)
|
|
348
|
+
def load_file(path, allow_filesystem_tools: nil, env_interpolation: nil)
|
|
349
|
+
config = Configuration.load_file(path, env_interpolation: env_interpolation)
|
|
334
350
|
swarm = config.to_swarm(allow_filesystem_tools: allow_filesystem_tools)
|
|
335
351
|
|
|
336
352
|
# Apply hooks if any are configured (YAML-only feature)
|