本地部署大模型完全指南⑧:实战应用——本地AI编程助手

前7篇文章我们从硬件选型到性能优化全走了一遍。最后一篇来点实在的——把本地模型变成你VS Code里的AI编程搭档,让代码补全、审查、重构一条龙自动化。

前言:为什么需要本地编程助手?

云端编程助手(如GitHub Copilot、Cursor)很强,但有两个痛点:

  • 数据隐私:完整代码库上传到云端,企业场景不放心
  • 离线受限:出差、内网开发环境无法使用
  • 费用不菲:Copilot $10/月,一堆团队账号开销不小

本地模型解决所有问题——脱机可用、数据不出门、一次投入无限次调用。


一、VS Code集成方案

1.1 方案一:Continue插件(推荐)

Continue 是最成熟的开源代码AI插件,原生支持Ollama。

安装步骤:

VS Code → 扩展 → 搜索 "Continue" → 安装

配置Continue连接Ollama:

安装完成后,打开 config.json(按 Ctrl+Shift+P 输入 Continue: Open Config):

{
  "models": [
    {
      "title": "本地DeepSeek",
      "provider": "ollama",
      "model": "deepseek-r1:7b",
      "contextLength": 8192
    },
    {
      "title": "本地Qwen代码版",
      "provider": "ollama",
      "model": "qwen2.5-coder:7b",
      "contextLength": 8192
    },
    {
      "title": "代码补全专用",
      "provider": "ollama",
      "model": "qwen2.5-coder:7b",
      "completionOptions": {
        "temperature": 0.2,
        "topP": 0.9
      }
    }
  ],
  "tabAutocompleteModel": {
    "title": "代码自动补全",
    "provider": "ollama",
    "model": "qwen2.5-coder:7b"
  },
  "slashCommands": [
    {
      "name": "edit",
      "description": "编辑选中代码"
    },
    {
      "name": "review",
      "description": "审查选中代码"
    },
    {
      "name": "test",
      "description": "为选中代码生成单元测试"
    }
  ]
}

配置完成后即可使用:

  • Tab 键:内联代码补全
  • Ctrl+I:内联对话
  • Ctrl+L:在侧边栏对话
  • 选中代码 → Ctrl+L:针对代码提问

1.2 方案二:CodeGPT插件(更全面的选择)

# VS Code安装CodeGPT
# 扩展市场搜索 "CodeGPT" 并安装

配置Ollama连接:

CodeGPT设置 → 模型供应商 → Ollama
→ 填写API地址:http://localhost:11434
→ 选择模型:qwen2.5-coder:7b

CodeGPT vs Continue 功能对比:

功能 Continue CodeGPT
代码补全 Tab内联补全
代码审查 命令模式
对话 侧边栏 侧边栏
自定义Agent
MCP支持 有(新版)
多模型切换 原生支持 原生支持

二、自定义代码助手工作流

2.1 代码审查自动化

Continue支持自定义流程,我们可以创建一个代码审查Agent:

创建 ~/.continue/slashCommands/review.py

# review命令:代码审查
def review_context(code: str, language: str) -> str:
    return f"""你是一个经验丰富的代码审查员。
请审查以下{language}代码,从以下维度给出反馈:

## 审查清单

1. **代码正确性**:是否有逻辑错误或bug?
2. **安全性**:是否有SQL注入、XSS、认证越权等安全问题?
3. **性能**:是否有性能瓶颈或冗余计算?
4. **可维护性**:命名规范、代码结构、注释质量
5. **最佳实践**:是否遵循语言/框架的惯用写法

## 输出格式

请用以下格式输出审查结果:

### 发现的问题

| 严重程度 | 位置 | 问题描述 | 建议 |
|---------|------|---------|------|
| 🔴 严重 | 第X行 | ... | ... |
| 🟡 一般 | 第X行 | ... | ... |
| 🟢 建议 | 第X行 | ... | ... |

### 改进建议

1. 具体建议...
2. 优化方向...

### 代码优化版

(如果发现问题,给出优化后的完整代码)

---

待审查代码:

```{language}
{code}
```"""

2.2 自定义提示词模板

在Continue中创建常用提示词模板:

## 模板一:代码解释

请解释以下代码的功能、输入输出和关键逻辑步骤:

{selectedCode}
使用思维链方式,先分析整体架构,再逐行解释关键部分。


## 模板二:添加注释

为以下代码添加符合语言规范的注释,包括:

  1. 文件/模块级别注释(功能、作者、日期)
  2. 函数注释(功能、参数、返回值、异常)
  3. 关键逻辑的说明注释

{selectedCode}


## 模板三:重构建议

请对以下代码进行重构评估:

  1. 是否存在重复代码可以提取?
  2. 函数是否过长需要拆分?
  3. 命名是否需要改进?
  4. 是否可以用设计模式优化?
  5. 如果重构,给出具体方案

{selectedCode}


## 模板四:单元测试生成

请为以下函数生成全面的单元测试:

  • 覆盖正常输入、边界情况、异常输入
  • 使用 pytest 框架
  • 包含 mock 外部依赖
  • 测试命名要有描述性

{selectedCode}

2.3 自动提交消息生成

# git_commit.py — 根据Git diff生成提交信息
import subprocess
import requests

def generate_commit_message():
    """根据git diff生成提交信息"""
    
    # 获取git diff
    diff = subprocess.getoutput("git diff --cached --stat")
    diff_content = subprocess.getoutput("git diff --cached")
    
    if not diff_content:
        print("没有暂存的变更")
        return
    
    prompt = f"""基于以下Git变更信息,生成一个规范的提交消息。

变更文件概览:
{diff}

变更详情:
{diff_content}

请按以下格式生成提交消息:

(): <简短描述(不超过50字)>

<详细描述(可选,每行不超过72字)>

<关联Issue(可选)>


type 必须是以下之一:feat, fix, docs, style, refactor, perf, test, chore, ci
请用中文写描述。
"""
    
    response = requests.post("http://localhost:11434/api/generate", json={
        "model": "qwen2.5-coder:7b",
        "prompt": prompt,
        "stream": False,
        "options": {"temperature": 0.3}
    })
    
    return response.json()["response"]

if __name__ == "__main__":
    msg = generate_commit_message()
    print("\n推荐提交信息:")
    print(msg)

三、项目级代码理解

3.1 RAG接入项目代码库

把整个项目代码当知识库,问AI任何代码问题:

# project_rag.py — 项目代码RAG
import os
from pathlib import Path
from typing import List, Dict
import requests

class ProjectCodeRAG:
    """项目代码RAG系统"""
    
    def __init__(self, project_path: str):
        self.project_path = Path(project_path)
        self.ollama_base = "http://localhost:11434"
        self.code_embeddings = []  # [(embedding, filepath, snippet), ...]
        self.supported_exts = {'.py', '.js', '.ts', '.jsx', '.tsx', 
                               '.java', '.go', '.rs', '.cpp', '.h',
                               '.vue', '.css', '.scss', '.json', '.yaml'}
        self._index_project()
    
    def _split_code(self, content: str, filepath: str) -> List[Dict]:
        """按函数/类分割代码"""
        import re
        
        chunks = []
        
        # 匹配函数定义
        func_pattern = r'(?:def |function |public |private |protected |fn |export )\w+\s*\([^)]*\)'
        # 匹配类定义
        class_pattern = r'(?:class |struct |interface )\w+'
        
        all_matches = []
        for pattern in [class_pattern, func_pattern]:
            for match in re.finditer(pattern, content):
                all_matches.append((match.start(), match.end(), match.group()))
        
        # 如果没找到函数/类,按文件整体处理
        if not all_matches:
            return [{"file": filepath, "content": content[:2000]}]
        
        # 提取每个代码块
        all_matches.sort()
        for i, (start, end, name) in enumerate(all_matches):
            # 获取代码块结束位置(下一个匹配开始或文件结束)
            chunk_end = all_matches[i+1][0] if i + 1 < len(all_matches) else len(content)
            chunk_content = content[start:chunk_end].strip()
            
            if len(chunk_content) > 50:  # 过滤太短的片段
                chunks.append({
                    "file": filepath,
                    "name": name,
                    "content": chunk_content
                })
        
        return chunks
    
    def _index_project(self):
        """构建项目索引"""
        print(f"正在索引项目:{self.project_path}")
        
        for file_path in self.project_path.rglob('*'):
            if file_path.suffix.lower() not in self.supported_exts:
                continue
            if 'node_modules' in str(file_path) or '.git' in str(file_path):
                continue
            
            try:
                content = file_path.read_text(encoding='utf-8', errors='ignore')
                chunks = self._split_code(content, str(file_path.relative_to(self.project_path)))
                
                for chunk in chunks:
                    # 获取向量
                    response = requests.post(f"{self.ollama_base}/api/embeddings", json={
                        "model": "nomic-embed-text",
                        "prompt": chunk["content"]
                    })
                    
                    if response.status_code == 200:
                        embedding = response.json()["embedding"]
                        self.code_embeddings.append((embedding, chunk))
                
                print(f"  ✓ {file_path.name}")
            except Exception as e:
                print(f"  ✗ {file_path.name}: {e}")
        
        print(f"\n共索引 {len(self.code_embeddings)} 个代码片段")
    
    def query(self, question: str, top_k: int = 5) -> str:
        """查询项目代码"""
        import numpy as np
        
        # 向量化问题
        response = requests.post(f"{self.ollama_base}/api/embeddings", json={
            "model": "nomic-embed-text",
            "prompt": question
        })
        query_embedding = response.json()["embedding"]
        
        # 计算相似度
        similarities = []
        for emb, chunk in self.code_embeddings:
            sim = np.dot(emb, query_embedding) / (
                np.linalg.norm(emb) * np.linalg.norm(query_embedding)
            )
            similarities.append((sim, chunk))
        
        similarities.sort(key=lambda x: x[0], reverse=True)
        top_chunks = similarities[:top_k]
        
        # 构建上下文
        context = ""
        for i, (sim, chunk) in enumerate(top_chunks):
            context += f"\n### 文件 {chunk['file']}(相似度:{sim:.2f})\n```\n{chunk['content']}\n```\n"
        
        # 生成回答
        prompt = f"""基于以下项目代码,回答问题。

## 项目代码片段

{context}

## 问题
{question}

## 回答要求
1. 引用具体的文件路径和代码行
2. 如果代码中没有相关信息,请说明
3. 保持回答简洁、精确
"""
        
        response = requests.post(f"{self.ollama_base}/api/generate", json={
            "model": "qwen2.5-coder:7b",
            "prompt": prompt,
            "stream": False,
            "options": {"temperature": 0.3}
        })
        
        return response.json()["response"]

# 使用示例
# rag = ProjectCodeRAG("/path/to/your/project")
# result = rag.query("这个项目如何进行用户认证?")
# print(result)

3.2 代码库文档自动生成

# doc_gen.py — 自动生成项目文档
import os
from pathlib import Path

class ProjectDocGenerator:
    """项目文档自动生成器"""
    
    def __init__(self, project_path: str):
        self.project_path = Path(project_path)
        self.ollama_base = "http://localhost:11434"
    
    def generate_readme(self):
        """生成README.md"""
        # 获取项目结构
        structure = self._get_project_structure()
        # 获取主要文件内容摘要
        summaries = self._get_main_files_summary()
        
        prompt = f"""根据以下项目信息,生成一个专业的README.md文档。

项目结构:
{structure}

主要文件摘要:
{summaries}

请生成README.md,包含:
1. 项目名称和简介
2. 技术栈
3. 核心功能
4. 快速开始(安装+使用)
5. 项目结构说明
6. 贡献指南
7. 许可信息

格式要专业、清晰,使用Markdown。
"""
        
        response = requests.post(f"{self.ollama_base}/api/generate", json={
            "model": "qwen2.5:14b",
            "prompt": prompt,
            "stream": False
        })
        
        readme_content = response.json()["response"]
        
        # 写入文件
        output_path = self.project_path / "README.md"
        output_path.write_text(readme_content, encoding='utf-8')
        print(f"README.md 已生成:{output_path}")
    
    def _get_project_structure(self, max_depth=3):
        """获取项目目录结构"""
        structure = []
        self._walk_dir(self.project_path, 0, max_depth, structure)
        return '\n'.join(structure)
    
    def _walk_dir(self, path, depth, max_depth, result):
        if depth > max_depth:
            return
        if path.name.startswith('.') or path.name == 'node_modules':
            return
        
        indent = '  ' * depth
        if path.is_dir():
            result.append(f"{indent}📁 {path.name}/")
            for child in sorted(path.iterdir()):
                self._walk_dir(child, depth + 1, max_depth, result)
        elif path.is_file():
            result.append(f"{indent}📄 {path.name}")
    
    def _get_main_files_summary(self):
        """获取主要文件的摘要"""
        main_files = [
            "package.json", "pyproject.toml", "Cargo.toml",
            "main.py", "app.py", "index.js", "main.go",
            "docker-compose.yml", "Dockerfile", ".env.example"
        ]
        
        summaries = []
        for fname in main_files:
            fpath = self.project_path / fname
            if fpath.exists():
                content = fpath.read_text(encoding='utf-8', errors='ignore')[:500]
                summaries.append(f"### {fname}\n```\n{content}\n```")
        
        return '\n\n'.join(summaries)

四、Git工作流集成

4.1 自动代码审查Hook

创建Git pre-commit hook,提交前自动审查代码:

#!/bin/bash
# .git/hooks/pre-commit — 提交前代码审查

echo "🔍 运行AI代码审查..."

# 获取暂存的文件
staged_files=$(git diff --cached --name-only --diff-filter=ACM)

for file in $staged_files; do
    # 只审查代码文件
    case "$file" in
        *.py|*.js|*.ts|*.java|*.go|*.rs)
            # 获取diff内容
            diff_content=$(git diff --cached "$file")
            
            if [ ${#diff_content} -gt 100 ]; then
                echo "审查: $file"
                
                # 调用Ollama审查
                response=$(curl -s -X POST http://localhost:11434/api/generate \
                    -d "{\"model\":\"qwen2.5-coder:7b\",\"prompt\":\"审查以下代码变更,只输出'通过'或有问题的具体行号和建议,控制在200字以内:\n$diff_content\",\"stream\":false}")
                
                result=$(echo "$response" | python3 -c "import sys,json;print(json.load(sys.stdin).get('response',''))" 2>/dev/null)
                
                if echo "$result" | grep -qi "严重\|bug\|安全"; then
                    echo "⚠️ 发现严重问题:"
                    echo "$result"
                    exit 1
                fi
            fi
            ;;
    esac
done

echo "✅ AI审查通过"

4.2 自动生成Changelog

# changelog.py — 自动生成Changelog
import subprocess
import requests
from datetime import datetime

def generate_changelog(since_tag: str = None):
    """根据Git log生成Changelog"""
    
    # 获取Git日志
    if since_tag:
        log_cmd = f"git log {since_tag}..HEAD --oneline --no-merges"
    else:
        log_cmd = "git log --oneline --no-merges -30"
    
    git_log = subprocess.getoutput(log_cmd)
    
    prompt = f"""根据以下Git提交记录,生成规范的CHANGELOG。

提交记录:
{git_log}

请按以下格式输出:
## [版本号] - 发布日期

### 新增功能
- ...

### Bug修复
- ...

### 重构/优化
- ...

### 文档
- ...

格式要求:
1. 用中文总结变更
2. 按功能分类
3. 每个变更点一行,简洁描述
4. 去掉Git commit hash
"""

    response = requests.post("http://localhost:11434/api/generate", json={
        "model": "qwen2.5:7b",
        "prompt": prompt,
        "stream": False,
        "options": {"temperature": 0.3}
    })
    
    return response.json()["response"]

五、综合效率工具:AI命令助手

5.1 Shell命令生成

# shell_assistant.py — 终端命令助手
def ask_shell(task_description: str) -> str:
    """把自然语言转换为shell命令"""
    
    prompt = f"""用户需要完成以下任务,请生成对应的Shell/PowerShell命令:

任务:{task_description}

要求:
1. 只输出命令本身,不要解释
2. 确保命令安全(不加rm -rf等破坏性命令)
3. 如果任务有风险,先输出说明再提供命令
4. 使用Windows PowerShell兼容的语法
"""
    
    response = requests.post("http://localhost:11434/api/generate", json={
        "model": "deepseek-r1:7b",
        "prompt": prompt,
        "stream": False,
        "options": {"temperature": 0.2}
    })
    
    return response.json()["response"]

# 使用
print(ask_shell("查找所有超过100MB的Python文件"))
# 输出:Get-ChildItem -Recurse -Filter *.py | Where-Object { $_.Length -gt 100MB }

5.2 代码脚手架生成

# scaffold.py — 项目脚手架生成
def generate_scaffold(project_type: str, project_name: str) -> str:
    """生成项目脚手架结构"""
    
    prompt = f"""生成一个 {project_type} 项目的脚手架。

项目名称:{project_name}

输出格式:

project_name/
├── src/
│ ├── init.py
│ ├── …
├── tests/
│ ├── …
├── README.md
├── requirements.txt


对每个文件给出其核心内容(关键代码,不是全部),用注释标注文件路径。
"""
    
    response = requests.post("http://localhost:11434/api/generate", json={
        "model": "qwen2.5-coder:7b",
        "prompt": prompt,
        "stream": False,
        "options": {"temperature": 0.4}
    })
    
    return response.json()["response"]

# 快速生成Flask API项目骨架
print(generate_scaffold("Flask REST API", "my-api"))

六、监控与统计

6.1 使用统计看板

# stats.py — AI助手使用统计
import json
import os
from datetime import datetime

class UsageStats:
    """AI助手使用统计"""
    
    def __init__(self, stats_file="ai_stats.json"):
        self.stats_file = stats_file
        self.stats = self._load()
    
    def _load(self):
        if os.path.exists(self.stats_file):
            with open(self.stats_file) as f:
                return json.load(f)
        return {"total_requests": 0, "by_date": {}, "by_model": {}}
    
    def record(self, model: str, tokens: int, duration: float):
        """记录一次调用"""
        today = datetime.now().strftime("%Y-%m-%d")
        
        self.stats["total_requests"] += 1
        self.stats["by_date"][today] = self.stats["by_date"].get(today, 0) + 1
        self.stats["by_model"][model] = self.stats["by_model"].get(model, 0) + 1
        
        self._save()
    
    def _save(self):
        with open(self.stats_file, 'w') as f:
            json.dump(self.stats, f, indent=2, ensure_ascii=False)
    
    def report(self):
        """生成使用报告"""
        print("=" * 50)
        print("AI助手使用统计")
        print("=" * 50)
        print(f"总调用次数:{self.stats['total_requests']}")
        print(f"\n按日期:")
        for date, count in sorted(self.stats["by_date"].items()):
            print(f"  {date}: {count}次")
        print(f"\n按模型:")
        for model, count in sorted(self.stats["by_model"].items()):
            print(f"  {model}: {count}次")

# 使用
stats = UsageStats()
stats.record("qwen2.5-coder:7b", 256, 1.5)
stats.report()

七、总结与展望

7.1 整本书回顾

这个系列从零开始,带你走完了本地部署大模型的完整旅程:

① 硬件选型 → ② Ollama部署 → ③ RAG知识库
④ API服务化 → ⑤ 前端界面 → ⑥ 多模型管理
⑦ 性能优化 → ⑧ 编程助手 → 完成!🎉

7.2 进阶方向

本地大模型的世界远不止于此。接下来你可以探索:

  • 模型微调:用LoRA在自有数据上训练专属模型
  • 多模态:部署视觉模型(LLaVA、Qwen-VL)
  • Agent系统:让AI自主调用工具和执行任务
  • 分布式推理:多机多卡集群部署

7.3 资源推荐

Ollama官网:https://ollama.com
模型下载:https://ollama.com/library
Continue插件:https://continue.dev
Open WebUI:https://github.com/open-webui/open-webui
Dify平台:https://github.com/langgenius/dify

全文完

8篇文章,从选显卡到写代码,你一定已经拥有了一套完整的本地AI工作环境。数据安全、离线可用、无限调用——自己的模型,才是最好的模型。

需要完整脚本和配置文件的同学,可以看我主页的付费资源专栏。

有问题欢迎评论区留言,大家一起讨论!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐