Day 1:搭骨架——让 Agent "看懂"你的代码
从两个项目到第三个,套路已经很熟了
项目一做了客服 Agent——输入一句话,输出一句话。项目二做了文档分析 Agent——输入一份文档,输出结构化数据。 两个项目到第三个,套路已经很熟了项目一做了客服 Agent——输入一句话,输出一句话。项目二做了文档分析 Agent——输入一份文档,输出结构化数据。
项目三做什么?代码助手。
场景很简单:你写了一堆代码,想看有没有明显的 bug、变量名是否规范、函数有没有写注释。把代码扔给 Agent,它自动审查,生成报告。
但代码和文档不一样。文档是自然语言,代码是有结构的文本——缩进有意义,关键字有含义,变量之间有关联。Agent 不能"当作文档读",它需要理解代码的语法结构。
今天做什么
写一个最简单的代码加载器。读取 Python 文件,提取关键信息——文件名、行数、类数量、函数数量、注释行数。不做 AI 分析,只做代码统计。
这跟项目一 Day 1 的"回声筒"是一个道理——先让管线跑通,再往上加智能。
你需要什么
- Python 3.10+
- 一个文本编辑器
- 不需要 API Key、不需要联网、不需要花钱
- Day 1 只用 Python 内置模块(
ast、os、pathlib),无需安装任何第三方库
📦 本项目全程使用的框架版本:
框架 版本 首次用到 langchain ≥0.3 Day 2 pydantic ≥2.0 Day 2 mcp 0.9.x Day 5 streamlit ≥1.28 Day 6 每天会在对应章节给出具体安装命令,不用第一天全部装完。
📁 代码目录
code_assistant/
├── sample_code.py ← 准备一份示例代码
└── day1.py ← 唯一一个文件,50行代码
先创建一份示例代码,让 Agent 有东西可读:
python
# sample_code.py — 给Agent当"审查对象"
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然后是今天的核心代码:
python
# day1.py — 代码文件加载与基础分析
import os
import ast
from pathlib import Path
# ===== 如果示例文件不存在,自动创建 =====
SAMPLE = "sample_code.py"
if not os.path.exists(SAMPLE):
sample_content = '''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, "w", encoding="utf-8") as f:
f.write(sample_content)
print(f"📝 已创建示例文件: {SAMPLE}\n")
# ===== 代码分析 Pipeline =====
class CodePipeline:
"""代码文件分析流水线"""
def __init__(self):
self.analysis_results = []
def load_file(self, filepath: str) -> dict:
"""加载代码文件,返回原始文本和 AST"""
print(f"📂 加载文件: {filepath}")
with open(filepath, "r", encoding="utf-8") as f:
source = f.read()
# 尝试解析 AST(抽象语法树,理解代码结构的基础)
try:
tree = ast.parse(source)
parse_ok = True
except SyntaxError as e:
print(f" ⚠️ 语法错误: {e}")
tree = None
parse_ok = False
print(f" ✅ 加载成功,{len(source)} 字符,解析{'成功' if parse_ok else '失败'}")
return {"filename": filepath, "source": source, "tree": tree, "parse_ok": parse_ok}
def get_stats(self, analysis: dict) -> dict:
"""获取代码统计信息"""
source = analysis["source"]
tree = analysis["tree"]
lines = source.split("\n")
# 基础统计
total_lines = len(lines)
blank_lines = sum(1 for line in lines if not line.strip())
comment_lines = sum(1 for line in lines if line.strip().startswith("#"))
code_lines = total_lines - blank_lines - comment_lines
stats = {
"文件名": analysis["filename"],
"总行数": total_lines,
"代码行": code_lines,
"注释行": comment_lines,
"空行": blank_lines,
"文件大小": f"{len(source) / 1024:.1f} KB",
}
# AST 级别的统计(只有语法正确才能做)
if tree and analysis["parse_ok"]:
classes = [node for node in ast.walk(tree) if isinstance(node, ast.ClassDef)]
functions = [node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)]
stats["类数量"] = len(classes)
stats["函数数量"] = len(functions)
stats["类名"] = [c.name for c in classes]
stats["函数名"] = [f.name for f in functions]
# 检查哪些函数缺少文档字符串
missing_docs = [
f.name for f in functions
if not ast.get_docstring(f)
]
stats["缺少文档字符串"] = missing_docs if missing_docs else ["无"]
return stats
def preview(self, stats: dict):
"""可视化展示统计结果"""
print(f"\n📊 代码分析报告")
print(f"{'=' * 40}")
# 第一组:基础统计
basics = ["文件名", "总行数", "代码行", "注释行", "空行", "文件大小"]
for key in basics:
if key in stats:
print(f" {key}: {stats[key]}")
# 第二组:结构统计
print(f" ---")
structure = ["类数量", "函数数量"]
for key in structure:
if key in stats:
print(f" {key}: {stats[key]}")
# 详细信息
if "类名" in stats:
print(f" 类: {', '.join(stats['类名'])}")
if "函数名" in stats:
print(f" 函数: {', '.join(stats['函数名'])}")
if "缺少文档字符串" in stats:
missing = stats["缺少文档字符串"]
if missing != ["无"]:
print(f" ⚠️ 缺少文档字符串: {', '.join(missing)}")
# ===== 运行 =====
pipeline = CodePipeline()
analysis = pipeline.load_file(SAMPLE)
if analysis["parse_ok"]:
stats = pipeline.get_stats(analysis)
pipeline.preview(stats)
else:
print("❌ 文件存在语法错误,跳过 AST 分析")运行
bash
python day1.py你应该看到:
📂 加载文件: sample_code.py
✅ 加载成功,419 字符,解析成功
📊 代码分析报告
========================================
文件名: sample_code.py
总行数: 25
代码行: 16
注释行: 2
空行: 7
文件大小: 0.4 KB
---
类数量: 1
函数数量: 5
类: Calculator
函数: add, subtract, multiply, divide, greet, process_data
⚠️ 缺少文档字符串: add, subtract, multiply, process_dataAgent 已经指出了哪些函数缺少文档字符串——虽然它还不会写内容,但它知道"这里应该有但还没有"。
你学到了什么
ast 模块让 Agent "看懂"代码结构。 ast.parse() 把 Python 代码解析成抽象语法树——有了这棵树,你可以"问"代码任何结构性问题:有几个类?每个类有几个方法?哪个函数没有注释?参数类型是什么?这些都是纯文本分析做不到的。
跟项目二 Day 1 的区别: 项目二读的是自然语言文档,结构模糊;项目三读的是 Python 代码,结构精确——AST 解析器直接告诉你"第 5 行是函数定义"。这种结构性让你可以做一些文档做不到的深度分析。
今天做的是"代码读得懂",不是"代码写得好"。 统计只是开始——知道哪些函数缺文档、有几个类——明天让 Agent 做真正的审查:代码质量、潜在 Bug、命名规范。
明天的预告
今天 Agent 知道代码"长什么样"。明天用 LCEL 管道搭一条审查链:代码进去 → Prompt 告诉 Agent 审查标准 → MockLLM 生成报告 → Pydantic 结构化输出。从"统计"到"评价"。
Day 1 完成。Agent 能读 Python 代码、解析 AST、统计结构、发现缺注释的函数。地基对了。

