Skip to content

Day 1:搭骨架——让 Agent "看懂"你的代码

从两个项目到第三个,套路已经很熟了

项目一做了客服 Agent——输入一句话,输出一句话。项目二做了文档分析 Agent——输入一份文档,输出结构化数据。 两个项目到第三个,套路已经很熟了项目一做了客服 Agent——输入一句话,输出一句话。项目二做了文档分析 Agent——输入一份文档,输出结构化数据。

项目三做什么?代码助手。

场景很简单:你写了一堆代码,想看有没有明显的 bug、变量名是否规范、函数有没有写注释。把代码扔给 Agent,它自动审查,生成报告。

但代码和文档不一样。文档是自然语言,代码是有结构的文本——缩进有意义,关键字有含义,变量之间有关联。Agent 不能"当作文档读",它需要理解代码的语法结构。

今天做什么

写一个最简单的代码加载器。读取 Python 文件,提取关键信息——文件名、行数、类数量、函数数量、注释行数。不做 AI 分析,只做代码统计

这跟项目一 Day 1 的"回声筒"是一个道理——先让管线跑通,再往上加智能。

你需要什么

  • Python 3.10+
  • 一个文本编辑器
  • 不需要 API Key、不需要联网、不需要花钱
  • Day 1 只用 Python 内置模块(astospathlib),无需安装任何第三方库

📦 本项目全程使用的框架版本:

框架版本首次用到
langchain≥0.3Day 2
pydantic≥2.0Day 2
mcp0.9.xDay 5
streamlit≥1.28Day 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_data

Agent 已经指出了哪些函数缺少文档字符串——虽然它还不会写内容,但它知道"这里应该有但还没有"。

你学到了什么

ast 模块让 Agent "看懂"代码结构。 ast.parse() 把 Python 代码解析成抽象语法树——有了这棵树,你可以"问"代码任何结构性问题:有几个类?每个类有几个方法?哪个函数没有注释?参数类型是什么?这些都是纯文本分析做不到的。

跟项目二 Day 1 的区别: 项目二读的是自然语言文档,结构模糊;项目三读的是 Python 代码,结构精确——AST 解析器直接告诉你"第 5 行是函数定义"。这种结构性让你可以做一些文档做不到的深度分析。

今天做的是"代码读得懂",不是"代码写得好"。 统计只是开始——知道哪些函数缺文档、有几个类——明天让 Agent 做真正的审查:代码质量、潜在 Bug、命名规范。

明天的预告

今天 Agent 知道代码"长什么样"。明天用 LCEL 管道搭一条审查链:代码进去 → Prompt 告诉 Agent 审查标准 → MockLLM 生成报告 → Pydantic 结构化输出。从"统计"到"评价"。


Day 1 完成。Agent 能读 Python 代码、解析 AST、统计结构、发现缺注释的函数。地基对了。