From a49d977f554cc560a906d6746a8a9f09277ce37c Mon Sep 17 00:00:00 2001 From: SmartDever02 Date: Wed, 3 Dec 2025 21:01:16 -0300 Subject: [PATCH] feat: Add AgentInvoke component for agent-to-agent invocation (#11550) - Implement AgentInvoke component for internal agent-to-agent calls - Enable modular AI portal architecture with specialized agents - Add dynamic routing and parameter passing between agents - Include example templates: portal agent, multi-step workflow, dynamic selection - Support session management and timeout configuration - Direct internal invocation for better performance vs HTTP Resolves #11550 --- agent/component/agent_invoke.py | 326 ++++++++++++++++++ .../dynamic_agent_selection_example.json | 251 ++++++++++++++ .../multi_step_workflow_example.json | 161 +++++++++ agent/templates/portal_agent_example.json | 205 +++++++++++ 4 files changed, 943 insertions(+) create mode 100644 agent/component/agent_invoke.py create mode 100644 agent/templates/dynamic_agent_selection_example.json create mode 100644 agent/templates/multi_step_workflow_example.json create mode 100644 agent/templates/portal_agent_example.json diff --git a/agent/component/agent_invoke.py b/agent/component/agent_invoke.py new file mode 100644 index 000000000..a440f8f71 --- /dev/null +++ b/agent/component/agent_invoke.py @@ -0,0 +1,326 @@ +# +# Copyright 2025 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +""" +AgentInvoke Component + +This component enables agent-to-agent invocation within RAGFlow, allowing a portal +agent to dynamically delegate tasks to specialized downstream agents/workflows. + +Key Features: +- Dynamic routing: Route user requests to specialized agents based on intent +- Parameter passing: Pass inputs and context to downstream agents +- Result aggregation: Collect and return agent outputs +- Modular design: Agents can be developed and maintained independently + +Use Case Example: + Portal Agent receives: "Analyze Q3 sales data" + -> Invokes Sales Analysis Agent with parameters: {"period": "Q3", ...} + -> Sales Analysis Agent processes and returns results + -> Portal Agent formats and returns to user +""" + +import asyncio +import json +import logging +import os +import time +from abc import ABC + +from agent.component.base import ComponentBase, ComponentParamBase +from common.connection_utils import timeout + + +class AgentInvokeParam(ComponentParamBase): + """ + Parameters for the AgentInvoke component. + + Attributes: + agent_id (str): The ID of the agent/workflow to invoke + agent_name (str): Optional human-readable name for logging + inputs (dict): Input parameters to pass to the agent + query (str): Optional query/question to pass to the agent + timeout_seconds (int): Maximum time to wait for agent response + create_new_session (bool): Whether to create a new session for each invocation + """ + + def __init__(self): + super().__init__() + self.agent_id = "" + self.agent_name = "" + self.inputs = {} + self.query = "" + self.timeout_seconds = 300 # 5 minutes default + self.create_new_session = True + self.session_id = None + + def check(self): + """Validate component parameters""" + self.check_empty(self.agent_id, "Agent ID") + self.check_positive_integer(self.timeout_seconds, "Timeout in seconds") + self.check_boolean(self.create_new_session, "Create new session") + + +class AgentInvoke(ComponentBase, ABC): + """ + Component for invoking downstream agents/workflows. + + This component allows a portal agent to delegate tasks to specialized + agents, enabling a modular, maintainable AI portal architecture. + """ + + component_name = "AgentInvoke" + + @timeout(int(os.environ.get("COMPONENT_EXEC_TIMEOUT", 360))) + def _invoke(self, **kwargs): + """ + Invoke a downstream agent and collect its output. + + Args: + **kwargs: Additional context from the parent workflow + + Returns: + dict: Agent output including answer, references, and metadata + """ + if self.check_if_canceled("AgentInvoke processing"): + return + + # Get tenant ID from canvas + tenant_id = self._canvas.get_tenant_id() + if not tenant_id: + error_msg = "Tenant ID not available in canvas context" + logging.error(error_msg) + self.set_output("_ERROR", error_msg) + return error_msg + + # Resolve agent_id if it's a variable reference + agent_id = self._param.agent_id + if agent_id and agent_id.startswith("{") and agent_id.endswith("}"): + try: + agent_id = self._canvas.get_variable_value(agent_id) + except Exception as e: + logging.warning(f"Failed to resolve agent_id variable: {e}") + + if not agent_id: + error_msg = "Agent ID is required but was not provided" + logging.error(error_msg) + self.set_output("_ERROR", error_msg) + return error_msg + + # Resolve query from parameter or variable + query = self._param.query + if query and query.startswith("{") and query.endswith("}"): + try: + query = self._canvas.get_variable_value(query) + except Exception as e: + logging.warning(f"Failed to resolve query variable: {e}") + query = "" + + # Build inputs dictionary by resolving all variable references + inputs = {} + if self._param.inputs: + for key, value in self._param.inputs.items(): + if isinstance(value, str) and value.startswith("{") and value.endswith("}"): + try: + inputs[key] = self._canvas.get_variable_value(value) + except Exception as e: + logging.warning(f"Failed to resolve input '{key}' variable: {e}") + inputs[key] = value + else: + inputs[key] = value + + # Determine session_id + session_id = None + if not self._param.create_new_session: + session_id = self._param.session_id + if session_id and session_id.startswith("{") and session_id.endswith("}"): + try: + session_id = self._canvas.get_variable_value(session_id) + except Exception: + session_id = None + + agent_name = self._param.agent_name or agent_id + logging.info(f"AgentInvoke: Invoking agent '{agent_name}' (ID: {agent_id}) with query: {query[:100]}...") + + try: + # Import here to avoid circular dependencies + from api.db.services.canvas_service import completion as agent_completion + + # Invoke the agent and collect output + result = self._invoke_agent_sync( + agent_completion, + tenant_id=tenant_id, + agent_id=agent_id, + query=query, + inputs=inputs, + session_id=session_id, + timeout=self._param.timeout_seconds + ) + + if result.get("error"): + error_msg = result["error"] + logging.error(f"AgentInvoke: Agent '{agent_name}' failed: {error_msg}") + self.set_output("_ERROR", error_msg) + return error_msg + + # Set outputs + self.set_output("answer", result.get("answer", "")) + self.set_output("reference", result.get("reference", {})) + self.set_output("session_id", result.get("session_id", "")) + self.set_output("metadata", result.get("metadata", {})) + self.set_output("result", result.get("answer", "")) # Alias for compatibility + + logging.info(f"AgentInvoke: Agent '{agent_name}' completed successfully") + return result.get("answer", "") + + except Exception as e: + error_msg = f"Failed to invoke agent '{agent_name}': {str(e)}" + logging.exception(error_msg) + self.set_output("_ERROR", error_msg) + return error_msg + + def _invoke_agent_sync(self, completion_func, tenant_id, agent_id, query, inputs, session_id, timeout): + """ + Synchronously invoke an agent and wait for completion. + + This method handles the async completion generator and collects the full output. + + Args: + completion_func: The agent completion function to call + tenant_id (str): Tenant/user ID + agent_id (str): Agent ID to invoke + query (str): Query/question for the agent + inputs (dict): Input parameters for the agent + session_id (str): Optional session ID to continue conversation + timeout (int): Timeout in seconds + + Returns: + dict: Collected agent output + """ + import asyncio + + # Create result container + result = { + "answer": "", + "reference": {}, + "session_id": "", + "metadata": {}, + "error": None + } + + async def _run_agent(): + """Async wrapper to run the agent completion""" + try: + answer_parts = [] + final_session_id = None + final_reference = {} + metadata = {} + + # Run the agent completion generator + async for event_data in completion_func( + tenant_id=tenant_id, + agent_id=agent_id, + query=query, + inputs=inputs, + session_id=session_id + ): + # Check if task was canceled + if self.check_if_canceled("AgentInvoke processing"): + result["error"] = "Task was canceled" + return + + # Parse SSE format: "data:{json}\n\n" + if isinstance(event_data, str) and event_data.startswith("data:"): + json_str = event_data[5:].strip() + try: + event = json.loads(json_str) + except json.JSONDecodeError: + continue + else: + event = event_data + + # Extract session_id if present + if "session_id" in event: + final_session_id = event["session_id"] + + # Handle different event types + event_type = event.get("event", "") + data = event.get("data", {}) + + if event_type == "message": + # Accumulate message content + content = data.get("content", "") + if content: + answer_parts.append(content) + + elif event_type == "message_end": + # Final message with references + if data and data.get("reference"): + final_reference = data["reference"] + + elif event_type == "workflow_finished": + # Agent completed successfully + outputs = data.get("outputs", {}) + metadata = { + "elapsed_time": data.get("elapsed_time", 0), + "created_at": data.get("created_at", time.time()) + } + # If outputs contain content, use it as the answer + if isinstance(outputs, dict) and outputs.get("content"): + answer_parts = [outputs["content"]] + break + + elif event_type == "error": + # Error occurred during agent execution + result["error"] = data.get("message", "Unknown error") + return + + # Combine all answer parts + result["answer"] = "".join(answer_parts) + result["reference"] = final_reference + result["session_id"] = final_session_id or session_id or "" + result["metadata"] = metadata + + except Exception as e: + result["error"] = str(e) + logging.exception(f"Error during agent invocation: {e}") + + # Run the async function with timeout + try: + # Get or create event loop + loop = self._canvas._loop if hasattr(self._canvas, '_loop') else None + + if loop and loop.is_running(): + # If we're already in an async context, schedule the coroutine + future = asyncio.run_coroutine_threadsafe(_run_agent(), loop) + future.result(timeout=timeout) + else: + # Run in a new event loop + asyncio.run(asyncio.wait_for(_run_agent(), timeout=timeout)) + + except asyncio.TimeoutError: + result["error"] = f"Agent invocation timed out after {timeout} seconds" + logging.error(result["error"]) + except Exception as e: + result["error"] = f"Failed to execute agent: {str(e)}" + logging.exception(result["error"]) + + return result + + def thoughts(self) -> str: + """Return current component status for UI display""" + agent_name = self._param.agent_name or "agent" + return f"Invoking {agent_name}..." + diff --git a/agent/templates/dynamic_agent_selection_example.json b/agent/templates/dynamic_agent_selection_example.json new file mode 100644 index 000000000..7fd6a596c --- /dev/null +++ b/agent/templates/dynamic_agent_selection_example.json @@ -0,0 +1,251 @@ +{ + "description": "Dynamic Agent Selection Based on Document Type", + "graph": { + "nodes": [ + { + "id": "begin", + "data": { + "name": "Start", + "component_name": "Begin", + "x": 100, + "y": 300 + } + }, + { + "id": "switch_0", + "data": { + "name": "Detect Document Type", + "component_name": "Switch", + "x": 300, + "y": 300 + } + }, + { + "id": "agent_invoke_pdf", + "data": { + "name": "PDF Analyzer", + "component_name": "AgentInvoke", + "x": 500, + "y": 100 + } + }, + { + "id": "agent_invoke_excel", + "data": { + "name": "Spreadsheet Analyzer", + "component_name": "AgentInvoke", + "x": 500, + "y": 250 + } + }, + { + "id": "agent_invoke_image", + "data": { + "name": "Image Analyzer", + "component_name": "AgentInvoke", + "x": 500, + "y": 400 + } + }, + { + "id": "agent_invoke_text", + "data": { + "name": "Text Analyzer", + "component_name": "AgentInvoke", + "x": 500, + "y": 550 + } + }, + { + "id": "message_0", + "data": { + "name": "Analysis Result", + "component_name": "Message", + "x": 700, + "y": 300 + } + } + ], + "edges": [ + { + "source": "begin", + "target": "switch_0" + }, + { + "source": "switch_0", + "target": "agent_invoke_pdf", + "label": "pdf" + }, + { + "source": "switch_0", + "target": "agent_invoke_excel", + "label": "excel" + }, + { + "source": "switch_0", + "target": "agent_invoke_image", + "label": "image" + }, + { + "source": "switch_0", + "target": "agent_invoke_text", + "label": "default" + }, + { + "source": "agent_invoke_pdf", + "target": "message_0" + }, + { + "source": "agent_invoke_excel", + "target": "message_0" + }, + { + "source": "agent_invoke_image", + "target": "message_0" + }, + { + "source": "agent_invoke_text", + "target": "message_0" + } + ] + }, + "components": { + "begin": { + "obj": { + "component_name": "Begin", + "params": { + "prologue": "Upload any document and I'll analyze it using the appropriate specialist.", + "mode": "conversational" + } + }, + "downstream": ["switch_0"], + "upstream": [] + }, + "switch_0": { + "obj": { + "component_name": "Switch", + "params": { + "conditions": [ + { + "case_name": "pdf", + "expression": "sys.files[0].mime_type.contains('pdf')", + "goto": ["agent_invoke_pdf"] + }, + { + "case_name": "excel", + "expression": "sys.files[0].mime_type.contains('spreadsheet') or sys.files[0].name.endswith('.xlsx')", + "goto": ["agent_invoke_excel"] + }, + { + "case_name": "image", + "expression": "sys.files[0].mime_type.contains('image')", + "goto": ["agent_invoke_image"] + } + ], + "default_goto": ["agent_invoke_text"] + } + }, + "downstream": ["agent_invoke_pdf", "agent_invoke_excel", "agent_invoke_image", "agent_invoke_text"], + "upstream": ["begin"] + }, + "agent_invoke_pdf": { + "obj": { + "component_name": "AgentInvoke", + "params": { + "agent_id": "YOUR_PDF_ANALYZER_AGENT_ID", + "agent_name": "PDF Analysis Agent", + "query": "{sys.query}", + "inputs": { + "files": "{sys.files}", + "extract_tables": true, + "ocr_enabled": true + }, + "timeout_seconds": 300, + "create_new_session": true + } + }, + "downstream": ["message_0"], + "upstream": ["switch_0"] + }, + "agent_invoke_excel": { + "obj": { + "component_name": "AgentInvoke", + "params": { + "agent_id": "YOUR_EXCEL_ANALYZER_AGENT_ID", + "agent_name": "Spreadsheet Analysis Agent", + "query": "{sys.query}", + "inputs": { + "files": "{sys.files}", + "analyze_formulas": true, + "generate_charts": true + }, + "timeout_seconds": 240, + "create_new_session": true + } + }, + "downstream": ["message_0"], + "upstream": ["switch_0"] + }, + "agent_invoke_image": { + "obj": { + "component_name": "AgentInvoke", + "params": { + "agent_id": "YOUR_IMAGE_ANALYZER_AGENT_ID", + "agent_name": "Image Analysis Agent", + "query": "{sys.query}", + "inputs": { + "files": "{sys.files}", + "detect_objects": true, + "extract_text": true + }, + "timeout_seconds": 180, + "create_new_session": true + } + }, + "downstream": ["message_0"], + "upstream": ["switch_0"] + }, + "agent_invoke_text": { + "obj": { + "component_name": "AgentInvoke", + "params": { + "agent_id": "YOUR_TEXT_ANALYZER_AGENT_ID", + "agent_name": "Text Analysis Agent", + "query": "{sys.query}", + "inputs": { + "files": "{sys.files}", + "summarize": true, + "extract_entities": true + }, + "timeout_seconds": 120, + "create_new_session": true + } + }, + "downstream": ["message_0"], + "upstream": ["switch_0"] + }, + "message_0": { + "obj": { + "component_name": "Message", + "params": { + "content": "{agent_invoke_pdf@answer}{agent_invoke_excel@answer}{agent_invoke_image@answer}{agent_invoke_text@answer}" + } + }, + "downstream": [], + "upstream": ["agent_invoke_pdf", "agent_invoke_excel", "agent_invoke_image", "agent_invoke_text"] + } + }, + "history": [], + "path": ["begin"], + "retrieval": { + "chunks": [], + "doc_aggs": [] + }, + "globals": { + "sys.query": "", + "sys.user_id": "", + "sys.conversation_turns": 0, + "sys.files": [] + } +} + diff --git a/agent/templates/multi_step_workflow_example.json b/agent/templates/multi_step_workflow_example.json new file mode 100644 index 000000000..3f71a7e8d --- /dev/null +++ b/agent/templates/multi_step_workflow_example.json @@ -0,0 +1,161 @@ +{ + "description": "Multi-Step Workflow - Sentiment Analysis → Issues → Action Items", + "graph": { + "nodes": [ + { + "id": "begin", + "data": { + "name": "Start", + "component_name": "Begin", + "x": 100, + "y": 200 + } + }, + { + "id": "agent_invoke_sentiment", + "data": { + "name": "Sentiment Analysis", + "component_name": "AgentInvoke", + "x": 300, + "y": 200 + } + }, + { + "id": "agent_invoke_issues", + "data": { + "name": "Extract Key Issues", + "component_name": "AgentInvoke", + "x": 500, + "y": 200 + } + }, + { + "id": "agent_invoke_actions", + "data": { + "name": "Generate Action Items", + "component_name": "AgentInvoke", + "x": 700, + "y": 200 + } + }, + { + "id": "message_0", + "data": { + "name": "Final Report", + "component_name": "Message", + "x": 900, + "y": 200 + } + } + ], + "edges": [ + { + "source": "begin", + "target": "agent_invoke_sentiment" + }, + { + "source": "agent_invoke_sentiment", + "target": "agent_invoke_issues" + }, + { + "source": "agent_invoke_issues", + "target": "agent_invoke_actions" + }, + { + "source": "agent_invoke_actions", + "target": "message_0" + } + ] + }, + "components": { + "begin": { + "obj": { + "component_name": "Begin", + "params": { + "prologue": "I'll analyze customer feedback and generate actionable insights for you.", + "mode": "workflow" + } + }, + "downstream": ["agent_invoke_sentiment"], + "upstream": [] + }, + "agent_invoke_sentiment": { + "obj": { + "component_name": "AgentInvoke", + "params": { + "agent_id": "YOUR_SENTIMENT_ANALYSIS_AGENT_ID", + "agent_name": "Sentiment Analysis Agent", + "query": "{sys.query}", + "inputs": { + "analysis_depth": "detailed", + "include_emotions": true + }, + "timeout_seconds": 120, + "create_new_session": true + } + }, + "downstream": ["agent_invoke_issues"], + "upstream": ["begin"] + }, + "agent_invoke_issues": { + "obj": { + "component_name": "AgentInvoke", + "params": { + "agent_id": "YOUR_ISSUE_EXTRACTION_AGENT_ID", + "agent_name": "Key Issues Extraction Agent", + "query": "Based on the sentiment analysis, extract the top 5 key issues:\n\n{agent_invoke_sentiment@answer}", + "inputs": { + "max_issues": 5, + "prioritize": "negative_sentiment" + }, + "timeout_seconds": 120, + "create_new_session": true + } + }, + "downstream": ["agent_invoke_actions"], + "upstream": ["agent_invoke_sentiment"] + }, + "agent_invoke_actions": { + "obj": { + "component_name": "AgentInvoke", + "params": { + "agent_id": "YOUR_ACTION_ITEMS_AGENT_ID", + "agent_name": "Action Items Generator Agent", + "query": "Generate actionable recommendations for these issues:\n\n{agent_invoke_issues@answer}", + "inputs": { + "format": "actionable", + "include_timeline": true, + "include_ownership": true + }, + "timeout_seconds": 150, + "create_new_session": true + } + }, + "downstream": ["message_0"], + "upstream": ["agent_invoke_issues"] + }, + "message_0": { + "obj": { + "component_name": "Message", + "params": { + "content": "# Customer Feedback Analysis Report\n\n## Sentiment Analysis\n{agent_invoke_sentiment@answer}\n\n## Key Issues Identified\n{agent_invoke_issues@answer}\n\n## Recommended Actions\n{agent_invoke_actions@answer}" + } + }, + "downstream": [], + "upstream": ["agent_invoke_actions"] + } + }, + "history": [], + "path": ["begin"], + "retrieval": { + "chunks": [], + "doc_aggs": [] + }, + "globals": { + "sys.query": "", + "sys.user_id": "", + "sys.conversation_turns": 0, + "sys.files": [] + } +} + diff --git a/agent/templates/portal_agent_example.json b/agent/templates/portal_agent_example.json new file mode 100644 index 000000000..c1ef7b802 --- /dev/null +++ b/agent/templates/portal_agent_example.json @@ -0,0 +1,205 @@ +{ + "description": "Portal Agent Example - Agent-to-Agent Invocation", + "graph": { + "nodes": [ + { + "id": "begin", + "data": { + "name": "Begin", + "component_name": "Begin", + "x": 100, + "y": 100 + } + }, + { + "id": "categorize_0", + "data": { + "name": "Intent Classifier", + "component_name": "Categorize", + "x": 300, + "y": 100 + } + }, + { + "id": "agent_invoke_sales", + "data": { + "name": "Sales Analysis Agent", + "component_name": "AgentInvoke", + "x": 500, + "y": 50 + } + }, + { + "id": "agent_invoke_customer", + "data": { + "name": "Customer Service Agent", + "component_name": "AgentInvoke", + "x": 500, + "y": 200 + } + }, + { + "id": "agent_invoke_docs", + "data": { + "name": "Document Processing Agent", + "component_name": "AgentInvoke", + "x": 500, + "y": 350 + } + }, + { + "id": "message_0", + "data": { + "name": "Response", + "component_name": "Message", + "x": 700, + "y": 200 + } + } + ], + "edges": [ + { + "source": "begin", + "target": "categorize_0" + }, + { + "source": "categorize_0", + "target": "agent_invoke_sales", + "label": "sales" + }, + { + "source": "categorize_0", + "target": "agent_invoke_customer", + "label": "customer_service" + }, + { + "source": "categorize_0", + "target": "agent_invoke_docs", + "label": "documents" + }, + { + "source": "agent_invoke_sales", + "target": "message_0" + }, + { + "source": "agent_invoke_customer", + "target": "message_0" + }, + { + "source": "agent_invoke_docs", + "target": "message_0" + } + ] + }, + "components": { + "begin": { + "obj": { + "component_name": "Begin", + "params": { + "prologue": "Welcome to our AI Portal! I can help you with sales analysis, customer service, or document processing.", + "mode": "conversational" + } + }, + "downstream": ["categorize_0"], + "upstream": [] + }, + "categorize_0": { + "obj": { + "component_name": "Categorize", + "params": { + "description": "Classify user intent to route to appropriate specialized agent", + "llm_id": "", + "items": [ + { + "name": "sales", + "description": "Questions about sales data, revenue, reports, or business metrics" + }, + { + "name": "customer_service", + "description": "Customer support requests, complaints, or general inquiries" + }, + { + "name": "documents", + "description": "Document processing, analysis, or extraction tasks" + } + ] + } + }, + "downstream": ["agent_invoke_sales", "agent_invoke_customer", "agent_invoke_docs"], + "upstream": ["begin"] + }, + "agent_invoke_sales": { + "obj": { + "component_name": "AgentInvoke", + "params": { + "agent_id": "YOUR_SALES_AGENT_ID_HERE", + "agent_name": "Sales Analysis Agent", + "query": "{sys.query}", + "inputs": { + "period": "current", + "metrics": ["revenue", "units", "growth"] + }, + "timeout_seconds": 180, + "create_new_session": true + } + }, + "downstream": ["message_0"], + "upstream": ["categorize_0"] + }, + "agent_invoke_customer": { + "obj": { + "component_name": "AgentInvoke", + "params": { + "agent_id": "YOUR_CUSTOMER_SERVICE_AGENT_ID_HERE", + "agent_name": "Customer Service Agent", + "query": "{sys.query}", + "inputs": {}, + "timeout_seconds": 120, + "create_new_session": true + } + }, + "downstream": ["message_0"], + "upstream": ["categorize_0"] + }, + "agent_invoke_docs": { + "obj": { + "component_name": "AgentInvoke", + "params": { + "agent_id": "YOUR_DOCUMENT_PROCESSING_AGENT_ID_HERE", + "agent_name": "Document Processing Agent", + "query": "{sys.query}", + "inputs": { + "files": "{sys.files}" + }, + "timeout_seconds": 300, + "create_new_session": true + } + }, + "downstream": ["message_0"], + "upstream": ["categorize_0"] + }, + "message_0": { + "obj": { + "component_name": "Message", + "params": { + "content": "{agent_invoke_sales@answer}{agent_invoke_customer@answer}{agent_invoke_docs@answer}" + } + }, + "downstream": [], + "upstream": ["agent_invoke_sales", "agent_invoke_customer", "agent_invoke_docs"] + } + }, + "history": [], + "path": ["begin"], + "retrieval": { + "chunks": [], + "doc_aggs": [] + }, + "globals": { + "sys.query": "", + "sys.user_id": "", + "sys.conversation_turns": 0, + "sys.files": [] + } +} +