swarm_sdk 2.4.4 → 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/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
- 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/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