在私有化代码 AI 平台的开发中,最难的不是让大模型写代码,而是让它像“资深架构师”一样审代码。

传统的 AI 审查往往只是简单的文本交互,容易产生“幻觉”,且无法识别企业特有的编程规范。在 CodeFlow AI 项目中,我构建了一个 Code Review Agent。它不再仅仅依靠 LLM 的盲猜,而是通过 “静态扫描(硬规则)+ RAG(私有规则)+ AST(上下文感知)” 的三位一体架构,实现了对代码改动的精准“切片”与深度审查。

本文将带你深入底层,看我们如何利用 LangChain 编排 ReAct 模式,如何用 AST 解决长文本 Token 限制,以及如何让 AI 真正读懂企业的“私规”。

开发 Code Review Agent 的核心思路是:“不要只靠大模型盲猜,要给它配备专业的工具和参考资料。”

传统的 AI 审查只是把代码丢给 LLM,它虽然能发现逻辑问题,但往往会产生“幻觉”,或不符合企业特定的规范。你的方案通过 “静态扫描(硬规则)+ RAG(私有规则)+ LLM(逻辑推理)” 完美解决了这个问题。

下面是基于 LangChain + Pylint + Milvus 的详细 Python 代码实现逻辑。


1. 核心架构:Code Review Agent 实现逻辑

第一步:定义 Agent 的“手”(Tools)

Agent 需要能够执行系统命令(运行 Pylint)和查询数据库(Milvus)。

import subprocess
from langchain.tools import tool
from pymilvus import Collection

# 工具 1:静态代码扫描工具
@tool
def run_pylint(file_path: str) -> str:
    """运行 Pylint 获取代码的静态扫描结果,捕获基础语法错误和规范问题。"""
    try:
        # 执行命令:pylint --disable=C0114... (可根据需要配置)
        result = subprocess.run(
            ['pylint', file_path, '--exit-zero'], 
            capture_output=True, text=True
        )
        return result.stdout if result.stdout else "未发现明显的静态扫描问题。"
    except Exception as e:
        return f"扫描执行失败: {str(e)}"

# 工具 2:私有规范检索工具 (RAG)
@tool
def search_enterprise_rules(query: str) -> str:
    """从向量库中检索与当前代码逻辑相关的企业内部开发规范。"""
    # 假设你已经微调并部署了 BGE 模型,这里直接从 Milvus 检索
    # search_params = {"metric_type": "L2", "params": {"nprobe": 10}}
    # results = collection.search(data=[query_vector], ...)
    
    # 模拟检索返回结果
    rules = [
        "规范 ID 001: 严禁在 for/while 循环中使用 list.append(),建议使用列表推导式或预分配空间以提升性能。",
        "规范 ID 005: 所有数据库查询必须使用参数化查询,防止 SQL 注入。",
        "规范 ID 012: 关键逻辑函数必须包含详细的 docstring。"
    ]
    return "\n".join(rules)
第二步:设计 Agent 的“大脑”(Prompt 模板)

我们要给模型设定一个“严谨质检员”的人设,并要求它遵循 ReAct 模式(思考 -> 行动 -> 观察)。

from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

SYSTEM_PROMPT = """你是一名资深架构师,负责企业私有代码库的代码审查(Code Review)。
你的任务是结合‘静态扫描结果’和‘企业内部规范’对用户提供的代码 Diff 进行深度审查。

请遵循以下流程:
1. 思考 (Thought): 分析代码改动了什么,哪些地方可能违反规范或存在逻辑漏洞。
2. 行动 (Action): 调用 run_pylint 检查基础语法,调用 search_enterprise_rules 检索相关规范。
3. 观察 (Observation): 分析工具返回的结果。
4. 最终回复: 生成一份专业的审查报告,包含:代码质量评分、发现的问题列表(附带改进建议)、违规点。

注意:如果代码逻辑没有问题,也要给予肯定的评价。
"""

prompt = ChatPromptTemplate.from_messages([
    ("system", SYSTEM_PROMPT),
    ("human", "请审查以下代码改动(Diff):\n{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])
第三步:组装 Agent 并运行

使用 LangChain 的 create_react_agent 将大脑和手连接起来。

from langchain.agents import AgentExecutor, create_react_agent
from langchain_openai import ChatOpenAI

# 1. 初始化模型 (连接你的 Ollama/Qwen2.5-Coder)
llm = ChatOpenAI(
    model="qwen2.5-coder-7b-instruct", 
    openai_api_base="http://localhost:11434/v1", # Ollama 地址
    api_key="ollama"
)

# 2. 定义工具集
tools = [run_pylint, search_enterprise_rules]

# 3. 创建 ReAct Agent
agent = create_react_agent(llm, tools, prompt)

# 4. 创建执行器
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# 5. 模拟一次代码提交的 Diff 审查
code_diff = """
--- a/user_service.py
+++ b/user_service.py
@@ -10,5 +10,8 @@
 def process_users(user_list):
     active_users = []
     for user in user_list:
-        active_users.append(user)
+        if user.is_active:
+            active_users.append(user)  # 待审查的代码
     return active_users
"""

# 执行审查
response = agent_executor.invoke({"input": code_diff})
print(response["output"])

2. 技术细节解析(面试高频追问)

Q1: 为什么要先运行 Pylint 而不是直接让 LLM 看代码?
  • 回答:Pylint 属于确定性工具,它能 100% 准确地捕捉语法错误、未定义变量等硬伤。如果让 LLM 去找这些问题,既浪费 Token 又可能漏掉。Agent 的逻辑应该是:“工具能解决的给工具,工具解决不了的逻辑和架构问题给 LLM。”

Q2: RAG 在这个 Agent 里起到了什么作用?
  • 回答:解决了 “通用模型不懂私人规范” 的问题。比如你的公司规定“禁止使用 list.append”,这是 Qwen 默认不知道的。通过 RAG,我们将 list.append 作为关键词在 Milvus 中检索,检索出“禁止使用”的规范说明,Agent 看到规范后,就会在审查报告中指出:“根据企业规范 ID 001,此处建议改为列表推导式。”

Q3: 如何处理大规模代码的 Diff?
  • 这是一个非常好的问题。在面试中,如果你能把“如何处理大规模代码的 Diff”讲清楚,面试官会立刻觉得你具备解决企业级复杂场景的能力,而不是只会在实验室跑 Demo。

我们用一个具体的业务场景来把这个过程“具象化”。


场景设定

假设你有一个超大的文件 finance_service.py,总共有 3000 行代码。
用户提交了一个 Git Diff,修改了其中的第 1500 行,内容如下:

code Python

downloadcontent_copy

expand_less

# Git Diff 内容
--- a/finance_service.py
+++ b/finance_service.py
@@ -1500,1 +1500,1 @@
-    total = price * 0.05
+    total = price * self.get_tax_rate(user_id)

痛点:

  1. 如果只传 Diff:LLM 不知道 price 是从哪来的,也不知道 self.get_tax_rate 这个函数内部是怎么实现的。LLM 会说:“我无法判断这个逻辑是否正确,因为我看不见上下文。”

  2. 如果传整个 3000 行文件:Token 太多,浪费钱且响应慢,模型可能在长文本中“迷失”。


具象化解决方案:AST 解析 + 依赖检索

第一步:AST 解析——“定位手术范围”

我们不只看那 1 行,我们要看这 1 行所属的“逻辑块”(通常是一个函数或一个类)。

  1. 解析文件:使用 Python 的 ast 模块把 finance_service.py 变成一棵树。

  2. 定位函数:通过第 1500 行这个数字,在 AST 树中向上查找,发现这一行属于一个叫做 calculate_order_total 的函数。

  3. 提取函数全貌
    Agent 会自动截取这个函数的完整代码(假设是第 1480 行到 1510 行):

    code Python

    downloadcontent_copy

    expand_less
    def calculate_order_total(self, user_id, price):
        # ... 一些逻辑 ...
        total = price * self.get_tax_rate(user_id)  # 修改就在这里
        return total

    面试官亮点: “我通过 AST 确保了传给模型的代码是逻辑完整的,而不是断章取义的碎片。”

第二步:Call Graph (调用链分析)——“寻找外部援军”

此时模型看到了 self.get_tax_rate(user_id),但它还是不知道这个税率是怎么算的。如果税率算法有 Bug 怎么办?

  1. 提取符号:从 AST 中识别出当前函数调用了外部符号 get_tax_rate。

  2. 查询向量库 (Milvus):Agent 拿着 get_tax_rate 这个函数名,去向量库里搜。

  3. 获取定义:向量库返回了 get_tax_rate 在另一个文件 tax_utils.py 里的实现。

    code Python

    downloadcontent_copy

    expand_less
    # 检索到的依赖代码
    def get_tax_rate(self, user_id):
        if user_id.startswith("VIP"):
            return 0.01
        return 0.08
第三步:构造最终的审查上下文 (Context)

现在,Agent 给 LLM 的 Prompt 就不再是那 1 行 Diff,而是一个精心准备的包

System: 你是一个审查专家。
Context:

  1. [目标函数]: calculate_order_total 的完整逻辑...

  2. [相关依赖]: get_tax_rate 函数的定义是...

  3. Input (Diff): 将 * 0.05 改为 * self.get_tax_rate(user_id)。
    Task: 请审查。


Python 代码逻辑实现(简化版)

你可以向面试官展示你对 ast 库的运用:

code Python

downloadcontent_copy

expand_less

import ast

def get_function_context_by_line(file_content, target_line):
    # 将代码转为 AST 树
    tree = ast.parse(file_content)
    
    for node in ast.walk(tree):
        # 寻找包含目标行的函数节点
        if isinstance(node, ast.FunctionDef):
            if node.lineno <= target_line <= node.end_lineno:
                # 使用 ast.get_source_segment 获取这个函数的完整代码段
                return ast.get_source_segment(file_content, node)
    return None

# 模拟流程
raw_code = "..." # 3000行代码
target_line = 1500
context_code = get_function_context_by_line(raw_code, target_line)

# 然后把 context_code 发送给向量库检索它里面用到的函数定义

这样讲的好处(面试加分点):

  1. 精准性:你告诉面试官,你的 Agent 拥有“全局视野”,它能像人类专家一样,点开相关的函数定义去看。

  2. 成本控制:你通过 AST 剪枝,只把最相关的 50 行代码发给 LLM,而不是 3000 行,极大地节省了 Token。

  3. 架构思维:你展示了如何结合 确定性工具(AST)概率性工具(LLM) 来解决问题。

面试官可能会追问: “如果 get_tax_rate 又调用了别的函数,你会无限递归下去吗?”
你的回答: “不会,我会设置一个 Depth=1 或 2 的限制。通常一级依赖就足以让模型做出 90% 的准确判断,过深的依赖会引入过多噪音。”

这个具体的例子是否让你对 Code Review Agent 的实战实现有了更深刻的理解?如果 OK,我们可以进入 2. Test Generation Agent 的详细拆解了。

3. 面试官可能会问的“坑”:

问:如果 Pylint 报错非常多,超出了模型的 Context 长度怎么办?
:我会对 Pylint 的结果进行预过滤。只提取“Error”和“Warning”级别的日志,过滤掉不重要的“Convention(风格)”或“Information”建议。同时,我们会对报错进行分类聚合,同类问题只给出一个代表性示例,从而节省上下文空间。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐