Download src/routing/dispatch.py from Snapkitty/sovereign-engine-v2: direct link, hf CLI and curl.
- Browser
- Download file 8.72 kB
-
https://huggingface.co/Snapkitty/sovereign-engine-v2/resolve/main/src/routing/dispatch.py
- Command line
-
hf download hf://Snapkitty/sovereign-engine-v2/src/routing/dispatch.py
-
curl -L -o dispatch.py https://huggingface.co/Snapkitty/sovereign-engine-v2/resolve/main/src/routing/dispatch.py
8.72 kB
| """ | |
| Stage 10 + 11: AgentDispatch + MergeOutput | |
| AgentDispatch: | |
| Takes the sparse routing weights and fires the active experts. | |
| Each expert is an async callable: (input, context) -> output. | |
| Experts run concurrently (asyncio.gather). | |
| Timeout per expert — stalled experts are dropped, not waited on. | |
| MergeOutput: | |
| Combines expert outputs using their routing weights. | |
| Strategy options: | |
| - weighted_concat: concatenate with weight annotation | |
| - weighted_avg: average numeric fields by weight | |
| - highest_weight: return only the top-weighted output | |
| - ensemble_text: join text outputs with weight-proportional prominence | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| from dataclasses import dataclass, field | |
| from typing import Any, Callable, Awaitable | |
| from .sparse import RoutingWeights | |
| # ========================================== | |
| # Expert type | |
| # ========================================== | |
| Expert = Callable[[str, dict[str, Any]], Awaitable[dict[str, Any]]] | |
| # ========================================== | |
| # Dispatch | |
| # ========================================== | |
| class ExpertResult: | |
| expert_name: str | |
| weight: float | |
| output: dict[str, Any] | None | |
| success: bool | |
| error: str | None = None | |
| latency_ms: float = 0.0 | |
| class DispatchResult: | |
| expert_results: list[ExpertResult] | |
| active_count: int | |
| success_count: int | |
| failed_experts: list[str] | |
| routing_weights: RoutingWeights | |
| merged_output: dict[str, Any] | |
| class AgentDispatch: | |
| """ | |
| Stage 10: Fire active experts concurrently. | |
| """ | |
| def __init__( | |
| self, | |
| experts: dict[str, Expert], | |
| timeout_ms: int = 30000, | |
| merge_strategy: str = "weighted_concat" | |
| ): | |
| self.experts = experts | |
| self.timeout_s = timeout_ms / 1000.0 | |
| self.merge_strategy = merge_strategy | |
| self._merger = MergeOutput() | |
| async def dispatch( | |
| self, | |
| input_text: str, | |
| context: dict[str, Any], | |
| routing: RoutingWeights | |
| ) -> DispatchResult: | |
| active = routing.active_experts | |
| tasks = [] | |
| for expert_name in active: | |
| weight = routing.weights.get(expert_name, 0.0) | |
| if weight < 1e-6: | |
| continue | |
| expert_fn = self.experts.get(expert_name) | |
| if expert_fn is None: | |
| continue | |
| task = self._run_expert(expert_name, weight, expert_fn, input_text, context) | |
| tasks.append(task) | |
| if not tasks: | |
| return DispatchResult( | |
| expert_results=[], | |
| active_count=0, | |
| success_count=0, | |
| failed_experts=[], | |
| routing_weights=routing, | |
| merged_output={"error": "No active experts after dispatch"} | |
| ) | |
| results: list[ExpertResult] = await asyncio.gather(*tasks) | |
| failed = [r.expert_name for r in results if not r.success] | |
| succeeded = [r for r in results if r.success] | |
| merged = self._merger.merge(succeeded, self.merge_strategy) | |
| return DispatchResult( | |
| expert_results=results, | |
| active_count=len(tasks), | |
| success_count=len(succeeded), | |
| failed_experts=failed, | |
| routing_weights=routing, | |
| merged_output=merged | |
| ) | |
| async def _run_expert( | |
| self, | |
| name: str, | |
| weight: float, | |
| fn: Expert, | |
| input_text: str, | |
| context: dict[str, Any] | |
| ) -> ExpertResult: | |
| import time | |
| t0 = time.monotonic() | |
| try: | |
| output = await asyncio.wait_for( | |
| fn(input_text, context), | |
| timeout=self.timeout_s | |
| ) | |
| latency = (time.monotonic() - t0) * 1000 | |
| return ExpertResult( | |
| expert_name=name, | |
| weight=weight, | |
| output=output, | |
| success=True, | |
| latency_ms=latency | |
| ) | |
| except asyncio.TimeoutError: | |
| return ExpertResult( | |
| expert_name=name, | |
| weight=weight, | |
| output=None, | |
| success=False, | |
| error=f"Timeout after {self.timeout_s}s" | |
| ) | |
| except Exception as e: | |
| return ExpertResult( | |
| expert_name=name, | |
| weight=weight, | |
| output=None, | |
| success=False, | |
| error=str(e) | |
| ) | |
| # ========================================== | |
| # Merge Output | |
| # ========================================== | |
| class MergeOutput: | |
| """ | |
| Stage 11: Combine expert outputs using routing weights. | |
| """ | |
| def merge( | |
| self, | |
| results: list[ExpertResult], | |
| strategy: str = "weighted_concat" | |
| ) -> dict[str, Any]: | |
| if not results: | |
| return {"output": None, "experts": []} | |
| if strategy == "highest_weight": | |
| return self._highest_weight(results) | |
| elif strategy == "weighted_avg": | |
| return self._weighted_avg(results) | |
| elif strategy == "ensemble_text": | |
| return self._ensemble_text(results) | |
| else: | |
| return self._weighted_concat(results) | |
| def _weighted_concat(self, results: list[ExpertResult]) -> dict[str, Any]: | |
| """ | |
| Return all outputs annotated with their weights. | |
| Agents downstream can decide how to use them. | |
| """ | |
| total_weight = sum(r.weight for r in results) | |
| outputs = [] | |
| for r in sorted(results, key=lambda x: x.weight, reverse=True): | |
| normalized_weight = r.weight / max(total_weight, 1e-6) | |
| outputs.append({ | |
| "expert": r.expert_name, | |
| "weight": normalized_weight, | |
| "output": r.output, | |
| "latency_ms": r.latency_ms | |
| }) | |
| # Primary output = highest weighted expert's output | |
| primary = results[0].output if results else {} | |
| return { | |
| "output": primary, | |
| "expert_outputs": outputs, | |
| "strategy": "weighted_concat", | |
| "expert_count": len(results) | |
| } | |
| def _highest_weight(self, results: list[ExpertResult]) -> dict[str, Any]: | |
| best = max(results, key=lambda r: r.weight) | |
| return { | |
| "output": best.output, | |
| "expert": best.expert_name, | |
| "weight": best.weight, | |
| "strategy": "highest_weight" | |
| } | |
| def _weighted_avg(self, results: list[ExpertResult]) -> dict[str, Any]: | |
| """ | |
| Average numeric values across outputs, weighted by routing weight. | |
| Non-numeric keys taken from highest-weight expert. | |
| """ | |
| total_weight = sum(r.weight for r in results) or 1.0 | |
| merged: dict[str, Any] = {} | |
| # Collect all keys | |
| all_keys: set[str] = set() | |
| for r in results: | |
| if r.output: | |
| all_keys.update(r.output.keys()) | |
| for key in all_keys: | |
| # Try weighted average of numeric values | |
| numeric_vals = [] | |
| for r in results: | |
| if r.output and key in r.output: | |
| val = r.output[key] | |
| if isinstance(val, (int, float)): | |
| numeric_vals.append((val, r.weight)) | |
| if numeric_vals: | |
| merged[key] = sum(v * w for v, w in numeric_vals) / total_weight | |
| else: | |
| # Non-numeric: take from highest-weight expert | |
| best = max(results, key=lambda r: r.weight) | |
| if best.output and key in best.output: | |
| merged[key] = best.output[key] | |
| merged["strategy"] = "weighted_avg" | |
| return merged | |
| def _ensemble_text(self, results: list[ExpertResult]) -> dict[str, Any]: | |
| """ | |
| Join text outputs in weight order. | |
| Higher-weight experts appear first. | |
| """ | |
| sorted_results = sorted(results, key=lambda r: r.weight, reverse=True) | |
| parts = [] | |
| for r in sorted_results: | |
| if r.output: | |
| text = r.output.get("text") or r.output.get("content") or str(r.output) | |
| pct = int(r.weight * 100) | |
| parts.append(f"[{r.expert_name} {pct}%]: {text}") | |
| return { | |
| "output": "\n\n".join(parts), | |
| "strategy": "ensemble_text", | |
| "expert_count": len(results) | |
| } | |