site logo

Marico's space

Morph:AST 级重构,LLM 描述意图而非代码

编程技术 2026-06-02 14:50:14 6

最近折腾了一个 LLM(大型语言模型)驱动的代码重构工具 Morph,踩了几个坑,这篇把问题说清楚。

平时用 LLM 做代码重构,输出的是一个 diff,reviewer 只能一行行读、盲信模型。你根本没法知道模型是不是漏了某个引用、搞坏了 import,或者悄悄改了什么逻辑——除非一行行把代码看完。

Morph 换了个思路。它不要求 LLM 生成代码,而是让 LLM 用结构化的类型化操作计划来描述要改什么——比如 RenameSymbol、MoveFunction、ExtractModule 之类的。reviewer 几秒钟扫完十个结构化操作,就知道要改什么、为什么改、按什么顺序改。转换引擎会对真实代码库的依赖图验证计划,用 tree-sitter 做 AST(抽象语法树)操作原子化应用每个操作,运行测试套件确认正确性,然后把干净的变更提交 review。任何失败的转换都会自动回滚。

LLM 的活儿是声明意图,不是写代码。剩下的交给 Morph 的引擎。

为什么类型化计划比源码生成更好

当重构被表达为类型化计划时,每个操作在运行前都是可验证的。计划验证器检查文件存在性、符号存在性、依赖冲突和操作冲突——这些都是针对真实依赖图做的。转换器按依赖顺序应用操作。验证器每次应用后都跑 pytest——任何失败都会触发自动回滚。

源码生成没有任何这些保证。类型化计划有。

完整流程

自然语言目标输入 LLM Planner,输出经过验证的 TransformationPlan。计划验证器基于 NetworkX 依赖图检查文件存在性、符号存在性、依赖冲突和操作冲突。转换器用 tree-sitter AST 操作按依赖顺序应用操作,同时先备份文件。验证器跑 pytest——任何失败触发自动回滚。干净的变更通过 GitPython 交给暂存管理器,并在报告中总结。

支持的操作类型

每个操作都是一个类型化的 Pydantic 模型。LLM 填充字段——Morph 验证并执行。

依赖图是怎么工作的

在验证任何计划之前,Morph 会用 tree-sitter 解析整个代码库,构建一个 NetworkX 依赖图。这个图用来:

  • 检测哪些文件 import 了被移动或重命名的符号
  • 对操作排序,确保依赖项在被依赖项之前更新
  • 警告某个移动会级联影响下游文件
  • 防止模块提取引入循环依赖

这就是 Morph 敢在真实代码库上跑的原因——计划是在真实依赖结构上验证过的,动手之前就已经确保安全了。

回滚保证

每次非 dry-run 的应用调用都会在修改任何文件之前快照所有受影响的文件。如果 pytest 报告转换后失败,Morph 会自动从快照恢复。工作区始终保持在干净的已知良好状态。

实测效果

用 OpenRouter 在 anthropic/claude-haiku-4-5 上跑了真实 dry-run——LLM 解析了自然语言重命名目标,在 5 秒内生成了经过验证的 RenameSymbol 计划。完整输出和复现步骤在 RESULTS.md 里。

安装

pip install -e .

本地推理的话,装好 Ollama 然后拉取模型:

ollama pull gemma4:e4b

云端后端的话,配置对应的环境变量:
OPENROUTER_API_KEY - OpenRouter(推荐)
OPENAI_API_KEY - OpenAI
ANTHROPIC_API_KEY - Anthropic

使用方式

用自然语言描述你想做什么,Morph 会自己推断出操作:

morph refactor --goal "rename calculate_total to compute_total" ./src

预览计划但不修改任何文件:

morph refactor --goal "extract validation logic into validate_input()" ./src --dry-run

生成并保存计划,方便执行前检查:

morph plan --goal "add type annotations to all functions in utils.py" ./src --output plan.json

应用已保存的计划:

morph refactor --plan plan.json ./src

验证代码库通过自己的测试套件:

morph verify ./src

生成上次运行的 Markdown 报告:

morph report ./src --format markdown --output REFACTOR_REPORT.md

支持的模型

Morph 可以对接任何 provider。OpenRouter 是推荐的起点——一个 API key 可以路由到下方所有模型,不需要单独注册账号。

Planner 使用 temperature=0.1——低随机性让结构化输出的结果更稳定。未知模型字符串会自动通过 OpenRouter 路由,不需要加 --backend 标志。

CLI 命令参考

morph refactor --goal "..." PATH - 从目标生成计划并应用
morph refactor --plan FILE PATH - 应用之前保存的计划
morph refactor ... --dry-run - 显示计划但不修改文件
morph plan --goal "..." PATH - 仅生成并显示计划
morph verify PATH - 运行测试套件并报告通过/失败
morph report PATH - 生成上次运行的 Markdown/JSON 报告

常用标志:--model--backend--dry-run--no-rollback--output

开发

克隆项目并以可编辑模式安装(包含开发依赖):

git clone https://github.com/dakshjain-1616/morph
cd morph
pip install -e ".[dev]"

运行完整测试套件:

pytest tests/ -v

代码检查和类型检查:

ruff check morph/ && mypy morph/

我是怎么用 NEO 搭出来的

这个项目是用 NEO 搭的。(NEO 是一个完全自主的 AI 工程代理,能写代码、做 AI/ML 任务,包括模型评测、提示词优化和端到端 AI 流水线开发。)

需求是做一个重构 CLI,核心是让 LLM 用结构化类型化计划描述意图,而不是生成原始代码;需要 AST 级别的执行、依赖图验证、测试失败自动回滚、支持多 LLM 后端。NEO 完整实现了:LLM Planner 生成类型化 TransformationPlan 输出(temperature=0.1)、7 个类型化 Pydantic 操作模型、Plan Validator 基于 NetworkX 图检查文件存在性/符号存在性/依赖冲突、Transformer 用 tree-sitter AST 操作按依赖顺序应用(带文件备份)、Verifier 跑 pytest 失败自动快照回滚、Staging Manager(GitPython)、报告生成器,以及包含全部 6 条命令和关键标志的完整 CLI。

你怎么用 NEO 来用和扩展这个项目

用它安全地重构生产代码库。
不要让 LLM 重写文件,用自然语言描述重构目标。Morph 会基于真实依赖图验证计划,原子化应用,测试失败自动回滚。dry-run 模式让你在动手之前精确看到会发生什么。

用保存的计划工作流做团队 review。
morph plan --goal "..." --output plan.json 生成结构化计划但不应用。把 JSON 发给团队,执行前 review。reviewer 看到的是十个类型化操作,不是一堆原始 diff——review 更快、更好判断。

在 CI/CD 流水线里用它做重构步骤。
morph verify PATH 跑测试套件并返回通过/失败的退出码,可以作为 CI 步骤组合使用。配合 morph refactor--dry-run,可以搭出一个流水线:提议、review、应用重构,每个阶段都有自动化测试验证。

扩展它,加入更多操作类型。
每个操作都是 operations 层里的类型化 Pydantic 模型。新操作遵循相同模式:定义 Pydantic 模型、实现转换器逻辑、注册。LLM Planner、Plan Validator 和 CLI 会自动识别它。

结语

Morph 把重构从"代码生成"变成了"意图声明"。LLM 用结构化、可验证的计划描述要改什么。引擎做机械性的工作。测试确认正确性。结果是:运行前可审计、运行后可验证、如果搞砸了自动可逆的重构。

代码在这里:https://github.com/dakshjain-1616/Morph