利用Agent自动进行代码审查,这个设想听起来很理想——能够显著节省人力成本。

最初以为实现起来很容易:通过GitHub API获取PR diff,然后交给GPT-4处理,等待它返回审查意见即可。然而实践后发现,事情远没那么简单——我接连踩了三个坑,白白浪费了一周时间。
坑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输出具体的行号和代码示例。通过结构化的输出格式,让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}")
这才是关键所在——审查意见必须能直接指导代码修改,否则就是徒劳无功。
坑3:成本爆炸,审查一个PR花费了30美元
最后一个坑最令人心疼——费用。使用GPT-4审查一个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反复尝试不同的审查策略,一下子就飙升到了30美元。
解决方案很实际:换个策略。先用GPT-3.5-turbo进行快速初筛,找出可能存在问题的文件,然后仅对这些文件使用GPT-4做深度审查。效率几乎不受影响,成本却大幅下降。
# 先用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/java/example/Example.java",
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组合使用降低成本。
