research-agent / src /cli.py
abhid1234's picture
cost controls: concise mode, read-only-survivors, flash-lite extraction
4a9ccd7 verified
Raw
History Blame Contribute Delete
3.21 kB
"""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()
@app.command()
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()