Ctrl K
ComunidadGuías…

examples/cocoindex_agents/flows/run.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Run one of the sample flows with a real model and report its model calls.

    uv run python examples/cocoindex_agents/flows/run.py summaries   # from the repository root
    LLM_MODEL=anthropic/claude-sonnet-5-5 uv run python examples/cocoindex_agents/flows/run.py report

A flow is a directory with a ``prompt.md`` and its sample data; outputs go to
``<flow>/out`` and the CocoIndex state to ``<flow>/.coco_db``. The prompt's
paths are relative to the flow directory. Run a flow twice unchanged, then
edit one input and run again, to see the model calls it saves
(``README.md`` records what was measured). ``--stats`` prints one line of
cache usage and ``--plan`` the plan it chose, with what each node
reused; both go to stderr, the answer to stdout.
"""

from __future__ import annotations

import argparse
import logging
import os
import pathlib
import re
import sys
import time

import cocoindex as coco
import litellm

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))

from cocoindex_agents.app import DEFAULT_MODEL, build_app, default_config  # noqa: E402
from cocoindex_agents.litellm_client import LiteLLMClient  # noqa: E402
from cocoindex_agents.model import MODEL_CLIENT  # noqa: E402
from cocoindex_agents.report import RunReport, render_plan, render_stats_line  # noqa: E402
from cocoindex_agents.tools import AGENTS_CONFIG  # noqa: E402
from cocoindex_agents.trace import SessionScratch  # noqa: E402

FLOWS = pathlib.Path(__file__).resolve().parent


def _expand(prompt_path: pathlib.Path) -> str:
    """The prompt, with ``$NAME`` references filled from the environment.

    A flow over data that is not checked in (``drive``: a Google Drive folder
    of the user's) names it this way; an unset name is an error, not an empty
    string in the prompt.
    """
    raw = prompt_path.read_text()
    missing = sorted(
        {v for v in re.findall(r"\$([A-Z][A-Z0-9_]*)", raw) if v not in os.environ}
    )
    if missing:
        raise SystemExit(
            f"{prompt_path} refers to {', '.join('$' + v for v in missing)}; "
            "set it in the environment (see flows/README.md)."
        )
    return os.path.expandvars(raw)


def main(argv: list[str] | None = None) -> int:
    flows = sorted(p.parent.name for p in FLOWS.glob("*/prompt.md"))
    parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
    parser.add_argument("flow", choices=flows)
    parser.add_argument(
        "--model",
        default=os.environ.get("LLM_MODEL", DEFAULT_MODEL),
        help="LiteLLM model id (default: $LLM_MODEL, else %(default)s)",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action="count",
        default=0,
        help="log every model turn's action; twice to log every request as the model saw it",
    )
    parser.add_argument(
        "--stats",
        action="store_true",
        help="one line of cache usage: what the run would have cost with no cache, "
        "and the share reused (stderr)",
    )
    parser.add_argument(
        "--plan",
        action="store_true",
        help="the plan chosen, as a tree, with what each node reused "
        "or recomputed and why (stderr)",
    )
    args = parser.parse_args(argv)

    logging.basicConfig(level=logging.WARNING, format="%(levelname)s %(message)s")
    logging.getLogger("cocoindex_agents").setLevel(
        logging.DEBUG if args.verbose else logging.INFO
    )
    if args.verbose < 2:
        logging.getLogger("cocoindex_agents.litellm_client.messages").setLevel(
            logging.INFO
        )

    flow_dir = FLOWS / args.flow
    prompt = _expand(flow_dir / "prompt.md")
    os.chdir(flow_dir)
    # LiteLLM's aiohttp transport leaves a pending close task at interpreter
    # exit ("Task was destroyed but it is pending"); httpx does not.
    litellm.disable_aiohttp_transport = True
    env = coco.Environment(coco.Settings.from_env(db_path=flow_dir / ".coco_db"))
    client = LiteLLMClient()
    config = default_config(args.model)
    env.context_provider.provide(MODEL_CLIENT, client)
    env.context_provider.provide(AGENTS_CONFIG, config)
    scratch = SessionScratch()
    app = build_app(
        f"cocoindex_agents_{args.flow}", prompt, env=env, config=config, scratch=scratch
    )

    started = time.monotonic()
    result = app.update_blocking()
    report = RunReport.from_result(
        args.flow,
        result.trace,
        ran=scratch.ran,
        model=args.model,
        wall_seconds=time.monotonic() - started,
        usage=client.usage,
    )
    print(f"answer: {result.answer}")
    if args.plan:
        print(render_plan(report), file=sys.stderr)
    if args.stats:
        print(render_stats_line(report), file=sys.stderr)
    else:
        by_role = ", ".join(f"{r}={n}" for r, n in report.model_calls_by_role().items())
        print(
            f"model calls: {report.spent.model_calls} ({by_role or 'none'})",
            file=sys.stderr,
        )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())