Class: SolidLoop::Middlewares::ToolCallXmlParser
- Inherits:
-
Object
- Object
- SolidLoop::Middlewares::ToolCallXmlParser
- Defined in:
- app/services/solid_loop/middlewares/tool_call_xml_parser.rb
Instance Method Summary collapse
- #call(env) ⇒ Object
-
#initialize(app) ⇒ ToolCallXmlParser
constructor
A new instance of ToolCallXmlParser.
Constructor Details
#initialize(app) ⇒ ToolCallXmlParser
Returns a new instance of ToolCallXmlParser.
4 5 6 |
# File 'app/services/solid_loop/middlewares/tool_call_xml_parser.rb', line 4 def initialize(app) @app = app end |
Instance Method Details
#call(env) ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'app/services/solid_loop/middlewares/tool_call_xml_parser.rb', line 8 def call(env) # 1. Let the downstream pipeline run (ResponseParsing) @app.call(env) # 2. On the way back up, ResponseParsing has already created the message. # We check if there's a message, if it has content, and NO tool calls. msg = env. return unless msg && msg.content.present? && msg.tool_calls.empty? extracted = extract_from_legacy_xml(msg.content, env.payload[:model]) if extracted.any? # Revival, tool-call persistence and successor-job dispatch commit # atomically (same-DB transactional job backend): a crash or # rollback leaves neither a revived loop without jobs nor jobs for # rows that never committed. env.loop.transaction do revived = env.loop.transition_status( from: :completed, to: :running, expected_execution_token: nil, execution_token: env.execution_token ) next unless revived # Save raw JSON representation just in case it's needed msg.update!(tool_calls_raw: extracted) extracted.each do |tc| begin args = JSON.parse(tc.dig("function", "arguments") || "{}") msg.tool_calls.create!( tool_call_id: tc["id"], function_name: tc.dig("function", "name"), arguments: args ) rescue JSON::ParserError => e error_msg = "Invalid JSON in tool call arguments: #{e.}. Raw: #{tc.dig('function', 'arguments')}" Rails.logger.error error_msg env.loop..create!( role: "tool", tool_call_id: tc["id"], content: error_msg, status: "success" ) end end # Explicit ordered scope — never rely on association default order. persisted_tool_calls = msg.tool_calls.ordered.to_a # Same emission policy as ResponseParsing: parallel agents dispatch # every call at once; sequential agents dispatch only the first and # let ResponseCreation chain the rest. calls_to_dispatch = if env.agent.sequential_tool_calls? persisted_tool_calls.first(1) else persisted_tool_calls end calls_to_dispatch.each do |tool_call| SolidLoop.enqueue!(SolidLoop::ToolExecutionJob, tool_call.id, env.execution_token) end end end end |