LangChain-based AI code review pipeline with fix suggestions and per-repo memory.
Accepts a PR diff (raw text or fetched from GitHub) and runs a 7-step LCEL pipeline:
- Classify the change type and risk level (parallel)
- Recall similar past findings for the same repo (RAG via Chroma)
- Analyze for bugs, security issues, code quality, test coverage, and architecture alignment (parallel)
- Prioritize all findings by severity
- Generate fix patches for every BLOCKING/MAJOR finding (AI-written code patches)
- Verdict — APPROVE / REQUEST_CHANGES / NEEDS_DISCUSSION
- Summarize in 2–4 sentences
Each completed review is embedded and stored locally so future reviews on the same repo benefit from institutional memory.
langchain-prj/
├── pyproject.toml
├── .env.example
├── .chroma/ # Chroma persistent vector store (gitignored)
├── src/
│ └── code_review/
│ ├── models.py # Pydantic: Finding, FixSuggestion, PRReviewInput/Output
│ ├── service.py # CodeReviewService — chain + async memory indexing
│ ├── github.py # Optional: fetch diff from GitHub API
│ ├── memory/
│ │ ├── embeddings.py # Singleton HuggingFaceEmbeddings (all-MiniLM-L6-v2)
│ │ ├── store.py # ReviewMemoryStore: Chroma recall() + index_findings()
│ │ └── recall_runnable.py # RunnableLambda for LCEL inject step
│ └── chains/
│ ├── review_chain.py # Main LCEL orchestrator (all 7 steps)
│ ├── fix_suggestions.py # FixSuggestionChain (batched patches)
│ ├── classify/
│ │ ├── change_type.py
│ │ └── risk_assessment.py
│ └── analyze/
│ ├── bug_detection.py
│ ├── security_audit.py
│ ├── code_quality.py
│ ├── test_coverage.py
│ └── architecture.py
├── tests/
│ ├── test_review_chain.py
│ └── test_memory.py
└── examples/
└── run_review.py
| Layer | Package |
|---|---|
| LLM | langchain-anthropic — claude-sonnet-4-6 |
| Chains | langchain (LCEL) |
| Vector store | langchain-chroma + chromadb |
| Embeddings | langchain-huggingface + sentence-transformers/all-MiniLM-L6-v2 |
| Models | pydantic v2 |
| GitHub fetch | httpx |
| Config | python-dotenv |
class Finding(BaseModel):
finding_id: str # uuid4, used to link back fix patches
category: Literal["bug", "security", "quality", "test", "architecture"]
severity: Literal["BLOCKING", "MAJOR", "MINOR", "SUGGESTION"]
file: Optional[str]
line: Optional[int]
message: str
suggestion: Optional[str]
fix: Optional[FixSuggestion] # populated for BLOCKING/MAJOR findings
class FixSuggestion(BaseModel):
finding_id: str
original_snippet: str
suggested_patch: str # unified diff or replacement block
explanation: str
class PRReviewOutput(BaseModel):
status: Literal["success", "cancelled", "failed"]
verdict: Optional[Literal["APPROVE", "REQUEST_CHANGES", "NEEDS_DISCUSSION"]]
summary: Optional[str]
findings: list[Finding]
change_type: Optional[str]
risk_level: Optional[str]
past_similar_count: int # findings recalled from memory- Storage: Chroma persisted at
.chroma/, namespaced byrepoin metadata - Embeddings:
all-MiniLM-L6-v2loaded once as module singleton (~80MB, no API key) - Recall: Before ANALYZE — top-5 similar past findings injected as
{past_context}into analysis prompts; returns""on first review - Index: After review completes —
asyncio.create_task()fires a thread-executor Chroma upsert; logs errors, never raises
chain = (
RunnablePassthrough.assign(change_type=..., risk_level=...) # Step 1: CLASSIFY
| RunnablePassthrough.assign(past_context=recall_runnable) # Step 2: RECALL
| RunnablePassthrough.assign(**analyze_parallel) # Step 3: ANALYZE
| prioritize_chain # Step 4: PRIORITIZE
| RunnablePassthrough.assign(fix_suggestions=fix_chain) # Step 5: FIX
| verdict_chain # Step 6: VERDICT
| summarize_chain # Step 7: SUMMARIZE
)Fast-fail: risk_level=critical + BLOCKING security finding → status=cancelled, skip steps 4–7.
pyproject.toml+.env.examplemodels.pymemory/(embeddings → store → recall_runnable)chains/classify/chains/analyze/(prompts include{past_context})chains/fix_suggestions.pychains/review_chain.pygithub.pyservice.pyexamples/run_review.pytests/
# Minimal run
ANTHROPIC_API_KEY=... python examples/run_review.py
# Confirm memory works (run twice on same repo)
# Second run should show past_similar_count > 0
# Fast-fail test: diff with hardcoded secret → expect BLOCKING finding