我用4小时做了一个AI自动写代码工具,全过程分享

更新日期: 2026-02-28 阅读: 22 标签: 工具

我写代码快两年了,一直用免费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脚本,你可以直接拿去用:

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科技公司的官网,页面简洁有科技感,不要后台")


还能怎么改进

用下来发现有几个地方可以做得更好:

  1. 生成速度:现在是挨个生成文件,可以用多线程同时生成,会快很多。

  2. 代码质量:换更强的新模型,代码会更准。

  3. 反复修改:现在生成完就完了,可以根据索引文件,让AI再改几遍代码,让质量更高。


最后说几句

AI自动写代码不是要干掉程序员,而是让程序员能做更多事。就像以前马车被汽车取代,赶马车的人没了,但修汽车的人多了。

关键是你用AI来干什么。

最开始大家拼算力,后来拼算法,现在都在拼模型怎么蒸馏得更好用。现在的AI能力已经能做很多事了,不一定非要追最新的。提示词工程师这个角色,确实是时候重视起来了。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/13346

相关推荐

推荐6款好用、免费的远程控制软件【远程管理工具】

远程办公就需要远程连接的工具,当然,你说你用VPN那也是没有毛病的。远程桌面工具也极大的方便了我们进行远程技术支持、远程办公的便利性,但是呢,很多时候,有些工具不支持电脑或者手机操作

7款最好的笔记工具

编程容易产生挫折,即使作为一种业余爱好也可能是这样。建立一个网页,手机APP或桌面应用都是个很大的工程,好的记笔记技能是让这个工程井然有序的关键,也是克服压力、绝望和倦怠的好方法。

常用的前端开发者的工具、库和资源

这篇文章简单的分享一套我认为有助于提升开发者工作流的工具集。这套工具集中的大部分你可能见过,也可能没见过,如果有哪个/些让你眼前一亮,那么我的分享就很值了。这个列表包含许多种类的资源,所以这里我将它们分组整理。

在线工具 - 程序员实用工具集

在线实用工具集-免费提供站长常用工具,包含代码格式化工具、代码转换工具、压缩、加密解密工具等,工具在手,事半功倍,工作无忧。

web前端程序员代码编辑器推荐

今天给大家分享前端程序员最爱用的代码编辑器,来看看你用哪款?包括:Visual Studio Code、Atom、HBuilder、Sublime Text、Dreamweaver、Brackets、Notepad++

欺骗技术13款开源工具分享

一旦被那些受利益驱使或有政府背景的黑客团伙盯上,在这场不太公平的攻防博弈中,你会明显感到力不从心。他们有充足的时间,有娴熟的技术和丰富的资源,而且只要在无数次的尝试中成功一次就可以大获全胜

面向软件开发人员的7款产品路线图工具

产品路线图软件可以帮助软件产品经理完成核心的规划任务,并向项目团队成员和相关人员通报目标和状态。产品工具可以帮助团队制定战略、确定目标的优先级、安排要完成的工作,并使每个人在整个产品生命周期中步调一致

7款代码对比工具

在程序开发的过程中,程序员会经常对源代码以及库文件进行代码对比,在这篇文章里我们向大家介绍六款程序员常用的代码比较工具。

程序员常用命令行工具

WordGrinder它是一款使用起来很简单,但拥有足够的编写和发布功能的文字编辑器。Proselint:它是一款全能的实时检查工具。GNU Aspell:

Web前端开发网推出: 600+免费在线工具

在当今快节奏的工作环境中,效率工具已成为提升工作和学习效率的关键。Web前端开发网工具箱作为一个集合了500多款实用工具的在线平台,为程序员、设计师、办公人士和普通用户提供了无需下载安装、直接在浏览器中使用的便捷解决方案。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!