From a8f06f571890dd16a1428db4aa265ec695d91b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AC=A3=E5=8D=97=E7=A7=91=E6=8A=80?= Date: Thu, 13 Mar 2025 00:54:01 +0800 Subject: [PATCH] Test prompt (#305) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 退出指令无法识别 (#302) 修复指令text包含标点的情况下,无法正确识别退出指令的问题 * 优化提示词 (#303) * 添加 使用 coze 会话。 (#282) 历史消息在coze 上可配置历史消息的轮次。 同个会话id,coze 上自动带历史上下文。 会话和当前session关联。一次连接一个会话,断开后,重连是新会话无历史消息。 * update:去除无用代码 --------- Co-authored-by: Jad Co-authored-by: Jiao Haoyang <108573524+XuSenfeng@users.noreply.github.com> Co-authored-by: xu <494462498@qq.com> Co-authored-by: hrz <1710360675@qq.com> --- main/xiaozhi-server/config.yaml | 5 +- .../core/handle/intentHandler.py | 4 +- .../core/providers/intent/base.py | 2 +- .../providers/intent/intent_llm/intent_llm.py | 92 ++++++++++++++----- .../providers/intent/nointent/nointent.py | 2 +- .../core/providers/llm/coze/coze.py | 15 ++- 6 files changed, 92 insertions(+), 28 deletions(-) diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index a441930f..da98f1b4 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -181,8 +181,9 @@ LLM: CozeLLM: # 定义LLM API类型 type: coze - bot_id: 你的bot_id - user_id: 你的user_id + # bot_id和user_id的内容写在引号之内 + bot_id: "你的bot_id" + user_id: "你的user_id" personal_access_token: 你的coze个人令牌 LMStudioLLM: # 定义LLM API类型 diff --git a/main/xiaozhi-server/core/handle/intentHandler.py b/main/xiaozhi-server/core/handle/intentHandler.py index d651cc05..4ceef2e7 100644 --- a/main/xiaozhi-server/core/handle/intentHandler.py +++ b/main/xiaozhi-server/core/handle/intentHandler.py @@ -2,6 +2,7 @@ from config.logger import setup_logging import json from core.handle.sendAudioHandle import send_stt_message from core.utils.dialogue import Message +from core.utils.util import remove_punctuation_and_length from config.functionCallConfig import FunctionCallConfig import asyncio from enum import Enum @@ -105,6 +106,7 @@ async def handle_user_intent(conn, text): async def check_direct_exit(conn, text): """检查是否有明确的退出命令""" + _, text = remove_punctuation_and_length(text) cmd_exit = conn.cmd_exit for cmd in cmd_exit: if text == cmd: @@ -123,7 +125,7 @@ async def analyze_intent_with_llm(conn, text): # 对话历史记录 dialogue = conn.dialogue try: - intent_result = await conn.intent.detect_intent(dialogue.dialogue, text) + intent_result = await conn.intent.detect_intent(conn, dialogue.dialogue, text) # 尝试解析JSON结果 try: diff --git a/main/xiaozhi-server/core/providers/intent/base.py b/main/xiaozhi-server/core/providers/intent/base.py index 9726f1be..a691d6cb 100644 --- a/main/xiaozhi-server/core/providers/intent/base.py +++ b/main/xiaozhi-server/core/providers/intent/base.py @@ -20,7 +20,7 @@ class IntentProviderBase(ABC): logger.bind(tag=TAG).debug("Set LLM for intent provider") @abstractmethod - async def detect_intent(self, dialogue_history: List[Dict], text: str) -> str: + async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str: """ 检测用户最后一句话的意图 Args: diff --git a/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py b/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py index 198fa59f..04319438 100644 --- a/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py +++ b/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py @@ -1,7 +1,7 @@ from typing import List, Dict from ..base import IntentProviderBase from config.logger import setup_logging - +import re TAG = __name__ logger = setup_logging() @@ -20,42 +20,90 @@ class IntentProvider(IntentProviderBase): 格式化后的系统提示词 """ intent_list = [] + + """ + "continue_chat": "1.继续聊天, 除了播放音乐和结束聊天的时候的选项, 比如日常的聊天和问候, 对话等", + "end_chat": "2.结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候", + "play_music": "3.播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图" + """ for key, value in self.intent_options.items(): if key == "play_music": - intent_list.append(f"{value} [歌名]") + intent_list.append("3.播放音乐, 用户希望你可以播放音乐, 只用于播放音乐的意图") + elif key == "end_chat": + intent_list.append("2.结束聊天, 用户发来如再见之类的表示结束的话, 不想再进行对话的时候") + elif key == "continue_chat": + intent_list.append("1.继续聊天, 除了播放音乐和结束聊天的时候的选项, 比如日常的聊天和问候, 对话等") else: intent_list.append(value) - + + # "如果是唱歌、听歌、播放音乐,请指定歌名,格式为'播放音乐 [识别出的歌名]'。\n" + # "如果听不出具体歌名,可以返回'随机播放音乐'。\n" + # "只需要返回意图结果的json,不要解释。" + # "返回格式如下:\n" prompt = ( - "你是一个意图识别助手。你需要根据和用户的对话记录,重点分析用户的最后一句话,判断用户意图属于以下哪一类:\n" - f"{', '.join(intent_list)}\n" - "如果是唱歌、听歌、播放音乐,请指定歌名,格式为'播放音乐 [识别出的歌名]'。\n" - "如果听不出具体歌名,可以返回'随机播放音乐'。\n" - "只需要返回意图结果的json,不要解释。" - "返回格式如下:\n" - "{intent: '用户意图'}" + "你是一个意图识别助手。你需要根据和用户的对话记录,重点分析用户的最后一句话,判断用户意图属于以下哪一类(使用标志):\n" + "" + f"{', '.join(intent_list)}" + "\n" + "你需要按照以下的步骤处理用户的对话" + "1. 思考出对话的意图是哪一类的" + "2. 属于1和2的意图, 直接返回,返回格式如下:\n" + "{intent: '用户意图'}\n" + "3. 属于3的意图,则继续分析用户希望播放的音乐\n" + "4. 如果无法识别出具体歌名,可以返回'随机播放音乐'\n" + "{intent: '播放音乐 [获取的音乐名字]'}\n" + "下面是几个处理的示例(思考的内容不返回, 只返回json部分, 无额外的内容)\n" + "```" + "用户: 你今天怎么样?\n" + "思考(不返回): 用户发来的数据是一个问候语,属于继续聊天的意图, 是种类1, 种类1的需求是直接返回\n" + "返回结果: {intent: '继续聊天'}\n" + "```" + "用户: 我今天有点累了, 我们明天再聊吧\n" + "思考(不返回): 用户表达了今天不想继续对话,属于结束聊天的意图, 是种类2, 种类2的需求是直接返回\n" + "返回结果: {intent: '结束聊天'}\n" + "```" + "用户: 我今天有点累了, 我们明天再聊吧\n" + "思考(不返回): 用户表达了今天不想继续对话,属于结束聊天的意图, 是种类2, 种类2的需求是直接返回\n" + "返回结果: {intent: '结束聊天'}\n" + "```" + "用户: 你可以播放一首中秋月给我听吗\n" + "思考(不返回): 用户表达了想听音乐的续签,属于播放音乐的意图, 是种类3, 种类3的需求需要继续判断播放的音乐, 这里用户希望的歌曲名明确给出是中秋月\n" + "返回结果: {intent: '播放音乐 [中秋月]'}\n" + "```" + "你现在可以使用的音乐的名称如下(使用标志):\n" ) return prompt - async def detect_intent(self, dialogue_history: List[Dict], text:str) -> str: - logger.bind(tag=TAG).info(f"分析用户意图: {text}") + async def detect_intent(self, conn, dialogue_history: List[Dict], text:str) -> str: if not self.llm: raise ValueError("LLM provider not set") # 构建用户最后一句话的提示 msgStr = "" - for msg in dialogue_history: - if msg.role == "user": - msgStr += f"User: {msg.content}\n" - elif msg.role== "assistant": - msgStr += f"Assistant: {msg.content}\n" + + # 只使用最后两句即可 + if len(dialogue_history) >= 2: + # 保证最少有两句话的时候处理 + msgStr += f"{dialogue_history[-2].role}: {dialogue_history[-2].content}\n" + msgStr += f"{dialogue_history[-1].role}: {dialogue_history[-1].content}\n" + msgStr += f"User: {text}\n" - user_prompt = f"请分析用户的意图:\n{msgStr}" + user_prompt = f"当前的对话如下:\n{msgStr}" + prompt_music = f"{self.promot}\n{conn.music_handler.music_files}\n" + logger.bind(tag=TAG).debug(f"User prompt: {prompt_music}") # 使用LLM进行意图识别 intent = self.llm.response_no_stream( - system_prompt=self.promot, + system_prompt=prompt_music, user_prompt=user_prompt ) - intent_result = intent.strip() - logger.bind(tag=TAG).info(f"意图识别结果: {intent_result}") - return intent_result + # 使用正则表达式提取大括号中的内容 + # 使用正则表达式提取 {} 中的内容 + match = re.search(r'\{.*?\}', intent) + if match: + result = match.group(0) # 获取匹配到的内容(包含 {}) + print(result) # 输出:{intent: '播放音乐 [中秋月]'} + intent = result + else: + intent = "{intent: '继续聊天'}" + logger.bind(tag=TAG).info(f"Detected intent: {intent}") + return intent.strip() \ No newline at end of file diff --git a/main/xiaozhi-server/core/providers/intent/nointent/nointent.py b/main/xiaozhi-server/core/providers/intent/nointent/nointent.py index 70ea96fb..7efccbfe 100644 --- a/main/xiaozhi-server/core/providers/intent/nointent/nointent.py +++ b/main/xiaozhi-server/core/providers/intent/nointent/nointent.py @@ -7,7 +7,7 @@ logger = setup_logging() class IntentProvider(IntentProviderBase): - async def detect_intent(self, dialogue_history: List[Dict], text: str) -> str: + async def detect_intent(self, conn, dialogue_history: List[Dict], text: str) -> str: """ 默认的意图识别实现,始终返回继续聊天 Args: diff --git a/main/xiaozhi-server/core/providers/llm/coze/coze.py b/main/xiaozhi-server/core/providers/llm/coze/coze.py index feb62ea6..b1b14d91 100644 --- a/main/xiaozhi-server/core/providers/llm/coze/coze.py +++ b/main/xiaozhi-server/core/providers/llm/coze/coze.py @@ -11,19 +11,31 @@ from cozepy import Coze, TokenAuth, Message, ChatStatus, MessageContentType, Cha TAG = __name__ logger = setup_logging() + class LLMProvider(LLMProviderBase): def __init__(self, config): self.personal_access_token = config.get("personal_access_token") self.bot_id = config.get("bot_id") self.user_id = config.get("user_id") + self.session_conversation_map = {} # 存储session_id和conversation_id的映射 def response(self, session_id, dialogue): coze_api_token = self.personal_access_token coze_api_base = COZE_CN_BASE_URL last_msg = next(m for m in reversed(dialogue) if m["role"] == "user") - + coze = Coze(auth=TokenAuth(token=coze_api_token), base_url=coze_api_base) + conversation_id = self.session_conversation_map.get(session_id) + + # 如果没有找到conversation_id,则创建新的对话 + if not conversation_id: + conversation = coze.conversations.create( + messages=[ + ] + ) + conversation_id = conversation.id + self.session_conversation_map[session_id] = conversation_id # 更新映射 for event in coze.chat.stream( bot_id=self.bot_id, @@ -31,6 +43,7 @@ class LLMProvider(LLMProviderBase): additional_messages=[ Message.build_user_question_text(last_msg["content"]), ], + conversation_id=conversation_id, ): if event.event == ChatEventType.CONVERSATION_MESSAGE_DELTA: print(event.message.content, end="", flush=True)