Execute multiple tool calls in parallel, yielding results as they complete. This is the streaming version of execute_tools_parallel - results are yielded immediately when each tool completes, without waiting for all. Args: manager: ToolManager instance to execute tools
(
manager: ToolManager,
calls: list[CTPToolCall],
timeout: float | None = None,
on_tool_start: Callable[[CTPToolCall], Awaitable[None]] | None = None,
max_concurrency: int = 4,
batch_timeout: float | None = None,
)
| 144 | |
| 145 | |
| 146 | async def stream_execute_tools( |
| 147 | manager: ToolManager, |
| 148 | calls: list[CTPToolCall], |
| 149 | timeout: float | None = None, |
| 150 | on_tool_start: Callable[[CTPToolCall], Awaitable[None]] | None = None, |
| 151 | max_concurrency: int = 4, |
| 152 | batch_timeout: float | None = None, |
| 153 | ) -> AsyncIterator[CTPToolResult]: |
| 154 | """ |
| 155 | Execute multiple tool calls in parallel, yielding results as they complete. |
| 156 | |
| 157 | This is the streaming version of execute_tools_parallel - results are |
| 158 | yielded immediately when each tool completes, without waiting for all. |
| 159 | |
| 160 | Args: |
| 161 | manager: ToolManager instance to execute tools |
| 162 | calls: List of CTPToolCall objects to execute |
| 163 | timeout: Timeout per tool execution (uses default if not specified) |
| 164 | on_tool_start: Async callback invoked when each tool starts |
| 165 | max_concurrency: Maximum concurrent executions (default: 4) |
| 166 | batch_timeout: Global timeout for entire batch (auto-computed if None) |
| 167 | |
| 168 | Yields: |
| 169 | CTPToolResult objects as each tool completes (in completion order) |
| 170 | """ |
| 171 | if not calls: |
| 172 | return |
| 173 | |
| 174 | effective_timeout = timeout or manager.tool_timeout |
| 175 | sem = asyncio.Semaphore(max_concurrency) |
| 176 | queue: asyncio.Queue[CTPToolResult] = asyncio.Queue() |
| 177 | |
| 178 | async def execute_single(call: CTPToolCall) -> None: |
| 179 | """Execute a single tool call and put result in queue.""" |
| 180 | start_time = datetime.now(UTC) |
| 181 | |
| 182 | async with sem: |
| 183 | # Invoke start callback |
| 184 | if on_tool_start: |
| 185 | try: |
| 186 | await on_tool_start(call) |
| 187 | except Exception as e: |
| 188 | logger.warning( |
| 189 | f"on_tool_start callback failed for {call.tool}: {e}" |
| 190 | ) |
| 191 | |
| 192 | # Execute the tool |
| 193 | tool_result = await manager.execute_tool( |
| 194 | call.tool, |
| 195 | call.arguments, |
| 196 | namespace=call.namespace if call.namespace != "default" else None, |
| 197 | timeout=effective_timeout, |
| 198 | ) |
| 199 | |
| 200 | end_time = datetime.now(UTC) |
| 201 | |
| 202 | # Convert ToolCallResult to CTPToolResult |
| 203 | ctp_result = CTPToolResult( |