|
|
||
|---|---|---|
| issues | ||
| src/llm_codegen | ||
| tests | ||
| .gitignore | ||
| .python-version | ||
| README.md | ||
| create-test.issue | ||
| design.json | ||
| llmcodegen.py | ||
| pyproject.toml | ||
README.md
LLM 代码生成工具(自举版 · 增强版)
本项目是一个基于大语言模型的智能代码生成与维护工具。它不仅能够根据项目 README.md 描述自动生成完整的 Python 包代码,还支持在现有项目上增量添加功能和自动修复 Bug。工具采用 uv 管理依赖,包含单元测试、并行检查、断点续写等特性,并通过一个面向 LLM 的中间设计层来提升生成质量和可维护性。
特别说明
我已经实现了一个简易版本,请在此基础上修改、拓展、开发:
#!/home/songsenand/env/.venv/bin/python
"""
基于LLM的自动化代码生成工具
根据README.md文件,自动生成项目文件结构并填充代码,执行必要命令。
"""
import json
import os
import subprocess
import sys
from typing import List, Dict, Optional, Any, Tuple
from pathlib import Path
import typer
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskID
from loguru import logger
from openai import OpenAI
# ==================== 配置 ====================
DANGEROUS_COMMANDS = ["rm", "sudo", "chmod", "dd", "mkfs", "> /dev/sda", "format"]
ALLOWED_COMMANDS = [] # 可设置白名单,为空则只检查黑名单
app = typer.Typer(help="基于LLM的自动化代码生成工具")
console = Console()
# ==================== 工具函数 ====================
def is_dangerous_command(cmd: str) -> Tuple[bool, str]:
"""
判断命令是否危险
返回 (是否危险, 原因)
"""
cmd_lower = cmd.lower()
for danger in DANGEROUS_COMMANDS:
if danger in cmd_lower:
return True, f"包含危险关键词 '{danger}'"
return False, ""
# ==================== 核心类 ====================
class CodeGenerator:
"""代码生成器,封装所有逻辑"""
def __init__(
self,
api_key: Optional[str] = None,
base_url: str = "https://api.deepseek.com",
model: str = "deepseek-reasoner",
output_dir: str = "./generated",
log_file: Optional[str] = None,
):
"""
初始化生成器
Args:
api_key: OpenAI API密钥,默认从环境变量DEEPSEEK_APIKEY读取
base_url: API基础URL
model: 使用的模型
output_dir: 输出根目录
log_file: 日志文件路径,默认自动生成
"""
self.api_key = api_key or os.getenv("DEEPSEEK_APIKEY")
if not self.api_key:
raise ValueError("必须提供API密钥,或设置环境变量DEEPSEEK_APIKEY")
self.client = OpenAI(api_key=self.api_key, base_url=base_url)
self.model = model
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
# 配置日志
if log_file is None:
log_file = self.output_dir / "generator.log"
logger.remove() # 移除默认handler
logger.add(sys.stderr, level="WARNING") # 控制台输出INFO及以上
logger.add(log_file, rotation="10 MB", level="DEBUG") # 文件记录DEBUG
logger.info(f"日志已初始化,保存至: {log_file}")
self.readme_content = None
self.progress: Optional[Progress] = None
self.tasks: Dict[str, TaskID] = {} # 任务ID映射
def _call_llm(
self,
system_prompt: str,
user_prompt: str,
temperature: float = 0.2,
expect_json: bool = True,
) -> Dict[str, Any]:
"""
调用LLM并返回解析后的JSON
"""
logger.debug(f"调用LLM,模型: {self.model}")
logger.debug(f"System: {system_prompt[:200]}...")
logger.debug(f"User: {user_prompt[:200]}...")
try:
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
temperature=temperature,
response_format={"type": "json_object"} if expect_json else None,
)
message = response.choices[0].message
content = message.content
# 记录思考过程(如果存在)
if hasattr(message, "reasoning_content") and message.reasoning_content:
logger.info(f"模型思考过程: {message.reasoning_content}")
logger.debug(f"LLM原始响应: {content[:500]}...")
if expect_json:
result = json.loads(content)
else:
result = {"content": content}
return result
except json.JSONDecodeError as e:
logger.error(f"JSON解析失败: {e}")
raise ValueError(f"LLM返回的不是有效JSON: {content[:200]}")
except Exception as e:
logger.error(f"LLM调用失败: {e}")
raise
def parse_readme(self, readme_path: Path) -> str:
"""
读取README文件内容
"""
logger.info(f"读取README文件: {readme_path}")
try:
with open(readme_path, "r", encoding="utf-8") as f:
content = f.read()
logger.debug(f"README内容长度: {len(content)} 字符")
return content
except Exception as e:
logger.error(f"读取README失败: {e}")
raise
def get_project_structure(self) -> Tuple[List[str], Dict[str, List[str]]]:
"""
根据README内容,让LLM生成文件列表和依赖关系
Returns:
(files, dependencies)
files: 按顺序需要生成的文件路径列表
dependencies: 字典 {file: [依赖文件路径]}
"""
system_prompt = (
"你是一个软件架构师。请根据README描述,分析需要生成哪些源代码文件,并确定它们的生成顺序,"
"同时给出每个文件生成时最少需要读取哪些已有文件作为上下文。"
"返回严格的JSON对象,包含两个字段:\n"
"- files: 数组,按生成顺序排列的文件路径(相对于项目根目录)\n"
"- dependencies: 对象,键为文件路径,值为该文件依赖的已有文件路径列表(可为空)\n"
"注意:依赖文件必须是已存在的参考文件,不要包含待生成的文件。"
)
user_prompt = f"README内容如下:\n\n{self.readme_content}"
result = self._call_llm(system_prompt, user_prompt)
files = result.get("files", [])
dependencies = result.get("dependencies", {})
if not files:
raise ValueError("LLM未返回任何文件列表")
logger.info(f"解析到 {len(files)} 个待生成文件")
logger.debug(f"文件列表: {files}")
logger.debug(f"依赖关系: {dependencies}")
return files, dependencies
def generate_file(
self,
file_path: str,
prompt_instruction: str,
dependency_files: List[str],
) -> Tuple[str, str, List[str]]:
"""
生成单个文件,返回 (代码, 描述, 命令列表)
"""
# 读取依赖文件内容
context_content = []
if self.readme_content:
context_content.append(f"### 项目 README ###\n{self.readme_content}\n")
for dep in dependency_files:
dep_path = Path(dep)
if not dep_path.exists():
# 尝试相对于当前目录或输出目录查找
alt_path = self.output_dir / dep
if alt_path.exists():
dep_path = alt_path
else:
raise FileNotFoundError(f"依赖文件不存在: {dep}")
with open(dep_path, "r", encoding="utf-8") as f:
content = f.read()
context_content.append(f"### 文件: {dep_path.name} (路径: {dep}) ###\n{content}\n")
full_context = "\n".join(context_content)
system_prompt = (
"你是一个专业的编程助手。根据用户指令和提供的上下文文件,生成完整的代码。"
"返回严格的JSON对象,包含三个字段:\n"
"- code: (string) 生成的完整代码\n"
"- description: (string) 简短的中文功能描述\n"
"- commands: (array of string) 生成此文件后需要执行的操作系统命令列表(如编译、安装依赖等),若无则返回空数组"
)
user_prompt = f"{prompt_instruction}\n\n参考文件上下文:\n{full_context}"
result = self._call_llm(system_prompt, user_prompt)
code = result.get("code", "")
description = result.get("description", "")
commands = result.get("commands", [])
if not isinstance(commands, list):
commands = []
return code, description, commands
def execute_command(self, cmd: str, cwd: Optional[Path] = None) -> None:
"""
执行单个命令,检查风险
"""
dangerous, reason = is_dangerous_command(cmd)
if dangerous:
logger.error(f"危险命令被阻止: {cmd},原因: {reason}")
raise RuntimeError(f"危险命令: {cmd} ({reason})")
logger.info(f"执行命令: {cmd}")
try:
result = subprocess.run(
cmd,
shell=True,
cwd=cwd or self.output_dir,
capture_output=True,
text=True,
timeout=300, # 5分钟超时
)
logger.debug(f"命令返回码: {result.returncode}")
if result.stdout:
logger.debug(f"stdout: {result.stdout[:500]}")
if result.stderr:
logger.warning(f"stderr: {result.stderr[:500]}")
if result.returncode != 0:
raise subprocess.CalledProcessError(result.returncode, cmd)
except subprocess.TimeoutExpired:
logger.error(f"命令执行超时: {cmd}")
raise
except Exception as e:
logger.error(f"命令执行失败: {e}")
raise
def run(self, readme_path: Path):
"""
主执行流程
"""
logger.info("=" * 50)
logger.info("开始代码生成流程")
logger.info(f"README: {readme_path}")
logger.info(f"输出目录: {self.output_dir}")
# 初始化阶段:用rich输出状态(不会被日志级别过滤)
console.print("[bold yellow]🔍 正在解析README...[/bold yellow]")
self.readme_content = self.parse_readme(readme_path)
console.print("[bold yellow]📋 正在分析项目结构...[/bold yellow]")
files, dependencies = self.get_project_structure()
console.print(f"[green]✅ 解析完成,共 {len(files)} 个文件待生成[/green]")
# 3. 创建进度条
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
console=console,
) as progress:
self.progress = progress
# 创建总任务
total_task = progress.add_task("[cyan]整体进度...", total=len(files))
# 依次生成每个文件
for idx, file in enumerate(files, 1):
logger.info(f"处理文件 [{idx}/{len(files)}]: {file}")
# 创建子任务(可选)
file_task = progress.add_task(f"生成 {file}", total=None)
try:
# 获取依赖文件
deps = dependencies.get(file, [])
# 构造生成指令
instruction = f"请根据README描述和依赖文件,生成文件 '{file}' 的完整代码。"
# 调用LLM生成代码
code, desc, commands = self.generate_file(file, instruction, deps)
logger.info(f"生成完成: {file} - {desc}")
# 写入文件
output_path = self.output_dir / file
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "w", encoding="utf-8") as f:
f.write(code)
logger.info(f"已写入: {output_path}")
# 执行命令
for cmd in commands:
logger.info(f"准备执行命令: {cmd}")
self.execute_command(cmd, cwd=self.output_dir)
except Exception as e:
logger.error(f"处理文件 {file} 失败: {e}")
# 可选:继续或终止
raise
finally:
progress.remove_task(file_task)
progress.update(total_task, advance=1)
logger.success("所有文件处理完成!")
# ==================== CLI入口 ====================
@app.command()
def main(
readme: Path = typer.Argument(..., exists=True, file_okay=True, dir_okay=False, help="README.md文件路径"),
output_dir: Optional[Path] = typer.Option(None, "--output", "-o", help="输出根目录,默认为readme所在目录"),
api_key: Optional[str] = typer.Option(None, "--api-key", envvar="DEEPSEEK_APIKEY", help="API密钥,也可通过环境变量DEEPSEEK_APIKEY设置"),
base_url: str = typer.Option("https://api.deepseek.com", "--base-url", help="API基础URL"),
model: str = typer.Option("deepseek-reasoner", "--model", "-m", help="使用的模型"),
log_file: Optional[str] = typer.Option(None, "--log", help="日志文件路径(默认输出目录下generator.log)"),
):
"""
根据README自动生成项目代码
"""
if output_dir is None:
output_dir = readme.parent
try:
generator = CodeGenerator(
api_key=api_key,
base_url=base_url,
model=model,
output_dir=output_dir,
log_file=log_file,
)
generator.run(readme)
except Exception as e:
logger.error(f"程序异常退出: {e}")
raise typer.Exit(code=1)
if __name__ == "__main__":
app()
✨ 核心特性
- 📦 自动生成:解析
README.md,分析需要生成的文件列表及依赖关系,按顺序生成每个文件的代码。 - 📋 中间设计层:生成一个
design.json文件,包含项目结构、文件关联、功能摘要等信息。后续所有代码生成均以该 JSON + README 作为上下文,确保 LLM 始终理解全局设计。 - 🧩 增量功能开发:通过编写需求工单(如
feature.issue),描述新增功能,工具自动分析现有代码并生成新增或修改的文件。 - 🐞 自动 Bug 修复:通过编写Bug 工单(如
bug.issue),描述问题现象,工具结合代码和错误信息生成修复补丁。 - 🔧 命令执行:生成文件后可自动执行建议命令(如安装依赖、运行构建),内置危险命令拦截(执行命令失败不会终止任务,仅记录错误)。
- ✅ 单元测试:使用
pytest编写测试用例,支持测试覆盖率统计。 - 🔍 并行检查:生成代码后并行运行多个检查工具(
pylint、mypy、black等),收集错误信息。 - 🔄 自修复:将检查错误、README、design.json 和相关代码提交给 LLM,自动生成修复补丁并应用。
- ⏯️ 断点续写:生成过程中断后可自动从上次中断处继续,状态保存在
.llm_generator_state.json。 - 🖥️ 命令行工具:提供
llm-codegen命令,支持多种操作模式。 - 📝 详细日志:所有操作、LLM 响应、错误均通过
loguru记录到文件。 - 🎨 美观输出:使用
rich显示进度条和彩色状态。
🚀 安装
依赖
- Python 3.9+
- 使用
uv管理包
# 安装依赖
uv add [dev]
配置 API 密钥
设置环境变量(推荐):
export DEEPSEEK_APIKEY="your-api-key"
或在命令行中通过 --api-key 传入。
📖 使用方法
工具支持三种操作模式,通过子命令区分:
llm-codegen init README.md # 从零初始化项目
llm-codegen enhance feature.issue # 根据需求工单增强项目
llm-codegen fix bug.issue # 根据Bug工单修复项目
1. 初始化项目 (init)
根据 README.md 生成完整的项目骨架和代码。
llm-codegen init path/to/README.md -o ./generated
流程:
- 读取
README.md,调用 LLM 生成中间设计文件design.json(位于输出目录)。 - 基于
design.json和README.md按顺序生成每个文件。 - 生成完成后执行可选命令、检查和自动修复。
2. 增强项目 (enhance)
当已有项目需要添加新功能时,编写一个需求工单(如 add-logging.issue),然后运行:
llm-codegen enhance add-logging.issue -o ./project
需求工单模板(feature.issue):
# 需求工单示例
name: 添加日志记录功能
description: 为所有核心函数增加日志输出,记录调用参数和执行时间。
affected_files:
- src/llm_codegen/core.py
- src/llm_codegen/utils.py
acceptance_criteria:
- 每个公共函数应记录开始和结束日志
- 日志级别为 INFO,包含函数名和参数
- 使用 loguru 记录
工具会自动:
- 读取现有项目的
design.json和代码。 - 分析需求,确定需要修改的文件。
- 生成代码变更(新增或修改文件)。
- 执行检查和修复。
3. 修复 Bug (fix)
发现 Bug 时,编写一个Bug 工单(如 crash-on-empty.issue),然后运行:
llm-codegen fix crash-on-empty.issue -o ./project
Bug 工单模板(bug.issue):
# Bug 工单示例
name: 当输入为空时程序崩溃
description: 调用 parse_readme 时若 README 为空文件,抛出未处理的 IndexError。
steps_to_reproduce:
- 创建空文件 empty.md
- 运行 llm-codegen init empty.md
expected_behavior: 应给出友好提示并退出。
actual_behavior: 抛出 IndexError 并打印堆栈。
affected_files:
- src/llm_codegen/core.py
工具会自动:
- 定位相关代码。
- 结合错误信息生成修复方案。
- 应用补丁,并重新运行测试验证。
🧠 中间设计层 (design.json)
design.json 是工具与 LLM 之间的“通用语言”,它记录了项目的完整设计蓝图,结构如下:
{
"project_name": "MyProject",
"version": "1.0.0",
"description": "项目简短描述",
"files": [
{
"path": "src/llm_codegen/core.py",
"summary": "核心生成逻辑,包含 CodeGenerator 类",
"dependencies": ["src/llm_codegen/utils.py"],
"functions": [
{
"name": "generate_file",
"summary": "生成单个文件,返回代码和命令",
"inputs": ["file_path", "prompt", "deps"],
"outputs": ["code", "commands"]
}
],
"classes": [...]
}
],
"commands": [
"pip install -e .",
"pytest tests/"
],
"check_tools": ["pytest", "pylint", "mypy"]
}
该文件由 LLM 在 init 阶段生成,并在后续所有操作中作为上下文提供给 LLM,确保每次生成都符合整体设计。
🔄 核心工作流
初始化流程
- 读取
README.md,调用 LLM 生成design.json。 - 解析
design.json,获得文件列表和依赖关系。 - 按顺序生成每个文件,生成时上下文包括:
README.mddesign.json- 已生成的依赖文件内容
- 执行文件关联的命令(如安装依赖)。
- (可选)运行检查工具,若有错误则触发自修复。
增强/修复流程
- 读取项目根目录下的
design.json和现有代码。 - 解析需求/缺陷工单,识别受影响文件。
- 调用 LLM 生成代码变更(可新增文件或修改现有文件),上下文包括:
README.mddesign.json- 所有受影响文件的当前内容
- 工单内容
- 应用变更,更新
design.json中的摘要(如果新增了函数/类)。 - 执行检查与修复。
📝 工单模板
需求工单 (feature.issue)
name: <功能名称>
description: <详细描述>
affected_files: # 可能影响到的文件(可选,留空则让 LLM 自动分析)
- path/to/file1.py
- path/to/file2.py
acceptance_criteria: # 验收条件(列表)
- 条件1
- 条件2
Bug 工单 (bug.issue)
name: <Bug 标题>
description: <详细描述>
steps_to_reproduce: # 复现步骤
- 步骤1
- 步骤2
expected_behavior: <期望行为>
actual_behavior: <实际行为>
affected_files: # 可能相关的文件(可选)
- path/to/file.py
⚙️ 配置
通过 pyproject.toml 的 [tool.llm-codegen] 部分自定义行为:
[tool.llm-codegen]
check_tools = ["pytest", "pylint", "mypy", "black"]
max_retries = 3
dangerous_commands = ["rm", "sudo", "chmod", "dd"]
🛠️ 开发指南
环境设置
# 安装 uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# 创建虚拟环境并激活
uv venv
source .venv/bin/activate
# 安装项目(可编辑模式)和开发依赖
uv pip install -e ".[dev]"
项目结构
生成的项目将包含以下文件和目录:
.
├── README.md # 项目说明(原始输入)
├── pyproject.toml # 项目元数据、依赖、脚本入口
├── src/
│ └── llm_codegen/ # 主代码包
│ ├── __init__.py
│ ├── cli.py # 命令行入口(typer)
│ ├── core.py # 核心生成逻辑(CodeGenerator 类)
│ ├── checker.py # 并行检查与修复模块
│ ├── utils.py # 工具函数(危险命令判断、文件操作)
│ └── models.py # 数据模型(Pydantic)
├── tests/ # 单元测试
│ ├── __init__.py
│ ├── test_cli.py
│ ├── test_core.py
│ └── test_checker.py
└── logs/ # 运行日志(自动创建)
运行测试
pytest tests/
编写工单示例
项目生成后,issues/ 目录下会包含示例工单文件,可参考编写。
📌 注意事项
- 中间设计文件
design.json是核心资产,请勿手动修改(除非你完全理解设计意图),否则可能导致后续生成偏差。 - 断点续写状态文件
.llm_generator_state.json自动管理,无需手动干预。 - 若
README.md或design.json发生重大变更导致结构不一致,工具会提示并建议重新初始化。
通过引入中间设计层和工单驱动机制,本工具不仅实现了从零生成,更成为项目的“AI 协作者”,能够持续参与功能迭代与缺陷修复,大幅提升开发效率。