我用4小时做了一个AI自动写代码工具,全过程分享
我写代码快两年了,一直用免费AI帮忙。用过GPT,也用过Gemini,但这些免费模型都有个问题:给的内容一多,它们就开始胡言乱语。Gemini虽然号称能处理超长文本,但实际用起来,到后面就不知道在说什么了。
前两天我花4小时做了个小工具,让多个AI模型一起干活,接力写完整个项目代码。今天把这个方法分享出来,希望对你有帮助。
为什么要想新办法
之前我写过一篇用Qwen2.5-Coder加Ollama搭本地AI代码助手的文章,用的是单个模型。但单个模型做复杂项目时,经常顾头不顾尾。
要是用Claude会好很多,代码写得也准。但我就是想用免费的,所以得想个新招。
我做技术的习惯是,遇到问题先自己磕。这次的目标很明确:让AI自动写完整个项目的代码,而且要用多个模型接力完成。
我的实现思路
整个工具的工作流程是这样的:
第一步:让AI理解需求
我先让一个模型看原始需求,列出一份需求清单。这里有个关键点:必须把需求限制在原始范围内,不能让AI自己乱加功能。我用的是gpt-oss:20b这个模型,它说话比较实在,不会乱发挥。
第二步:规划技术栈
有了需求清单,就让第二个模型根据需求选技术。原则是:用主流的,能少用就少用,免得引出乱七八糟的问题。比如我要的是一个简单网页,AI要是给我整出Next.js那一套,那就过头了。这里我用qwen2.5-coder:14b,它中文理解好,写代码也还行。
第三步:设计文件结构
接着让模型根据技术栈和需求,画出整个项目的文件地图。哪个文件放哪,用来干什么,都得写清楚。这一步还是用qwen2.5-coder:14b。
第四步:规划每个文件的内容
有了文件地图,就开始给每个文件写索引。索引里写清楚这个文件是干什么的,里面有什么方法,这些方法用来做什么。这一步更偏向写代码,所以我换成codellama:13b。
第五步:排好生成顺序
文件之间会有引用关系。比如工具函数被好几个文件调用,那就得先生成工具函数,再生业务代码,最后生入口文件。这一步也是codellama:13b来干。
第六步:逐个生成代码
按照排好的顺序,让AI逐个写出每个文件的代码。还是用codellama:13b。
第七步:写项目文档
最后根据前面所有的材料,生成一份完整的项目文档。包括原始需求、需求清单、技术栈、文件结构、怎么运行、怎么部署,全都写清楚。这一步用qwen2.5-coder:14b。
跑这个工具需要什么配置
我是在自己电脑上跑的,配置如下:
硬件:
系统:Windows 10 专业版 22H2
CPU:Intel i5 14代
显卡:NVIDIA RTX 5060Ti 16G(实际用的时候,显卡占用偶尔冲到97%,大部分时间在53%左右)
内存:32GB DDR5(我感觉16G其实也够)
硬盘:NVMe固态(三个模型加起来差不多32G:codellama:13b是7.4G,gpt-oss:20b是13G,qwen2.5-coder:14b是12G)
软件:
Ollama 0.16.1
Python 3.10
实际效果怎么样
我用一个简单项目试了试。项目越复杂,AI推理的时间就越长。
最后生成了完整的代码文件,每个文件还配了一个索引文件,里面写清楚了函数的作用和参数。项目文档也自动生成了,打开就能看到整个项目的来龙去脉。
完整脚本代码
下面是完整的Python脚本,你可以直接拿去用:
import os
import re
import json
import requests
from typing import Dict, List, Optional
class PromptManager:
@staticmethod
def get_refine_prompt(raw_req: str) -> str:
return f"""
你是一名有经验的全栈开发。请把下面这个原始需求,转成一份能直接开工的开发方案。
【原始需求】:
{raw_req}
【你要做的】:
1. 看懂需求里没明说但确实需要的东西,把需求补完整,但不能超出原始需求的范围。
2. 把理解到的需求细化,列出具体的功能。
3. 根据这些功能,给出这个项目特有的文件目录怎么分。
4. 列出这个项目要用到的技术栈。
5. 对照原始需求,说清楚哪些功能能做,哪些不能做。
6. 列出来的功能必须是能写代码实现的,不能实现的技术不要往上写。
【输出要求】:
- 只输出【功能列表】和【技术栈清单】这两块,别的什么都不要写。
- 不要说废话,每句话都要像代码注释一样,说到点子上。
"""
@staticmethod
def get_structure_prompt(user_req: str) -> str:
return f"""针对这个需求:{user_req}
任务:设计一个项目目录结构,要符合行业习惯,一看就懂。
要求(必须照做):
1. 只输出文件路径清单,不要解释,不要用Markdown的代码块。
2. 根据需求里用的技术,把文件分到对应的文件夹里。
3. 每行格式必须是:路径/文件名 : 这个文件是干什么的
4. 路径和文件名里不能有星号、空格这些乱七八糟的符号。
5. 不能把所有文件都堆在根目录,路径前面不要加斜杠。
6. 不能只写文件夹名字,每个文件夹里必须有具体的文件。
7. 只输出路径清单,不要开场白,不要总结。
8. 每个文件的功能描述要说清楚,不能两个文件干一样的事。
"""
@staticmethod
def get_order_prompt(tree_map: str, paths: List[str]) -> str:
return f"""
项目文件结构:
{tree_map}
任务:根据文件之间的引用关系,给下面的文件排个生成顺序。
排序规则:
1. 先写那些被很多文件引用的基础文件(比如公共工具、配置文件)。
2. 再写具体的业务功能文件。
3. 最后写项目的入口文件。
要排序的文件:
{", ".join(paths)}
要求:只输出排序后的路径,一行一个,不要解释。
"""
@staticmethod
def get_code_prompt(refined_req: str, tree_map: str, file_path: str, tech_stack: str) -> str:
ext = file_path.split('.')[-1].lower() if '.' in file_path else 'txt'
lang_map = {
'html': 'HTML',
'js': 'JavaScript',
'css': 'CSS',
'py': 'Python',
'json': 'JSON'
}
current_lang = lang_map.get(ext, "对应语言")
return f"""项目需求:{refined_req}
【整个项目的文件结构】(写代码时照着这个路径引用别的文件)
{tree_map}
现在要写的文件是:{file_path}
技术限制:这个文件里只能写 {current_lang} 代码,不能混别的语言。
任务:严格按照下面的格式输出,不要把[INDEX]和[CODE]混在一起。
[INDEX_START]
在这里写清楚:
1. 这个文件是干什么的,和别的文件是什么关系(比如引用了上面结构里的哪个文件,或者被谁调用)
2. 这个文件里有哪些函数,每个函数参数是什么,返回什么,代码从哪行到哪行,逻辑是什么
[INDEX_END]
[CODE_START]
1. 根据上面[INDEX_START]里写的引用关系,写出具体的代码。不要用Markdown的代码块符号,不要加解释。
2. 代码里引用别的文件,必须用【整个项目的文件结构】里写的相对路径!
3. 代码要符合这个文件该干的事,不要抄别的文件的内容。
4. 绝对不能在 {file_path} 里写不是 {current_lang} 的代码。
[CODE_END]
"""
class AIProjectGenerator:
def __init__(
self,
model_name: str = "qwen2.5-coder:14b-instruct-q6_K",
ollama_base_url: str = "http://localhost:11434",
ctx_size: int = 8192,
temperature: float = 0.1
):
self.model_name = model_name
self.ollama_base_url = ollama_base_url
self.ctx_size = ctx_size
self.temperature = temperature
self.output_dir = "code"
self.project_info = {"需求": "", "文件结构": ""}
self.raw_structure = ""
if not os.path.exists(self.output_dir):
os.makedirs(self.output_dir)
def _call_ollama(self, prompt: str) -> str:
payload = {
"model": self.model_name,
"prompt": prompt,
"stream": False,
"options": {"num_ctx": self.ctx_size, "temperature": self.temperature}
}
try:
response = requests.post(f"{self.ollama_base_url}/api/generate", json=payload, timeout=300)
return response.json().get("response", "").strip()
except Exception as e:
return f"Error: {str(e)}"
def _clean_path(self, path_str: str) -> str:
clean = re.sub(r'[*+\-\[\]\s]', '', path_str)
clean = re.sub(r'[?<>|"]', '', clean)
return clean.strip().lstrip('/')
def _refine_requirement(self, raw_req: str):
print(f"正在分析需求...")
prompt = PromptManager.get_refine_prompt(raw_req)
self.project_info["需求"] = self._call_ollama(prompt)
print(f"需求分析完成:\n")
print(self.project_info["需求"])
return self.project_info["需求"]
def generate_file_structure(self, refined_req: str):
print(f"开始规划项目结构...")
prompt = PromptManager.get_structure_prompt(refined_req)
self.raw_structure = self._call_ollama(prompt)
self.project_info["文件结构"] = self._build_tree_string()
print("\n项目结构:")
print(self.project_info["文件结构"])
def _build_tree_string(self) -> str:
lines = self.raw_structure.split("\n")
path_to_desc = {}
paths = []
for line in lines:
if ":" in line:
parts = line.split(":", 1)
clean_p = self._clean_path(parts[0])
if clean_p and "." in clean_p:
paths.append(clean_p)
path_to_desc[clean_p] = parts[1].strip()
tree = {}
for path in paths:
curr = tree
for part in [p for p in path.split('/') if p]:
curr = curr.setdefault(part, {})
output = [f"{self.output_dir}/"]
def _render(node, curr_p="", vis_p=""):
keys = sorted(node.keys())
for i, k in enumerate(keys):
is_last = (i == len(keys) - 1)
this_p = f"{curr_p}/{k}".lstrip('/')
is_dir = len(node[k]) > 0
desc = path_to_desc.get(this_p, "")
output.append(f"{vis_p}{'└── ' if is_last else '├── '}{k}{'/' if is_dir else ''}{' : ' + desc if desc else ''}")
if is_dir:
_render(node[k], this_p, vis_p + (" " if is_last else "│ "))
_render(tree)
return "\n".join(output)
def _analyze_logical_order(self, paths: List[str]) -> List[str]:
print("分析文件生成顺序...")
prompt = PromptManager.get_order_prompt(self.project_info["文件结构"], paths)
result = self._call_ollama(prompt)
ordered = [self._clean_path(p) for p in result.split('\n') if p.strip()]
return [p for p in ordered if p in paths] + [p for p in paths if p not in ordered]
def generate_single_file(self, file_path: str):
clean_file_path = self._clean_path(file_path)
target_path = os.path.join(self.output_dir, clean_file_path)
prompt = PromptManager.get_code_prompt(
self.project_info["需求"],
self.project_info["文件结构"],
clean_file_path,
self.project_info["需求"]
)
raw_response = self._call_ollama(prompt)
index_pattern = re.compile(r"\[INDEX_START\](.*?)\[INDEX_END\]", re.DOTALL | re.IGNORECASE)
code_pattern = re.compile(r"\[CODE_START\](.*?)\[CODE_END\]", re.DOTALL | re.IGNORECASE)
index_match = index_pattern.search(raw_response)
code_match = code_pattern.search(raw_response)
index_content = index_match.group(1).strip() if index_match else "没有生成索引"
if code_match:
code_content = code_match.group(1).strip()
else:
code_content = raw_response
if index_match:
code_content = code_content.replace(index_match.group(0), "")
code_content = re.sub(r'```[a-zA-Z0-9]*\n?', '', code_content)
code_content = code_content.replace('```', '').strip()
for tag in ["[INDEX_START]", "[INDEX_END]", "[CODE_START]", "[CODE_END]"]:
code_content = re.compile(re.escape(tag), re.IGNORECASE).sub("", code_content)
os.makedirs(os.path.dirname(target_path), exist_ok=True)
with open(target_path, "w", encoding="utf-8") as f:
f.write(code_content)
with open(f"{target_path}.index", "w", encoding="utf-8") as f:
f.write(index_content)
print(f"完成: {clean_file_path}")
def finalize_project_doc(self):
doc_path = os.path.join(self.output_dir, "PROJECT_SUMMARY.md")
tree_md = self._build_tree_string()
file_desc_list = ""
for line in self.project_info["文件结构"].split("\n"):
if ":" in line:
file_desc_list += f"- {line.strip()}\n"
summary_content = f"""# 项目文档
## 1. 需求
{self.project_info['需求']}
## 2. 文件结构{tree_md}
## 3. 文件说明
{file_desc_list}
说明:每个代码文件都有一个对应的.index文件,里面写了函数的作用和参数。
"""
with open(doc_path, "w", encoding="utf-8") as f:
f.write(summary_content)
print(f"\n项目文档已生成:{doc_path}")
def run(self, raw_req: str):
print(f"需求:{raw_req}")
refined_req = self._refine_requirement(raw_req)
self.generate_file_structure(refined_req)
paths = []
for line in self.raw_structure.split("\n"):
if ":" in line:
p = self._clean_path(line.split(":")[0])
if "." in p:
paths.append(p)
if not paths:
return
sorted_paths = self._analyze_logical_order(list(set(paths)))
for p in sorted_paths:
self.generate_single_file(p)
self.finalize_project_doc()
print(f"\n项目生成完成!")
if __name__ == "__main__":
gen = AIProjectGenerator()
gen.run("用vue3写一个AI科技公司的官网,页面简洁有科技感,不要后台")还能怎么改进
用下来发现有几个地方可以做得更好:
生成速度:现在是挨个生成文件,可以用多线程同时生成,会快很多。
代码质量:换更强的新模型,代码会更准。
反复修改:现在生成完就完了,可以根据索引文件,让AI再改几遍代码,让质量更高。
最后说几句
AI自动写代码不是要干掉程序员,而是让程序员能做更多事。就像以前马车被汽车取代,赶马车的人没了,但修汽车的人多了。
关键是你用AI来干什么。
最开始大家拼算力,后来拼算法,现在都在拼模型怎么蒸馏得更好用。现在的AI能力已经能做很多事了,不一定非要追最新的。提示词工程师这个角色,确实是时候重视起来了。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!