swarm_sdk 2.7.1 → 2.7.3
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/builder.rb +17 -4
- data/lib/swarm_sdk/agent/chat.rb +8 -0
- data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +106 -3
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +7 -0
- data/lib/swarm_sdk/agent/definition.rb +10 -6
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +5 -2
- data/lib/swarm_sdk/swarm/agent_initializer.rb +15 -6
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +20 -2
- data/lib/swarm_sdk/tools/delegate.rb +8 -2
- 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: 9d0b44f3edf346a30b0442027c9ca2e8cf06e3e2c074c89f483c0eab0964a592
|
|
4
|
+
data.tar.gz: b7c690fd69303078c70c252a09a9e4f46b63f0f4a438a3fc9188cacc30e44109
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e0881dd9ae7fefc423bdbad4d56d535be627b622f2301eae1c12858d8198f7f55472198f6413535bccd768b17368233ac24ca5d2ab58417cbee9a7a68e8cdc79
|
|
7
|
+
data.tar.gz: a2af0ea8bf741a017196ce6db4d17a7b1bdc9fed2e517c35ae17c2336b950d25300186970f15b746fbc7dfd5c2679a53b9596d9f675c5a44089dc10672d542a0
|
|
@@ -275,6 +275,10 @@ module SwarmSDK
|
|
|
275
275
|
# backend: "GetBackendHelp",
|
|
276
276
|
# :qa
|
|
277
277
|
#
|
|
278
|
+
# @example With delegation options (preserve_context controls context persistence)
|
|
279
|
+
# delegates_to :frontend,
|
|
280
|
+
# { agent: :backend, tool_name: "AskBackend", preserve_context: false }
|
|
281
|
+
#
|
|
278
282
|
# @param agent_names_and_options [Array<Symbol, Hash>] Agent names and/or hash with custom tool names
|
|
279
283
|
# @return [void]
|
|
280
284
|
def delegates_to(*agent_names_and_options)
|
|
@@ -282,11 +286,20 @@ module SwarmSDK
|
|
|
282
286
|
case item
|
|
283
287
|
when Symbol, String
|
|
284
288
|
# Simple format: :frontend
|
|
285
|
-
@delegates_to << { agent: item.to_sym, tool_name: nil }
|
|
289
|
+
@delegates_to << { agent: item.to_sym, tool_name: nil, preserve_context: true }
|
|
286
290
|
when Hash
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
@delegates_to << {
|
|
291
|
+
if item.key?(:agent)
|
|
292
|
+
# Full config format: { agent: :backend, tool_name: "Custom", preserve_context: false }
|
|
293
|
+
@delegates_to << {
|
|
294
|
+
agent: item[:agent].to_sym,
|
|
295
|
+
tool_name: item[:tool_name],
|
|
296
|
+
preserve_context: item.fetch(:preserve_context, true),
|
|
297
|
+
}
|
|
298
|
+
else
|
|
299
|
+
# Hash format: { frontend: "AskFrontend", backend: nil }
|
|
300
|
+
item.each do |agent, tool_name|
|
|
301
|
+
@delegates_to << { agent: agent.to_sym, tool_name: tool_name, preserve_context: true }
|
|
302
|
+
end
|
|
290
303
|
end
|
|
291
304
|
else
|
|
292
305
|
raise ConfigurationError, "delegates_to accepts Symbols or Hashes, got #{item.class}"
|
data/lib/swarm_sdk/agent/chat.rb
CHANGED
|
@@ -207,6 +207,13 @@ module SwarmSDK
|
|
|
207
207
|
# --- SwarmSDK Abstraction API ---
|
|
208
208
|
# These methods provide SwarmSDK-specific semantics without exposing RubyLLM internals
|
|
209
209
|
|
|
210
|
+
# Check if streaming is enabled for this agent
|
|
211
|
+
#
|
|
212
|
+
# @return [Boolean] true if streaming is enabled
|
|
213
|
+
def streaming_enabled?
|
|
214
|
+
@streaming_enabled
|
|
215
|
+
end
|
|
216
|
+
|
|
210
217
|
# Model information
|
|
211
218
|
def model_id
|
|
212
219
|
@llm_chat.model.id
|
|
@@ -681,6 +688,7 @@ module SwarmSDK
|
|
|
681
688
|
if @streaming_enabled
|
|
682
689
|
# Reset chunk type tracking for new streaming request
|
|
683
690
|
@last_chunk_type = nil
|
|
691
|
+
|
|
684
692
|
@llm_chat.complete(**options) do |chunk|
|
|
685
693
|
emit_content_chunk(chunk)
|
|
686
694
|
end
|
|
@@ -82,6 +82,88 @@ module SwarmSDK
|
|
|
82
82
|
|
|
83
83
|
private
|
|
84
84
|
|
|
85
|
+
# Format citations for appending to response content
|
|
86
|
+
#
|
|
87
|
+
# Creates a markdown-formatted citations section with numbered links.
|
|
88
|
+
#
|
|
89
|
+
# @param citations [Array<String>] Array of citation URLs
|
|
90
|
+
# @return [String] Formatted citations section
|
|
91
|
+
def format_citations(citations)
|
|
92
|
+
return "" if citations.nil? || citations.empty?
|
|
93
|
+
|
|
94
|
+
formatted = "\n\n# Citations\n"
|
|
95
|
+
citations.each_with_index do |citation, index|
|
|
96
|
+
formatted += "- [#{index + 1}] #{citation}\n"
|
|
97
|
+
end
|
|
98
|
+
formatted
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Emit citations as a content_chunk event
|
|
102
|
+
#
|
|
103
|
+
# @param formatted_citations [String] Formatted citations text
|
|
104
|
+
# @param model_id [String] Model identifier
|
|
105
|
+
# @return [void]
|
|
106
|
+
def emit_citations_chunk(formatted_citations, model_id)
|
|
107
|
+
LogStream.emit(
|
|
108
|
+
type: "content_chunk",
|
|
109
|
+
agent: @agent_context.name,
|
|
110
|
+
chunk_type: "citations",
|
|
111
|
+
content: formatted_citations,
|
|
112
|
+
tool_calls: nil,
|
|
113
|
+
model: model_id,
|
|
114
|
+
)
|
|
115
|
+
rescue StandardError => e
|
|
116
|
+
RubyLLM.logger.debug("SwarmSDK: Failed to emit citations chunk: #{e.message}")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Extract citations and search results from an assistant message
|
|
120
|
+
#
|
|
121
|
+
# These fields are provided by some LLM providers (e.g., Perplexity's sonar models)
|
|
122
|
+
# when the model performs web search or cites sources.
|
|
123
|
+
#
|
|
124
|
+
# @param message [RubyLLM::Message] Assistant message with potential citations
|
|
125
|
+
# @return [Hash] Citations and search results (empty if not present)
|
|
126
|
+
def extract_citations_and_search(message)
|
|
127
|
+
return {} unless message.raw&.body
|
|
128
|
+
|
|
129
|
+
body = message.raw.body
|
|
130
|
+
|
|
131
|
+
# For streaming responses, body might be empty - check Fiber-local
|
|
132
|
+
# (set by LLMInstrumentationMiddleware with accumulated SSE chunks)
|
|
133
|
+
if body.is_a?(String) && body.empty?
|
|
134
|
+
fiber_body = Fiber[:last_sse_body]
|
|
135
|
+
body = fiber_body if fiber_body
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
return {} unless body
|
|
139
|
+
|
|
140
|
+
# Handle SSE streaming responses (body is a string starting with "data:")
|
|
141
|
+
if body.is_a?(String) && body.start_with?("data:")
|
|
142
|
+
# Parse the LAST SSE event which contains citations
|
|
143
|
+
last_data_line = body.split("\n").reverse.find { |l| l.start_with?("data:") && !l.include?("[DONE]") && !l.include?("message_stop") }
|
|
144
|
+
if last_data_line
|
|
145
|
+
body = JSON.parse(last_data_line.sub(/^data:\s*/, ""))
|
|
146
|
+
end
|
|
147
|
+
elsif body.is_a?(String)
|
|
148
|
+
# Regular JSON string response
|
|
149
|
+
body = JSON.parse(body)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Handle Faraday::Response objects (has .body method)
|
|
153
|
+
body = body.body if body.respond_to?(:body) && !body.is_a?(Hash)
|
|
154
|
+
|
|
155
|
+
return {} unless body.is_a?(Hash)
|
|
156
|
+
|
|
157
|
+
result = {}
|
|
158
|
+
result[:citations] = body["citations"] if body["citations"]
|
|
159
|
+
result[:search_results] = body["search_results"] if body["search_results"]
|
|
160
|
+
result
|
|
161
|
+
rescue StandardError => e
|
|
162
|
+
# Includes JSON::ParserError and other parsing errors
|
|
163
|
+
RubyLLM.logger.debug("SwarmSDK: Failed to extract citations: #{e.message}")
|
|
164
|
+
{}
|
|
165
|
+
end
|
|
166
|
+
|
|
85
167
|
# Extract usage information from an assistant message
|
|
86
168
|
#
|
|
87
169
|
# @param message [RubyLLM::Message] Assistant message with usage data
|
|
@@ -202,6 +284,7 @@ module SwarmSDK
|
|
|
202
284
|
return unless @chat.hook_executor
|
|
203
285
|
|
|
204
286
|
usage_info = extract_usage_info(message)
|
|
287
|
+
citations_data = extract_citations_and_search(message)
|
|
205
288
|
|
|
206
289
|
context = Hooks::Context.new(
|
|
207
290
|
event: :agent_step,
|
|
@@ -213,9 +296,11 @@ module SwarmSDK
|
|
|
213
296
|
tool_calls: format_tool_calls(message.tool_calls),
|
|
214
297
|
finish_reason: "tool_calls",
|
|
215
298
|
usage: usage_info,
|
|
299
|
+
citations: citations_data[:citations],
|
|
300
|
+
search_results: citations_data[:search_results],
|
|
216
301
|
tool_executions: tool_executions.empty? ? nil : tool_executions,
|
|
217
302
|
timestamp: Time.now.utc.iso8601,
|
|
218
|
-
},
|
|
303
|
+
}.compact,
|
|
219
304
|
)
|
|
220
305
|
|
|
221
306
|
agent_hooks = @chat.hook_agent_hooks[:agent_step] || []
|
|
@@ -238,6 +323,22 @@ module SwarmSDK
|
|
|
238
323
|
return unless @chat.hook_executor
|
|
239
324
|
|
|
240
325
|
usage_info = extract_usage_info(message)
|
|
326
|
+
citations_data = extract_citations_and_search(message)
|
|
327
|
+
|
|
328
|
+
# Format content with citations appended
|
|
329
|
+
content_with_citations = message.content
|
|
330
|
+
if citations_data[:citations] && !citations_data[:citations].empty?
|
|
331
|
+
formatted_citations = format_citations(citations_data[:citations])
|
|
332
|
+
content_with_citations = message.content + formatted_citations
|
|
333
|
+
|
|
334
|
+
# Also modify the original message for Result.content
|
|
335
|
+
message.content = content_with_citations
|
|
336
|
+
|
|
337
|
+
# Emit citations chunk if streaming is enabled
|
|
338
|
+
if @chat.streaming_enabled?
|
|
339
|
+
emit_citations_chunk(formatted_citations, message.model_id)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
241
342
|
|
|
242
343
|
# Use override if set (e.g., "finish_agent"), otherwise default to "stop"
|
|
243
344
|
finish_reason = @finish_reason_override || "stop"
|
|
@@ -249,13 +350,15 @@ module SwarmSDK
|
|
|
249
350
|
swarm: @chat.hook_swarm,
|
|
250
351
|
metadata: {
|
|
251
352
|
model: message.model_id,
|
|
252
|
-
content:
|
|
353
|
+
content: content_with_citations, # Content with citations appended
|
|
253
354
|
tool_calls: nil, # Final response has no tool calls
|
|
254
355
|
finish_reason: finish_reason,
|
|
255
356
|
usage: usage_info,
|
|
357
|
+
citations: citations_data[:citations],
|
|
358
|
+
search_results: citations_data[:search_results],
|
|
256
359
|
tool_executions: tool_executions.empty? ? nil : tool_executions,
|
|
257
360
|
timestamp: Time.now.utc.iso8601,
|
|
258
|
-
},
|
|
361
|
+
}.compact,
|
|
259
362
|
)
|
|
260
363
|
|
|
261
364
|
agent_hooks = @chat.hook_agent_hooks[:agent_stop] || []
|
|
@@ -13,10 +13,15 @@ module SwarmSDK
|
|
|
13
13
|
#
|
|
14
14
|
# @return [void]
|
|
15
15
|
def inject_llm_instrumentation
|
|
16
|
+
puts "=== inject_llm_instrumentation called ===" if ENV["DEBUG_CITATIONS"]
|
|
16
17
|
return unless @provider
|
|
17
18
|
|
|
19
|
+
puts "=== Has provider ===" if ENV["DEBUG_CITATIONS"]
|
|
20
|
+
|
|
18
21
|
faraday_conn = @provider.connection&.connection
|
|
19
22
|
return unless faraday_conn
|
|
23
|
+
|
|
24
|
+
puts "=== Has faraday connection ===" if ENV["DEBUG_CITATIONS"]
|
|
20
25
|
return if @llm_instrumentation_injected
|
|
21
26
|
|
|
22
27
|
provider_name = @provider.class.name.split("::").last.downcase
|
|
@@ -31,8 +36,10 @@ module SwarmSDK
|
|
|
31
36
|
|
|
32
37
|
@llm_instrumentation_injected = true
|
|
33
38
|
|
|
39
|
+
puts "=== MIDDLEWARE INJECTED ===" if ENV["DEBUG_CITATIONS"]
|
|
34
40
|
RubyLLM.logger.debug("SwarmSDK: Injected LLM instrumentation middleware for agent #{@agent_name}")
|
|
35
41
|
rescue StandardError => e
|
|
42
|
+
puts "=== MIDDLEWARE INJECTION ERROR: #{e.message} ===" if ENV["DEBUG_CITATIONS"]
|
|
36
43
|
LogStream.emit_error(e, source: "instrumentation", context: "inject_middleware", agent: @agent_name)
|
|
37
44
|
RubyLLM.logger.debug("SwarmSDK: Failed to inject LLM instrumentation: #{e.message}")
|
|
38
45
|
end
|
|
@@ -325,16 +325,20 @@ module SwarmSDK
|
|
|
325
325
|
delegation_config.flat_map do |item|
|
|
326
326
|
case item
|
|
327
327
|
when Symbol, String
|
|
328
|
-
# Simple format: :frontend → {agent: :frontend, tool_name: nil}
|
|
329
|
-
[{ agent: item.to_sym, tool_name: nil }]
|
|
328
|
+
# Simple format: :frontend → {agent: :frontend, tool_name: nil, preserve_context: true}
|
|
329
|
+
[{ agent: item.to_sym, tool_name: nil, preserve_context: true }]
|
|
330
330
|
when Hash
|
|
331
331
|
# Could be already normalized or hash format
|
|
332
332
|
if item.key?(:agent)
|
|
333
|
-
# Already normalized: {agent: :frontend, tool_name: "Custom"}
|
|
334
|
-
[
|
|
333
|
+
# Already normalized: {agent: :frontend, tool_name: "Custom", preserve_context: false}
|
|
334
|
+
[{
|
|
335
|
+
agent: item[:agent].to_sym,
|
|
336
|
+
tool_name: item[:tool_name],
|
|
337
|
+
preserve_context: item.fetch(:preserve_context, true),
|
|
338
|
+
}]
|
|
335
339
|
else
|
|
336
340
|
# Hash format in array: {frontend: "AskFrontend"}
|
|
337
|
-
item.map { |agent, tool_name| { agent: agent.to_sym, tool_name: tool_name } }
|
|
341
|
+
item.map { |agent, tool_name| { agent: agent.to_sym, tool_name: tool_name, preserve_context: true } }
|
|
338
342
|
end
|
|
339
343
|
else
|
|
340
344
|
raise ConfigurationError, "Invalid delegation config format: #{item.inspect}"
|
|
@@ -343,7 +347,7 @@ module SwarmSDK
|
|
|
343
347
|
elsif delegation_config.is_a?(Hash)
|
|
344
348
|
# Hash format: {frontend: "AskFrontend", backend: nil}
|
|
345
349
|
delegation_config.map do |agent, tool_name|
|
|
346
|
-
{ agent: agent.to_sym, tool_name: tool_name }
|
|
350
|
+
{ agent: agent.to_sym, tool_name: tool_name, preserve_context: true }
|
|
347
351
|
end
|
|
348
352
|
else
|
|
349
353
|
raise ConfigurationError, "delegates_to must be an Array or Hash, got #{delegation_config.class}"
|
|
@@ -39,8 +39,6 @@ module SwarmSDK
|
|
|
39
39
|
emit_request_event(env, start_time)
|
|
40
40
|
|
|
41
41
|
# Wrap existing on_data to capture raw SSE chunks for streaming
|
|
42
|
-
# This allows us to capture the full streaming response for instrumentation
|
|
43
|
-
# Check if env.request exists and has on_data (only set for streaming requests)
|
|
44
42
|
if env.request&.on_data
|
|
45
43
|
original_on_data = env.request.on_data
|
|
46
44
|
env.request.on_data = proc do |chunk, bytes, response_env|
|
|
@@ -64,6 +62,11 @@ module SwarmSDK
|
|
|
64
62
|
response_env.body
|
|
65
63
|
end
|
|
66
64
|
|
|
65
|
+
# Store SSE body in Fiber-local for citation extraction
|
|
66
|
+
# This allows append_citations_to_content to access the full SSE body
|
|
67
|
+
# even though response.body is empty for streaming responses
|
|
68
|
+
Fiber[:last_sse_body] = raw_body if accumulated_raw_chunks.any?
|
|
69
|
+
|
|
67
70
|
# Emit response event
|
|
68
71
|
emit_response_event(response_env, start_time, end_time, duration, raw_body)
|
|
69
72
|
end
|
|
@@ -91,8 +91,9 @@ module SwarmSDK
|
|
|
91
91
|
# @param agent_name [Symbol] Name of the delegating agent
|
|
92
92
|
# @param delegating_chat [Agent::Chat, nil] The chat instance of the agent doing the delegating
|
|
93
93
|
# @param custom_tool_name [String, nil] Optional custom tool name (overrides auto-generated name)
|
|
94
|
+
# @param preserve_context [Boolean] Whether to preserve conversation context between delegations (default: true)
|
|
94
95
|
# @return [Tools::Delegate] Delegation tool
|
|
95
|
-
def create_delegation_tool(name:, description:, delegate_chat:, agent_name:, delegating_chat: nil, custom_tool_name: nil)
|
|
96
|
+
def create_delegation_tool(name:, description:, delegate_chat:, agent_name:, delegating_chat: nil, custom_tool_name: nil, preserve_context: true)
|
|
96
97
|
Tools::Delegate.new(
|
|
97
98
|
delegate_name: name,
|
|
98
99
|
delegate_description: description,
|
|
@@ -101,6 +102,7 @@ module SwarmSDK
|
|
|
101
102
|
swarm: @swarm,
|
|
102
103
|
delegating_chat: delegating_chat,
|
|
103
104
|
custom_tool_name: custom_tool_name,
|
|
105
|
+
preserve_context: preserve_context,
|
|
104
106
|
)
|
|
105
107
|
end
|
|
106
108
|
|
|
@@ -219,7 +221,7 @@ module SwarmSDK
|
|
|
219
221
|
#
|
|
220
222
|
# @param delegator_name [Symbol, String] Name of the agent doing the delegating
|
|
221
223
|
# @param delegator_chat [Agent::Chat] Chat instance of the delegator
|
|
222
|
-
# @param delegation_config [Hash] Delegation configuration with :agent and :
|
|
224
|
+
# @param delegation_config [Hash] Delegation configuration with :agent, :tool_name, and :preserve_context keys
|
|
223
225
|
# @param tool_configurator [ToolConfigurator] Tool configuration helper
|
|
224
226
|
# @param create_nested_instances [Boolean] Whether to create new instances for nested delegation
|
|
225
227
|
# @return [void]
|
|
@@ -227,10 +229,11 @@ module SwarmSDK
|
|
|
227
229
|
delegate_name_sym = delegation_config[:agent]
|
|
228
230
|
delegate_name_str = delegate_name_sym.to_s
|
|
229
231
|
custom_tool_name = delegation_config[:tool_name]
|
|
232
|
+
preserve_context = delegation_config.fetch(:preserve_context, true)
|
|
230
233
|
|
|
231
234
|
# Check if target is a registered swarm
|
|
232
235
|
if @swarm.swarm_registry&.registered?(delegate_name_str)
|
|
233
|
-
wire_swarm_delegation(delegator_name, delegator_chat, delegate_name_str, custom_tool_name)
|
|
236
|
+
wire_swarm_delegation(delegator_name, delegator_chat, delegate_name_str, custom_tool_name, preserve_context)
|
|
234
237
|
elsif @swarm.agent_definitions.key?(delegate_name_sym)
|
|
235
238
|
wire_agent_delegation(
|
|
236
239
|
delegator_name: delegator_name,
|
|
@@ -239,6 +242,7 @@ module SwarmSDK
|
|
|
239
242
|
custom_tool_name: custom_tool_name,
|
|
240
243
|
tool_configurator: tool_configurator,
|
|
241
244
|
create_nested_instances: create_nested_instances,
|
|
245
|
+
preserve_context: preserve_context,
|
|
242
246
|
)
|
|
243
247
|
else
|
|
244
248
|
raise ConfigurationError,
|
|
@@ -252,8 +256,9 @@ module SwarmSDK
|
|
|
252
256
|
# @param delegator_chat [Agent::Chat] Chat instance of the delegator
|
|
253
257
|
# @param swarm_name [String] Name of the registered swarm
|
|
254
258
|
# @param custom_tool_name [String, nil] Optional custom tool name
|
|
259
|
+
# @param preserve_context [Boolean] Whether to preserve context between delegations
|
|
255
260
|
# @return [void]
|
|
256
|
-
def wire_swarm_delegation(delegator_name, delegator_chat, swarm_name, custom_tool_name)
|
|
261
|
+
def wire_swarm_delegation(delegator_name, delegator_chat, swarm_name, custom_tool_name, preserve_context)
|
|
257
262
|
tool = create_delegation_tool(
|
|
258
263
|
name: swarm_name,
|
|
259
264
|
description: "External swarm: #{swarm_name}",
|
|
@@ -261,13 +266,14 @@ module SwarmSDK
|
|
|
261
266
|
agent_name: delegator_name,
|
|
262
267
|
delegating_chat: delegator_chat,
|
|
263
268
|
custom_tool_name: custom_tool_name,
|
|
269
|
+
preserve_context: preserve_context,
|
|
264
270
|
)
|
|
265
271
|
|
|
266
272
|
# Register in tool registry (Plan 025)
|
|
267
273
|
delegator_chat.tool_registry.register(
|
|
268
274
|
tool,
|
|
269
275
|
source: :delegation,
|
|
270
|
-
metadata: { delegate_name: swarm_name, delegation_type: :swarm },
|
|
276
|
+
metadata: { delegate_name: swarm_name, delegation_type: :swarm, preserve_context: preserve_context },
|
|
271
277
|
)
|
|
272
278
|
end
|
|
273
279
|
|
|
@@ -282,8 +288,9 @@ module SwarmSDK
|
|
|
282
288
|
# @param custom_tool_name [String, nil] Optional custom tool name
|
|
283
289
|
# @param tool_configurator [ToolConfigurator] Tool configuration helper
|
|
284
290
|
# @param create_nested_instances [Boolean] Whether to create new instances if not found
|
|
291
|
+
# @param preserve_context [Boolean] Whether to preserve context between delegations
|
|
285
292
|
# @return [void]
|
|
286
|
-
def wire_agent_delegation(delegator_name:, delegator_chat:, delegate_name_sym:, custom_tool_name:, tool_configurator:, create_nested_instances:)
|
|
293
|
+
def wire_agent_delegation(delegator_name:, delegator_chat:, delegate_name_sym:, custom_tool_name:, tool_configurator:, create_nested_instances:, preserve_context:)
|
|
287
294
|
delegate_definition = @swarm.agent_definitions[delegate_name_sym]
|
|
288
295
|
|
|
289
296
|
# Determine which chat instance to use
|
|
@@ -316,6 +323,7 @@ module SwarmSDK
|
|
|
316
323
|
agent_name: delegator_name,
|
|
317
324
|
delegating_chat: delegator_chat,
|
|
318
325
|
custom_tool_name: custom_tool_name,
|
|
326
|
+
preserve_context: preserve_context,
|
|
319
327
|
)
|
|
320
328
|
|
|
321
329
|
# Register in tool registry (Plan 025)
|
|
@@ -325,6 +333,7 @@ module SwarmSDK
|
|
|
325
333
|
metadata: {
|
|
326
334
|
delegate_name: delegate_name_sym,
|
|
327
335
|
delegation_mode: delegate_definition.shared_across_delegations ? :shared : :isolated,
|
|
336
|
+
preserve_context: preserve_context,
|
|
328
337
|
},
|
|
329
338
|
)
|
|
330
339
|
end
|
|
@@ -238,7 +238,14 @@ module SwarmSDK
|
|
|
238
238
|
return unless LogStream.emitter
|
|
239
239
|
|
|
240
240
|
metadata_without_duplicates = context.metadata.except(
|
|
241
|
-
:model,
|
|
241
|
+
:model,
|
|
242
|
+
:content,
|
|
243
|
+
:tool_calls,
|
|
244
|
+
:finish_reason,
|
|
245
|
+
:usage,
|
|
246
|
+
:tool_executions,
|
|
247
|
+
:citations,
|
|
248
|
+
:search_results,
|
|
242
249
|
)
|
|
243
250
|
|
|
244
251
|
LogStream.emit(
|
|
@@ -251,6 +258,8 @@ module SwarmSDK
|
|
|
251
258
|
tool_calls: context.metadata[:tool_calls],
|
|
252
259
|
finish_reason: context.metadata[:finish_reason],
|
|
253
260
|
usage: context.metadata[:usage],
|
|
261
|
+
citations: context.metadata[:citations],
|
|
262
|
+
search_results: context.metadata[:search_results],
|
|
254
263
|
tool_executions: context.metadata[:tool_executions],
|
|
255
264
|
metadata: metadata_without_duplicates,
|
|
256
265
|
)
|
|
@@ -261,7 +270,14 @@ module SwarmSDK
|
|
|
261
270
|
return unless LogStream.emitter
|
|
262
271
|
|
|
263
272
|
metadata_without_duplicates = context.metadata.except(
|
|
264
|
-
:model,
|
|
273
|
+
:model,
|
|
274
|
+
:content,
|
|
275
|
+
:tool_calls,
|
|
276
|
+
:finish_reason,
|
|
277
|
+
:usage,
|
|
278
|
+
:tool_executions,
|
|
279
|
+
:citations,
|
|
280
|
+
:search_results,
|
|
265
281
|
)
|
|
266
282
|
|
|
267
283
|
LogStream.emit(
|
|
@@ -274,6 +290,8 @@ module SwarmSDK
|
|
|
274
290
|
tool_calls: context.metadata[:tool_calls],
|
|
275
291
|
finish_reason: context.metadata[:finish_reason],
|
|
276
292
|
usage: context.metadata[:usage],
|
|
293
|
+
citations: context.metadata[:citations],
|
|
294
|
+
search_results: context.metadata[:search_results],
|
|
277
295
|
tool_executions: context.metadata[:tool_executions],
|
|
278
296
|
metadata: metadata_without_duplicates,
|
|
279
297
|
)
|
|
@@ -37,7 +37,7 @@ module SwarmSDK
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
attr_reader :delegate_name, :delegate_target, :tool_name
|
|
40
|
+
attr_reader :delegate_name, :delegate_target, :tool_name, :preserve_context
|
|
41
41
|
|
|
42
42
|
# Initialize a delegation tool
|
|
43
43
|
#
|
|
@@ -48,6 +48,7 @@ module SwarmSDK
|
|
|
48
48
|
# @param swarm [Swarm] The swarm instance (provides hook_registry, delegation_call_stack, swarm_registry)
|
|
49
49
|
# @param delegating_chat [Agent::Chat, nil] The chat instance of the agent doing the delegating (for accessing hooks)
|
|
50
50
|
# @param custom_tool_name [String, nil] Optional custom tool name (overrides auto-generated name)
|
|
51
|
+
# @param preserve_context [Boolean] Whether to preserve conversation context between delegations (default: true)
|
|
51
52
|
def initialize(
|
|
52
53
|
delegate_name:,
|
|
53
54
|
delegate_description:,
|
|
@@ -55,7 +56,8 @@ module SwarmSDK
|
|
|
55
56
|
agent_name:,
|
|
56
57
|
swarm:,
|
|
57
58
|
delegating_chat: nil,
|
|
58
|
-
custom_tool_name: nil
|
|
59
|
+
custom_tool_name: nil,
|
|
60
|
+
preserve_context: true
|
|
59
61
|
)
|
|
60
62
|
super()
|
|
61
63
|
|
|
@@ -65,6 +67,7 @@ module SwarmSDK
|
|
|
65
67
|
@agent_name = agent_name
|
|
66
68
|
@swarm = swarm
|
|
67
69
|
@delegating_chat = delegating_chat
|
|
70
|
+
@preserve_context = preserve_context
|
|
68
71
|
|
|
69
72
|
# Use custom tool name if provided, otherwise generate using canonical method
|
|
70
73
|
@tool_name = custom_tool_name || self.class.tool_name_for(delegate_name)
|
|
@@ -224,6 +227,9 @@ module SwarmSDK
|
|
|
224
227
|
# Push delegate target onto call stack to track delegation chain
|
|
225
228
|
call_stack.push(@delegate_target)
|
|
226
229
|
begin
|
|
230
|
+
# Clear conversation if preserve_context is false
|
|
231
|
+
@delegate_chat.clear_conversation unless @preserve_context
|
|
232
|
+
|
|
227
233
|
response = @delegate_chat.ask(message, source: "delegation")
|
|
228
234
|
response.content
|
|
229
235
|
ensure
|
data/lib/swarm_sdk/version.rb
CHANGED