From 6b8d2502b7fd212a4f5e1786fa70928cc9d62f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E8=83=BD=E6=B7=B7?= Date: Thu, 15 May 2025 06:32:47 +0800 Subject: [PATCH] Update ollama.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为本地ollama部署qwen3大语言模型添加适配!在识别到启用为qwen3大模型的时候自动添加no_think停用推理模式,并过滤标签转入给TTS(因为qwen3虽然用no_think停用推理后还是会输出转入到TTS) --- .../core/providers/llm/ollama/ollama.py | 113 ++++++++++++++++-- 1 file changed, 105 insertions(+), 8 deletions(-) diff --git a/main/xiaozhi-server/core/providers/llm/ollama/ollama.py b/main/xiaozhi-server/core/providers/llm/ollama/ollama.py index fb34ced5..aad6354f 100644 --- a/main/xiaozhi-server/core/providers/llm/ollama/ollama.py +++ b/main/xiaozhi-server/core/providers/llm/ollama/ollama.py @@ -21,27 +21,67 @@ class LLMProvider(LLMProviderBase): api_key="ollama" # Ollama doesn't need an API key but OpenAI client requires one ) + # 检查是否是qwen3模型 + self.is_qwen3 = self.model_name and self.model_name.lower().startswith("qwen3") + def response(self, session_id, dialogue): try: + # 如果是qwen3模型,在用户最后一条消息中添加/no_think指令 + if self.is_qwen3: + # 复制对话列表,避免修改原始对话 + dialogue_copy = dialogue.copy() + + # 找到最后一条用户消息 + for i in range(len(dialogue_copy) - 1, -1, -1): + if dialogue_copy[i]["role"] == "user": + # 在用户消息前添加/no_think指令 + dialogue_copy[i]["content"] = "/no_think " + dialogue_copy[i]["content"] + logger.bind(tag=TAG).debug(f"为qwen3模型添加/no_think指令") + break + + # 使用修改后的对话 + dialogue = dialogue_copy + responses = self.client.chat.completions.create( model=self.model_name, messages=dialogue, stream=True ) - is_active=True + is_active = True + # 用于处理跨chunk的标签 + buffer = "" + for chunk in responses: try: delta = chunk.choices[0].delta if getattr(chunk, 'choices', None) else None content = delta.content if hasattr(delta, 'content') else '' + if content: - if '' in content: + # 将内容添加到缓冲区 + buffer += content + + # 处理缓冲区中的标签 + while '' in buffer and '' in buffer: + # 找到完整的标签并移除 + pre = buffer.split('', 1)[0] + post = buffer.split('', 1)[1] + buffer = pre + post + + # 处理只有开始标签的情况 + if '' in buffer: is_active = False - content = content.split('')[0] - if '' in content: + buffer = buffer.split('', 1)[0] + + # 处理只有结束标签的情况 + if '' in buffer: is_active = True - content = content.split('')[-1] - if is_active: - yield content + buffer = buffer.split('', 1)[1] + + # 如果当前处于活动状态且缓冲区有内容,则输出 + if is_active and buffer: + yield buffer + buffer = "" # 清空缓冲区 + except Exception as e: logger.bind(tag=TAG).error(f"Error processing chunk: {e}") @@ -51,6 +91,22 @@ class LLMProvider(LLMProviderBase): def response_with_functions(self, session_id, dialogue, functions=None): try: + # 如果是qwen3模型,在用户最后一条消息中添加/no_think指令 + if self.is_qwen3: + # 复制对话列表,避免修改原始对话 + dialogue_copy = dialogue.copy() + + # 找到最后一条用户消息 + for i in range(len(dialogue_copy) - 1, -1, -1): + if dialogue_copy[i]["role"] == "user": + # 在用户消息前添加/no_think指令 + dialogue_copy[i]["content"] = "/no_think " + dialogue_copy[i]["content"] + logger.bind(tag=TAG).debug(f"为qwen3模型添加/no_think指令") + break + + # 使用修改后的对话 + dialogue = dialogue_copy + stream = self.client.chat.completions.create( model=self.model_name, messages=dialogue, @@ -58,8 +114,49 @@ class LLMProvider(LLMProviderBase): tools=functions, ) + is_active = True + buffer = "" + for chunk in stream: - yield chunk.choices[0].delta.content, chunk.choices[0].delta.tool_calls + try: + delta = chunk.choices[0].delta if getattr(chunk, 'choices', None) else None + content = delta.content if hasattr(delta, 'content') else None + tool_calls = delta.tool_calls if hasattr(delta, 'tool_calls') else None + + # 如果是工具调用,直接传递 + if tool_calls: + yield None, tool_calls + continue + + # 处理文本内容 + if content: + # 将内容添加到缓冲区 + buffer += content + + # 处理缓冲区中的标签 + while '' in buffer and '' in buffer: + # 找到完整的标签并移除 + pre = buffer.split('', 1)[0] + post = buffer.split('', 1)[1] + buffer = pre + post + + # 处理只有开始标签的情况 + if '' in buffer: + is_active = False + buffer = buffer.split('', 1)[0] + + # 处理只有结束标签的情况 + if '' in buffer: + is_active = True + buffer = buffer.split('', 1)[1] + + # 如果当前处于活动状态且缓冲区有内容,则输出 + if is_active and buffer: + yield buffer, None + buffer = "" # 清空缓冲区 + except Exception as e: + logger.bind(tag=TAG).error(f"Error processing function chunk: {e}") + continue except Exception as e: logger.bind(tag=TAG).error(f"Error in Ollama function call: {e}")