Day 2:LCEL 审查链——让 Agent "评价"你的代码
昨天的问题
Day 1 的 Agent 只是"统计"——几个类、几个函数、谁缺注释。但它不知道代码写得好不好。add(self, a, b): return a + b 这个函数没有类型注解、没有参数校验——Agent 看到了缺文档字符串,但没看出更深层的问题。 天的问题Day 1 的 Agent 只是"统计"——几个类、几个函数、谁缺注释。但它不知道代码写得好不好。add(self, a, b): return a + b 这个函数没有类型注解、没有参数校验——Agent 看到了缺文档字符串,但没看出更深层的问题。
今天 Agent 要像一个真正的 Code Reviewer——不只是"看到",而是"评价"。
今天做什么
构建一条代码审查链:
跟项目二的逻辑一样:定义输出格式 → 写审查 Prompt → MockLLM 模拟 → Pydantic 解析。不同的是——Prompt 里不是"提取信息",而是"按标准审查"。
我们定义 4 个审查维度:命名规范、函数职责、文档注释、潜在风险。
📁 代码目录
code_assistant/
├── sample_code.py ← Day1 的示例代码
├── day1.py
└── day2.py ← 新增!审查链,80行代码
python
# day2.py — LCEL 代码审查链
from langchain.prompts import ChatPromptTemplate
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from typing import List
# ===== Day1 的代码加载(复用)=====
import os
SAMPLE = "sample_code.py"
if not os.path.exists(SAMPLE):
with open(SAMPLE, "w", encoding="utf-8") as f:
f.write('''class Calculator:
"""简单计算器"""
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
"""除法运算(缺少参数校验)"""
return a / b
def greet(name):
"""生成问候语"""
return f"Hello, {name}!"
def process_data(data):
result = []
for item in data:
if item > 0:
result.append(item * 2)
return result
''')
with open(SAMPLE, "r", encoding="utf-8") as f:
source_code = f.read()
print(f"📂 加载代码: {SAMPLE} ({len(source_code)} 字符)\n")
# ===== Day1 结束 =====
# ===== Day2 新增:审查报告结构 =====
class FunctionIssue(BaseModel):
"""单个函数的问题"""
function_name: str = Field(description="函数名")
severity: str = Field(description="严重程度:high / medium / low")
category: str = Field(description="问题类别:命名/职责/文档/风险")
description: str = Field(description="问题的详细描述")
suggestion: str = Field(description="修改建议")
class CodeReview(BaseModel):
"""完整的代码审查报告"""
total_score: int = Field(description="总分(满分100)")
naming_score: int = Field(description="命名规范得分(满分25)")
responsibility_score: int = Field(description="函数职责得分(满分25)")
documentation_score: int = Field(description="文档注释得分(满分25)")
safety_score: int = Field(description="安全与健壮性得分(满分25)")
summary: str = Field(description="一句话总评")
issues: List[FunctionIssue] = Field(description="发现的问题列表")
parser = PydanticOutputParser(pydantic_object=CodeReview)
# ===== Prompt 模板 =====
REVIEW_PROMPT = """你是一位资深 Python 代码审查员。请按照以下四个维度审查代码:
### 审
查维度
1. **命名规范(25分)**:变量名、函数名、类名是否符合 Python 命名约定
2. **函数职责(25分)**:每个函数是否只做一件事,是否过长或过于复杂
3. **文档注释(25分)**:函数是否有文档字符串,注释是否清晰有用
4. **安全与健壮性(25分)**:是否有输入校验、异常处理、边界条件检查
### 输
出要求
- 总分 100 分,四个维度各 25 分
- 对每个函数给出问题描述和修改建议
- 严重程度:high(必须改)、medium(建议改)、low(锦上添花)
{format_instructions}
### 待
审查代码
```python
{source_code}
```
"""
prompt = ChatPromptTemplate.from_template(REVIEW_PROMPT)
# ===== MockLLM —— 返回预设的审查结果 =====
class MockLLM:
"""模拟 LLM 审查代码"""
def invoke(self, messages):
import json
# 模拟审查结果 —— 基于 sample_code.py 的已知问题
review = CodeReview(
total_score=62,
naming_score=22,
responsibility_score=18,
documentation_score=10,
safety_score=12,
summary="代码结构清晰但缺少参数校验和文档字符串,健壮性需要加强",
issues=[
FunctionIssue(
function_name="divide",
severity="high",
category="风险",
description="没有检查除数是否为零,传入 divide(x, 0) 会抛出 ZeroDivisionError",
suggestion="添加 if b == 0 检查,返回有意义的结果或抛出更友好的异常"
),
FunctionIssue(
function_name="process_data",
severity="high",
category="风险",
description="没有检查 data 是否为 None 或非列表类型",
suggestion="添加参数校验:if not isinstance(data, list): raise TypeError(...)"
),
FunctionIssue(
function_name="add",
severity="medium",
category="文档",
description="缺少文档字符串,不知道该函数的用途和参数类型",
suggestion="添加 docstring 说明参数类型和返回值"
),
FunctionIssue(
function_name="subtract",
severity="medium",
category="文档",
description="缺少文档字符串",
suggestion="添加 docstring"
),
FunctionIssue(
function_name="multiply",
severity="medium",
category="文档",
description="缺少文档字符串",
suggestion="添加 docstring"
),
FunctionIssue(
function_name="process_data",
severity="low",
category="命名",
description="函数名 'process_data' 有点模糊——它在做什么处理?",
suggestion="建议改为更具体的名字,如 filter_and_double_positives"
),
]
)
return review.model_dump_json()
llm = MockLLM()
# ===== 组装 LCEL 链 =====
chain = prompt | llm | parser
# ===== 运行审查 =====
print("=" * 50)
print("🔍 开始代码审查...")
print("=" * 50)
result = chain.invoke({
"source_code": source_code,
"format_instructions": parser.get_format_instructions()
})
# 输出审查报告
print(f"\n📊 审查报告")
print(f"{'=' * 50}")
print(f" 总分: {result.total_score}/100")
print(f" 命名规范: {result.naming_score}/25")
print(f" 函数职责: {result.responsibility_score}/25")
print(f" 文档注释: {result.documentation_score}/25")
print(f" 安全健壮: {result.safety_score}/25")
print(f" 总评: {result.summary}")
print(f"\n🔍 发现 {len(result.issues)} 个问题:")
for issue in result.issues:
icon = {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(issue.severity, "⚪")
print(f" {icon} [{issue.severity}] {issue.function_name}")
print(f" 类别: {issue.category}")
print(f" 问题: {issue.description}")
print(f" 建议: {issue.suggestion}")
print()运行
bash
python day2.py你应该看到:
📂 加载代码: sample_code.py (419 字符)
==================================================
🔍 开始代码审查...
==================================================
📊 审查报告
==================================================
总分: 62/100
命名规范: 22/25
函数职责: 18/25
文档注释: 10/25
安全健壮: 12/25
总评: 代码结构清晰但缺少参数校验和文档字符串,健壮性需要加强
🔍 发现 6 个问题:
🔴 [high] divide
类别: 风险
问题: 没有检查除数是否为零...
建议: 添加 if b == 0 检查...
🔴 [high] process_data
类别: 风险
问题: 没有检查 data 是否为 None 或非列表类型
建议: 添加参数校验...
🟡 [medium] add
类别: 文档
问题: 缺少文档字符串...
建议: 添加 docstring 说明参数类型和返回值
...你学到了什么
代码审查的本质是"照镜子"。 Agent 没有在"创造"标准——你在 Prompt 里定义了 4 个维度(命名、职责、文档、安全),Agent 只是拿着这把尺子去量代码。每个维度 25 分,总分 100 分。你今天写的这个 Prompt 框架,用在真实 LLM 上效果会更好——因为真实 LLM 能理解代码语义,而 MockLLM 返回的是你预设好的审查结论。
跟项目二 Day 2 的对比: 项目二是"从文本中提取",项目三是"对代码做评价"。输入格式变了(Python 代码 vs 合同文本),Prompt 重点变了(审查标准 vs 提取规则),但 LCEL 管道完全一样——prompt | llm | parser。同样的骨架,换一个 Prompt,就是一个全新的 Agent。
MockLLM 的价值再次体现: 不用 LLM API,你就能验证整个审查流程——评分出来了,问题列出来了,严重程度用 emoji 标出来了。明天换一个真实代码文件跑,MockLLM 不会给你新的结果——但你会看到流程已经在生产环境中能跑的样子。
明天的预告
今天 Agent 一次审查一个文件。明天用 asyncio 并发审查——一个项目的所有 Python 文件同时审,节省时间。
Day 2 完成。Agent 现在是代码审查员了——四个维度逐项打分,每个问题有描述有建议有严重程度。

