Qwen3-ASR-1.7B与VSCode插件开发:语音编程助手教程
1. 为什么需要语音编程助手
写代码时,手指在键盘上飞舞,但有时候思路卡住了,想快速记录一个想法,或者正在调试时想临时加个注释,却不想打断当前的专注状态。这时候,如果能直接说出“给这个函数加个错误处理”或者“把这行注释掉”,代码就自动完成,会是什么体验?
这不是科幻场景。Qwen3-ASR-1.7B作为一款开源语音识别模型,已经能在中文、英文及22种方言场景下稳定输出高质量文本,尤其在带背景音、语速较快或口音较重的情况下依然保持可靠识别率。它不依赖云端API,可以本地部署,响应快、隐私好,特别适合集成进开发工具里。
而VSCode作为目前最主流的代码编辑器,拥有成熟的插件生态和清晰的扩展机制。把这两者结合起来,就能做出一个真正属于程序员自己的语音编程助手——不需要联网、不上传语音、不依赖第三方服务,所有识别都在本地完成,说出口令,代码就生成。
这个教程不会从零讲ASR原理,也不会堆砌一堆配置参数。我会带你一步步搭建起一个可运行的VSCode插件,核心功能包括:实时语音监听、命令识别、自然语言转代码片段、快捷指令映射。整个过程用的是真实开发中会遇到的路径、问题和解法,不是理想化的Demo。
2. 开发前的环境准备
2.1 确认系统与Python环境
语音识别对计算资源有一定要求,但Qwen3-ASR-1.7B在消费级显卡(如RTX 3060及以上)或带核显的现代笔记本上都能流畅运行。如果你没有独立显卡,也不用担心——我们默认使用CPU推理模式,识别延迟稍高(约1.5秒内),但完全可用。
首先确认你已安装Python 3.9或更高版本:
python --version # 应输出类似:Python 3.10.12推荐使用虚拟环境隔离依赖,避免与其他项目冲突:
python -m venv asr-env source asr-env/bin/activate # macOS/Linux # 或 asr-env\Scripts\activate.bat # Windows2.2 安装Qwen3-ASR本地推理服务
Qwen3-ASR官方提供了开箱即用的推理框架,我们直接使用它启动一个轻量HTTP服务,供VSCode插件调用。
安装核心依赖:
pip install torch transformers accelerate sentencepiece safetensors pip install git+https://github.com/QwenLM/Qwen3-ASR.git下载模型权重(首次运行会自动触发):
# 模型将缓存在 ~/.cache/huggingface/hub/ from qwen3_asr import Qwen3ASR # 这行会触发模型下载(约3.2GB),耐心等待 model = Qwen3ASR.from_pretrained("Qwen/Qwen3-ASR-1.7B")为简化后续调用,我们写一个最小化服务脚本asr_server.py:
# asr_server.py from flask import Flask, request, jsonify from qwen3_asr import Qwen3ASR import torch import numpy as np import io import wave app = Flask(__name__) model = None @app.before_first_request def load_model(): global model print("Loading Qwen3-ASR-1.7B...") model = Qwen3ASR.from_pretrained("Qwen/Qwen3-ASR-1.7B") model.eval() if torch.cuda.is_available(): model = model.to("cuda") @app.route("/transcribe", methods=["POST"]) def transcribe(): if 'audio' not in request.files: return jsonify({"error": "No audio file provided"}), 400 audio_file = request.files['audio'] audio_bytes = audio_file.read() # 将WAV字节流转换为numpy数组(16-bit PCM, mono, 16kHz) try: with io.BytesIO(audio_bytes) as f: with wave.open(f, 'rb') as wav: n_channels, sampwidth, framerate, n_frames, comptype, compname = wav.getparams() if framerate != 16000 or n_channels != 1: return jsonify({"error": "Only 16kHz mono WAV supported"}), 400 audio_data = np.frombuffer(wav.readframes(n_frames), dtype=np.int16) audio_array = audio_data.astype(np.float32) / 32768.0 # 归一化到[-1, 1] except Exception as e: return jsonify({"error": f"Audio decode failed: {str(e)}"}), 400 try: result = model.transcribe(audio_array) return jsonify({ "text": result["text"], "language": result.get("language", "unknown"), "duration_sec": len(audio_array) / 16000 }) except Exception as e: return jsonify({"error": f"Transcription failed: {str(e)}"}), 500 if __name__ == "__main__": app.run(host="127.0.0.1", port=8000, debug=False)启动服务:
python asr_server.py # 输出:* Running on http://127.0.0.1:8000现在,你的本地ASR服务已就绪。你可以用curl测试一下:
# 准备一个1秒的静音WAV(或用手机录一句“你好世界”) curl -X POST http://127.0.0.1:8000/transcribe \ -F "audio=@test.wav" \ -H "Content-Type: multipart/form-data"正常应返回类似:
{"text": "你好世界", "language": "zh", "duration_sec": 1.2}2.3 创建VSCode插件基础结构
VSCode插件本质是一个Node.js程序。我们用官方脚手架快速初始化:
npm install -g yo generator-code yo code按提示选择:
- New Extension (TypeScript)
- 扩展名称:
voice-coder - 作者名:你的名字
- 描述:A voice programming assistant powered by Qwen3-ASR-1.7B
- 初始化Git仓库:Yes
进入项目目录:
cd voice-coder npm install此时,src/extension.ts是插件主入口。我们先不做任何修改,确保基础插件能正常加载:
# 在VSCode中按 Ctrl+Shift+P(Windows/Linux)或 Cmd+Shift+P(macOS) # 输入 "Developer: Install Extension from VSIX..." 并选择 ./dist/voice-coder-0.0.1.vsix # 重启VSCode,按 Ctrl+Shift+P 输入 "Hello World",应看到弹窗一切就绪。接下来,我们开始让这个“Hello World”真正开口说话。
3. 实现语音监听与命令识别
3.1 在插件中接入麦克风
VSCode插件运行在Electron渲染进程中,无法直接访问navigator.mediaDevices.getUserMedia(),因为VSCode禁用了部分Web API的安全策略。但我们可以通过VSCode的webview机制绕过限制——创建一个隐藏的Webview页面,由它负责采集音频。
在src/extension.ts中添加:
import * as vscode from 'vscode'; export function activate(context: vscode.ExtensionContext) { // 注册命令:启动语音监听 let disposable = vscode.commands.registerCommand('voice-coder.startListening', async () => { const panel = vscode.window.createWebviewPanel( 'voiceListener', 'Voice Listener', vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [vscode.Uri.file(context.extensionPath)] } ); // 加载一个简单的HTML页面,用于录音 panel.webview.html = getWebviewContent(context.extensionPath); // 监听Webview发来的识别结果 panel.webview.onDidReceiveMessage( message => { if (message.command === 'transcription') { handleTranscription(message.text); } }, undefined, context.subscriptions ); }); context.subscriptions.push(disposable); } function getWebviewContent(extensionPath: string): string { return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Voice Listener</title> <style>body { margin: 0; padding: 0; background: transparent; }</style> </head> <body> <script> // 请求麦克风权限并开始录音 let mediaRecorder; let audioContext; let analyser; let dataArray; async function startRecording() { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // 创建AudioContext用于处理音频 audioContext = new (window.AudioContext || window.webkitAudioContext)(); const source = audioContext.createMediaStreamSource(stream); analyser = audioContext.createAnalyser(); analyser.fftSize = 256; source.connect(analyser); // 初始化MediaRecorder mediaRecorder = new MediaRecorder(stream); const chunks = []; mediaRecorder.ondataavailable = event => { chunks.push(event.data); }; mediaRecorder.onstop = () => { const blob = new Blob(chunks, { type: 'audio/wav' }); const reader = new FileReader(); reader.onload = () => { // 发送base64编码的WAV数据到VSCode插件 const base64 = reader.result.split(',')[1]; window.parent.postMessage({ command: 'sendAudio', data: base64 }, '*'); }; reader.readAsDataURL(blob); }; // 录制1.5秒后自动停止(短语音更易识别) setTimeout(() => { if (mediaRecorder.state === 'recording') { mediaRecorder.stop(); } }, 1500); mediaRecorder.start(); } catch (err) { console.error('Microphone access denied:', err); window.parent.postMessage({ command: 'error', message: 'Microphone access denied' }, '*'); } } // 页面加载完成后立即开始 window.addEventListener('load', startRecording); </script> </body> </html>`; } function handleTranscription(text: string) { // 这里是核心:把语音识别出的文本,映射成代码操作 console.log('Recognized:', text); const editor = vscode.window.activeTextEditor; if (!editor) return; // 简单示例:识别到特定指令就执行对应操作 if (text.includes('加注释')) { addComment(editor); } else if (text.includes('删除上一行')) { deletePreviousLine(editor); } else if (text.includes('生成函数')) { generateFunction(editor); } else { // 兜底:插入原始文本 const selection = editor.selection; editor.edit(editBuilder => { editBuilder.insert(selection.active, text); }); } } function addComment(editor: vscode.TextEditor) { const line = editor.document.lineAt(editor.selection.active.line); const indent = ' '.repeat(line.firstNonWhitespaceCharacterIndex); const comment = `${indent}// ${new Date().toLocaleTimeString()}`; editor.edit(editBuilder => { editBuilder.insert(new vscode.Position(line.lineNumber + 1, 0), '\\n' + comment); }); } function deletePreviousLine(editor: vscode.TextEditor) { const lineNum = editor.selection.active.line; if (lineNum > 0) { const range = new vscode.Range(lineNum - 1, 0, lineNum, 0); editor.edit(editBuilder => { editBuilder.delete(range); }); } } function generateFunction(editor: vscode.TextEditor) { const snippet = [ 'function ${1:name}(${2:params}) {', '\t${0:// body}', '}' ].join('\\n'); editor.insertSnippet(new vscode.SnippetString(snippet)); }这段代码做了三件事:
- 创建一个隐藏Webview,自动请求麦克风权限并录制1.5秒音频;
- 将录制的WAV转为base64发送回插件;
- 根据识别文本内容,执行预设的代码操作(加注释、删行、生成函数模板)。
注意:addComment、deletePreviousLine、generateFunction都是真实可用的VSCode编辑API,不是伪代码。
3.2 调用本地ASR服务
上面的Webview只负责录音,真正的语音识别要交给本地运行的Qwen3-ASR服务。我们在handleTranscription中不直接处理文本,而是把音频发给服务端:
修改getWebviewContent中的onstop处理逻辑:
mediaRecorder.onstop = () => { const blob = new Blob(chunks, { type: 'audio/wav' }); const reader = new FileReader(); reader.onload = async () => { try { // 发送WAV到本地ASR服务 const response = await fetch('http://127.0.0.1:8000/transcribe', { method: 'POST', body: blob, headers: { 'Content-Type': 'audio/wav' } }); const result = await response.json(); if (result.text) { // 将识别结果发回VSCode window.parent.postMessage({ command: 'transcription', text: result.text }, '*'); } else { throw new Error(result.error || 'Empty response'); } } catch (err) { console.error('ASR request failed:', err); window.parent.postMessage({ command: 'error', message: 'ASR service unavailable' }, '*'); } }; reader.readAsArrayBuffer(blob); };同时,在handleTranscription中移除直接处理逻辑,改为调用一个新函数:
async function handleTranscription(text: string) { console.log('ASR result:', text); const editor = vscode.window.activeTextEditor; if (!editor) return; // 解析自然语言指令 const action = parseVoiceCommand(text); if (action) { await executeAction(action, editor); } else { // 无法解析时,插入原始文本 const selection = editor.selection; editor.edit(editBuilder => { editBuilder.insert(selection.active, text); }); } } interface VoiceAction { type: 'insert' | 'comment' | 'delete' | 'snippet' | 'command'; payload?: string; } function parseVoiceCommand(text: string): VoiceAction | null { const lower = text.toLowerCase().trim(); if (lower.includes('加注释') || lower.includes('添加注释')) { return { type: 'comment' }; } if (lower.includes('删除上一行') || lower.includes('删掉上一行')) { return { type: 'delete' }; } if (lower.includes('生成函数') || lower.includes('创建函数')) { return { type: 'snippet', payload: 'function' }; } if (lower.includes('console log') || lower.includes('打印日志')) { return { type: 'insert', payload: 'console.log();' }; } if (lower.includes('for循环') || lower.includes('遍历数组')) { return { type: 'snippet', payload: 'for' }; } return null; } async function executeAction(action: VoiceAction, editor: vscode.TextEditor) { switch (action.type) { case 'comment': addComment(editor); break; case 'delete': deletePreviousLine(editor); break; case 'snippet': if (action.payload === 'function') { generateFunction(editor); } else if (action.payload === 'for') { insertForLoop(editor); } break; case 'insert': const selection = editor.selection; editor.edit(editBuilder => { editBuilder.insert(selection.active, action.payload || ''); }); break; } } function insertForLoop(editor: vscode.TextEditor) { const snippet = [ 'for (let i = 0; i < ${1:length}; i++) {', '\t${0:// body}', '}' ].join('\\n'); editor.insertSnippet(new vscode.SnippetString(snippet)); }现在,整个语音链路就通了:麦克风 → Webview录音 → HTTP POST到本地ASR服务 → 返回文本 → 解析指令 → 执行VSCode编辑操作。
4. 构建实用的语音命令映射系统
4.1 从固定指令到自然语言理解
上面的parseVoiceCommand是基于关键词匹配的,简单但脆弱。比如用户说“给我加个注释”,它能识别;但说“在这儿写个说明”就失败了。我们需要一个更鲁棒的方式。
Qwen3-ASR本身不提供NLU能力,但我们可以利用它的高精度识别结果,再加一层轻量级意图分类。这里不引入大模型,而是用规则+模糊匹配构建一个可维护的映射表。
在项目根目录新建commands.json:
{ "comment": { "patterns": [ "加注释", "添加注释", "写个注释", "这儿做个说明", "解释一下这个", "备注这个功能" ], "description": "在光标位置插入注释行" }, "delete": { "patterns": [ "删除上一行", "删掉上面那行", "去掉上边的", "清除前一行", "撤销上一步" ], "description": "删除光标所在行的上一行" }, "log": { "patterns": [ "console log", "打印日志", "输出到控制台", "log一下", "看看值是多少" ], "description": "插入 console.log() 语句" }, "for": { "patterns": [ "for循环", "遍历数组", "循环处理", "重复执行", "迭代这个" ], "description": "插入 for 循环模板" } }然后在插件中加载并使用它:
// src/commands.ts interface CommandPattern { patterns: string[]; description: string; } interface CommandsMap { [key: string]: CommandPattern; } let commandsMap: CommandsMap | null = null; export async function loadCommands(): Promise<CommandsMap> { if (commandsMap) return commandsMap; try { const configUri = vscode.Uri.joinPath( vscode.Uri.file(__dirname), '..', 'commands.json' ); const content = await vscode.workspace.fs.readFile(configUri); commandsMap = JSON.parse(content.toString()) as CommandsMap; return commandsMap; } catch (e) { console.error('Failed to load commands.json:', e); // 返回默认映射 return { "comment": { "patterns": ["加注释", "添加注释"], "description": "插入注释" } }; } } export function matchCommand(text: string): string | null { const map = commandsMap || {}; const lower = text.toLowerCase(); for (const [cmd, patternObj] of Object.entries(map)) { for (const pattern of patternObj.patterns) { if (lower.includes(pattern.toLowerCase())) { return cmd; } } } // 如果没匹配到,尝试模糊匹配(Levenshtein距离) return fuzzyMatch(text, map); } // 简单的模糊匹配(实际项目中可替换为 fast-levenshtein) function fuzzyMatch(text: string, map: CommandsMap): string | null { const lower = text.toLowerCase(); let bestMatch = null; let minDistance = 100; for (const [cmd, patternObj] of Object.entries(map)) { for (const pattern of patternObj.patterns) { const distance = levenshtein(lower, pattern.toLowerCase()); if (distance < minDistance && distance < 3) { minDistance = distance; bestMatch = cmd; } } } return bestMatch; } function levenshtein(a: string, b: string): number { if (a.length === 0) return b.length; if (b.length === 0) return a.length; const matrix: number[][] = []; for (let i = 0; i <= b.length; i++) { matrix[i] = [i]; } for (let j = 0; j <= a.length; j++) { matrix[0][j] = j; } for (let i = 1; i <= b.length; i++) { for (let j = 1; j <= a.length; j++) { if (b.charAt(i - 1) === a.charAt(j - 1)) { matrix[i][j] = matrix[i - 1][j - 1]; } else { matrix[i][j] = Math.min( matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1 ); } } } return matrix[b.length][a.length]; }在extension.ts中使用:
import { loadCommands, matchCommand } from './commands'; // 替换原来的 parseVoiceCommand async function parseVoiceCommand(text: string): Promise<VoiceAction | null> { const commands = await loadCommands(); const matched = matchCommand(text); if (matched === 'comment') return { type: 'comment' }; if (matched === 'delete') return { type: 'delete' }; if (matched === 'log') return { type: 'insert', payload: 'console.log();' }; if (matched === 'for') return { type: 'snippet', payload: 'for' }; return null; }这样,命令系统就具备了可配置性。团队成员可以随时编辑commands.json添加新指令,无需改代码。
4.2 支持上下文感知的智能补全
纯语音指令有时不够精确。比如用户说“把这个改成异步”,但“这个”指什么?我们需要结合编辑器上下文做判断。
VSCode提供了丰富的API获取当前状态。我们增强executeAction:
async function executeAction(action: VoiceAction, editor: vscode.TextEditor) { const document = editor.document; const selection = editor.selection; const line = document.lineAt(selection.active.line); switch (action.type) { case 'comment': // 检查光标是否在代码行上,如果是,注释该行;否则注释下一行 if (line.text.trim() && !line.text.trim().startsWith('//')) { const indent = ' '.repeat(line.firstNonWhitespaceCharacterIndex); const comment = `${indent}// ${line.text.trim()}`; editor.edit(editBuilder => { editBuilder.replace(line.range, comment); }); } else { addComment(editor); } break; case 'log': // 尝试提取变量名:光标前的单词 const wordRange = document.getWordRangeAtPosition( selection.active, /[\w$]+/g ); if (wordRange) { const word = document.getText(wordRange).trim(); if (word && word.length > 1) { const logText = `console.log('${word}:', ${word});`; editor.edit(editBuilder => { editBuilder.insert(selection.active, logText); }); return; } } // 默认插入空log const selectionEnd = selection.active.with(undefined, line.text.length); editor.edit(editBuilder => { editBuilder.insert(selectionEnd, 'console.log();'); }); break; case 'delete': // 删除上一行,但如果上一行是空行,继续往上找 let targetLine = selection.active.line - 1; while (targetLine >= 0) { const target = document.lineAt(targetLine); if (target.text.trim()) break; targetLine--; } if (targetLine >= 0) { const range = new vscode.Range(targetLine, 0, targetLine + 1, 0); editor.edit(editBuilder => { editBuilder.delete(range); }); } break; } }这种上下文感知让语音助手更像一个懂代码的同事,而不是机械的指令翻译器。
5. 提升体验的关键细节
5.1 语音反馈与状态可视化
用户说完话,需要明确的反馈:“我在听了”、“正在识别”、“已完成”。VSCode没有原生语音播放API,但我们可以通过状态栏和通知实现视觉反馈。
在activate函数中添加状态栏项:
let statusBarItem: vscode.StatusBarItem; export function activate(context: vscode.ExtensionContext) { statusBarItem = vscode.window.createStatusBarItem( vscode.StatusBarAlignment.Left, 100 ); statusBarItem.text = "$(mic) Voice Coder"; statusBarItem.tooltip = "Click to start listening"; statusBarItem.command = 'voice-coder.startListening'; statusBarItem.show(); // 注册命令 let disposable = vscode.commands.registerCommand('voice-coder.startListening', async () => { // 显示正在监听 statusBarItem.text = "$(sync~spin) Listening..."; statusBarItem.backgroundColor = new vscode.ThemeColor('statusBar.noFolderBackground'); const panel = vscode.window.createWebviewPanel( 'voiceListener', 'Voice Listener', vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [vscode.Uri.file(context.extensionPath)] } ); panel.webview.html = getWebviewContent(context.extensionPath); panel.webview.onDidReceiveMessage( async message => { if (message.command === 'transcription') { statusBarItem.text = "$(check) Done"; statusBarItem.backgroundColor = new vscode.ThemeColor('statusBar.successBackground'); await handleTranscription(message.text); // 2秒后恢复默认状态 setTimeout(() => { statusBarItem.text = "$(mic) Voice Coder"; statusBarItem.backgroundColor = undefined; }, 2000); } else if (message.command === 'error') { statusBarItem.text = "$(alert) Error"; statusBarItem.backgroundColor = new vscode.ThemeColor('statusBar.errorBackground'); vscode.window.showErrorMessage(`Voice Coder: ${message.message}`); setTimeout(() => { statusBarItem.text = "$(mic) Voice Coder"; statusBarItem.backgroundColor = undefined; }, 3000); } }, undefined, context.subscriptions ); }); context.subscriptions.push(disposable, statusBarItem); }这样,用户点击状态栏图标时,能看到实时状态变化,心里有底。
5.2 错误处理与降级策略
网络请求可能失败,ASR服务可能未启动,麦克风可能被占用。我们要让插件足够健壮:
- 当ASR服务不可达时,自动切换到浏览器内置的
SpeechRecognitionAPI(作为备用方案); - 当麦克风被拒绝时,给出明确指引;
- 识别失败时,提供重试按钮。
在Webview中添加降级逻辑:
// 尝试ASR服务,失败则回退到Web Speech API async function sendToASR(blob) { try { const response = await fetch('http://127.0.0.1:8000/transcribe', { method: 'POST', body: blob, headers: { 'Content-Type': 'audio/wav' } }); return await response.json(); } catch (e) { console.warn('ASR service failed, falling back to browser API'); return fallbackSpeechRecognition(blob); } } async function fallbackSpeechRecognition(blob) { return new Promise((resolve, reject) => { const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)(); recognition.lang = 'zh-CN'; recognition.interimResults = false; recognition.onresult = (event) => { const transcript = event.results[0][0].transcript; resolve({ text: transcript }); }; recognition.onerror = (event) => { reject(event.error); }; // 模拟从blob读取音频(实际中需转换) recognition.start(); }); }虽然浏览器API准确率不如Qwen3-ASR,但在紧急情况下能保证基本功能不中断。
5.3 性能优化与资源管理
持续监听麦克风会耗电,且可能引发隐私担忧。我们采用“按需激活”策略:
- 不常驻监听,每次点击才启动一次1.5秒录音;
- Webview在任务完成后自动销毁;
- ASR服务只在需要时启动(可配合VSCode的
onStartupFinished事件)。
在extension.ts中添加清理逻辑:
let currentPanel: vscode.WebviewPanel | null = null; function createWebviewPanel() { if (currentPanel) { currentPanel.dispose(); } currentPanel = vscode.window.createWebviewPanel( 'voiceListener', 'Voice Listener', vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: false, // 关键:隐藏时销毁 localResourceRoots: [vscode.Uri.file(context.extensionPath)] } ); currentPanel.onDidDispose(() => { currentPanel = null; }); return currentPanel; }这样,插件既轻量又安全,符合VSCode插件的最佳实践。
6. 总结
这个语音编程助手不是炫技的玩具,而是一个真正能融入日常开发流程的工具。它用Qwen3-ASR-1.7B解决了语音识别的核心难题——在中文复杂场景下的高准确率和稳定性;用VSCode插件机制实现了与编辑器的深度集成;用可配置的命令映射系统保证了长期可维护性。
实际用下来,最打动我的不是技术多酷,而是几个小细节带来的流畅感:状态栏的实时反馈让你知道它在工作;上下文感知的console.log能自动提取变量名;commands.json让非开发者也能参与功能扩展。这些设计让工具真正服务于人,而不是让人去适应工具。
当然,它还有提升空间:支持连续对话、加入代码语义理解、适配更多语言特性。但一个好的起点,从来不是追求完美,而是先解决一个真实痛点——比如,当你正沉浸在调试中,突然想到一个修复思路,不用切出键盘,只需说一句“加个try catch”,代码就出现在眼前。
如果你也想试试,整个项目已整理好,包含完整代码、配置说明和打包脚本。下一步,或许你可以为它加上“生成单元测试”或“解释选中代码”的功能,让它真正成为你专属的编程搭档。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。