| """ChatOrchestrator — central coordinator for all agent interactions.""" |
|
|
| import uuid |
| from typing import AsyncIterator, Optional |
|
|
| from agentic_rag.data.models import AgentEvent, AgentInput, AgentOutput, Message |
| from agentic_rag.runtime.stream_bus import get_stream_bus |
| from agentic_rag.runtime.turn_manager import get_turn_manager |
| from agentic_rag.runtime.unified_context import UnifiedContext |
|
|
|
|
| class ChatOrchestrator: |
| """The central conductor for the Agentic RAG system. |
| |
| Orchestrates the full lifecycle of each user request: |
| 1. Create/set up UnifiedContext |
| 2. Route to the appropriate ReActEngine via AgentRouter |
| 3. Execute the agent (streaming or non-streaming) |
| 4. Persist results to memory |
| 5. Emit events via StreamBus |
| """ |
|
|
| def __init__(self): |
| self.stream_bus = get_stream_bus() |
| self.turn_manager = get_turn_manager() |
|
|
| async def process( |
| self, |
| query: str, |
| session_id: str = "", |
| mode: str | None = None, |
| has_media: bool = False, |
| context: UnifiedContext | None = None, |
| ) -> AgentOutput: |
| """Process a query through the full pipeline (non-streaming).""" |
| if context is None: |
| context = UnifiedContext.create(session_id=session_id) |
|
|
| |
| turn = self.turn_manager.start_turn(context.session_id) |
|
|
| |
| engine = await self._route(query, has_media, mode) |
| input_data = AgentInput(query=query) |
|
|
| try: |
| output = await engine.run(input_data, turn_id=turn.turn_id) |
|
|
| |
| self.turn_manager.update_usage( |
| turn.turn_id, |
| prompt_tokens=output.usage.get("prompt_tokens", 0), |
| completion_tokens=output.usage.get("completion_tokens", 0), |
| ) |
| self.turn_manager.finish_turn(turn.turn_id) |
|
|
| |
| await self._persist_memory(context, query, output.final_answer) |
|
|
| return output |
| except Exception as e: |
| self.turn_manager.finish_turn(turn.turn_id, error=str(e)) |
| raise |
|
|
| async def process_stream( |
| self, |
| query: str, |
| session_id: str = "", |
| mode: str | None = None, |
| has_media: bool = False, |
| context: UnifiedContext | None = None, |
| ) -> AsyncIterator[AgentEvent]: |
| """Process a query with streaming output.""" |
| if context is None: |
| context = UnifiedContext.create(session_id=session_id) |
|
|
| turn = self.turn_manager.start_turn(context.session_id) |
| engine = await self._route(query, has_media, mode) |
| input_data = AgentInput(query=query) |
|
|
| full_answer = "" |
| try: |
| async for event in engine.stream(input_data, turn_id=turn.turn_id): |
| self.stream_bus.publish(turn.turn_id, event) |
| if event.event_type.value == "text_delta": |
| full_answer += event.data.get("content", "") |
| yield event |
|
|
| self.turn_manager.finish_turn(turn.turn_id) |
| await self._persist_memory(context, query, full_answer) |
|
|
| except Exception as e: |
| self.turn_manager.finish_turn(turn.turn_id, error=str(e)) |
| yield AgentEvent(event_type="error", data={"message": str(e)}) |
|
|
| async def _route(self, query: str, has_media: bool, mode: str | None): |
| """Route query to the correct ReActEngine via AgentRouter.""" |
| from agentic_rag.services.llm.factory import get_llm |
| from agentic_rag.orchestration.l1_tools.registry import get_tool_registry |
| from agentic_rag.agent.router import AgentRouter |
|
|
| llm = get_llm() |
| tool_registry = get_tool_registry() |
| self._ensure_tools(tool_registry) |
|
|
| router = AgentRouter(llm, tool_registry) |
| return await router.route(query=query, has_media=has_media, preferred_mode=mode) |
|
|
| async def _persist_memory(self, context: UnifiedContext, query: str, answer: str) -> None: |
| """Save the interaction to memory.""" |
| try: |
| memory = context.memory_manager |
| if memory: |
| memory.add_message(context.session_id, Message.user(query)) |
| memory.add_message(context.session_id, Message.assistant(answer)) |
| except Exception: |
| pass |
|
|
| @staticmethod |
| def _ensure_tools(registry) -> None: |
| """Ensure built-in tools are registered.""" |
| if registry.tool_count == 0: |
| from agentic_rag.orchestration.l1_tools.rag_tools import RAGSearchTool |
| from agentic_rag.orchestration.l1_tools.web_tools import WebFetchTool, WebSearchTool |
| from agentic_rag.orchestration.l1_tools.code_tools import CodeExecuteTool |
|
|
| registry.register(RAGSearchTool()) |
| registry.register(WebSearchTool()) |
| registry.register(WebFetchTool()) |
| registry.register(CodeExecuteTool()) |
|
|
|
|
| |
| _orchestrator: Optional[ChatOrchestrator] = None |
|
|
|
|
| def get_orchestrator() -> ChatOrchestrator: |
| global _orchestrator |
| if _orchestrator is None: |
| _orchestrator = ChatOrchestrator() |
| return _orchestrator |
|
|