资讯动态

基于Agent与AST解析的Python仓库架构图自动生成方案

发布时间:2026/9/8 7:32:44 来源:尧图企业网站定制
接手一个没有架构文档、没有设计说明的代码仓库最痛苦的时刻不是你读不懂某一行代码而是你根本不知道该从哪里开始读。几十个模块、上千个文件之间的调用关系纠缠在一起画架构图这件“本该帮助理解的事情”本身就成了一个巨大的工程。手动打开一个个文件去梳理调用链再手工拖出模块依赖关系动辄一天时间画出来的图还不一定准确。这就是 Agent 类工具最近在架构分析场景里越来越受关注的原因。它把“扫描代码、解析依赖、理解模块职责、生成架构图”这条链路自动化了让开发者从低效的体力活中解脱出来。这篇文章会讲清楚背后的实现原理并给出一套可落地的代码示例教你如何用“代码扫描 AST 解析 Agent 语义理解 可视化渲染”的流水线自动把任意 Python 代码仓库变成一张带模块职责说明的架构图。先说结论这套方案真正降低的是理解存量系统的成本。它不一定适合所有项目但当你面对一个刚接手的老系统、要做技术栈梳理或准备架构评审时它比纯手画快一个数量级。1. 这篇文章真正要解决的问题很多团队其实有架构文档但文档普遍存在三个问题写完就过期、粒度对不上、没人敢只依靠它去做改造。手动画图能最贴近现状但代价是时间成本极高尤其当仓库里有几百个文件时靠人肉梳理调用关系几乎不可能。我们要解决的核心问题是把“代码仓库现状”自动转化为“可阅读、可分享、可用于评审的架构图”。这里的关键词是“现状”——图不是凭记忆画的而是从源码里实时生成的。具体来说需要回答三个问题仓库里有哪些核心模块它们各自负责什么业务模块之间依赖谁、谁在上层、谁在底层如何把这些信息以一张清晰的架构图呈现出来传统的代码分析工具比如pydeps、dependency-cruiser能生成依赖图但它们停留在“ import 关系”层面输出结果往往是几百个节点的大网缺少模块语义。而 Agent 的增量价值在于它能理解类名、包名、方法名里隐含的业务含义自动做“同类归纳”和“层级判断”生成的是人能看懂的架构视图而不是机械的调用关系矩。所以这篇文章适合三类读者刚接手存量系统的开发者、需要做技术梳理和后端文档建设的工程师以及准备做架构评审或技术分享的人。2. 核心概念代码解析、Agent 与架构图生成在进入实操之前先厘清几个容易混淆的概念。2.1 架构图不等于依赖图依赖图Dependency Graph描述的是“哪些文件 import 了哪些文件”信息量大但噪音也大。架构图Architecture Diagram则是在依赖图之上做了抽象把相近职责的文件聚合成模块再画出模块间的依赖方向。架构图的价值在于“抽象层”它省略了细节但保留了主干。所以生成架构图难点不在画图而在“如何从底层依赖中归纳出高层模块”。2.2 Agent 在这里扮演什么角色通常提到的 Agent是指能自主完成多步骤任务的 AI 系统。在本文的场景里Agent 不再是简单地回答一个问题而是承担“架构理解”这个认知任务给它一批代码扫描结果让它输出一份结构化的 JSON 架构描述。Agent 与传统静态分析工具的本质区别在于静态分析只能告诉你“A 引用了 B”Agent 能告诉你“A 是订单模块的服务层它通过 DAL 模块访问数据库”。前者是语法事实后者是语义判断。举例来说看到OrderService、OrderRepository、OrderController这三个类静态工具只看到三个节点Agent 能识别出它们同属于“订单模块”并区分出 controller、service、repository 三个层次。2.3 可视化渲染只负责最后一公里渲染层解决的是信息呈现问题。看懂架构图的方式有很多常见的有SVG、HTML、PlantUML 等。本文的示例采用“生成 Graphviz DOT 描述文件再渲染为 SVG”的方式因为 DOT 是文本格式便于查看、修改和版本管理同时 Graphviz 的布局引擎稳定可靠适合工程化输出。2.4 适用场景与边界这个方案最适合的是结构清晰、命名规范的中小型代码仓库尤其是 Python 项目。如果仓库本身是微服务架构通过在入口层做服务级扫描效果会更好。但要注意它不擅长处理历史包袱极重、命名混乱、模块边界不清的巨型仓库——Agent 会努力给出推断但推断结果的可靠性会下降这时候必须靠人工审核兜底。3. 整体方案设计整套系统按流水线设计分为四个阶段。扫描阶段遍历代码仓库收集源代码文件过滤掉生成代码、第三方依赖和构建产物。解析阶段使用 AST抽象语法树提取类、方法、import 关系生成机器可读的仓库摘要。理解阶段Agent 接收仓库摘要进行模块聚合和依赖方向判断输出 JSON 格式的架构模型。渲染阶段把架构模型转成 DOT 图描述再渲染成 SVG 图片。流水线的好处是每一层都能独立检验。扫描阶段可以验证文件是否齐全解析阶段可以核对类与依赖是否准确理解阶段可以人工检查模块划分是否合理渲染阶段则可以调整图形的展示方式而不影响前面的分析。设计时有一个重要取舍不要让 Agent 直接读全部源码而是先让 AST 解析“压缩”信息只把类名、方法名、import 关系和文件路径交给 Agent。这样既节省 token也降低无关代码的干扰。4. 环境准备与前置条件本文的示例代码全部使用 Python 实现建议使用 Python 3.10 及以上版本。你还需要安装 Graphviz 渲染引擎以及一个可用的 LLM APIOpenAI 兼容接口即可也可以使用本地部署模型。安装基础依赖pip install requests安装 Graphviz 引擎macOS 示例brew install graphvizUbuntu / Debian 系统使用sudo apt-get install graphvizWindows 用户需要到 Graphviz 官网下载安装包并确保dot命令已经加入系统 PATH。安装完成后可以通过下面的命令验证dot -V如果输出了版本号说明渲染引擎已经就绪。LLM 部分需要一个环境变量配置。为了兼容不同的服务商示例代码基于 OpenAI Chat Completions 接口风格编写你可以把LLM_BASE_URL指向任意兼容服务例如 DeepSeek、通义千问、本地 VLLM 等export LLM_API_KEY你的密钥 export LLM_BASE_URLhttps://api.openai.com/v1 export LLM_MODELgpt-4o-mini模型版本以你实际使用的服务为准代码里通过环境变量读取不会写死。5. 代码实现仓库扫描与 AST 解析先建立项目结构arch-agent/ ├── main.py ├── config.py └── analyzer/ ├── __init__.py ├── scanner.py ├── ast_parser.py ├── agent_analyzer.py └── renderer.py5.1 仓库扫描器analyzer/scanner.py负责遍历目录收集所有符合条件的源码文件# 文件路径analyzer/scanner.py import os from pathlib import Path # 默认需要忽略的目录 IGNORE_DIRS { .git, .idea, .vscode, __pycache__, node_modules, venv, venv3, .venv, dist, build, target, htmlcov } # 默认解析的源码后缀 SOURCE_SUFFIX {.py} def scan_code_files(project_root: str) - list: project_root Path(project_root).resolve() matched [] for root, dirs, filenames in os.walk(project_root): # 原地过滤需要跳过的目录避免继续向下遍历 dirs[:] [d for d in dirs if d not in IGNORE_DIRS] for filename in filenames: suffix Path(filename).suffix if suffix in SOURCE_SUFFIX: abs_path Path(root) / filename rel_path abs_path.relative_to(project_root) matched.append(str(rel_path)) return sorted(matched)这个模块的逻辑很简单但有两个细节值得注意第一dirs[:]这种原地修改写法可以直接影响os.walk的后续遍历达到剪枝效果第二把相对路径作为输出而不是绝对路径是为了后面给 Agent 的信息更简洁也方便统计模块归属。5.2 AST 解析器analyzer/ast_parser.py使用 Python 标准库ast解析源码提取类信息、方法列表和 import 依赖# 文件路径analyzer/ast_parser.py import ast from dataclasses import dataclass, field dataclass class ClassInfo: name: str file: str lineno: int methods: list field(default_factorylist) dataclass class ImportInfo: module: str names: list file: str lineno: int def parse_file(file_path: str) - dict: with open(file_path, r, encodingutf-8) as f: source f.read() tree ast.parse(source, filenamefile_path) classes [] imports [] for node in ast.walk(tree): if isinstance(node, ast.ClassDef): methods [ item.name for item in node.body if isinstance(item, ast.FunctionDef) ] classes.append(ClassInfo( namenode.name, filefile_path, linenonode.lineno, methodsmethods )) elif isinstance(node, ast.Import): imports.append(ImportInfo( module, names[alias.name for alias in node.names], filefile_path, linenonode.lineno )) elif isinstance(node, ast.ImportFrom): imports.append(ImportInfo( modulenode.module or , names[alias.name for alias in node.names], filefile_path, linenonode.lineno )) return { classes: classes, imports: imports }这里对 import 的提取做了简化处理from x import y和import x都会被记录但不会在语法层面生成完整的依赖关系图。原因是架构图关心的是“模块级依赖”而不是“文件级依赖”。文件之间的精确依赖追踪更适合在后续的模块聚合阶段处理。5.3 汇总仓库摘要在main.py中把扫描结果和 AST 解析结果汇总为一个结构化的“仓库摘要”并控制传给 Agent 的数据量# 文件路径main.py import json from pathlib import Path from analyzer.scanner import scan_code_files from analyzer.ast_parser import parse_file def analyze_files(rel_files: list, project_root: str) - dict: class_summary [] import_summary [] for rel in rel_files: abs_path Path(project_root) / rel try: result parse_file(str(abs_path)) except SyntaxError: # 某些文件可能是生成代码或加密文件跳过 continue for cls in result[classes]: class_summary.append({ class_name: cls.name, file: rel, line: cls.lineno, methods: cls.methods[:10] }) for imp in result[imports]: import_summary.append({ module: imp.module, names: imp.names[:5], file: rel, line: imp.lineno }) return { file_count: len(rel_files), classes: class_summary[:300], imports: import_summary[:500] }对类和方法做截断是为了避免超大仓库把上下文窗口打满。如果项目规模很大更应该分批处理而不是一次性全部塞给 Agent。6. 代码实现Agent 理解代码仓库结构6.1 统一的 LLM 接口analyzer/agent_analyzer.py负责调用 LLM并强制返回 JSON。为了兼容不同服务商这里使用 HTTP 请求方式实现# 文件路径analyzer/agent_analyzer.py import json import os import requests def llm_json(user_prompt: str, system_prompt: str 你是资深架构师) - dict: 调用兼容 OpenAI 接口的 LLM 服务并强制要求返回 JSON。 如果服务商不支持 response_format可移除该参数。 api_key os.getenv(LLM_API_KEY, ) base_url os.getenv(LLM_BASE_URL, https://api.openai.com/v1) model os.getenv(LLM_MODEL, gpt-4o-mini) resp requests.post( f{base_url}/chat/completions, headers{Authorization: fBearer {api_key}}, json{ model: model, messages: [ {role: system, content: system_prompt}, {role: user, content: user_prompt} ], response_format: {type: json_object}, temperature: 0.2 }, timeout120 ) resp.raise_for_status() content resp.json()[choices][0][message][content] return json.loads(content)这里设置较低的temperature是为了让输出尽量稳定减少架构理解上的随机性。如果你的服务商不支持response_format删除该参数即可但一定要在 prompt 里强烈要求只输出 JSON。6.2 架构理解 PromptAgent 的“认知能力”其实完全取决于 prompt 的设计。架构理解任务与常规问答不同它要求模型做归纳和抽象因此 prompt 必须包含四件事背景定义、输入描述、输出格式、质量要求。ARCHITECTURE_SYSTEM_PROMPT 你是一名资深软件架构师。你的任务是根据代码仓库分析数据 识别核心业务模块和技术组件并输出一个 JSON 格式的架构描述。 你需要遵守以下规则 1. 将职责相近的类归并为一个模块例如 UserController、UserService、UserRepository 都应属于 user 模块。 2. 使用不超过 15 个字的短语描述每个模块的职责。 3. 抽取模块之间的主要依赖关系忽略细枝末节的引用。 4. 如果某个模块承担基础支撑职责如数据库访问、消息队列、公共工具请明确标注。 5. 只输出 JSON不要输出任何解释性文字。 def analyze_with_agent(project_summary: dict) - dict: user_prompt json.dumps(project_summary, ensure_asciiFalse, indent2) return llm_json(user_prompt, system_promptARCHITECTURE_SYSTEM_PROMPT)Prompt 里的第一条规则决定了模块划分质量。如果仓库本身有清晰的分层结构Agent 通常能准确识别出api、service、dal、common这样的模块如果仓库命名混乱输出质量会明显下降这属于模型能力边界后面会专门讲怎么规避。6.3 期望的 Agent 输出格式为了让渲染层能直接消费Agent 必须输出固定结构。实际使用中建议在 prompt 中附加一个 JSON Schema 示例这里给出简化版{ modules: [ { name: order, desc: 订单核心业务, classes: [OrderService, OrderController] }, { name: dal, desc: 数据访问层, classes: [OrderRepository, UserRepository] } ], dependencies: [ { from: order, to: dal, desc: 订单模块访问数据库 } ] }这个输出相比原始的“类清单 import 列表”已经完成了从语法事实到语义架构的转换。7. 代码实现可视化架构图渲染analyzer/renderer.py负责把 Agent 输出的架构模型转换成 DOT 描述再用 Graphviz 渲染成 SVG# 文件路径analyzer/renderer.py def build_dot(arch_model: dict) - str: lines [ digraph architecture {, rankdirTB;, node [shapebox, stylerounded,filled, fillcolor#f0f4ff];, edge [color#555555, arrowheadnormal]; ] for module in arch_model.get(modules, []): label module.get(name, unknown) desc module.get(desc, ) safe_label label.replace(, ) safe_desc desc.replace(, ) lines.append(f {safe_label} [label{safe_label}\\n{safe_desc}];) for dep in arch_model.get(dependencies, []): src dep.get(from, ) dst dep.get(to, ) lines.append(f {src} - {dst};) lines.append(}) return \n.join(lines) def render_svg(dot_text: str, output_path: str) - None: import subprocess dot_file output_path.replace(.svg, .dot) with open(dot_file, w, encodingutf-8) as f: f.write(dot_text) subprocess.run( [dot, -Tsvg, dot_file, -o, output_path], checkTrue )这里有一个常见问题模块名或描述里可能包含双引号直接拼接字符串会破坏 DOT 语法所以代码里做了简单的转义处理。如果模块名中经常出现特殊字符建议统一使用 JSON 转义后再拼接。8. 完整运行流程与验证把所有模块组装起来。main.py的完整代码如下# 文件路径main.py import json import sys from pathlib import Path from analyzer.scanner import scan_code_files from analyzer.ast_parser import parse_file from analyzer.agent_analyzer import analyze_with_agent from analyzer.renderer import build_dot, render_svg def analyze_files(rel_files: list, project_root: str) - dict: class_summary [] import_summary [] for rel in rel_files: abs_path Path(project_root) / rel try: result parse_file(str(abs_path)) except SyntaxError: continue for cls in result[classes]: class_summary.append({ class_name: cls.name, file: rel, line: cls.lineno, methods: cls.methods[:10] }) for imp in result[imports]: import_summary.append({ module: imp.module, names: imp.names[:5], file: rel, line: imp.lineno }) return { file_count: len(rel_files), classes: class_summary[:300], imports: import_summary[:500] } def main(project_root: str, output_dir: str output) - None: files scan_code_files(project_root) print(f扫描到 {len(files)} 个源码文件) if not files: print(未扫描到任何源码文件请检查路径和后缀配置) sys.exit(1) summary analyze_files(files, project_root) print(f解析到 {len(summary[classes])} 个类 f{len(summary[imports])} 条 import 记录) print(Agent 正在分析架构...) arch_model analyze_with_agent(summary) print(架构分析完成模块数量, len(arch_model.get(modules, []))) Path(output_dir).mkdir(exist_okTrue) dot_text build_dot(arch_model) with open(f{output_dir}/architecture.json, w, encodingutf-8) as f: json.dump(arch_model, f, ensure_asciiFalse, indent2) svg_path f{output_dir}/architecture.svg render_svg(dot_text, svg_path) print(f架构图已生成{svg_path}) if __name__ __main__: if len(sys.argv) 2: print(用法python main.py /path/to/project) sys.exit(1) main(sys.argv[1])运行命令python main.py /path/to/your/python/project预期的执行过程是扫描到 128 个源码文件 解析到 156 个类382 条 import 记录 Agent 正在分析架构... 架构分析完成模块数量 8 架构图已生成output/architecture.svg在output目录下会生成两个文件architecture.json是 Agent 输出的结构化架构模型architecture.svg是可视化的架构图。先看 JSON 里的模块划分是否符合预期再看 SVG 的模块之间是否有明显反向依赖。如果渲染出来的图有几百个节点说明 Agent 没有做有效的聚合这是最常见的问题下一章会讲调整方法。如果运行失败优先检查三点LLM 服务是否能连通、API Key 是否有效、Graphviz 的dot命令是否在 PATH 中。9. 常见问题与排查方法问题现象可能原因排查方式解决方案扫描不到任何文件项目路径不对或源码后缀未配置打印project_root实际值确认目录内容检查路径扩展SOURCE_SUFFIXAST 解析大量报错仓库包含非 Python 语法文件或文件编码问题查看异常堆栈中的文件路径加入异常忽略逻辑或配置编码为utf-8Agent 输出的架构图节点过多模块聚合粒度太细查看architecture.json中的modules数量在 prompt 中强调“合并同类模块”并给出模块示例模块间依赖方向明显错误依赖判断受 import 顺序影响检查 JSON 中依赖的from与to是否与实际调用一致增加类方法调用关系特征作为输入或人工修正LLM 返回非 JSON 内容服务不支持response_format或上下文太长导致截断查看原始返回内容移除response_format并降低传参数据量dot命令找不到Graphviz 未安装或 PATH 未配置执行dot -V安装 Graphviz 并重启终端大仓库超时一次传入的类与 import 数量过多统计摘要数据量按目录分批分析最后合并模块视图其中“节点过多”是最需要重视的问题。架构图的价值在抽象如果 Agent 没有把 156 个类聚合成 8 个模块而是输出 80 个模块那这张图仍然是不可读的。遇到这种情况可以在 prompt 里增加“模块数量上限为 10 到 15 个”的约束并在系统提示词中加入两个示例模型会更容易模仿示例的粒度。另一个容易被忽略的问题是Agent 的架构分析结果并不能替代人工确认。尤其是涉及数据库迁移、核心交易链路分析时建议把 Agent 生成的架构图作为“初稿”或“讨论基础”而不是直接作为技术方案的唯一依据。10. 最佳实践与工程化建议把整套方案放到真实工程环境中还需要补上下面几块内容。10.1 用配置管理扫描与聚合策略扫描目录、忽略列表、模块聚合数量、模型名称等参数都应该外置到配置文件中而不是写在代码里。这样当仓库体积变化或团队使用不同模型时不需要改动主逻辑。比较直接的方案是引入一个config.json让main.py启动时读取。10.2 保存中间产物每一轮分析都保留repository_summary.json和architecture.json。这有两个好处一是调试 prompt 时可以重新使用同一份仓库摘要避免每次重新调用 AST 解析和 LLM二是当架构图生成异常时可以快速定位问题出现在理解阶段还是渲染阶段。这个做法也能降低 LLM 调用的重复花费。10.3 对敏感代码使用本地模型代码仓库本身是团队的核心资产。如果项目涉及未公开的商业逻辑不建议直接调用外部云端模型。可以选择本地部署的模型服务例如通过 VLLM 部署经过授权的小参数模型把LLM_BASE_URL指向本地地址从链路层面保护代码内容。要注意即便使用本地模型也应该遵守公司对代码外发和数据合规的既有规定。10.4 将架构图纳入文档流程生成了图形不是终点架构图要进入团队可维护的文档流程才有价值。建议在 CI 中增加一个手动触发的流水线任务当主分支代码更新时重新生成架构图并提交到文档仓库。这样架构图会始终跟随代码现状避免“文档又一次过期”的尴尬。10.5 为 Agent 补充调用关系特征AST 只提供了 import 级别的依赖Agent 在做模块聚合时错判率会比较高。如果希望依赖方向更准确可以在 AST 解析阶段增加“函数调用关系”提取例如解析Call节点记录“哪个类的实例调用了哪个方法”。这类特征越多Agent 对依赖方向的判断就越可靠。10.6 人工抽查和反馈闭环任何基于 LLM 的分析工具都应该有一个人工反馈环节。建议在首次使用某个仓库时随机抽取 5 个模块检查类归属是否正确再检查 3 条依赖方向是否与代码实际行为一致。如果发现问题把修正结果追加到 prompt 示例中让 Agent 在下一轮分析时学到团队的划分标准。这种“少样本校正”是成本最低的方式也比频繁调整系统提示词更有效。11. 总结与后续学习方向这套方案把“自动解析代码仓库输出可视化架构图”拆成了可复用的四个阶段扫描、解析、理解、渲染。扫描和解析依赖成熟稳定的工具链理解依赖 Agent 的归纳能力渲染依赖 Graphviz 这类开源渲染引擎。四个阶段彼此独立每一层都有对应的验证手段这是它能在真实工程中被使用的基础。如果你打算在项目里实践我的建议很简单先找一个你已经很熟悉的中小型 Python 仓库跑一遍把 Agent 输出的模块划分和依赖方向与自己脑中的架构图对比。你会发现它有两类价值——一是帮你把已知的架构整理成可分享的图形二是指出你原本没有注意到的隐藏依赖。后者的价值往往比前者更大。接下来可以继续深入的方向有几个扩展解析器支持 Java、TypeScript 等更多语言可以在 AST 层引入 tree-sitter让 Agent 输出更丰富的架构视图比如分层图、调用时序图可以在架构模型阶段增加视图维度把整套流程封装成命令行工具并接入 CI则是把它推向团队级工具的必经之路。架构分析是个很难“完美”的领域但有了 Agent 之后这个方向的自动化程度已经比大多数人想象的高很多了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价