LangGraph · Agentic RAG · Evaluation
I'm Ingyu Koh. I build LangGraph agents with explicit state, bounded tools, verified citations, and a principled abstention path. Then I measure them against a real baseline, so the extra complexity has to show up in the numbers.
Featured project
A reproducible LangGraph agent that answers numeric questions over SEC 10-K facts. It plans, retrieves, grades metadata, verifies provenance, cites an SEC filing, and refuses to guess. It is benchmarked head-to-head against a single-pass baseline that uses the same corpus and retriever.
Four LangGraph nodes over a typed AgentState. Every node appends a timed event to the trace, so each answer comes with a record of how it was reached.
These are output from AgenticSystem.answer() at main, with the retrieval hit list shortened. The second question asks about a company that isn't in the corpus. BM25 still returns plausible revenue facts, but the grader rejects all of them, so the agent abstains.
"value": 391035000000, "unit": "USD", "citation_id": "aapl-2024-revenue", "source_url": "https://www.sec.gov/Archives/ edgar/data/320193/000032019324000123/", "trace": [ {"node": "plan", "ticker": "AAPL", "metric": "revenue", "fiscal_year": 2024}, {"node": "retrieve", "hit_ids": ["aapl-2024-revenue", "aapl-2022-revenue", … 10 more]}, {"node": "grade", "exact_candidates": ["aapl-2024-revenue"]}, {"node": "verify", "answerable": true} ]
"value": null, "answerable": false, "reason": "missing or ambiguous company, metric, fiscal year, or SEC fact", "trace": [ {"node": "plan", "ticker": null, "metric": "revenue", "fiscal_year": 2024}, {"node": "retrieve", "hit_ids": ["aapl-2024-revenue", "googl-2024-revenue", … 10 more]}, {"node": "grade", "exact_candidates": []}, {"node": "verify", "answerable": false} ]
Every failure is committed to results/failures.md. The baseline misses 22 of 64 cases, and they fall into two patterns that the graph was designed to fix:
| Failure pattern | Cases | Example | Graph node that fixes it |
|---|---|---|---|
| Right company and metric, wrong fiscal year | 18 | aapl-2024-net-income → returned another year's value | plan extracts the year, then grade requires an exact match |
| Answered a question with no supporting fact | 4 | unanswerable-unknown-company → 391035000000 | verify abstains unless exactly one fact survives grading |
data/manifest.json content-hashes the facts, questions, and provenance. If any of them change, tests fail before scoring.git clone https://github.com/ingyukoh/agentic-rag-eval && cd agentic-rag-eval python -m venv .venv && source .venv/bin/activate pip install -e '.[dev]' ruff check . && pytest -q python -m agentic_rag_eval.bench # regenerates results/benchmark.md docker compose up --build bench # or run it without a local Python
Experience
Independent, public projects. Each one can be cloned, tested, and benchmarked.
StateGraph (plan → retrieve → grade → verify) for numeric questions over SEC 10-K facts, with an explicit abstention path and SEC-linked citations.Scope: These are portfolio systems that show current engineering ability. They have not been deployed for a client. I wrote the orchestration, evaluation, and serving code. SEC data, LangGraph, and FastAPI are third-party.
Evidence map
Typed state, explicit nodes and edges, and a compiled graph.
agent/system.pyA transparent BM25 retriever, shared with the baseline so the comparison is fair.
retrieval/An injection guard and allowlisted tools, with adversarial tests.
evidence-first-agentic-ragFastAPI responses that carry a per-node timing trace, plus health checks.
api.py