Entry-point for plan-and-execute workflows using MCP servers as tool providers. Usage:: from agent import PlanExecuteRunner from llm import LiteLLMBackend runner = PlanExecuteRunner(llm=LiteLLMBackend("watsonx/meta-llama/llama-3-3-70b-instruct")) result = await
| 78 | |
| 79 | |
| 80 | class PlanExecuteRunner(AgentRunner): |
| 81 | """Entry-point for plan-and-execute workflows using MCP servers as tool providers. |
| 82 | |
| 83 | Usage:: |
| 84 | |
| 85 | from agent import PlanExecuteRunner |
| 86 | from llm import LiteLLMBackend |
| 87 | |
| 88 | runner = PlanExecuteRunner(llm=LiteLLMBackend("watsonx/meta-llama/llama-3-3-70b-instruct")) |
| 89 | result = await runner.run("What are the assets at site MAIN?") |
| 90 | print(result.answer) |
| 91 | |
| 92 | Args: |
| 93 | llm: LLM backend used for planning, tool selection, and summarisation. |
| 94 | server_paths: Override MCP server specs. Keys must match the server |
| 95 | names the planner will assign steps to. Values are |
| 96 | either a uv entry-point name (str) or a Path to a |
| 97 | script file. Defaults to all five registered servers. |
| 98 | """ |
| 99 | |
| 100 | def __init__( |
| 101 | self, |
| 102 | llm: LLMBackend, |
| 103 | server_paths: dict[str, Path | str] | None = None, |
| 104 | ) -> None: |
| 105 | super().__init__(llm, server_paths) |
| 106 | self._meter = _TokenMeter(llm) |
| 107 | self._planner = Planner(self._meter) |
| 108 | self._executor = Executor(self._meter, server_paths) |
| 109 | |
| 110 | async def run(self, question: str) -> OrchestratorResult: |
| 111 | """Run the full plan-execute loop for a question. |
| 112 | |
| 113 | Steps: |
| 114 | 1. Discover available servers from registered MCP servers. |
| 115 | 2. Use the LLM to decompose the question into an execution plan. |
| 116 | 3. Execute each plan step by routing tool calls to MCP servers. |
| 117 | 4. Summarise the step results into a final answer. |
| 118 | |
| 119 | Args: |
| 120 | question: The user question to answer. |
| 121 | |
| 122 | Returns: |
| 123 | OrchestratorResult with the final answer, the generated plan, and |
| 124 | the per-step execution trajectory. |
| 125 | """ |
| 126 | with agent_run_span( |
| 127 | "plan-execute", model=self._llm.model_id, question=question |
| 128 | ) as span: |
| 129 | run_started = time.perf_counter() |
| 130 | self._meter.reset() |
| 131 | |
| 132 | # 1. Discover |
| 133 | _log.info("Discovering server capabilities...") |
| 134 | server_descriptions = await self._executor.get_server_descriptions() |
| 135 | |
| 136 | # 2. Plan |
| 137 | _log.info("Planning...") |
no outgoing calls