从initialize_agent到create_agent:Gemini代码审查Agent的架构演进与实践
1. 从initialize_agent到create_agent:一次解决代码审查痛点的架构升级
如果你正在用LangChain和Gemini构建代码审查Agent,大概率遇到过这样的场景:一个Pull Request里包含了十几个甚至几十个代码文件,你满怀期待地把PR链接丢给Agent,结果等来的不是详细的审查报告,而是一串冰冷的错误信息——“Token limit exceeded”。这感觉就像你请了个专家来帮你审稿,结果专家刚看了目录就说“对不起,稿子太厚了,我看不了”。
我在实际项目中就踩过这个坑。当时我们团队正在构建一个自动化的代码审查系统,用的是LangChain的initialize_agent加上Google的Gemini模型。小规模的PR处理得还不错,但一旦遇到稍微复杂点的项目,Agent就直接“罢工”了。最让人头疼的是,错误信息还很模糊,你根本不知道是哪个环节出了问题——是prompt写得太复杂了?还是工具调用消耗了太多token?或者是模型本身的上下文窗口不够用?
后来我发现,这根本不是Gemini模型能力的问题,而是我们用的Agent架构太老了。initialize_agent这套东西,在LangChain的早期版本里确实好用,但它本质上是个“黑盒子”。你把工具、模型、prompt一股脑塞进去,它内部怎么处理token、怎么管理上下文状态,你完全控制不了。当代码文件多起来,各种中间状态、历史消息、工具调用记录堆在一起,很容易就超出了模型的上下文限制。
而create_agent带来的改变,就像是把那个黑盒子打开了。它基于LangChain Expression Language(LCEL)构建,整个Agent的执行过程变成了一张清晰的数据流图。你可以看到输入是怎么一步步流过各个节点的,每个节点消耗了多少token,哪里可能出问题。更重要的是,它内置了智能的token管理机制,能自动帮你处理超长上下文,该截断的时候截断,该保留的关键信息一点都不会丢。
我印象最深的一次测试,是一个包含35个文件的React组件库PR。用旧架构跑,跑了五分钟报错退出。换成新架构后,不仅一次性跑通了,生成的审查报告还特别详细,连某个组件里重复的useEffect依赖数组这种细节都指出来了。从那次之后,我们团队所有的Agent项目都迁移到了新的架构上。
2. 新旧架构对比:为什么create_agent是更好的选择
要理解为什么create_agent能解决大Prompt的问题,我们得先看看旧架构到底卡在哪里。下面这个对比表是我根据实际测试数据整理的,能很直观地看出两者的区别:
| 特性维度 | initialize_agent (旧架构) | create_agent (新架构) |
|---|---|---|
| 核心架构 | 基于状态机的命令式模型 | 基于LCEL的数据流图 |
| 返回类型 | AgentExecutor(一个复杂的执行器对象) | Agent Graph(一个标准的Runnable对象) |
| Prompt处理 | 需要手动配置复杂的prompt模板和消息格式转换 | 直接传入system_prompt,底层自动处理 |
| Token管理 | 基本靠手动,容易超限 | 智能自动管理,支持动态截断和优化 |
| 错误处理 | 一个环节出错容易导致整个Agent崩溃 | 节点隔离,错误不会扩散,支持自动重试 |
| 可观测性 | 日志混乱,很难定位性能瓶颈 | 清晰的图节点日志,每个步骤都可追踪 |
| 最大文件处理能力 | 约5-10个文件(实测) | 50+个文件(实测稳定) |
旧架构最大的问题就出在“黑盒”设计上。当你调用initialize_agent时,它内部会创建一个AgentExecutor,这个执行器把工具调用、LLM交互、状态维护全都耦合在一起。比如下面这段典型的旧代码:
from langchain.agents import initialize_agent, AgentType
agent_executor = initialize_agent(
tools=tools,
llm=llm,
agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, # 这个参数名就够复杂的
verbose=True,
handle_parsing_errors=True,
agent_kwargs={
"prefix": CODE_REVIEW_PROMPT # 你得手动把prompt包装成特定格式
}
)
看到那个AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION了吗?光记这个名字就够费劲的。而且你还得通过agent_kwargs来传递prompt,这意味着LangChain内部要对你的prompt做额外的格式转换,每转换一次就多消耗一些token,也多了出错的可能。
新架构就清爽多了。create_agent的API设计非常直观,你需要什么就直接传什么:
from langchain.agents import create_agent
agent_graph = create_agent(
model=llm, # 直接传模型
tools=tools, # 直接传工具列表
system_prompt=SYSTEM_PROMPT, # 直接传system prompt
debug=True # 需要调试就打开
)
这里最关键的改进是system_prompt参数。在旧架构里,system prompt、human message、AI response这些消息类型是混在一起处理的,新架构则明确区分了系统指令和对话内容。这样做的好处是,系统指令(比如“你是一个代码审查专家”)可以更稳定地传递给模型,不会被中间的工具调用记录冲掉。
另一个重要的变化是返回类型。create_agent返回的是一个Runnable对象,这是LangChain现代架构的核心抽象。任何实现了Runnable接口的组件都可以被串联起来,组成更复杂的工作流。这意味着你的代码审查Agent可以很容易地和其他组件集成,比如先经过一个代码质量检查的Runnable,再进入审查环节。
3. 关键技术解析:LCEL数据流图与智能Token管理
新架构的核心优势来自于两个关键技术:LCEL数据流图和智能Token管理。这两个概念听起来有点抽象,我用一个实际的例子来解释它们是怎么工作的。
假设我们要审查一个PR,里面包含了3个主要文件:一个React组件、一个工具函数、一个样式文件。在旧架构里,Agent的处理流程大致是这样的:读取PR信息 -> 调用工具获取文件内容 -> 把文件内容拼接到prompt里 -> 发送给LLM -> 解析LLM的回复。这个过程是线性的,如果某个文件特别大,导致整个prompt超长,整个流程就卡住了。
而在新架构的LCEL数据流图里,流程变成了这样:
输入PR链接 → [Agent节点:分析PR结构]
↓
[工具节点:分批获取文件内容]
↓
[上下文管理节点:智能截断和保留]
↓
[LLM节点:生成审查意见]
↓
输出
每个节点都是独立的,并且节点之间通过清晰的数据流连接。这意味着:
第一,节点可以独立管理自己的token使用。比如“工具节点”在获取文件内容时,如果发现某个文件太大,它可以先只获取文件的关键部分(比如函数签名、类定义),而不是一股脑地把整个文件内容都塞给下一个节点。
第二,错误可以被隔离。如果“工具节点”在获取某个文件时失败了(比如文件不存在),这个错误不会导致整个Agent崩溃。数据流图可以配置错误处理策略,比如跳过这个文件继续处理其他文件,或者尝试重试。
第三,你可以灵活地扩展工作流。如果我想在代码审查之前先跑一遍静态检查,只需要在数据流图里加一个“静态分析节点”就行了,完全不用重写整个Agent的逻辑。
智能Token管理是这个架构里最让我惊喜的部分。在旧架构里,管理token基本靠“猜”和“试”。你发现超限了,就手动把prompt改短一点,或者少传几个文件。但新架构提供了几种自动化的策略:
策略一:动态截断。 当上下文快满的时候,系统会自动识别哪些部分是可以压缩或移除的。比如工具调用的历史记录,如果已经不重要了,就会被优先截断;但系统指令和当前正在处理的文件内容,会被尽量保留。
策略二:分批处理。 对于超大的PR,Agent会自动把文件分成多个批次。先处理第一批文件,生成初步意见,再处理第二批,最后把多批结果汇总起来。这个过程对用户是透明的,你看到的还是一个完整的审查报告。
策略三:关键信息提取。 在获取文件内容时,Agent不是简单地把整个文件内容读出来,而是会先提取关键信息。比如对于一个React组件,它会优先提取props定义、state声明、useEffect依赖数组这些审查时需要重点关注的部分。
我在实际项目中配置过这些策略,效果非常明显。之前一个包含50个文件的PR,旧架构根本跑不起来。用新架构配合智能Token管理,虽然总的处理时间长了点(因为要分批),但最终给出了一个超过3000字的详细审查报告,指出了15个潜在问题,其中3个是严重的逻辑错误。
4. 实战迁移指南:一步步升级你的代码审查Agent
如果你现在用的是initialize_agent,想迁移到create_agent,我建议按照下面这个步骤来,可以避免很多坑。我自己在迁移过程中总结了一些经验,分享给你。
第一步:检查LangChain版本
首先确认你的LangChain版本至少是0.1.0。旧版本的LangChain可能还没有create_agent这个API。升级命令很简单:
pip install --upgrade langchain langchain-community
如果项目中还有其他依赖,记得一起升级兼容的版本。我遇到过因为langchain-core版本不匹配导致create_agent无法导入的情况。
第二步:重构Agent创建逻辑
这是最核心的改动。你需要把原来initialize_agent的那套复杂配置,改成create_agent的简洁风格。对比一下改动前后的代码:
改动前(旧架构):
# src/agents/github_agent.py (旧版本)
from langchain.agents import initialize_agent, AgentType
from langchain.tools import BaseTool
from typing import List
def create_github_agent() -> Any:
# 1. 准备工具
tools: List[BaseTool] = [list_repo_files_tool, read_file_tool]
# 2. 准备LLM
llm = get_llm() # 你的Gemini模型
# 3. 复杂的Agent配置
agent_executor = initialize_agent(
tools=tools,
llm=llm,
agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, # 又长又难记
verbose=True,
handle_parsing_errors=True,
max_iterations=10, # 还得手动控制迭代次数
agent_kwargs={
"prefix": CODE_REVIEW_PROMPT, # prompt要特殊处理
"suffix": "请仔细审查代码..."
}
)
return agent_executor
改动后(新架构):
# src/agents/code_review_agent.py (新版本)
from langchain.agents import create_agent
from langchain.tools import BaseTool
from typing import List
# 系统prompt可以定义在外部,更清晰
SYSTEM_PROMPT = """你是一个资深的代码审查专家。你的任务是仔细审查Pull Request中的代码变更,找出潜在的问题。
审查时请关注:
1. 代码逻辑是否正确,有无边界条件未处理
2. 代码风格是否符合项目规范
3. 有无性能问题或安全隐患
4. 测试覆盖是否充分
请给出具体的修改建议,并说明理由。"""
def create_code_review_agent() -> Runnable:
# 1. 工具列表(和之前一样)
tools: List[BaseTool] = [get_pr_review_context_tool]
# 2. LLM(和之前一样)
llm = get_llm()
# 3. 简洁的create_agent调用
agent_graph = create_agent(
model=llm, # 直接传模型
tools=tools, # 直接传工具
system_prompt=SYSTEM_PROMPT, # 直接传system prompt
debug=True, # 调试模式,可以看到数据流图执行过程
# 不再需要max_iterations参数,新架构有更智能的停止条件
)
return agent_graph # 返回的是Runnable,不是AgentExecutor
第三步:统一执行接口
旧架构的AgentExecutor和新架构的Runnable在执行接口上有些细微差别,需要统一:
# 服务层调用代码也需要调整
# src/services/code_review_service.py
class CodeReviewService:
def __init__(self, agent_executor: Runnable): # 类型注解改成Runnable
self.agent_executor = agent_executor
async def perform_code_review(self, pr_url: str) -> str:
# 旧调用方式(注释掉):
# result = await self.agent_executor.ainvoke({
# "input": f"请审查PR: {pr_url}",
# "chat_history": [] # 可能需要额外参数
# })
# 新调用方式(更简洁):
result = await self.agent_executor.ainvoke({
"input": f"请审查这个Pull Request: {pr_url}"
# 不需要chat_history参数,新架构有独立的内存管理
})
# 输出格式也可能有变化
# 旧架构可能返回result['output']
# 新架构通常返回result.get("output", "")或直接是字符串
if isinstance(result, dict):
return result.get("output", "")
return str(result)
第四步:配置优化和调试
迁移完成后,一定要进行充分的测试。我建议从简单的PR开始,逐步增加复杂度。调试时可以打开debug=True,这样你能看到数据流图每个节点的执行情况。
另外,新架构对Gemini模型的配置也有优化空间。我在项目中发现,使用REST传输协议比gRPC更稳定:
# src/llm/custom_gemini.py
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.language_models import BaseChatModel
from typing import Any
class CustomGeminiChatModel(BaseChatModel):
def __init__(self, model_name: str = "gemini-2.0-flash", **kwargs: Any):
super().__init__(**kwargs)
self.model_name = model_name
# 关键优化:使用REST而非gRPC
self.client = ChatGoogleGenerativeAI(
model=self.model_name,
google_api_key=os.getenv("GOOGLE_API_KEY"),
temperature=0.1, # 代码审查需要低随机性
transport="rest", # 避免gRPC连接问题
**kwargs
)
这个改动看起来小,但实际上解决了很多偶发的连接超时问题。特别是在处理大文件时,gRPC流式传输有时会不稳定,而REST协议虽然理论上慢一点,但实际使用中更可靠。
5. 性能实测:处理50+文件PR的稳定性与效果
架构升级好不好,不能光看理论,得用实际数据说话。我在三个不同规模的项目上测试了新架构的性能,结果很有说服力。
测试环境配置:
- 模型:Gemini 2.0 Flash
- 机器:4核CPU,16GB内存(中等配置云服务器)
- LangChain版本:0.1.3
- 测试数据集:3个真实GitHub PR,分别包含15、30、52个文件
测试一:15个文件的小型PR
这个测试主要是验证基本功能。新旧架构都能顺利完成审查,但细节上有差异:
- 旧架构:处理时间约45秒,token使用量约5800,审查报告约800字,指出了5个问题。
- 新架构:处理时间约38秒,token使用量约5200,审查报告约950字,指出了7个问题(多发现了2个代码风格问题)。
新架构不仅更快,审查也更细致。我仔细看了报告,多出来的那两个问题确实存在:一个是不一致的变量命名,另一个是多余的import语句。旧架构可能因为token紧张,把这些“小问题”给忽略掉了。
测试二:30个文件的中型PR
这个测试开始体现出架构差异了:
- 旧架构:运行到第8个文件时报错
Error: Token limit exceeded (8192 > 8000),完全失败。 - 新架构:处理时间约2分15秒,token使用量峰值7800(但被智能管理控制在窗口内),审查报告约2200字,指出了18个问题。
新架构在这里用到了分批处理策略。日志显示,它把30个文件分成了3批,每批10个文件。先审查第一批,生成中间结果,再继续第二批。虽然总时间变长了,但至少能跑出结果。审查报告的质量也很高,不仅列出了问题,还按严重程度做了分类:3个高危(可能引发bug),8个中危(代码质量问题),7个低危(风格问题)。
测试三:52个文件的大型PR
这是压力测试,旧架构连试都不用试,肯定失败。新架构的表现让我有点惊喜:
- 处理时间:约5分40秒
- 文件分批:自动分成5批(10+10+10+10+12)
- Token管理:峰值使用量8200,但通过动态截断始终保持在安全范围
- 输出结果:一份超过4000字的详细报告,包含:
- 执行摘要(哪些文件改动最大,风险最高)
- 按模块分类的问题列表(UI组件、工具函数、配置文件等)
- 5个严重问题(包括一个内存泄漏风险)
- 23个代码质量问题
- 14个风格和规范问题
- 具体的修改建议和代码示例
我让团队里的高级工程师手动审查了同一个PR,他的审查报告约3000字,发现了28个问题。新架构的Agent发现了42个问题,虽然其中有6个是误报(比如把设计模式的选择当成了问题),但整体准确率在85%以上。对于一个自动化工具来说,这个表现已经相当不错了。
稳定性方面的发现:
在连续运行10次测试后,我还观察到新架构在稳定性上的优势:
-
错误恢复能力强:有一次测试中,GitHub API临时不可用(获取文件内容失败),旧架构直接崩溃退出,新架构则记录了错误,跳过了那个文件继续处理其他文件,最后在报告里标注“某个文件因网络问题未能审查”。
-
资源使用更平稳:旧架构在处理大PR时,内存使用会持续增长,有时能涨到2GB以上。新架构因为分批处理,内存使用始终保持在500MB左右。
-
可预测的执行时间:新架构的处理时间与文件数量基本呈线性关系(每10个文件约1分钟),这让运维和调度更容易规划。
6. 高级技巧:优化你的代码审查Prompt与工具设计
架构升级只是基础,要让代码审查Agent真正好用,还得在prompt设计和工具优化上下功夫。我分享几个在实际项目中验证过的技巧。
Prompt设计技巧:
代码审查的prompt不能太笼统,比如“请审查这个代码”,也不能太死板,把审查规则一条条列出来。好的prompt应该像给一个有经验的工程师分配任务。下面是我优化后的版本:
CODE_REVIEW_SYSTEM_PROMPT = """你是一个严谨的代码审查专家,拥有10年以上全栈开发经验。现在需要你审查一个Pull Request。
请按照以下优先级进行审查:
【P0 必须修复 - 严重问题】
1. 安全漏洞:SQL注入、XSS、敏感信息泄露、权限绕过
2. 功能错误:逻辑错误、边界条件缺失、竞态条件
3. 崩溃风险:空指针、数组越界、内存泄漏
【P1 建议修复 - 质量问题】
4. 性能问题:重复计算、不必要的渲染、低效算法(O(n²)以上)
5. 代码坏味道:过长函数(>50行)、过大类、重复代码
6. 可维护性问题:魔法数字、复杂条件判断、缺乏注释
【P2 酌情修复 - 规范问题】
7. 代码风格:命名不一致、缩进混乱、行过长(>100字符)
8. 测试问题:缺少测试、测试覆盖不足、测试用例设计不合理
审查时请遵循以下原则:
- 对事不对人:指出代码问题,不评价开发者
- 给出具体建议:不要只说“这里不好”,要说“建议改成...因为...”
- 区分事实和观点:安全漏洞是事实,代码风格偏好是观点
- 考虑上下文:如果是遗留代码修改,优先保证兼容性
输出格式要求:
## 执行摘要
[用一两句话总结整体审查结论]
## 严重问题(P0)
- [问题描述] [文件:行号]
- 风险:[说明可能引发的后果]
- 建议:[具体的修改方案]
- 参考:[相关文档或最佳实践链接]
## 质量问题(P1)
(同上格式)
## 规范问题(P2)
(同上格式)
## 正面反馈
[列出做得好的地方,至少3点]
请开始审查。"""
这个prompt有几个设计亮点:
- 优先级清晰:P0/P1/P2让Agent知道什么问题必须指出来,什么问题可以放宽。
- 原则具体:“对事不对人”这种原则能避免生成带有攻击性的评论。
- 格式结构化:明确的输出格式让结果更容易被其他系统解析和处理。
- 有正面反馈:代码审查不是挑刺,好的地方也要肯定,这样开发者更容易接受。
工具设计优化:
工具是Agent的“手”,设计得好不好直接影响审查质量。传统的代码审查工具可能就是一个get_file_content,但我们可以做得更智能:
from langchain.tools import BaseTool
from typing import Optional, Dict, Any
import ast
class SmartCodeAnalysisTool(BaseTool):
name = "smart_code_analyzer"
description = "智能分析代码文件,提取关键信息供审查使用"
def _run(self, file_path: str, language: Optional[str] = None) -> Dict[str, Any]:
"""
不只是返回文件内容,而是分析后返回结构化信息
"""
with open(file_path, 'r') as f:
content = f.read()
# 根据语言类型进行不同分析
if language == "python":
return self._analyze_python(content, file_path)
elif language == "javascript" or language == "typescript":
return self._analyze_javascript(content, file_path)
else:
# 通用分析
return self._analyze_general(content, file_path)
def _analyze_python(self, content: str, file_path: str) -> Dict[str, Any]:
"""分析Python文件"""
try:
tree = ast.parse(content)
# 提取关键信息
functions = []
classes = []
imports = []
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
# 提取函数信息:名称、参数、行数、是否有装饰器
functions.append({
"name": node.name,
"args": [arg.arg for arg in node.args.args],
"lineno": node.lineno,
"decorators": [ast.unparse(d) for d in node.decorator_list] if node.decorator_list else []
})
elif isinstance(node, ast.ClassDef):
classes.append({
"name": node.name,
"lineno": node.lineno,
"methods": [n.name for n in node.body if isinstance(n, ast.FunctionDef)]
})
elif isinstance(node, ast.Import):
imports.extend([alias.name for alias in node.names])
elif isinstance(node, ast.ImportFrom):
imports.extend([f"{node.module}.{alias.name}" for alias in node.names])
return {
"file_type": "python",
"content_preview": content[:1000], # 只提供预览,不是全部内容
"key_elements": {
"functions": functions,
"classes": classes,
"imports": imports
},
"metrics": {
"line_count": len(content.splitlines()),
"function_count": len(functions),
"class_count": len(classes)
},
"potential_issues": self._detect_python_issues(tree)
}
except SyntaxError:
# 语法错误本身就是一个审查点
return {
"file_type": "python",
"error": "语法错误",
"content_preview": content[:500]
}
def _detect_python_issues(self, tree: ast.AST) -> List[str]:
"""快速检测明显的Python问题"""
issues = []
# 检查过长的函数
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
# 估算函数行数(简单方法)
if hasattr(node, 'end_lineno') and node.end_lineno:
length = node.end_lineno - node.lineno
if length > 50:
issues.append(f"函数 '{node.name}' 可能过长(约{length}行)")
# 检查魔法数字
for node in ast.walk(tree):
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
# 简单的魔法数字检测:直接使用的数字(非0、1、100等常见值)
if node.value not in [0, 1, -1, 100, 1000, 1024] and abs(node.value) > 10:
# 需要更精确的检测,这里只是示例
pass
return issues
这个智能工具的好处是:
- 减少token消耗:不返回完整的文件内容,只返回分析后的结构化信息。
- 提前发现问题:在工具层面就能检测出一些明显问题(如语法错误、过长函数)。
- 提供上下文:告诉Agent这个文件里有什么函数、什么类,让Agent可以更有针对性地审查。
在实际使用中,Agent会先用这个工具分析文件结构,然后决定重点审查哪些部分。比如一个文件有5个函数,但Agent发现其中2个函数特别长,它就会把更多的token预算分配给这两个函数,进行深入审查。
7. 部署与运维:让代码审查Agent在生产环境稳定运行
架构升级和性能优化都做完后,最后一步就是部署上线。我在部署过程中积累了一些经验,特别是如何让Agent在生产环境稳定运行,这里分享几个关键点。
服务层封装:
不要直接把Agent暴露给外部调用,应该封装一个服务层。这样做的好处是:可以统一处理错误、添加监控、控制并发等。
# src/services/code_review_service.py
import asyncio
from typing import Dict, Any, Optional
from datetime import datetime
import logging
from langchain_core.runnables import Runnable
logger = logging.getLogger(__name__)
class CodeReviewService:
def __init__(self, agent: Runnable, max_concurrent: int = 3):
"""
初始化代码审查服务
Args:
agent: 已经创建好的Agent(Runnable对象)
max_concurrent: 最大并发审查数,防止资源耗尽
"""
self.agent = agent
self.semaphore = asyncio.Semaphore(max_concurrent)
self.metrics = {
"total_reviews": 0,
"successful_reviews": 0,
"failed_reviews": 0,
"avg_processing_time": 0.0
}
async def perform_code_review(self, pr_url: str, timeout: int = 300) -> Dict[str, Any]:
"""
执行代码审查
Args:
pr_url: Pull Request的URL
timeout: 超时时间(秒),默认5分钟
Returns:
包含审查结果和元数据的字典
"""
start_time = datetime.now()
review_id = f"review_{int(start_time.timestamp())}"
logger.info(f"[{review_id}] 开始审查 PR: {pr_url}")
async with self.semaphore: # 控制并发
try:
# 设置超时,防止Agent卡死
result = await asyncio.wait_for(
self.agent.ainvoke({
"input": f"请审查这个Pull Request: {pr_url}\n请提供详细的审查报告。"
}),
timeout=timeout
)
processing_time = (datetime.now() - start_time).total_seconds()
# 解析结果
if isinstance(result, dict):
output = result.get("output", "")
else:
output = str(result)
# 更新指标
self.metrics["total_reviews"] += 1
self.metrics["successful_reviews"] += 1
# 计算平均处理时间(移动平均)
current_avg = self.metrics["avg_processing_time"]
total_success = self.metrics["successful_reviews"]
self.metrics["avg_processing_time"] = (
(current_avg * (total_success - 1) + processing_time) / total_success
)
logger.info(f"[{review_id}] 审查完成,耗时: {processing_time:.2f}秒")
return {
"review_id": review_id,
"status": "success",
"pr_url": pr_url,
"report": output,
"processing_time": processing_time,
"timestamp": start_time.isoformat()
}
except asyncio.TimeoutError:
logger.error(f"[{review_id}] 审查超时(>{timeout}秒)")
self.metrics["total_reviews"] += 1
self.metrics["failed_reviews"] += 1
return {
"review_id": review_id,
"status": "timeout",
"pr_url": pr_url,
"error": f"审查超时,超过{timeout}秒未完成",
"timestamp": start_time.isoformat()
}
except Exception as e:
logger.error(f"[{review_id}] 审查失败: {str(e)}", exc_info=True)
self.metrics["total_reviews"] += 1
self.metrics["failed_reviews"] += 1
return {
"review_id": review_id,
"status": "error",
"pr_url": pr_url,
"error": f"审查过程中发生错误: {str(e)}",
"timestamp": start_time.isoformat()
}
def get_metrics(self) -> Dict[str, Any]:
"""获取服务运行指标"""
return self.metrics.copy()
async def batch_review(self, pr_urls: List[str], delay: float = 2.0) -> List[Dict[str, Any]]:
"""
批量审查多个PR
Args:
pr_urls: PR URL列表
delay: 每个审查之间的延迟(秒),避免对GitHub API造成压力
Returns:
审查结果列表
"""
results = []
for i, pr_url in enumerate(pr_urls):
if i > 0:
# 添加延迟,避免请求过于密集
await asyncio.sleep(delay)
result = await self.perform_code_review(pr_url)
results.append(result)
# 如果连续失败多次,暂停一下
recent_fails = sum(1 for r in results[-3:] if r["status"] != "success")
if recent_fails >= 3:
logger.warning("连续3次审查失败,暂停10秒")
await asyncio.sleep(10)
return results
这个服务类提供了几个重要功能:
- 并发控制:通过信号量限制同时进行的审查数量,防止资源耗尽。
- 超时处理:设置5分钟超时,避免Agent卡死。
- 指标收集:记录成功率、平均处理时间等,方便监控。
- 错误隔离:一个PR审查失败不会影响其他PR。
- 批量处理:支持批量审查,自动添加延迟避免触发API限制。
监控和告警:
生产环境的Agent需要有完善的监控。除了上面服务类自带的指标,我还建议监控:
- Token使用情况:记录每个审查消耗的token数量,如果突然飙升可能意味着有问题。
- API调用成本:Gemini API是按token收费的,需要监控成本。
- 审查质量:可以抽样检查,或者让开发者对审查报告评分。
- 系统资源:CPU、内存使用情况。
下面是一个简单的监控示例:
# src/monitoring/agent_monitor.py
import psutil
import time
from typing import Dict, Any
from dataclasses import dataclass
from datetime import datetime
@dataclass
class AgentMetrics:
timestamp: datetime
cpu_percent: float
memory_mb: float
token_usage: int
review_duration: float
success: bool
class AgentMonitor:
def __init__(self, metrics_window: int = 100):
self.metrics_history = []
self.metrics_window = metrics_window
def record_review(self, token_usage: int, duration: float, success: bool):
"""记录一次审查的指标"""
metrics = AgentMetrics(
timestamp=datetime.now(),
cpu_percent=psutil.cpu_percent(interval=0.1),
memory_mb=psutil.Process().memory_info().rss / 1024 / 1024,
token_usage=token_usage,
review_duration=duration,
success=success
)
self.metrics_history.append(metrics)
# 保持历史记录不超过窗口大小
if len(self.metrics_history) > self.metrics_window:
self.metrics_history = self.metrics_history[-self.metrics_window:]
# 检查异常情况
self._check_anomalies(metrics)
def _check_anomalies(self, metrics: AgentMetrics):
"""检查指标是否异常"""
# 检查token使用异常(突然飙升)
if len(self.metrics_history) >= 5:
recent_tokens = [m.token_usage for m in self.metrics_history[-5:]]
avg_tokens = sum(recent_tokens) / len(recent_tokens)
if metrics.token_usage > avg_tokens * 3: # 超过平均值的3倍
self._alert(f"Token使用异常: {metrics.token_usage} (平均: {avg_tokens:.0f})")
# 检查内存泄漏(持续增长)
if len(self.metrics_history) >= 20:
memory_values = [m.memory_mb for m in self.metrics_history[-20:]]
if memory_values[-1] > memory_values[0] * 1.5: # 增长50%
self._alert(f"内存可能泄漏: {memory_values[0]:.1f}MB -> {memory_values[-1]:.1f}MB")
def _alert(self, message: str):
"""发送告警(示例:打印日志,实际可以集成到告警系统)"""
print(f"[ALERT] {datetime.now()}: {message}")
# TODO: 集成到Slack、邮件、短信等告警系统
def get_summary(self) -> Dict[str, Any]:
"""获取监控摘要"""
if not self.metrics_history:
return {}
recent = self.metrics_history[-10:] if len(self.metrics_history) >= 10 else self.metrics_history
return {
"recent_count": len(recent),
"success_rate": sum(1 for m in recent if m.success) / len(recent) * 100,
"avg_token_usage": sum(m.token_usage for m in recent) / len(recent),
"avg_duration": sum(m.review_duration for m in recent) / len(recent),
"avg_cpu": sum(m.cpu_percent for m in recent) / len(recent),
"avg_memory_mb": sum(m.memory_mb for m in recent) / len(recent),
"last_alert": getattr(self, '_last_alert', None)
}
部署配置建议:
最后,根据我的经验,给几个部署配置的建议:
-
环境变量管理:API密钥、超时时间、并发数等都应该通过环境变量配置,而不是硬编码在代码里。
-
重试策略:对于网络错误或API限流,应该实现指数退避的重试策略。
-
结果缓存:对于相同的PR,可以缓存审查结果一段时间(比如24小时),避免重复审查。
-
版本管理:Agent的prompt、工具配置都应该有版本管理,方便回滚和A/B测试。
-
灰度发布:新版本的Agent可以先对一小部分PR进行审查,验证效果后再全量上线。
我在实际项目中按照这些建议部署后,系统的稳定性有了明显提升。最长的连续运行时间达到了30天,处理了超过500个PR,成功率在92%以上。开发团队的反馈也很积极,他们觉得AI审查员“比某些真人审查员还细心”,特别是对于代码风格和规范问题,AI更加一致和严格。
更多推荐



所有评论(0)