Spaces:
Running
Running
| """Typer CLI entrypoint: `python -m src.cli review "topic"`.""" | |
| from __future__ import annotations | |
| import os | |
| import time | |
| import typer | |
| from rich.console import Console | |
| from rich.markdown import Markdown | |
| from rich.panel import Panel | |
| from . import utils | |
| app = typer.Typer(add_completion=False, help="Personal research agent.") | |
| console = Console() | |
| def review( | |
| topic: str = typer.Argument(..., help="Research topic to review."), | |
| max_papers: int = typer.Option(15, "--max-papers", help="Papers in final review."), | |
| depth: int = typer.Option(1, "--depth", help="Iterative gap-filling passes."), | |
| model: str = typer.Option("", "--model", help="Override the model for this run."), | |
| since: int = typer.Option(0, "--since", help="Only papers from this year onward."), | |
| comprehensive: bool = typer.Option( | |
| False, "--comprehensive", help="Longer, more detailed review (costs more)." | |
| ), | |
| ) -> None: | |
| """Search arXiv, read papers, and write a structured literature review.""" | |
| from .agent import build_graph | |
| console.print(Panel.fit(f"[bold]Research topic:[/bold] {topic}", border_style="cyan")) | |
| def progress(msg: str) -> None: | |
| console.print(f"[dim]路[/dim] {msg}") | |
| meter: dict = {} | |
| t0 = time.time() | |
| try: | |
| agent = build_graph( | |
| max_papers=max_papers, | |
| progress=progress, | |
| meter=meter, | |
| model=model or None, | |
| year_min=since, | |
| style="comprehensive" if comprehensive else "concise", | |
| ) | |
| result = agent.invoke( | |
| { | |
| "topic": topic, | |
| "papers": [], | |
| "review": "", | |
| "gaps": [], | |
| "depth": max(1, depth), | |
| "iteration": 0, | |
| } | |
| ) | |
| except Exception as err: | |
| console.print(f"[bold red]Run failed:[/bold red] {err}") | |
| raise typer.Exit(code=1) | |
| papers = result["papers"] | |
| review_text = result["review"] | |
| if not review_text.strip(): | |
| console.print("[bold red]No review produced.[/bold red]") | |
| raise typer.Exit(code=1) | |
| os.makedirs("reviews", exist_ok=True) | |
| path = os.path.join("reviews", f"{utils.sanitize_filename(topic)}.md") | |
| with open(path, "w", encoding="utf-8") as fh: | |
| fh.write(f"# Literature Review: {topic}\n\n") | |
| fh.write(review_text.rstrip() + "\n") | |
| # Validation warnings (non-fatal). | |
| report = utils.validate_review(review_text, papers[:max_papers]) | |
| if not report["valid"]: | |
| console.print("[yellow]Validation warnings:[/yellow]") | |
| for issue in report["issues"]: | |
| console.print(f" [yellow]![/yellow] {issue}") | |
| stats = {"elapsed_s": round(time.time() - t0, 1), **utils.cost_report(meter)} | |
| console.print(Panel.fit(f"[green]Saved:[/green] {path}", border_style="green")) | |
| console.print( | |
| f"[dim]{stats['elapsed_s']}s 路 {len(papers)} papers 路 {stats['llm_calls']} " | |
| f"LLM calls 路 {stats['in_tok']}+{stats['out_tok']} tok 路 " | |
| f"~${stats['est_cost_usd']}[/dim]" | |
| ) | |
| console.print(Markdown(review_text[:500] + ("..." if len(review_text) > 500 else ""))) | |
| if __name__ == "__main__": | |
| app() | |