三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

从零构建本地AI编程助手:基于RAG与LLM的智能副驾实践

从零构建本地AI编程助手:基于RAG与LLM的智能副驾实践

1. 从“代码搬运工”到“智能副驾”:为什么你需要一个Skill

如果你和我一样,每天的工作就是和代码打交道,那你肯定经历过这样的时刻:面对一个全新的框架,文档翻来覆去看不明白;接手一个遗留项目,满屏的魔法数字和神秘缩写让你无从下手;或者,只是想写一个简单的数据转换脚本,却要花半小时去搜索各种库的API用法。我们大部分时间,其实都花在了“查找”和“理解”上,而不是真正的“创造”上。

这就是我决定动手打造一个属于自己的“智能编程助手”的初衷。我不想再当一个被动的“代码搬运工”和“搜索引擎依赖者”。我希望有一个工具,它能理解我当前的项目上下文,能在我写代码时主动给出建议,能帮我快速查阅不熟悉的API,甚至能在我卡壳时,基于现有代码逻辑,给我一个可行的代码片段作为起点。听起来有点像某些商业IDE的智能补全?不,我要的远不止于此。我要的是一个可以深度定制、完全私有化、并且能随着我的知识库一起成长的“副驾驶”。

这个助手,我称之为Skill。它不是某个具体的软件,而是一套方法论和工具链的组合。其核心思想是:将你碎片化的编程知识、项目特定的业务逻辑、常用的工具链命令,都结构化地“教”给一个本地运行的AI模型,让它成为你在编码时的“第二大脑”。从零开始构建这样一个Skill,你会经历环境搭建、知识喂养、交互设计、效能优化四个完整的阶段。这个过程不仅能让你最终获得一个强大的生产力工具,更能让你对现代AI辅助编程的原理、局限和潜力有第一手的深刻理解。接下来,我就带你一步步拆解,如何从一张白纸开始,打造你的专属智能编程助手。

2. 基石构建:本地化环境与核心模型选型

在开始“喂养”AI之前,我们必须先为它准备好一个安全、可控且高性能的“家”。一个核心原则是:一切运行在本地。这保证了代码隐私,避免了网络延迟,也让你能完全掌控助手的“知识”和“性格”。

2.1 本地大语言模型(LLM)的抉择:能力、速度与成本的三角平衡

这是最关键的决策点。你需要一个足够聪明、响应速度快、且在你的硬件上跑得动的模型。市面上开源模型众多,我们需要从以下几个维度评估:

  1. 模型尺寸与能力:7B(70亿参数)模型是入门甜点,如Llama 3.1 8BQwen2.5 7B,它们能在消费级显卡(如RTX 4060 8GB)上流畅运行,具备良好的代码理解和生成能力。13B-34B模型(如Qwen2.5 32B,DeepSeek-Coder 33B)能力更强,但需要更多显存(通常16GB以上)。如果你的目标是深度代码分析和复杂逻辑推理,更大模型是值得投资的。
  2. 量化与推理引擎:原始模型文件(FP16)很大。我们必须使用量化技术(如GGUF、GPTQ格式)来压缩模型,牺牲极少精度以换取内存占用和速度的巨大提升。llama.cpp项目提供的GGUF格式及配套推理引擎,因其高效的CPU/GPU混合推理能力,成为本地部署的首选。
  3. “代码特化”模型:优先选择在代码数据上经过额外训练的模型,例如DeepSeek-Coder系列、CodeLlama系列、StarCoder系列。它们在代码补全、单文件生成、Bug查找等任务上表现通常优于通用模型。

我的选择与理由:经过多次实测,我最终选定了Qwen2.5-Coder-7B-Instruct-GGUF(Q4_K_M量化版)作为起步核心。理由如下:

  • 能力均衡:Qwen2.5 7B在代码基准测试(如HumanEval)上表现亮眼,指令跟随能力强,非常适合对话式编程辅助。
  • 硬件友好:Q4_K_M量化后,模型文件约4.5GB,在8GB显存的GPU上可以完全加载,纯CPU推理(依赖RAM)也尚可接受,确保了大多数开发环境的兼容性。
  • 工具链成熟:其GGUF格式被llama.cpp,Ollama,LM Studio等主流工具广泛支持,生态完善。

注意:模型选择没有银弹。建议你先用一个小模型(如7B)跑通全流程,验证工作流。后续完全可以无缝切换成更大、更强的模型,这是本地化方案的优势。

2.2 部署与交互框架:是选一体化工具还是自建管道?

有了模型文件,我们需要一个“服务器”来加载它并提供API,以及一个“客户端”来与之交互。

  • 方案A:一体化工具(快速上手)

    • LM Studio:图形化界面,对新手极其友好。下载模型、加载、运行聊天界面一气呵成,还内置了类OpenAI的本地API服务器。适合想快速体验、不愿折腾命令行环境的开发者。
    • Ollama:命令行工具,同样简单易用。通过ollama run qwen2.5:7b这样的命令就能拉取并运行模型。它管理模型、运行服务非常方便,是快速原型验证的利器。
  • 方案B:自建推理服务器(灵活可控)

    • llama.cpp+text-generation-webui:这是追求控制和灵活性的组合。llama.cpp提供高性能的底层推理;text-generation-webui(原名oobabooga)则在其之上提供了一个功能丰富的Web界面和完备的API(兼容OpenAI格式)。你可以精细控制生成参数、使用扩展插件、管理多个模型。
    • vLLM:如果你拥有多张GPU并追求极高的吞吐量(用于批处理任务),vLLM是生产级选择,但配置相对复杂。

我的搭建步骤:我选择了方案B,因为它为我后续的深度集成提供了最大自由度。

  1. 编译llama.cpp:从GitHub克隆最新代码,根据你的平台(我的是Ubuntu + CUDA)进行编译,开启GPU加速支持。
    make LLAMA_CUBLAS=1
  2. 下载模型GGUF文件:从Hugging Face等社区仓库找到选定的模型GGUF文件,下载到本地。
  3. 部署text-generation-webui
    git clone https://github.com/oobabooga/text-generation-webui cd text-generation-webui pip install -r requirements.txt
  4. 启动服务:在WebUI的“Model”标签页加载你的GGUF文件,然后在“Session”标签页启动。默认会在本地7860端口开启Web界面,同时5000端口提供兼容OpenAI的API。

至此,你的本地AI大脑已经启动并运行。你可以通过访问http://localhost:7860与它进行基础的对话测试。但这只是一个“通才”模型,它还不了解你的项目、你的代码风格、你的业务逻辑。下一步,就是赋予它“专长”。

3. 知识注入:让Skill真正理解你的项目上下文

一个不了解项目背景的AI助手,给出的建议往往是隔靴搔痒。我们必须系统性地将项目知识“喂”给它。这不仅仅是上传几个文件,而是构建一个结构化的项目知识库

3.1 知识库的原材料收集与预处理

你需要告诉Skill关于你项目的三方面信息:

  1. 代码库本身:这是核心。但直接扔给它整个项目根目录是低效的。你需要有策略地选择:

    • 关键源代码src/,lib/等目录下的主要业务逻辑文件。
    • 配置文件package.json,pyproject.toml,docker-compose.yml, 各种.env.exampleconfig/下的文件。这能让AI理解项目依赖和架构。
    • 文档README.md,docs/,ARCHITECTURE.md。这是项目的高层设计说明。
    • 构建与脚本Makefile,scripts/, CI/CD配置文件(如.github/workflows/)。这揭示了项目的工具链和自动化流程。
  2. 技术栈文档:你项目所用的主要框架、库的官方文档或精华教程。例如,如果你的项目用FastAPI,那么FastAPI的官方指南关键部分就很有价值。

  3. 团队规范与业务逻辑:内部的API设计规范、数据库ER图(可转为Markdown描述)、核心业务流程图、领域术语表等。这些是商业代码中最具价值也最独特的“知识”。

预处理的关键一步:代码切片与清理。直接将大文件(比如一个几千行的单体文件)塞给AI,会很快耗尽其上下文窗口,且信息杂乱。你需要一个简单的脚本,将大文件按函数、类或逻辑模块进行切割,并附上文件路径注释。同时,移除编译产物(node_modules/,__pycache__/,target/,dist/)和二进制文件。

3.2 构建向量数据库:实现知识的“即查即用”

我们不可能在每次提问时,都把整个知识库的所有文本都塞进AI的上下文(有长度限制,且成本高)。解决方案是使用检索增强生成(RAG)。其工作流程是:将知识库文本切成小块(chunks),转换为向量(embeddings)存入数据库;当用户提问时,将问题也转为向量,在数据库中快速查找最相关的几个文本块;最后,将这些相关块作为“参考材料”和问题一起送给AI模型,让它生成基于这些材料的答案。

实操步骤:使用ChromaDBSentence Transformers

  1. 安装依赖

    pip install chromadb sentence-transformers tiktoken # tiktoken用于文本切分
  2. 编写知识库嵌入脚本

    import os from chromadb import Documents, EmbeddingFunction, Clients, Settings import chromadb from sentence_transformers import SentenceTransformer from typing import List import tiktoken # 使用OpenAI的分词器来按token长度切分 # 1. 初始化嵌入模型(同样在本地运行) # 选择一个轻量且效果好的模型,例如 all-MiniLM-L6-v2 embed_model = SentenceTransformer('all-MiniLM-L6-v2') class LocalEmbeddingFunction(EmbeddingFunction): def __call__(self, input: Documents) -> Embeddings: # 将文本列表转换为向量 return embed_model.encode(input).tolist() # 2. 初始化Chroma客户端和集合 client = chromadb.PersistentClient(path="./my_skill_knowledge_db") collection = client.get_or_create_collection( name="project_docs", embedding_function=LocalEmbeddingFunction() ) # 3. 遍历项目目录,读取文件并切分 def chunk_text(text: str, chunk_size=500, overlap=50) -> List[str]: """使用tiktoken按token数切分文本,保持语义相对完整""" encoding = tiktoken.get_encoding("cl100k_base") # GPT-4/GPT-3.5使用的编码 tokens = encoding.encode(text) chunks = [] for i in range(0, len(tokens), chunk_size - overlap): chunk_tokens = tokens[i:i + chunk_size] chunks.append(encoding.decode(chunk_tokens)) return chunks project_root = "/path/to/your/project" documents = [] metadatas = [] ids = [] for root, dirs, files in os.walk(project_root): # 忽略一些目录 dirs[:] = [d for d in dirs if d not in ['node_modules', '__pycache__', '.git', 'dist', 'build']] for file in files: # 只处理文本文件 if file.endswith(('.py', '.js', '.ts', '.md', '.json', '.yml', '.yaml', '.txt', '.java', '.go')): file_path = os.path.join(root, file) try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() # 将文件路径和内容作为元数据 file_chunks = chunk_text(content) for i, chunk in enumerate(file_chunks): documents.append(chunk) metadatas.append({"source": file_path, "chunk_index": i}) ids.append(f"{file_path}_{i}") except Exception as e: print(f"Error reading {file_path}: {e}") # 4. 批量添加到向量数据库 if documents: collection.add( documents=documents, metadatas=metadatas, ids=ids ) print(f"成功嵌入 {len(documents)} 个文本块到知识库。")

运行这个脚本后,你就拥有了一个本地的、可查询的项目知识库。当用户提问“我们项目里用户认证是怎么实现的?”时,RAG系统会自动从知识库中检索出auth.pymiddleware/jwt.js等相关代码片段和文档,作为上下文提供给AI。

4. 交互界面与工作流集成:让Skill触手可及

一个需要频繁切换浏览器标签页或终端的助手,使用体验会大打折扣。我们的目标是将Skill深度集成到你的编码工作流中。

4.1 打造命令行客户端(CLI):终极效率之选

对于开发者而言,命令行是最直接、最快速的交互方式。我们可以用Python的clickargparse库构建一个CLI工具,比如就叫skill

核心功能设计

  • skill ask "如何添加一个新的API端点?":直接提问,CLI工具在后台执行RAG检索,调用本地模型API,流式打印回答。
  • skill code --file ./src/service.py --line 45:针对特定文件的特定行代码提问(例如“这个函数为什么这么写?”),CLI会自动将该文件内容作为重点上下文。
  • skill explain [某个错误信息]:解析错误日志,从知识库中查找可能相关的解决方案或代码。
  • skill sync:当项目代码更新后,手动或自动触发知识库的增量更新。

CLI核心代码示例(简化)

# skill_cli.py import click import requests import json from chromadb import PersistentClient from sentence_transformers import SentenceTransformer # 初始化本地嵌入模型和Chroma客户端 embed_model = SentenceTransformer('all-MiniLM-L6-v2') chroma_client = PersistentClient(path="./my_skill_knowledge_db") collection = chroma_client.get_collection("project_docs") # 本地模型API地址(text-generation-webui提供) LOCAL_API_URL = "http://localhost:5000/v1/chat/completions" def retrieve_context(question, top_k=3): """从向量库检索相关上下文""" query_embedding = embed_model.encode(question).tolist() results = collection.query( query_embeddings=[query_embedding], n_results=top_k ) # 拼接检索到的文档块 context = "\n\n---\n\n".join(results['documents'][0]) return context @click.group() def cli(): """你的智能编程助手Skill""" pass @cli.command() @click.argument('question') def ask(question): """向Skill提问""" # 1. 检索上下文 context = retrieve_context(question) # 2. 构建Prompt system_prompt = """你是一个专业的编程助手,熟悉当前项目的所有代码和文档。请严格根据提供的上下文信息回答问题。如果上下文信息不足,可以基于你的通用编程知识回答,但需说明这一点。""" user_prompt = f"""请参考以下项目上下文: {context} 问题:{question} """ # 3. 调用本地模型API payload = { "model": "local-model", # 模型名在text-generation-webui中设置 "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], "stream": True, "max_tokens": 1500 } response = requests.post(LOCAL_API_URL, json=payload, stream=True) # 4. 流式打印输出 click.echo("Skill: ", nl=False) for line in response.iter_lines(): if line: decoded_line = line.decode('utf-8') if decoded_line.startswith('data: '): if decoded_line == 'data: [DONE]': break try: data = json.loads(decoded_line[6:]) if 'choices' in data and data['choices']: content = data['choices'][0].get('delta', {}).get('content', '') if content: click.echo(content, nl=False) except json.JSONDecodeError: continue click.echo() # 换行 if __name__ == '__main__': cli()

将这个脚本安装为全局命令(例如通过pip install -e .),你就可以在终端的任何地方,随时使用skill ask来获取基于项目上下文的精准解答了。

4.2 集成代码编辑器:沉浸式辅助体验

CLI虽快,但在编码过程中,频繁切换终端仍会打断心流。更优解是集成到IDE/编辑器。

  • VS Code / Cursor:你可以开发一个VS Code扩展。扩展在后台运行一个本地服务(或直接调用你的CLI),监听编辑器事件(如当前打开的文件、选中的代码块),并提供侧边栏聊天面板或行内代码建议。对于更轻量的集成,可以直接配置VS Code的代码片段(Snippets)或任务(Tasks),调用你的skillCLI来生成代码片段。
  • Neovim / Emacs:对于终端编辑器爱好者,可以通过插件系统,将skill ask命令绑定到某个快捷键,并将回答直接输出到另一个buffer或浮动窗口中,实现完全不离开编辑器的交互。

一个简单的VS Code扩展思路:扩展激活后,启动你的本地Skill后端服务(如果未运行)。在编辑器中选中一段代码,右键菜单添加“Skill: 解释此代码”或“Skill: 重构建议”,扩展会将选中代码和文件路径作为上下文发送给后端,并将返回的结果显示在一个新的Webview面板中。

5. 效能跃升:Prompt工程与持续迭代优化

拥有了基础功能后,如何让Skill的回答更精准、更符合你的预期?这就需要精心设计Prompt(提示词),并建立一个反馈循环。

5.1 设计系统Prompt:定义助手的“角色”与“行为准则”

系统Prompt是每次对话开始前,你发给模型的“指令”,它决定了AI的应答风格和边界。一个优秀的系统Prompt应包含:

  1. 身份与能力:明确告知AI它的角色。(例如:“你是一个资深全栈工程师,专注于Python和JavaScript开发,精通系统设计,代码风格简洁高效。”)
  2. 回答规范
    • 结构化输出:要求它对复杂问题分点、分步骤回答,对代码解释提供“核心思路”、“关键步骤”、“潜在风险”等模块。
    • 引用来源:要求它在答案中明确指出信息来源于哪个文件(基于RAG提供的元数据),例如“根据src/auth/jwt_manager.py第30-45行的代码逻辑...”。
    • 不确定性表达:对于不确定或知识库中没有的内容,必须声明“根据现有项目文档,未找到明确说明,基于通用知识...”。
    • 代码格式:要求所有代码块必须标明语言类型。
  3. 安全与边界:明确禁止它执行或生成任何可能有害、不安全或超出项目范围的代码(例如,禁止建议安装未在package.json中列出的未知依赖)。

我的系统Prompt示例

你是我个人项目的专属编程助手“Skill”。你深度熟悉本项目([你的项目名])的所有源代码、技术文档和架构设计。你的核心职责是帮助我高效地开发、调试和理解本项目代码。 **你必须遵守以下规则:** 1. 回答必须基于我提供的“项目上下文”。上下文来自本项目的代码和文档向量数据库。 2. 在答案中,如果引用或推断自特定文件,请注明文件路径,例如 `[源自: src/utils/logger.py]`。 3. 对于代码修改建议,优先遵循本项目现有的代码风格和架构模式(如已提供的上下文所示)。 4. 如果问题超出项目上下文范围,你可以运用通用编程知识回答,但开头必须说明“项目文档中未明确提及,根据通用实践...”。 5. 输出代码时,使用正确的Markdown代码块并指定语言。 6. 对于复杂操作,请分步骤说明,并指出每一步的关键点和可能的风险。 7. 不要假设项目中存在未在上下文中出现的工具、库或模块。 现在,请基于以上规则,为我提供专业、精准、安全的协助。

将这个系统Prompt固化在你的CLI或后端服务中,每次请求都附带它,能极大提升回答的一致性和质量。

5.2 建立反馈与迭代机制:让Skill与你共同成长

Skill不是一次搭建就永远完美的。你需要一个机制来纠正它的错误,并丰富它的知识。

  1. 对话历史与评分:在你的CLI或界面中,实现一个简单的反馈功能。例如,每次回答后,可以按[T]表示回答好,[F]表示回答不准确。将[F]的对话(包括问题、错误回答、你纠正后的答案)保存到一个日志文件中。
  2. 定期复盘与知识库更新:每周或每两周,回顾这些“失败案例”。分析原因:
    • 是知识库缺少相关文件? -> 将缺失的文件或文档加入知识库,重新运行嵌入脚本。
    • 是Prompt指令不清晰导致AI误解? -> 优化你的系统Prompt。
    • 是模型本身能力不足? -> 考虑升级到更大参数的模型。
  3. “教学”模式:对于特别复杂或独特的业务逻辑,你可以主动“教”Skill。创建一个docs/for_skill.md文件,用清晰的语言描述这个业务模块的设计思路、核心算法、边界条件。然后将这个文件加入知识库。这相当于为你的项目编写了一份AI可读的专项说明书。

通过这种持续的“使用-反馈-优化”循环,你的Skill会变得越来越懂你,越来越懂你的项目,最终成为一个不可或缺的协作伙伴。这个过程本身,也是对你项目结构和知识管理的一次极佳梳理。

← 返回列表