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次测试后,我还观察到新架构在稳定性上的优势:

  1. 错误恢复能力强:有一次测试中,GitHub API临时不可用(获取文件内容失败),旧架构直接崩溃退出,新架构则记录了错误,跳过了那个文件继续处理其他文件,最后在报告里标注“某个文件因网络问题未能审查”。

  2. 资源使用更平稳:旧架构在处理大PR时,内存使用会持续增长,有时能涨到2GB以上。新架构因为分批处理,内存使用始终保持在500MB左右。

  3. 可预测的执行时间:新架构的处理时间与文件数量基本呈线性关系(每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有几个设计亮点:

  1. 优先级清晰:P0/P1/P2让Agent知道什么问题必须指出来,什么问题可以放宽。
  2. 原则具体:“对事不对人”这种原则能避免生成带有攻击性的评论。
  3. 格式结构化:明确的输出格式让结果更容易被其他系统解析和处理。
  4. 有正面反馈:代码审查不是挑刺,好的地方也要肯定,这样开发者更容易接受。

工具设计优化:

工具是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

这个智能工具的好处是:

  1. 减少token消耗:不返回完整的文件内容,只返回分析后的结构化信息。
  2. 提前发现问题:在工具层面就能检测出一些明显问题(如语法错误、过长函数)。
  3. 提供上下文:告诉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

这个服务类提供了几个重要功能:

  1. 并发控制:通过信号量限制同时进行的审查数量,防止资源耗尽。
  2. 超时处理:设置5分钟超时,避免Agent卡死。
  3. 指标收集:记录成功率、平均处理时间等,方便监控。
  4. 错误隔离:一个PR审查失败不会影响其他PR。
  5. 批量处理:支持批量审查,自动添加延迟避免触发API限制。

监控和告警:

生产环境的Agent需要有完善的监控。除了上面服务类自带的指标,我还建议监控:

  1. Token使用情况:记录每个审查消耗的token数量,如果突然飙升可能意味着有问题。
  2. API调用成本:Gemini API是按token收费的,需要监控成本。
  3. 审查质量:可以抽样检查,或者让开发者对审查报告评分。
  4. 系统资源: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)
        }

部署配置建议:

最后,根据我的经验,给几个部署配置的建议:

  1. 环境变量管理:API密钥、超时时间、并发数等都应该通过环境变量配置,而不是硬编码在代码里。

  2. 重试策略:对于网络错误或API限流,应该实现指数退避的重试策略。

  3. 结果缓存:对于相同的PR,可以缓存审查结果一段时间(比如24小时),避免重复审查。

  4. 版本管理:Agent的prompt、工具配置都应该有版本管理,方便回滚和A/B测试。

  5. 灰度发布:新版本的Agent可以先对一小部分PR进行审查,验证效果后再全量上线。

我在实际项目中按照这些建议部署后,系统的稳定性有了明显提升。最长的连续运行时间达到了30天,处理了超过500个PR,成功率在92%以上。开发团队的反馈也很积极,他们觉得AI审查员“比某些真人审查员还细心”,特别是对于代码风格和规范问题,AI更加一致和严格。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐