我用LangChain搭建代码审查Agent的3个坑

作者:技术拾光

时间:2026-06-10

浏览:606

基于LangChain搭建代码审查Agent时遇到三个主要问题:Token超限导致仅部分代码被审查,通过分块处理解决;输出建议不具体,采用结构化输出强制指定行号和修改方案;成本过高,改用GPT-3.5初筛再结合GPT-4深度审查,节省95%成本。

文章目录

用Agent来自动Review代码,这个想法听起来很美好——节省人力嘛。

我用LangChain搭建代码审查Agent的3个坑

一开始确实觉得简单:调用GitHub API拿到PR diff,丢给GPT-4,等它返回意见就行。结果发现,这事儿远没想象中那么简单——直接踩了3个坑,白白浪费了一周时间。

坑1:Token超限,Review只看了一半

PR里有2000行代码,结果Agent只看前500行就给出了意见——剩下的呢?直接无视了。

问题出在哪?GPT-4的上下文窗口是8K tokens,2000行代码加上diff格式,早就超了。

第一次尝试是这么写的:

from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain
from langchain.prompts import ChatPromptTemplate

llm = ChatOpenAI(model="gpt-4", temperature=0)
prompt = ChatPromptTemplate.from_template("Review this PR:{diff}Provide suggestions.")
chain = LLMChain(llm=llm, prompt=prompt)
result = chain.run(diff=pr_diff)

结果:只看了前300行。

怎么解决的?最简单的解法:把代码切成块,每块500行,一块一块地审,最后汇总。

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=4000,
    chunk_overlap=0,
    separators=["", "", " "]
)
chunks = splitter.split_text(pr_diff)
reviews = []
for chunk in chunks:
    review = chain.run(diff=chunk)
    reviews.append(review)

# 汇总所有意见
summary_prompt = ChatPromptTemplate.from_template("Summarize these reviews:{reviews}")
summary_chain = LLMChain(llm=llm, prompt=summary_prompt)
final_review = summary_chain.run(reviews="".join(reviews))

坑2:给了一堆“建议”但没一个能执行的

又一个让人头疼的问题:Agent说“建议优化性能”“建议加错误处理”——可具体改哪一行?完全没有。

根本原因在于Prompt写得太泛。第一次用的是:

Review this PR and provide suggestions.

结果可想而知,输出一堆泛泛而谈的空话。

那怎么办?强制要求它输出具体行号和代码示例。通过结构化的输出格式,让Agent必须给出可落地的建议。

from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List

class CodeReviewIssue(BaseModel):
    line_number: int = Field(description="Line number of the issue")
    severity: str = Field(description="Issue severity: high/medium/low")
    description: str = Field(description="Issue description")
    suggestion: str = Field(description="How to fix")

class CodeReview(BaseModel):
    issues: List[CodeReviewIssue] = Field(description="List of issues")

parser = PydanticOutputParser(pydantic_object=CodeReview)
prompt = ChatPromptTemplate.from_template(
    "Review this PR:{diff}"
    "{format_instructions}"
    "Return ONLY the structured output."
)
format_instructions = parser.get_format_instructions()
chain = LLMChain(llm=llm, prompt=prompt, output_parser=parser)
review = chain.run(diff=chunk, format_instructions=format_instructions)
for issue in review.issues:
    print(f"Line {issue.line_number} [{issue.severity}]: {issue.description}")
    print(f"Suggestion: {issue.suggestion}")

输出示例立马变得可执行:

Line 42 [high]: Potential SQL injection vulnerability
Suggestion: Use parameterized queries:
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))

Line 87 [medium]: Missing error handling
Suggestion: Add try-except block:
try:
    response = requests.get(url)
    response.raise_for_status()
except RequestException as e:
    logger.error(f"Request failed: {e}")

这才是关键所在——Review意见必须能直接指导修改,否则就是白费力气。

坑3:成本爆炸,Review一个PR花了30美元

最后一个坑最让人肉疼——钱。用GPT-4 Review一个1000行的PR,花了200K tokens,折合30美元。

为什么这么贵?GPT-4本身价格就不便宜,而且每次都要把完整diff发过去。第一次计算时觉得还好:

1000行diff ≈ 10K tokens
分块20块,每块5K tokens
GPT-4 input 0.03/1K tokens,output 0.06/1K tokens
总成本:(20 * 5K * 0.03) + (20 * 1K * 0.06) = 3 + 1.2 = 4.2美元

但实际用了200K tokens,因为Agent反复尝试不同的Review策略,一下子就跑到30美元了。

解决方案很实际:换个路线。用GPT-3.5-turbo做快速初筛,找出可能有问题的文件,然后再让GPT-4针对这些文件做深度Review。效率几乎不受影响,成本却直线下降。

# 先用GPT-3.5快速扫描
llm_fast = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
quick_scan_prompt = ChatPromptTemplate.from_template(
    "Quick scan this PR and identify files with potential issues:{diff}"
    "Return ONLY the list of files that need detailed review."
)
scan_chain = LLMChain(llm=llm_fast, prompt=quick_scan_prompt)
files_to_review = scan_chain.run(diff=pr_diff)

# 只对问题文件用GPT-4深度Review
llm_deep = ChatOpenAI(model="gpt-4", temperature=0)
for file in files_to_review:
    file_diff = extract_file_diff(pr_diff, file)
    review = deep_review_chain.run(diff=file_diff)

成本对比直接说明了效果:

方案1:纯GPT-4,200K tokens,30美元
方案2:GPT-3.5初筛 + GPT-4深度,20K tokens GPT-3.5 + 50K tokens GPT-4,0.06 + 1.5 = 1.56美元
节省:95%成本

最终方案:完整代码

经过一番折腾,最终的方案融合了前面的所有技巧——分块避免Token超限,结构化输出保证可执行,组合模型控制成本。下面是完整的代码实现:

import os
from typing import List
from github import Github
from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain
from langchain.prompts import ChatPromptTemplate
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field

# GitHub配置
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
github = Github(GITHUB_TOKEN)

# LangChain配置
llm_fast = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
llm_deep = ChatOpenAI(model="gpt-4", temperature=0)

class CodeReviewIssue(BaseModel):
    line_number: int
    severity: str
    description: str
    suggestion: str

class CodeReview(BaseModel):
    issues: List[CodeReviewIssue]

parser = PydanticOutputParser(pydantic_object=CodeReview)

def get_pr_diff(repo_name: str, pr_number: int) -> str:
    """获取PR的diff"""
    repo = github.get_repo(repo_name)
    pr = repo.get_pull(pr_number)
    return pr.get_files()[0].patch

def quick_scan(diff: str) -> List[str]:
    """快速扫描,识别需要Review的文件"""
    prompt = ChatPromptTemplate.from_template(
        "Quick scan this PR and identify files with potential issues:{diff}"
        "Return ONLY the list of file paths, one per line."
    )
    chain = LLMChain(llm=llm_fast, prompt=prompt)
    result = chain.run(diff=diff)
    return [f.strip() for f in result.split("") if f.strip()]

def deep_review(diff: str) -> CodeReview:
    """深度Review"""
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=4000,
        chunk_overlap=0,
        separators=["", "", " "]
    )
    chunks = splitter.split_text(diff)
    all_issues = []
    for chunk in chunks:
        prompt = ChatPromptTemplate.from_template(
            "Review this code:{diff}"
            "{format_instructions}"
            "Return ONLY the structured output."
        )
        chain = LLMChain(llm=llm_deep, prompt=prompt, output_parser=parser)
        review = chain.run(diff=chunk, format_instructions=parser.get_format_instructions())
        all_issues.extend(review.issues)
    return CodeReview(issues=all_issues)

def post_review_comment(repo_name: str, pr_number: int, review: CodeReview):
    """在PR上发布Review评论"""
    repo = github.get_repo(repo_name)
    pr = repo.get_pull(pr_number)
    for issue in review.issues:
        pr.create_review_comment(
            body=f"**[{issue.severity.upper()}]** {issue.description}"
                 f"Suggestion: {issue.suggestion}",
            commit=pr.get_commits().reversed[0],
            path="src/main/ja va/example/Example.ja va",
            position=issue.line_number
        )

def review_pr(repo_name: str, pr_number: int):
    """完整的Review流程"""
    diff = get_pr_diff(repo_name, pr_number)
    files_to_review = quick_scan(diff)
    for file in files_to_review:
        file_diff = extract_file_diff(diff, file)
        review = deep_review(file_diff)
        post_review_comment(repo_name, pr_number, review)

# 使用示例
if __name__ == "__main__":
    review_pr("helloworldtang/mq-tutorial", 123)

GitHub项目:https://github.com/helloworldtang/ai-code-review-agent

一句话总结:分块处理避免Token超限,结构化输出保证可执行,GPT-3.5 + GPT-4组合降低成本。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章