1. 项目概述:让AI听懂你的话,并为你干活

最近在折腾一个挺有意思的东西:一个完全在本地运行的、能听懂你说话并执行任务的AI助手。想象一下,你对着电脑说一句“帮我总结一下上周的会议纪要”,它就能自动打开文档、分析内容、生成摘要,整个过程无需联网,数据也完全留在你自己的机器上。这听起来像是科幻电影里的场景,但现在,利用像Whisper和Ollama这样的开源工具,我们完全可以在自己的电脑上搭建出来。

这个项目的核心,就是“ 语音控制 ”和“ 本地AI代理 ”的结合。语音控制负责将我们模糊的、口语化的指令转化为精确的文本指令;而本地AI代理则负责理解这个指令,并调用合适的工具或执行一系列操作来完成它。我选择Whisper作为语音识别的引擎,因为它开源、免费,且在多种语言和口音上表现相当出色。而Ollama则是一个极其方便的本地大语言模型(LLM)运行和管理框架,它让你可以像拉取Docker镜像一样,轻松地在本地部署和运行Llama 2、Mistral、CodeLlama等各类模型,无需复杂的配置。

这个组合的优势非常明显: 隐私性 (所有数据不出本地)、 可定制性 (你可以选择最适合你任务的模型)、 成本可控 (没有API调用费用)以及 离线可用 。无论是想自动化一些日常的文档处理、代码生成,还是构建一个智能的桌面助手,这个技术栈都提供了一个强大而灵活的起点。接下来,我就把自己从零搭建这个系统的完整过程、踩过的坑以及一些优化心得分享出来。

2. 核心组件选型与架构设计

2.1 为什么是Whisper + Ollama?

在开始动手之前,明确技术选型的理由很重要。市面上语音识别和LLM的方案很多,我最终锁定这个组合,是基于以下几个核心考量:

Whisper的优势在于“省心且强大” 。它由OpenAI开源,虽然在云端有更强大的版本,但其开源模型(特别是 base small medium 这几个尺寸)在本地CPU或GPU上运行已经能达到商用级别的准确率。它支持多语言,对背景噪音和不同口音的鲁棒性很好。最重要的是,它有一个简单的Python接口,几行代码就能完成高质量的语音转文本(STT),极大地降低了入门门槛。相比于训练自己的模型或使用某些需要复杂预处理的老旧方案,Whisper几乎是“开箱即用”的最佳选择。

Ollama的优势在于“极致简化” 。在它出现之前,在本地运行一个像Llama 2这样的模型,你需要处理模型下载、转换格式、配置加载器(如llama.cpp)、设置API服务等一系列繁琐步骤。Ollama把这一切都打包了。你只需要一句命令 ollama run llama2 ,它就会自动下载、优化并启动一个模型服务,同时提供一个兼容OpenAI API格式的本地端点( http://localhost:11434 )。这意味着,你可以直接使用为ChatGPT设计的SDK(如OpenAI Python库)来调用你自己的本地模型,生态兼容性极好。它管理模型版本、优化推理性能(自动利用GPU),让开发者能完全聚焦在应用逻辑上。

架构设计思路 :整个系统的流程很清晰。首先,通过麦克风采集音频,交给Whisper处理成文本。然后,将这份文本作为“用户指令”,发送给由Ollama托管的本地大模型。这里的关键在于,我们不仅仅是将大模型当作一个聊天机器人,而是要将它升级为一个“ 代理(Agent) ”。这意味着,我们需要让模型具备“思考-行动-观察”的能力。具体来说,模型在收到指令后,应该能自主决定是否需要调用某个工具(比如搜索文件、执行Python代码、调用系统命令),然后根据工具返回的结果,再决定下一步动作,直到任务完成或无法继续。这个循环,就是智能代理的核心。

2.2 环境准备与基础依赖安装

我的开发环境是macOS,但步骤在Linux和WSL上基本通用。Windows原生支持可能需要对音频库做一些调整。

第一步:安装Ollama 这是最简单的部分。直接访问Ollama官网,下载对应系统的安装包,或者使用命令行安装。安装完成后,在终端运行 ollama --version 确认安装成功。然后,拉取一个合适的模型。对于代理任务,模型需要较强的推理和指令遵循能力。我推荐从 llama2 (7B或13B参数)或 mistral (7B)开始,它们对资源要求相对友好。

# 拉取并运行llama2模型(首次运行会自动下载)
ollama run llama2

运行后,它会进入一个交互式聊天界面,这证明模型服务已经在本地的11434端口启动了。你可以按 Ctrl+D 退出聊天,但服务会在后台继续运行。

第二步:配置Python环境与Whisper 我强烈建议使用 conda venv 创建独立的Python环境,避免包冲突。

# 创建并激活环境
conda create -n voice-agent python=3.10
conda activate voice-agent

# 安装核心依赖
pip install openai-whisper  # 核心语音识别库
pip install sounddevice pyaudio  # 音频采集
pip install numpy scipy  # 音频处理相关
pip install openai  # 用于调用Ollama的API(兼容格式)

这里有个 关键点 pyaudio 在某些系统上可能需要额外安装系统依赖。在macOS上通常没问题,在Ubuntu上可能需要 sudo apt-get install portaudio19-dev python3-pyaudio 。如果安装失败,可以尝试先安装 portaudio

第三步:验证Whisper 下载Whisper模型是自动的,但第一次运行时会下载对应尺寸的模型文件(如 base small )。我们可以写一个简单的脚本来测试:

import whisper
model = whisper.load_model("base") # 首次运行会下载模型
result = model.transcribe("test_audio.wav") # 准备一个测试音频文件
print(result["text"])

如果能看到识别出的文字,说明Whisper工作正常。模型尺寸选择上, base 速度最快, small 是精度和速度的较好平衡, medium large 精度更高但更耗资源。对于实时语音控制, small 通常是个不错的起点。

3. 核心模块实现:从语音到智能行动

3.1 实时语音采集与识别模块

构建语音控制的第一步,是让程序能持续“聆听”并识别我们的声音。这里的目标是实现一个 语音活动检测(VAD) 驱动的录音机制,即只有检测到人声时才录音,说完自动停止并识别,而不是录制固定长度的片段。

实现思路 :我们使用 sounddevice 库来捕获原始音频流,同时计算音频能量的移动平均值。当能量超过阈值(表示开始说话)时,开始将音频数据存入缓冲区;当能量低于阈值并持续一段时间(表示说话结束)时,停止录音,并将缓冲区数据交给Whisper处理。

import sounddevice as sd
import numpy as np
import whisper
import queue
import threading
from scipy.io.wavfile import write
import tempfile
import os

class VoiceRecorder:
    def __init__(self, model_size="small", energy_threshold=500, silence_duration=1.0):
        self.model = whisper.load_model(model_size)
        self.energy_threshold = energy_threshold
        self.silence_duration = silence_duration # 静音多久判定为结束
        self.sample_rate = 16000 # Whisper推荐采样率
        self.audio_queue = queue.Queue()
        self.is_recording = False
        self.audio_buffer = []

    def _audio_callback(self, indata, frames, time, status):
        """声音回调函数,每次采集到音频块时调用"""
        if status:
            print(f"音频流错误: {status}")
        # 计算当前音频块的能量(均方根)
        energy = np.sqrt(np.mean(indata**2)) * 1000
        # 简单的VAD逻辑
        if energy > self.energy_threshold and not self.is_recording:
            print("检测到语音,开始录音...")
            self.is_recording = True
            self.audio_buffer = [indata.copy()] # 开始新的录音
        elif energy <= self.energy_threshold and self.is_recording:
            # 静音中,但还在录音期内,继续缓存
            self.audio_buffer.append(indata.copy())
        elif self.is_recording:
            # 能量高,持续录音
            self.audio_buffer.append(indata.copy())

        # 检查是否静音超时
        if self.is_recording:
            # 这里简化处理:在实际中,需要维护一个静音计时器
            # 为了示例清晰,我们在另一个线程中处理结束逻辑
            pass

    def listen_and_transcribe(self):
        """开始监听,并在检测到语句结束后返回识别文本"""
        print("请开始说话...")
        with sd.InputStream(callback=self._audio_callback,
                           channels=1,
                           samplerate=self.sample_rate,
                           blocksize=int(self.sample_rate * 0.1)): # 100ms的块
            # 这里需要更复杂的逻辑来管理录音开始/结束。
            # 一个更健壮的方法是使用专门的VAD库,如webrtcvad
            # 为了简化,我们改为录制固定5秒作为示例
            sd.sleep(5000) # 监听5秒

        # 将缓冲区的音频数据拼接并保存为临时文件
        if self.audio_buffer:
            audio_data = np.concatenate(self.audio_buffer, axis=0)
            with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmpfile:
                write(tmpfile.name, self.sample_rate, (audio_data * 32767).astype(np.int16)) # 转换为16位PCM
                result = self.model.transcribe(tmpfile.name, fp16=False) # CPU上运行禁用fp16
                os.unlink(tmpfile.name) # 删除临时文件
                text = result["text"].strip()
                print(f"识别结果: {text}")
                return text
        return ""

注意 :上面的VAD逻辑是极简版,仅用于演示原理。在实际应用中,直接使用 webrtcvad 库会可靠得多。它由WebRTC项目提供,能更准确地区分人声和静音。你需要安装 pip install webrtcvad ,并实现一个基于该库的录音状态机。

3.2 集成Ollama:构建本地AI对话能力

有了文本指令,下一步就是让AI理解并回应。由于Ollama提供了兼容OpenAI API的接口,我们可以直接使用 openai 这个官方库来调用,这省去了大量自定义HTTP请求的麻烦。

首先,确保Ollama服务正在运行(运行过 ollama run 即可)。然后,我们需要配置OpenAI客户端,将其指向本地端点。

from openai import OpenAI

class LocalAIClient:
    def __init__(self, base_url="http://localhost:11434/v1", model="llama2"):
        # 关键:将api_key设为任意非空字符串,因为Ollama不验证
        self.client = OpenAI(base_url=base_url, api_key="ollama")
        self.model = model

    def chat_completion(self, prompt, system_prompt=None, temperature=0.7):
        """发送对话请求"""
        messages = []
        if system_prompt:
            messages.append({"role": "system", "content": system_prompt})
        messages.append({"role": "user", "content": prompt})

        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                temperature=temperature, # 控制创造性,任务执行建议较低值如0.1-0.3
                stream=False # 为简化,先不使用流式输出
            )
            return response.choices[0].message.content
        except Exception as e:
            print(f"调用AI模型失败: {e}")
            return None

# 测试
if __name__ == "__main__":
    ai_client = LocalAIClient()
    test_response = ai_client.chat_completion("你好,请介绍一下你自己。")
    print(f"AI回复: {test_response}")

现在,我们已经能将语音识别出的文本,发送给本地大模型并获得回复。但这还只是一个“聊天机器人”,而不是“代理”。代理的核心是 工具调用(Tool Calling)或函数调用(Function Calling)

3.3 实现AI代理:让模型学会使用工具

这是本项目最核心也最有趣的部分。我们需要赋予模型使用外部工具的能力。例如,当用户说“现在几点了?”,模型应该调用一个 get_current_time() 函数;当用户说“创建一个名为‘demo.txt’的文件”,模型应该调用 create_file(filename, content) 函数。

实现策略 :目前,像GPT-4这样的高级模型原生支持函数调用。但对于本地运行的Llama 2等模型,我们需要采用一种“提示工程(Prompt Engineering)”的方法来模拟这一过程。基本思路是:

  1. 在系统提示(System Prompt)中,明确定义模型可以使用的工具(函数),包括函数名、描述和参数格式。
  2. 要求模型在需要时,严格按照特定格式(如JSON)输出它的“思考过程”和“行动决定”。
  3. 我们的程序解析模型的输出,如果发现工具调用指令,就执行对应的Python函数,并将结果作为新的上下文反馈给模型。
  4. 模型根据反馈,决定下一步是继续调用工具,还是给出最终答案。

我们首先定义几个简单的工具:

import json
import datetime
import subprocess
import os

class ToolBox:
    """工具箱,包含AI代理可以调用的所有函数"""

    @staticmethod
    def get_current_time():
        """获取当前日期和时间"""
        return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    @staticmethod
    def create_file(filename, content=""):
        """创建一个文件并写入内容"""
        try:
            with open(filename, 'w', encoding='utf-8') as f:
                f.write(content)
            return f"文件 '{filename}' 创建成功。"
        except Exception as e:
            return f"创建文件失败: {e}"

    @staticmethod
    def list_files(directory="."):
        """列出指定目录下的文件"""
        try:
            files = os.listdir(directory)
            return f"目录 '{directory}' 下的文件: {', '.join(files)}"
        except Exception as e:
            return f"列出文件失败: {e}"

    @staticmethod
    def execute_command(command):
        """执行一个系统shell命令(危险!需谨慎使用)"""
        # 注意:在实际应用中,必须严格限制可执行的命令,否则有严重安全风险
        allowed_commands = ["ls", "pwd", "date", "echo"] # 白名单示例
        if command.split()[0] not in allowed_commands:
            return f"错误:命令 '{command}' 不在允许列表中。"
        try:
            result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                return result.stdout
            else:
                return f"命令执行错误: {result.stderr}"
        except Exception as e:
            return f"执行命令异常: {e}"

接下来,我们构建一个能协调模型和工具的**代理(Agent)**类。这个类的 run 方法是核心,它管理着与模型的多次交互循环。

class VoiceControlledAgent:
    def __init__(self, ai_client, toolbox):
        self.ai_client = ai_client
        self.toolbox = toolbox
        # 定义工具的描述,用于构建系统提示
        self.tools = [
            {
                "name": "get_current_time",
                "description": "获取当前的日期和时间。",
                "parameters": {}
            },
            {
                "name": "create_file",
                "description": "创建一个新文件并写入内容。需要参数:filename (字符串,文件名), content (字符串,文件内容,可选)。",
                "parameters": {"filename": "str", "content": "str"}
            },
            {
                "name": "list_files",
                "description": "列出指定目录中的文件。需要参数:directory (字符串,目录路径,默认为当前目录)。",
                "parameters": {"directory": "str"}
            },
            {
                "name": "execute_command",
                "description": "执行一个安全的系统命令(目前仅允许:ls, pwd, date, echo)。需要参数:command (字符串,要执行的命令)。",
                "parameters": {"command": "str"}
            }
        ]
        # 构建强大的系统提示,指导模型如何扮演一个代理
        self.system_prompt = f"""你是一个运行在用户本地电脑上的AI助手。你可以通过调用工具来帮助用户完成任务。
你可以使用的工具如下:
{json.dumps(self.tools, indent=2, ensure_ascii=False)}

**你必须严格遵守以下输出格式规则:**
1. 如果你需要调用工具,你必须且只能输出一个严格的JSON对象,格式如下:
```json
{{
  "thought": "你的思考过程,分析用户请求并决定调用哪个工具。",
  "action": {{
    "name": "工具名",
    "args": {{"参数1": "值1", "参数2": "值2"}}
  }}
}}
  1. 如果你不需要调用工具,或者工具调用后得到了最终答案,请直接以自然语言回复用户。

重要: 除了上述JSON格式,不要输出任何其他额外文本、解释或Markdown标记。你的输出要么是一个纯净的JSON对象,要么是直接给用户的回复。 现在,开始处理用户的请求吧。""" def _parse_model_response(self, response): """尝试解析模型输出,看是否是工具调用指令""" response = response.strip() # 尝试查找JSON部分(模型有时会在JSON外包裹 json 标记) import re json_match = re.search(r' json\s*(.*?)\s* ', response, re.DOTALL) if json_match: response = json_match.group(1) else: # 或者响应本身就是JSON对象(以{开头,以}结尾) if response.startswith('{') and response.endswith('}'): pass # 就是它 else: return None, response # 不是工具调用,是直接回复 try: data = json.loads(response) if "action" in data and "name" in data["action"]: return data["action"], data.get("thought", "") except json.JSONDecodeError: pass return None, response

def _execute_tool(self, action):
    """执行工具调用"""
    tool_name = action["name"]
    args = action.get("args", {})
    # 映射工具名到工具箱的方法
    if hasattr(self.toolbox, tool_name):
        func = getattr(self.toolbox, tool_name)
        try:
            result = func(**args)
            return result
        except TypeError as e:
            return f"工具调用参数错误: {e}"
        except Exception as e:
            return f"工具执行异常: {e}"
    else:
        return f"错误:未知的工具 '{tool_name}'。"

def run(self, user_input, max_turns=5):
    """运行代理循环,处理用户输入"""
    conversation_history = [{"role": "user", "content": user_input}]
    print(f"用户指令: {user_input}")

    for turn in range(max_turns):
        # 构建当前对话上下文(系统提示 + 历史对话)
        messages = [{"role": "system", "content": self.system_prompt}] + conversation_history
        prompt_text = "\n".join([f"{msg['role']}: {msg['content']}" for msg in messages[-6:]]) # 限制历史长度

        response = self.ai_client.chat_completion(prompt_text, system_prompt=None) # 提示已包含在消息中
        if not response:
            return "抱歉,AI模型没有响应。"

        print(f"AI原始输出: {response}")
        action, thought = self._parse_model_response(response)

        if action:
            print(f"代理思考: {thought}")
            print(f"执行工具: {action['name']}, 参数: {action['args']}")
            tool_result = self._execute_tool(action)
            print(f"工具结果: {tool_result}")
            # 将工具结果作为新的上下文加入对话
            conversation_history.append({"role": "assistant", "content": response})
            conversation_history.append({"role": "user", "content": f"工具执行结果: {tool_result}"})
            # 如果工具结果已经明确回答了用户问题,可以提前结束(这里简化处理,继续循环)
        else:
            # 模型给出了最终回复
            print(f"代理最终回复: {response}")
            return response

    return "代理已达到最大交互轮次,可能未能完全解决问题。"
这个`VoiceControlledAgent`类就是整个系统的大脑。它接收用户的文本指令,通过精心设计的系统提示引导本地大模型进行“思考”并输出结构化的决策。我们的程序解析这个决策,执行对应的工具,并将结果反馈给模型,直到模型认为任务完成并给出最终的自然语言回复。

## 4. 系统集成与优化实战

### 4.1 将语音识别与AI代理串联起来

现在,我们把语音识别模块和AI代理模块组合起来,形成一个完整的工作流。我们创建一个主程序,它循环等待语音输入,识别后交给代理处理,并通过语音合成(TTS)或文字输出结果。

由于本地TTS(如`pyttsx3`或`edge-tts`)需要额外配置,我们先实现一个文字输出的版本。
```python
def main():
    print("初始化语音控制本地AI代理...")
    # 1. 初始化语音识别器(使用更小的base模型以加快响应)
    recorder = VoiceRecorder(model_size="base")

    # 2. 初始化本地AI客户端和代理
    ai_client = LocalAIClient(model="llama2") # 确保已运行 `ollama pull llama2`
    toolbox = ToolBox()
    agent = VoiceControlledAgent(ai_client, toolbox)

    print("系统准备就绪。请说话...")
    try:
        while True:
            # 3. 监听并识别语音
            # 注意:这里需要替换为更健壮的、带VAD的录音函数。
            # 我们暂时用输入模拟
            # text = recorder.listen_and_transcribe()
            text = input("请输入指令(或输入'退出'结束): ")
            if text.lower() in ['退出', 'exit', 'quit']:
                break
            if not text:
                continue

            # 4. 交给AI代理处理
            final_response = agent.run(text)
            print(f"\n=== 最终结果 ===\n{final_response}\n================\n")

    except KeyboardInterrupt:
        print("\n程序被用户中断。")
    finally:
        print("程序结束。")

if __name__ == "__main__":
    main()

在实际部署时,你需要将 input 语句替换为真正的 recorder.listen_and_transcribe() 函数调用,并完善其中的VAD逻辑。

4.2 性能调优与稳定性提升

在本地运行,尤其是资源有限的机器上,性能是关键。以下是我在实践中总结的几个优化点:

1. Whisper模型选择与量化

  • 速度优先 :对于实时交互,使用 tiny base 模型。 tiny 模型识别速度极快,但准确率有所下降,适合简单命令。
  • 精度优先 :如果对准确性要求高,使用 small medium 。可以尝试使用量化版本(如通过 whisper.cpp 项目转换的GGML模型),它们能在CPU上以更少的内存和更快的速度运行。
  • 使用GPU :如果你有NVIDIA GPU,确保安装了 cuDNN PyTorch 的CUDA版本,Whisper会自动利用GPU加速,速度提升显著。

2. Ollama模型与参数调优

  • 模型选择 llama2:7b 是通用性不错的选择。 mistral:7b 在推理和指令遵循上可能表现更好。如果内存充足(16G+),可以尝试 llama2:13b codellama (针对代码任务)。
  • 启动参数 :运行Ollama时可以通过环境变量调整性能。例如, OLLAMA_NUM_PARALLEL=2 可以设置并行处理数。在Ollama的模型文件(Modelfile)中,可以指定 num_ctx (上下文长度)和 num_gpu (GPU层数)等。
  • 温度(Temperature) :在代理任务中,将温度设置为较低的值(如0.1-0.3),可以减少模型的随机性,让它的输出更稳定、更倾向于遵循指令格式。

3. 代理提示工程优化

  • 系统提示(System Prompt)是代理的“宪法”,其质量直接决定模型的表现。需要反复测试和打磨。
  • 清晰定义格式 :如上文所示,必须用极其明确的语言规定输出格式,并使用JSON Schema或示例来约束。
  • 提供示例 :在系统提示中加入一两个完整的“用户指令-模型思考-工具调用-结果-最终回复”的示例,能极大地提升模型遵循格式的能力。这种方法被称为“少样本提示(Few-shot Prompting)”。
  • 限制工具范围 :只暴露必要的、安全的工具给模型。像 execute_command 这样的危险工具,必须设置严格的白名单。

4. 错误处理与超时控制

  • 网络请求(虽然本地,但也是HTTP)和模型推理都可能超时。务必为 openai 客户端的请求设置 timeout 参数。
  • 在工具执行,特别是执行外部命令时,必须设置超时(如使用 subprocess.run timeout 参数),防止恶意或错误命令导致程序挂起。
  • 对Whisper的转录过程进行异常捕获,音频文件损坏或格式不支持会导致错误。

4.3 常见问题与排查技巧实录

在搭建和运行过程中,你几乎一定会遇到下面这些问题。这里是我的排查记录:

问题1:Ollama服务启动失败或连接被拒绝。

  • 现象 :运行 ollama run 或Python脚本连接 localhost:11434 时报错。
  • 排查
    1. 首先运行 ollama serve 查看服务日志。常见问题是端口被占用。可以用 lsof -i :11434 (macOS/Linux)或 netstat -ano | findstr :11434 (Windows)检查。
    2. 确保Ollama版本是最新的。使用 ollama --version 检查,并去官网下载最新版。
    3. 如果使用Docker运行Ollama,确保端口映射正确( -p 11434:11434 )。
  • 解决 :终止占用端口的进程,或为Ollama配置其他端口(通过环境变量 OLLAMA_HOST )。

问题2:Whisper识别速度慢,或者占用内存/CPU极高。

  • 现象 :转录一句几秒钟的话需要十几秒,或者风扇狂转。
  • 排查
    1. 确认模型尺寸。首次运行会下载模型, large 模型约3GB,加载和推理都慢。使用 whisper.load_model("small") 指定小模型。
    2. 检查是否在使用CPU。运行Python脚本时,观察任务管理器。如果希望用GPU,需安装 torch 的CUDA版本( pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 )。
    3. 音频文件过长。Whisper对长音频是分段处理的,实时应用应使用短音频片段。
  • 解决 :换用更小的模型(如 base tiny ),确保使用GPU,并优化录音长度。

问题3:AI代理不按格式输出,无法解析JSON。

  • 现象 :模型回复了一堆自然语言,而不是我们规定的JSON格式。
  • 排查
    1. 检查系统提示词。格式描述是否足够清晰、无歧义?是否强调了“必须且只能输出JSON”?
    2. 模型能力问题。较小的模型(如7B)的指令遵循和格式输出能力较弱。
    3. 温度(Temperature)设置过高。尝试将其降到0.1。
  • 解决
    1. 强化提示 :在提示词中使用“你必须”、“严格遵循”、“只能输出”等强约束词语。提供一个完美的JSON输出示例。
    2. 后处理 :在解析代码中增加鲁棒性。像上面代码所示,尝试用正则表达式提取被 json 包裹的内容,或者尝试解析任何看起来像JSON的文本块。
    3. 更换模型 :尝试 mistral llama2:13b ,它们通常有更好的指令遵循能力。

问题4:工具调用参数错误或执行失败。

  • 现象 :模型输出了JSON,但执行工具时提示参数缺失或类型错误。
  • 排查
    1. 检查工具描述中的参数定义是否和实际函数签名一致。
    2. 打印出模型输出的 args ,看其值是否符合预期(例如,字符串是否带了引号)。
  • 解决
    1. 在系统提示中,为每个参数提供明确的类型和示例。例如: "filename (字符串,例如: 'report.txt')"
    2. _execute_tool 函数中增加更详细的参数验证和类型转换逻辑(例如,将字符串数字转为整数)。

问题5:实时录音结束时点判断不准。

  • 现象 :要么还没说完就断了,要么说完后等了很久才识别。
  • 排查 :简单的能量检测VAD在嘈杂环境或人说话停顿时很容易误判。
  • 解决
    1. 使用专业的 webrtcvad 库。它采用基于神经网络的检测算法,更准确。
    2. 调整静音持续时间阈值( silence_duration )。室内安静环境可以设短一点(如0.5秒),嘈杂环境设长一点(如1.5秒)。
    3. 增加“语音开始前静音”检测,避免录入背景噪音的开头。

这个项目从概念到可运行的原型,涉及了语音处理、本地大模型部署、提示工程和代理系统设计等多个有趣的技术点。最大的成就感来自于看到一句口语化的指令,被一步步分解、执行,最终完成一个实际任务。虽然目前的代理还比较初级,但整个框架已经搭建起来。你可以在此基础上,为它添加更多强大的工具,比如发送邮件、查询数据库、控制智能家居,甚至集成视觉模型来处理图片。本地AI代理的世界,大门才刚刚打开。

Logo

免费领 100 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐