|
| 1 | +"""Entry point for running the KORA routing agent over a task set. |
| 2 | +
|
| 3 | +Usage (finalized on launch day once the task I/O format is published): |
| 4 | +
|
| 5 | + python -m kora_router.main --tasks tasks.json --out results.json |
| 6 | +
|
| 7 | +For now this wires the pipeline end-to-end with a placeholder decision function |
| 8 | +so the container is runnable and testable before the tasks are released. |
| 9 | +""" |
| 10 | + |
| 11 | +from __future__ import annotations |
| 12 | + |
| 13 | +import argparse |
| 14 | +import json |
| 15 | +import os |
| 16 | +from pathlib import Path |
| 17 | +from typing import Any |
| 18 | + |
| 19 | +from .fireworks_client import FireworksClient |
| 20 | +from .local_model import LocalModel |
| 21 | +from .router import Route, RouteDecision, Router |
| 22 | + |
| 23 | + |
| 24 | +def default_decision(task: dict[str, Any]) -> RouteDecision: |
| 25 | + """Placeholder routing policy. |
| 26 | +
|
| 27 | + Replaced on launch day with the real KORA decision logic (deterministic |
| 28 | + rules first, local model for cheap-but-non-trivial work, remote only when |
| 29 | + nothing cheaper is confident). Until then, everything escalates to remote so |
| 30 | + the pipeline produces answers end-to-end. |
| 31 | + """ |
| 32 | + return RouteDecision(route=Route.REMOTE, reason="placeholder: escalate all") |
| 33 | + |
| 34 | + |
| 35 | +def load_tasks(path: Path) -> list[dict[str, Any]]: |
| 36 | + data = json.loads(path.read_text(encoding="utf-8")) |
| 37 | + if isinstance(data, dict) and "tasks" in data: |
| 38 | + return list(data["tasks"]) |
| 39 | + if isinstance(data, list): |
| 40 | + return data |
| 41 | + raise ValueError("unrecognized task file shape") |
| 42 | + |
| 43 | + |
| 44 | +def main() -> None: |
| 45 | + ap = argparse.ArgumentParser() |
| 46 | + ap.add_argument("--tasks", required=True, help="path to task JSON") |
| 47 | + ap.add_argument("--out", default="results.json") |
| 48 | + ap.add_argument("--remote-model", |
| 49 | + default=os.getenv("REMOTE_MODEL", ""), |
| 50 | + help="Fireworks model id (accounts/fireworks/models/...)") |
| 51 | + args = ap.parse_args() |
| 52 | + |
| 53 | + tasks = load_tasks(Path(args.tasks)) |
| 54 | + local = LocalModel() |
| 55 | + remote = FireworksClient(model=args.remote_model) |
| 56 | + router = Router(decide=default_decision, local=local, remote=remote) |
| 57 | + |
| 58 | + results = [] |
| 59 | + for task in tasks: |
| 60 | + r = router.run_task(task) |
| 61 | + results.append({ |
| 62 | + "id": r.task_id, |
| 63 | + "answer": r.answer, |
| 64 | + "route": r.route.value, |
| 65 | + "reason": r.reason, |
| 66 | + "remote_tokens": r.remote_tokens, |
| 67 | + }) |
| 68 | + |
| 69 | + total_remote = sum(r["remote_tokens"] for r in results) |
| 70 | + payload = { |
| 71 | + "results": results, |
| 72 | + "summary": { |
| 73 | + "n_tasks": len(results), |
| 74 | + "total_remote_tokens": total_remote, |
| 75 | + "remote_calls": remote.usage.calls, |
| 76 | + "route_counts": _route_counts(results), |
| 77 | + }, |
| 78 | + } |
| 79 | + Path(args.out).write_text(json.dumps(payload, indent=2), encoding="utf-8") |
| 80 | + print(f"wrote {args.out}: {len(results)} tasks, " |
| 81 | + f"{total_remote} remote tokens, {remote.usage.calls} remote calls") |
| 82 | + |
| 83 | + |
| 84 | +def _route_counts(results: list[dict]) -> dict[str, int]: |
| 85 | + counts: dict[str, int] = {} |
| 86 | + for r in results: |
| 87 | + counts[r["route"]] = counts.get(r["route"], 0) + 1 |
| 88 | + return counts |
| 89 | + |
| 90 | + |
| 91 | +if __name__ == "__main__": |
| 92 | + main() |
0 commit comments