| """Week 2 demo: topic -> search -> fetch pages -> Reader extracts structured Claims. |
| |
| Usage: |
| uv run python scripts/week2_reader.py "solid-state batteries" --pages 3 |
| |
| Writes outputs/week2-<topic>.json with the validated claims. This is still a flat |
| pipeline (LangGraph orchestration arrives in Week 4); it exists to exercise the |
| ReaderAgent end to end and to be reused by eval/run_eval.py. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import asyncio |
| import json |
| import logging |
| import re |
| import sys |
| from datetime import datetime, timezone |
| from pathlib import Path |
|
|
| import structlog |
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) |
|
|
| from polymath.agents.reader import ReaderAgent, ReaderResult |
| from polymath.config import settings |
| from polymath.tools.page_fetch import page_fetch |
| from polymath.tools.web_search import web_search |
|
|
| log = structlog.get_logger("week2") |
| OUTPUT_DIR = Path(__file__).resolve().parents[1] / "outputs" |
|
|
|
|
| async def gather_claims( |
| topic: str, n_pages: int = 3, max_results: int = 6 |
| ) -> list[ReaderResult]: |
| """Search the topic, fetch the top pages, run the Reader on each. |
| |
| Returns one ReaderResult per successfully-fetched page (so callers can |
| inspect per-page attempts/validated for metrics). |
| """ |
| results = await web_search(topic, max_results=max_results) |
| reader = ReaderAgent() |
|
|
| out: list[ReaderResult] = [] |
| fetched = 0 |
| for r in results: |
| if fetched >= n_pages: |
| break |
| url = r.get("url") |
| if not url: |
| continue |
| text = await page_fetch(url) |
| if not text: |
| continue |
| fetched += 1 |
| out.append(await reader.extract(text=text, source_url=url)) |
| return out |
|
|
|
|
| def _slugify(text: str) -> str: |
| slug = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-") |
| return slug[:60] or "topic" |
|
|
|
|
| def _configure_logging() -> None: |
| level = getattr(logging, settings.log_level.upper(), logging.INFO) |
| structlog.configure( |
| wrapper_class=structlog.make_filtering_bound_logger(level), |
| processors=[ |
| structlog.processors.add_log_level, |
| structlog.processors.TimeStamper(fmt="iso"), |
| structlog.processors.JSONRenderer(), |
| ], |
| ) |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser(description="Polymath Week 2 Reader demo.") |
| parser.add_argument("topic") |
| parser.add_argument("--pages", type=int, default=3, help="Pages to read (default 3).") |
| args = parser.parse_args() |
|
|
| _configure_logging() |
| results = asyncio.run(gather_claims(args.topic, n_pages=args.pages)) |
|
|
| all_claims = [c for res in results for c in res.claims] |
| OUTPUT_DIR.mkdir(parents=True, exist_ok=True) |
| ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S") |
| out_path = OUTPUT_DIR / f"week2-{_slugify(args.topic)}-{ts}.json" |
| out_path.write_text( |
| json.dumps( |
| { |
| "topic": args.topic, |
| "generated_utc": ts, |
| "claims": [c.model_dump() for c in all_claims], |
| }, |
| indent=2, |
| ensure_ascii=False, |
| ), |
| encoding="utf-8", |
| ) |
|
|
| n_pages = len(results) |
| n_valid_pages = sum(1 for r in results if r.validated) |
| print(f"\nPages read: {n_pages} | pages validated: {n_valid_pages} | claims: {len(all_claims)}") |
| print(f"Claims written to: {out_path}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|