通义千问1.5-1.8B-Chat-GPTQ-Int4项目实践:AI编程助手插件开发全流程

最近在折腾一个自己的编程小工具,想给常用的编辑器加个智能助手,能帮忙补全代码、写写注释什么的。市面上虽然有不少现成的AI编程插件,但要么收费不菲,要么响应速度慢,要么就是模型太大本地跑不动。于是我把目光投向了那些经过量化压缩的小模型,最终选定了通义千问的1.8B-Chat-GPTQ-Int4版本。这个模型个头小,对硬件要求低,但“脑子”还挺灵光,特别适合集成到本地插件里。

这篇文章,我就来和你分享一下,怎么从零开始,把这个小模型变成一个能实际干活儿的编程助手插件。整个过程不复杂,咱们一步步来,从技术选型聊到打包发布,目标是让你看完就能动手做一个属于自己的AI编程小帮手。

1. 为什么选择通义千问1.8B-Chat-GPTQ-Int4?

在开始敲代码之前,得先说说为什么选它。这决定了我们后面开发体验是顺畅还是坎坷。

首先,“小”就是优势。1.8B(18亿)参数,再经过GPTQ量化到INT4精度,整个模型文件可能就几百MB到1GB左右。这意味着它可以在消费级显卡(甚至一些集成显卡)上流畅运行,内存占用也友好。对于插件这种需要“随开随用”、不能太吃资源的场景,轻量化是首要考虑。

其次,“专”也很重要。通义千问-Chat版本是针对对话优化的,而编程助手本质上就是一个和开发者对话、理解代码上下文并给出建议的“对话场景”。这个模型在代码理解、指令跟随方面表现不错,虽然比不上那些动辄百亿参数的“巨无霸”,但对于代码补全、生成简单注释、解释错误信息这些常见需求,完全够用。

最后,“快”是体验关键。量化后的模型推理速度显著提升。你肯定不想写代码时,等一个补全建议要好几秒,那会打断思路。本地部署的轻量模型,延迟通常可以控制在毫秒到一两秒内,体验上就流畅多了。

简单来说,选它就像是给自家小店找了个“全能型”伙计:工资要求不高(硬件成本低),干活麻利(推理快),而且编程相关的活儿基本都能上手(能力匹配)。当然,它可能写不出非常复杂、创新的算法,但处理日常的重复性、模板化编码任务,绰绰有余。

2. 项目蓝图:插件架构设计

动手之前,咱们先画个草图,搞清楚这个插件由哪几部分组成,它们之间怎么“说话”。一个好的架构能让开发过程清晰,后期维护也省心。

我们的插件整体会采用经典的前后端分离架构,但都在本地运行。

2.1 核心组件拆解

想象一下这个插件的工作流程:你在编辑器里按下一个快捷键,插件前端收集当前的代码和你的请求,发送给后端模型,模型“思考”后返回结果,前端再把结果展示给你。对应到技术实现,就是下面几个部分:

  1. 模型服务后端:这是插件的“大脑”。它的核心任务就是加载通义千问模型,并提供一个HTTP API接口。前端发来请求,它调用模型推理,然后把生成的文本(代码、注释等)返回。我们会用比较流行的FastAPI来快速搭建这个服务。
  2. 编辑器插件前端:这是插件的“脸”和“手”。它负责集成到VS Code、JetBrains IDE等编辑器中,监听你的操作(比如快捷键),收集编辑器里的代码上下文,调用后端的API,最后把模型返回的结果插入到编辑器或者显示在UI面板里。这部分我们用TypeScript/JavaScript来写,兼容性更好。
  3. 通信桥梁:前后端通过HTTP API通信。我们定义好请求的格式(比如包含代码片段、问题提示、历史对话)和响应的格式(纯文本或结构化数据)。
  4. 配置管理:用户需要能简单配置,比如后端服务的地址(默认可能是localhost:8000)、触发的快捷键、哪些语言需要AI辅助等。

2.2 技术栈选择

  • 模型与推理:通义千问1.5-1.8B-Chat-GPTQ-Int4,使用 transformers 库加载,搭配 auto-gptq 进行量化模型推理。
  • 后端框架:Python + FastAPI。FastAPI轻量、异步支持好,自动生成API文档,开发调试方便。
  • 前端框架:取决于目标编辑器。
    • VS Code:使用其官方扩展API,基于Node.js/TypeScript开发。
    • JetBrains IDE (IntelliJ/PyCharm等):可以使用Java或Kotlin,或者利用其支持的前端技术(如基于WebView)。
    • 为了简化,本文将以VS Code扩展开发为例,因为其生态和文档最丰富。
  • 通信:RESTful HTTP API,数据格式用JSON。

架构图在脑子里清晰了,我们就可以开始准备“施工”环境了。

3. 环境搭建与模型服务部署

这是后端部分,我们先让模型的“大脑”转起来。

3.1 准备Python环境

建议使用Conda或venv创建一个独立的Python环境,避免包冲突。

# 使用conda创建环境
conda create -n qwen-coder-assistant python=3.10
conda activate qwen-coder-assistant

# 或者使用venv
python -m venv venv
# Windows: venv\Scripts\activate
# Linux/Mac: source venv/bin/activate

3.2 安装依赖库

安装核心的模型推理和Web框架包。

pip install torch transformers fastapi uvicorn
# 安装auto-gptq以支持GPTQ量化模型
pip install auto-gptq
# 可选,用于处理CORS(如果前端与后端域名端口不同)
pip install python-multipart

3.3 部署模型API服务

我们来创建一个简单的 model_server.py 文件。

# model_server.py
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
import uvicorn
import torch

# 定义API请求体的数据结构
class CodeCompletionRequest(BaseModel):
    prefix: str  # 光标前的代码
    suffix: str = ""  # 光标后的代码(可选,提供更多上下文)
    language: str = "python"  # 编程语言
    max_new_tokens: int = 128  # 生成的最大长度

class ChatRequest(BaseModel):
    message: str  # 用户的问题,例如“解释这段代码”或“为这个函数生成注释”
    code_context: str = ""  # 相关的代码上下文
    history: list = []  # 对话历史(可选)

# 初始化FastAPI应用
app = FastAPI(title="Qwen Coder Assistant API")

# 添加CORS中间件,允许前端跨域请求(开发时常用)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 生产环境应替换为具体的前端地址
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 全局变量,用于加载模型和tokenizer
model = None
tokenizer = None
chat_pipeline = None

@app.on_event("startup")
async def load_model():
    """启动时加载模型,避免每次请求都重复加载"""
    global model, tokenizer, chat_pipeline
    model_name = "Qwen/Qwen1.5-1.8B-Chat-GPTQ-Int4"  # Hugging Face模型ID

    print(f"正在加载模型: {model_name}...")
    try:
        tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
        # 注意:使用GPTQ量化模型需要指定 device_map 和 trust_remote_code
        model = AutoModelForCausalLM.from_pretrained(
            model_name,
            device_map="auto",  # 自动选择GPU或CPU
            trust_remote_code=True
        )
        # 创建文本生成的pipeline
        chat_pipeline = pipeline(
            "text-generation",
            model=model,
            tokenizer=tokenizer,
            max_new_tokens=512,
            temperature=0.7,  # 控制创造性,编程任务可以调低
            do_sample=True,
        )
        print("模型加载成功!")
    except Exception as e:
        print(f"模型加载失败: {e}")
        raise e

@app.get("/")
async def root():
    return {"message": "Qwen Coder Assistant API is running"}

@app.post("/api/v1/complete")
async def code_completion(request: CodeCompletionRequest):
    """代码补全端点"""
    if model is None:
        raise HTTPException(status_code=503, detail="Model not loaded")
    
    # 构建给模型的提示词。你可以根据效果调整这个模板。
    prompt = f"""你是一个AI编程助手。请根据下面的代码前缀,补全后续的代码。
编程语言:{request.language}
代码前缀:
```{request.language}
{request.prefix}

请只输出补全的代码部分,不要包含任何解释。"""

if request.suffix:
    prompt += f"\n代码后缀(仅作参考):\n```{request.language}\n{request.suffix}\n```"

try:
    # 这里使用pipeline进行生成
    result = chat_pipeline(prompt, max_new_tokens=request.max_new_tokens)[0]['generated_text']
    # 从生成的文本中提取出补全的部分(简单处理:移除提示词部分)
    completion = result.replace(prompt, "").strip()
    return {"completion": completion}
except Exception as e:
    raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/v1/chat") async def chat_with_ai(request: ChatRequest): """对话端点,用于生成注释、解释错误等""" if model is None: raise HTTPException(status_code=503, detail="Model not loaded")

# 构建对话提示词
system_prompt = "你是一个专业的编程助手,擅长解释代码、生成注释和回答编程问题。请用简洁清晰的语言回答。"
user_message = request.message
if request.code_context:
    user_message = f"相关代码:\n```\n{request.code_context}\n```\n问题:{request.message}"

# 简单处理对话历史(实际可以更复杂)
messages = [{"role": "system", "content": system_prompt}]
for h in request.history:
    messages.append(h)
messages.append({"role": "user", "content": user_message})

# 将消息列表转换为模型接受的格式(这里使用Qwen的对话格式)
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)

try:
    result = chat_pipeline(text, max_new_tokens=256)[0]['generated_text']
    # 提取助手的回复
    # 简单分割,实际应根据模型输出格式调整
    assistant_reply = result.split("assistant\n")[-1].strip() if "assistant\n" in result else result.split(system_prompt)[-1].strip()
    return {"reply": assistant_reply}
except Exception as e:
    raise HTTPException(status_code=500, detail=str(e))

if name == "main": # 启动服务,默认运行在 http://127.0.0.1:8000 uvicorn.run(app, host="127.0.0.1", port=8000)


保存文件后,在终端运行:

```bash
python model_server.py

看到“模型加载成功!”和Uvicorn启动的日志,就说明后端服务已经跑起来了。你可以用浏览器打开 http://127.0.0.1:8000/docs 看到自动生成的API文档,并且在那里测试 /api/v1/complete/api/v1/chat 接口。

大脑已经启动,接下来该为它制作一个交互界面了。

4. VS Code插件前端开发

现在,我们来打造插件的“脸面”,让它能在VS Code里和我们互动。VS Code扩展开发有一套固定的模式,跟着做不难。

4.1 创建扩展项目

首先,确保你安装了Node.js和VS Code。然后使用Yeoman和VS Code扩展生成器来搭建项目骨架。

# 全局安装Yeoman和VS Code扩展生成器
npm install -g yo generator-code

# 创建一个新目录并进入
mkdir qwen-coder-extension
cd qwen-coder-extension

# 运行生成器,按提示选择(这里选TypeScript)
yo code

在交互式命令行中,大致选择如下:

  • 选择 New Extension (TypeScript)
  • 输入扩展名,如 qwen-coder-assistant
  • 输入标识符(同上)
  • 输入描述
  • 是否初始化Git仓库,按需选择
  • 包管理器选择 npm

完成后,你会得到一个标准的VS Code扩展项目结构。

4.2 核心功能实现

我们需要修改 src/extension.ts 文件,并添加一些配置。

首先,定义我们的后端API地址(可以在配置中修改)。

// src/extension.ts
import * as vscode from 'vscode';

// 默认的后端API地址
const DEFAULT_API_BASE = 'http://127.0.0.1:8000';

async function callCodeCompletionAPI(prefix: string, suffix: string, language: string): Promise<string> {
    const config = vscode.workspace.getConfiguration('qwenCoder');
    const apiBase = config.get<string>('apiBaseUrl', DEFAULT_API_BASE);
    
    const response = await fetch(`${apiBase}/api/v1/complete`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ prefix, suffix, language, max_new_tokens: 128 })
    });
    
    if (!response.ok) {
        throw new Error(`API请求失败: ${response.statusText}`);
    }
    const data = await response.json();
    return data.completion || '';
}

async function callChatAPI(message: string, codeContext: string): Promise<string> {
    const config = vscode.workspace.getConfiguration('qwenCoder');
    const apiBase = config.get<string>('apiBaseUrl', DEFAULT_API_BASE);
    
    const response = await fetch(`${apiBase}/api/v1/chat`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message, code_context: codeContext })
    });
    
    if (!response.ok) {
        throw new Error(`API请求失败: ${response.statusText}`);
    }
    const data = await response.json();
    return data.reply || '';
}

然后,注册一个命令,用于触发代码补全。我们设计成:用户选中一段代码(或不选),然后运行命令,插件将光标前的代码发送给模型,并将补全结果插入到光标处。

// 在activate函数中注册命令
export function activate(context: vscode.ExtensionContext) {
    console.log('通义千问编程助手插件已激活');
    
    // 命令1:代码补全
    let disposableCompletion = vscode.commands.registerCommand('qwen-coder.completeCode', async () => {
        const editor = vscode.window.activeTextEditor;
        if (!editor) {
            vscode.window.showWarningMessage('请在活动编辑器中使用此命令。');
            return;
        }
        
        const document = editor.document;
        const position = editor.selection.active;
        const languageId = document.languageId;
        
        // 获取光标前的文本作为前缀
        const prefixStart = new vscode.Position(0, 0);
        const prefixRange = new vscode.Range(prefixStart, position);
        const prefixText = document.getText(prefixRange);
        
        // 获取光标后的文本作为后缀(可选,提供更多上下文)
        const suffixEnd = new vscode.Position(document.lineCount, 0);
        const suffixRange = new vscode.Range(position, suffixEnd);
        const suffixText = document.getText(suffixRange);
        
        vscode.window.withProgress({
            location: vscode.ProgressLocation.Notification,
            title: "AI正在思考...",
            cancellable: false
        }, async (progress) => {
            try {
                const completion = await callCodeCompletionAPI(prefixText, suffixText, languageId);
                if (completion) {
                    // 将补全的代码插入到光标位置
                    await editor.edit(editBuilder => {
                        editBuilder.insert(position, completion);
                    });
                    vscode.window.showInformationMessage('代码补全完成!');
                } else {
                    vscode.window.showWarningMessage('未获得补全建议。');
                }
            } catch (error: any) {
                vscode.window.showErrorMessage(`补全失败: ${error.message}`);
            }
        });
    });
    
    // 命令2:生成选中代码的注释/解释
    let disposableExplain = vscode.commands.registerCommand('qwen-coder.explainCode', async () => {
        const editor = vscode.window.activeTextEditor;
        if (!editor) {
            vscode.window.showWarningMessage('请在活动编辑器中使用此命令。');
            return;
        }
        
        const selection = editor.selection;
        const selectedText = editor.document.getText(selection);
        
        if (!selectedText.trim()) {
            vscode.window.showWarningMessage('请先选择一段代码。');
            return;
        }
        
        // 弹出一个输入框,让用户输入具体问题,或使用默认问题
        const userQuestion = await vscode.window.showInputBox({
            placeHolder: '例如:解释这段代码的功能,或直接按回车生成注释',
            prompt: '你想了解这段代码的什么?'
        });
        
        const message = userQuestion && userQuestion.trim() ? userQuestion : '请为这段代码生成简洁的注释或解释其功能。';
        
        vscode.window.withProgress({
            location: vscode.ProgressLocation.Notification,
            title: "AI正在分析代码...",
            cancellable: false
        }, async (progress) => {
            try {
                const explanation = await callChatAPI(message, selectedText);
                // 在一个新的输出面板或侧边栏显示结果
                // 这里简单使用信息框展示
                const panel = vscode.window.createWebviewPanel(
                    'codeExplanation',
                    'AI代码解释',
                    vscode.ViewColumn.Beside,
                    {}
                );
                panel.webview.html = `<!DOCTYPE html>
                <html>
                <body>
                    <h3>AI对代码的分析:</h3>
                    <pre style="background-color:#f4f4f4; padding:10px; border-radius:5px;">${selectedText}</pre>
                    <h3>解释/注释:</h3>
                    <div style="white-space: pre-wrap; background-color:#e8f4f8; padding:15px; border-radius:5px; border-left: 4px solid #2196F3;">${explanation.replace(/\n/g, '<br>')}</div>
                </body>
                </html>`;
            } catch (error: any) {
                vscode.window.showErrorMessage(`解释失败: ${error.message}`);
            }
        });
    });
    
    context.subscriptions.push(disposableCompletion, disposableExplain);
}

4.3 配置插件

我们需要修改 package.json 文件,添加上面注册的命令和配置项。

// 在 package.json 的 contributes 部分添加
"contributes": {
    "commands": [
        {
            "command": "qwen-coder.completeCode",
            "title": "Qwen Coder: AI Code Completion"
        },
        {
            "command": "qwen-coder.explainCode",
            "title": "Qwen Coder: Explain Selected Code"
        }
    ],
    "keybindings": [
        {
            "command": "qwen-coder.completeCode",
            "key": "ctrl+alt+c", // 或 "cmd+alt+c" on Mac
            "when": "editorTextFocus"
        }
    ],
    "configuration": {
        "title": "Qwen Coder Assistant",
        "properties": {
            "qwenCoder.apiBaseUrl": {
                "type": "string",
                "default": "http://127.0.0.1:8000",
                "description": "后端模型服务的API基础地址"
            }
        }
    }
}

现在,前端部分的核心功能就完成了。你可以按F5启动一个扩展开发宿主(Extension Development Host)来测试插件。在新的VS Code窗口里,打开一个代码文件,选中一些代码,然后通过命令面板(Ctrl+Shift+P)运行 Qwen Coder: Explain Selected Code 命令,看看效果。

5. 功能测试与打包发布

插件的基本骨架有了,但一个健壮的工具还需要打磨和包装。

5.1 本地测试与调试

  1. 启动后端:确保你的 model_server.py 在运行。
  2. 运行前端:在项目根目录按F5,会启动一个新的VS Code窗口(扩展开发宿主)。
  3. 测试功能
    • 在新窗口里写几行代码,将光标放在中间,按我们设置的快捷键(如Ctrl+Alt+C)或通过命令面板触发补全。
    • 选中一段代码,运行解释命令,查看生成的注释或解释是否合理。
  4. 检查配置:在VS Code设置中搜索“Qwen Coder”,可以修改API地址,如果你的后端运行在其他机器或端口上,这里就需要更新。
  5. 错误处理:尝试断开后端服务,看前端是否有友好的错误提示。我们在代码里用了 try...catchvscode.window.showErrorMessage 来捕获和显示错误。

5.2 打包与发布

当你对插件功能满意后,就可以打包分享给其他人了。

VS Code扩展使用vsce(VS Code Extensions)工具进行打包。

# 全局安装vsce
npm install -g @vscode/vsce

# 在项目根目录打包
vsce package

这会在当前目录生成一个 .vsix 文件(例如 qwen-coder-assistant-0.0.1.vsix)。其他人可以通过VS Code的“从VSIX安装...”选项来安装这个插件。

如果你想发布到VS Code市场,需要创建一个Azure DevOps账号并获取Personal Access Token (PAT),然后使用 vsce publish 命令。这涉及到更多步骤,可以参考官方文档。

5.3 后续优化思路

一个最小可行产品(MVP)完成了,但要让它更好用,还可以考虑:

  • 性能优化:后端可以启用更快的推理后端,如 vLLMTGI,或者使用 text-generation-inference 容器。
  • 上下文管理:改进提示词工程,让模型更好地理解复杂的代码文件和项目结构。
  • UI增强:实现一个更美观的侧边栏面板,支持对话历史、多种操作按钮(重构、调试、生成测试等)。
  • 多模型支持:让配置可以切换不同的本地模型或云端API。
  • 离线支持:确保所有依赖(包括模型文件)都能在离线环境下工作。

获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐