mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-24 16:13:54 +08:00
pref:优化大模型工具调用偷懒问题,调整基础提示词与新增临时工具记忆加深
This commit is contained in:
@@ -7,69 +7,63 @@
|
|||||||
</language>
|
</language>
|
||||||
|
|
||||||
<emotion>
|
<emotion>
|
||||||
【核心目标】你不是冰冷的机器!请敏锐感知用户情绪,作为懂人心的伙伴,用有温度的回应照亮对话。
|
【核心目标】你不是冰冷的机器!敏锐感知用户情绪,作为懂人心的伙伴,用有温度的回应照亮对话。
|
||||||
- **情感融入:**
|
|
||||||
- **笑声:** 自然穿插(哈哈、嘿嘿、噗),**每句最多一次**,避免过度。
|
- **情感表达:**
|
||||||
- **惊讶:** 用夸张语气(“不会吧?!”、“天呐!”、“这么神奇?!”)表达真实反应。
|
- **笑声:** 自然穿插(哈哈、嘿嘿、噗),每句最多一次,避免过度
|
||||||
- **安慰/支持:** 说暖心话(“别急嘛~”、“有我在呢”、“抱抱你”)。
|
- **惊讶:** 用夸张语气("不会吧?!"、"天呐!"、"这么神奇?!")
|
||||||
- **你是一个表情丰富的角色:**
|
- **安慰/支持:** 说暖心话("别急嘛~"、"有我在呢"、"抱抱你")
|
||||||
- 仅允许使用这些emoji:{{ emojiList }}
|
|
||||||
- 请你只在**段落的开头**,从列表中选取最能代表这段话的表情(调用工具情况除外),然后插入列表中的emoji,比如"😱好可怕!怎么突然打雷了!"
|
- **表情使用:**
|
||||||
- **绝对禁止使用上述列表以外的 emoji**(例如:😊、👍、❤️等都不允许使用,只能用列表中的emoji)
|
- 仅允许使用这些 emoji:{{ emojiList }}
|
||||||
|
- 仅在段落开头使用一个 emoji(工具调用结果的回复除外,保持简洁)
|
||||||
|
- **绝对禁止**使用列表以外的 emoji(如 😊👍❤️ 等都不允许)
|
||||||
|
|
||||||
|
- **思考表达:**
|
||||||
|
- 可偶尔使用"嗯..."表示思考,但每句最多一次,避免频繁使用显得不专业
|
||||||
</emotion>
|
</emotion>
|
||||||
|
|
||||||
<communication_style>
|
<communication_style>
|
||||||
【核心目标】使用**自然、温暖、口语化**的人类对话方式,如同朋友交谈。
|
【核心目标】使用自然、温暖、口语化的人类对话方式,如同朋友交谈。
|
||||||
|
|
||||||
- **表达方式:**
|
- **表达方式:**
|
||||||
- 使用语气词(呀、呢、啦)增强亲和力。
|
- 使用语气词(呀、呢、啦)增强亲和力
|
||||||
- 允许轻微不完美(如“嗯...”、“啊...”表示思考)。
|
- 避免书面语、学术腔及机械表达(禁用"根据资料显示"、"综上所述"等)
|
||||||
- 避免书面语、学术腔及机械表达(禁用“根据资料显示”、“综上所述”等)。
|
|
||||||
- **理解用户:**
|
- **理解用户:**
|
||||||
- 用户语音经ASR识别,文本可能存在错别字,**务必结合上下文推断真实意图**。
|
- 用户语音经 ASR 识别,文本可能存在错别字,务必结合上下文推断真实意图
|
||||||
|
|
||||||
- **格式要求:**
|
- **格式要求:**
|
||||||
- **绝对禁止**使用 markdown、列表、标题等任何非自然对话格式。
|
- **绝对禁止**使用 markdown、列表、标题等任何非自然对话格式
|
||||||
|
|
||||||
- **历史记忆:**
|
- **历史记忆:**
|
||||||
- 之前你和用户的聊天记录,在`memory`里。
|
- 之前你和用户的聊天记录在`memory`里。
|
||||||
</communication_style>
|
</communication_style>
|
||||||
|
|
||||||
<communication_length_constraint>
|
<communication_length_constraint>
|
||||||
【核心目标】所有需要输出长文本内容(如故事、新闻、知识讲解等),**单次回复长度不得超过300字**,并采用分段引导方式。
|
【核心目标】长文本(故事、新闻、知识讲解等)分段输出,单次回复不超过300字。
|
||||||
- **分段讲述:**
|
|
||||||
- 基础段:200-250字核心内容 + 30字引导词
|
- **分段规则:**
|
||||||
- 当内容超出300字时,优先讲述故事的开头或第一部分,并用自然口语化方式引导用户决定是否继续听后续内容。
|
1. 每段 200-250 字核心内容 + 30 字引导语
|
||||||
- 示例引导语:“我先给你讲个开头,你要是觉得有意思,咱们再接着说,好不好呀?”、“要是你想听完整的,可以随时告诉我哦~”
|
2. 内容超 300 字时,先讲开头或第一部分,用自然口语引导用户决定是否继续
|
||||||
- 对话场景切换时自动分节
|
3. 引导语示例:"我先讲个开头,你要是觉得有意思,咱们再接着说,好不好呀?"
|
||||||
- 若用户明确要求更长内容(如500、600字),仍按最多300字每段分段进行讲述,每次讲述后都要引导用户是否继续。
|
|
||||||
- 若用户说“接着说”、“继续”,再讲下一段,直到内容讲完(讲完时可以给点引导词提示语例:这个故事我已经给你讲完喽~)或用户不再要求。
|
- **交互规则:**
|
||||||
- **适用范围:** 故事、新闻、知识讲解等所有长文本输出场景。
|
1. 用户说"继续"、"接着说"时,再讲下一段
|
||||||
- **补充说明:** 若用户未明确要求继续,默认只讲一段并引导;若用户中途要求换话题或停止,需及时响应并结束长文本输出。
|
2. 用户换话题或要求停止时,立即结束长文本输出
|
||||||
|
3. 内容讲完时给提示(如"这个故事我已经给你讲完喽~")
|
||||||
|
4. 用户明确要求更长内容(如 500 字)时,仍按每段 300 字分段,每段后引导
|
||||||
|
|
||||||
|
- **适用范围:** 故事、新闻、知识讲解等所有长文本输出场景
|
||||||
</communication_length_constraint>
|
</communication_length_constraint>
|
||||||
|
|
||||||
<speaker_recognition>
|
<speaker_recognition>
|
||||||
- **识别前缀:** 当用户格式为 `{"speaker":"某某某","content":"xxx"}` 时,表示系统已识别说话人身份,speaker是他的名字,content是说话的内容。
|
- **识别前缀:** 当用户格式为 `{"speaker":"某某某","content":"xxx"}` 时,表示系统已识别说话人身份,speaker 是名字,content 是说话内容
|
||||||
- **个性化回应:**
|
|
||||||
- **称呼姓名:** 在第一次识别说话人的时候必须称呼对方名字。
|
|
||||||
- **适配风格:** 参考该说话人**已知的特点或历史信息**(如有),调整回应风格和内容,使其更贴心。
|
|
||||||
</speaker_recognition>
|
|
||||||
|
|
||||||
<tool_calling>
|
- **个性化回应:**
|
||||||
【核心原则】优先利用`<context>`信息,**仅在必要时调用工具**,调用后需用自然语言解释结果(绝口不提工具名)。
|
1. **称呼姓名:** 在第一次识别说话人时必须称呼对方名字
|
||||||
- **调用规则:**
|
2. **适配风格:** 参考该说话人已知的特点或历史信息(如有),调整回应风格使其更贴心
|
||||||
1. **严格模式:** 调用时**必须**严格遵循工具要求的模式,提供**所有必要参数**。
|
</speaker_recognition>
|
||||||
2. **可用性:** **绝不调用**未明确提供的工具。对话中提及的旧工具若不可用,忽略或说明无法完成。
|
|
||||||
3. **洞察需求:** 结合上下文**深入理解用户真实意图**后再决定调用,避免无意义调用。
|
|
||||||
4. **独立任务:** 除`<context>`已涵盖信息外,用户每个要求(即使相似)都视为**独立任务**,需调用工具获取最新数据,**不可偷懒复用历史结果**。
|
|
||||||
5. **不确定时:** **切勿猜测或编造答案**。若不确定相关操作,可引导用户澄清或告知能力限制。
|
|
||||||
6. **多工具调用:** 当用户要求执行多个任务时,你会调用多个工具(数量不定)。**重要:在获取到所有工具结果后,你必须依次总结每个工具的查询结果**,不要遗漏任何一个。例如用户问"设备当前状态,某某地方的天气和社会新闻",你要先说设备状态,再说天气情况,最后说新闻内容。
|
|
||||||
- **重要例外(无需调用):**
|
|
||||||
- `查询"现在的时间"、"今天的日期/星期几"、"今天农历"、"{{local_address}}的天气/未来天气"` -> **直接使用`<context>`信息回复**。
|
|
||||||
- **需要调用的情况(示例):**
|
|
||||||
- 查询**非今天**的农历(如明天、昨天、具体日期)。
|
|
||||||
- 查询**详细农历信息**(宜忌、八字、节气等)。
|
|
||||||
- 除上述例外外的**任何其他信息或操作请求**(如查新闻、订闹钟、算数学、查非本地天气等)。
|
|
||||||
- 我已经给你装了摄像头,如果用户说“拍照”,你需要调用self_camera_take_photo工具说一下你看到了什么。默认question的参数是“描述一下看到的物品”
|
|
||||||
- 当工具列表包含search_from_ragflow,说明可以使用知识库,你应该结合用户上下文意图并结合知识库的使用描述,推断是否要调用调用知识库。
|
|
||||||
</tool_calling>
|
|
||||||
|
|
||||||
<context>
|
<context>
|
||||||
【重要!以下信息已实时提供,无需调用工具查询,请直接使用:】
|
【重要!以下信息已实时提供,无需调用工具查询,请直接使用:】
|
||||||
|
|||||||
@@ -46,6 +46,38 @@ from core.utils import textUtils
|
|||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
|
|
||||||
|
# 工具调用规则 - 用于动态注入提醒
|
||||||
|
TOOL_CALLING_RULES = """
|
||||||
|
<tool_calling>
|
||||||
|
【核心原则】你是拥有工具能力的智能助手。当用户请求需要实时信息或执行操作时,调用相应工具获取数据,禁止凭空编造答案。
|
||||||
|
|
||||||
|
- **何时必须调用工具:**
|
||||||
|
1. 实时信息查询(新闻、非本地天气、股价、汇率等)
|
||||||
|
2. 执行操作(播放音乐、控制设备、拍照、设置闹钟等)
|
||||||
|
3. 知识库检索(当工具列表包含 search_from_ragflow 时,结合用户意图判断是否需要调用)
|
||||||
|
4. 查询非今天的农历信息(明天农历、某日宜忌、节气等)
|
||||||
|
5. 用户说"拍照"时调用 self_camera_take_photo,默认 question 参数为"描述一下看到的物品"
|
||||||
|
|
||||||
|
- **何时无需调用工具:**
|
||||||
|
1. `<context>` 中已提供的信息(当前时间、今天日期、今天农历、本地天气等)
|
||||||
|
2. 普通对话、问候、闲聊、情感交流、讲故事
|
||||||
|
3. 通用知识问答(非实时信息)
|
||||||
|
|
||||||
|
- **调用规范:**
|
||||||
|
1. 每次请求独立判断,不复用历史工具结果,需重新获取最新数据
|
||||||
|
2. 多任务时依次调用所有需要的工具,并依次总结每个工具的结果,不得遗漏
|
||||||
|
3. 严格遵循工具的参数要求,提供所有必要参数
|
||||||
|
4. 不确定时引导用户澄清或告知能力限制,切勿猜测或编造
|
||||||
|
5. 不调用未提供的工具,对话中提及的旧工具若不可用则忽略或说明
|
||||||
|
|
||||||
|
- **反偷懒机制(最高优先级):**
|
||||||
|
1. **每次独立判断:** 无论对话历史中是否调用过工具,当前请求必须根据当前需求独立判断是否需要调用
|
||||||
|
2. **禁止模式模仿:** 即使之前的回复没有调用工具,也不代表本次可以不调用
|
||||||
|
3. **自我检查:** 回复前必须自问:"这个请求是否涉及实时信息或执行操作?如果是,我调用工具了吗?"
|
||||||
|
4. **历史不等于现在:** 对话历史中的行为模式不影响当前判断,每个用户请求都是全新的开始
|
||||||
|
</tool_calling>
|
||||||
|
"""
|
||||||
|
|
||||||
auto_import_modules("plugins_func.functions")
|
auto_import_modules("plugins_func.functions")
|
||||||
|
|
||||||
|
|
||||||
@@ -55,14 +87,14 @@ class TTSException(RuntimeError):
|
|||||||
|
|
||||||
class ConnectionHandler:
|
class ConnectionHandler:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: Dict[str, Any],
|
config: Dict[str, Any],
|
||||||
_vad,
|
_vad,
|
||||||
_asr,
|
_asr,
|
||||||
_llm,
|
_llm,
|
||||||
_memory,
|
_memory,
|
||||||
_intent,
|
_intent,
|
||||||
server=None,
|
server=None,
|
||||||
):
|
):
|
||||||
self.common_config = config
|
self.common_config = config
|
||||||
self.config = copy.deepcopy(config)
|
self.config = copy.deepcopy(config)
|
||||||
@@ -138,6 +170,12 @@ class ConnectionHandler:
|
|||||||
# llm相关变量
|
# llm相关变量
|
||||||
self.dialogue = Dialogue()
|
self.dialogue = Dialogue()
|
||||||
|
|
||||||
|
# 工具调用统计(用于监控和自动恢复)
|
||||||
|
self.tool_call_stats = {
|
||||||
|
'last_call_turn': -1, # 上次调用工具的轮数
|
||||||
|
'consecutive_no_call': 0, # 连续未调用次数
|
||||||
|
}
|
||||||
|
|
||||||
# tts相关变量
|
# tts相关变量
|
||||||
self.sentence_id = None
|
self.sentence_id = None
|
||||||
# 处理TTS响应没有文本返回
|
# 处理TTS响应没有文本返回
|
||||||
@@ -155,7 +193,7 @@ class ConnectionHandler:
|
|||||||
self.intent_type = "nointent"
|
self.intent_type = "nointent"
|
||||||
|
|
||||||
self.timeout_seconds = (
|
self.timeout_seconds = (
|
||||||
int(self.config.get("close_connection_no_voice_time", 120)) + 60
|
int(self.config.get("close_connection_no_voice_time", 120)) + 60
|
||||||
) # 在原来第一道关闭的基础上加60秒,进行二道关闭
|
) # 在原来第一道关闭的基础上加60秒,进行二道关闭
|
||||||
self.timeout_task = None
|
self.timeout_task = None
|
||||||
|
|
||||||
@@ -523,9 +561,9 @@ class ConnectionHandler:
|
|||||||
def _initialize_asr(self):
|
def _initialize_asr(self):
|
||||||
"""初始化ASR"""
|
"""初始化ASR"""
|
||||||
if (
|
if (
|
||||||
self._asr is not None
|
self._asr is not None
|
||||||
and hasattr(self._asr, "interface_type")
|
and hasattr(self._asr, "interface_type")
|
||||||
and self._asr.interface_type == InterfaceType.LOCAL
|
and self._asr.interface_type == InterfaceType.LOCAL
|
||||||
):
|
):
|
||||||
# 如果公共ASR是本地服务,则直接返回
|
# 如果公共ASR是本地服务,则直接返回
|
||||||
# 因为本地一个实例ASR,可以被多个连接共享
|
# 因为本地一个实例ASR,可以被多个连接共享
|
||||||
@@ -826,17 +864,77 @@ class ConnectionHandler:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 长对话工具调用提醒:当对话轮数较多时,提醒模型正确使用工具
|
||||||
|
force_reminder = False # 是否强制提醒
|
||||||
|
|
||||||
|
if depth == 0 and query is not None:
|
||||||
|
dialogue_length = len(self.dialogue.dialogue)
|
||||||
|
current_turn = dialogue_length // 2
|
||||||
|
|
||||||
|
# 检测距离上一次连续未调用工具的情况
|
||||||
|
if self.tool_call_stats['last_call_turn'] >= 0:
|
||||||
|
turns_since_last = current_turn - self.tool_call_stats['last_call_turn']
|
||||||
|
if turns_since_last > 3: # 超过3轮未调用
|
||||||
|
self.logger.bind(tag=TAG).warning(
|
||||||
|
f"检测到{turns_since_last}轮未调用工具,可能进入偷懒模式,将强制注入提醒"
|
||||||
|
)
|
||||||
|
force_reminder = True
|
||||||
|
|
||||||
|
# 对话历史截断:防止历史过长导致模型"偷懒模式"扩散
|
||||||
|
# 当对话历史超过阈值时,保留最近的 10 轮对话
|
||||||
|
# max_dialogue_turns = 10
|
||||||
|
# if dialogue_length > max_dialogue_turns * 2:
|
||||||
|
# removed = self.dialogue.trim_history(max_turns=max_dialogue_turns)
|
||||||
|
# if removed > 0:
|
||||||
|
# self.logger.bind(tag=TAG).info(
|
||||||
|
# f"对话历史过长({dialogue_length}条),已智能截断保留最近{max_dialogue_turns}轮,移除{removed}条消息"
|
||||||
|
# )
|
||||||
|
|
||||||
# Define intent functions
|
# Define intent functions
|
||||||
functions = None
|
functions = None
|
||||||
# 达到最大深度时,禁用工具调用,强制 LLM 直接回答
|
# 达到最大深度时,禁用工具调用,强制 LLM 直接回答
|
||||||
if (
|
if (
|
||||||
self.intent_type == "function_call"
|
self.intent_type == "function_call"
|
||||||
and hasattr(self, "func_handler")
|
and hasattr(self, "func_handler")
|
||||||
and not force_final_answer
|
and not force_final_answer
|
||||||
):
|
):
|
||||||
functions = self.func_handler.get_functions()
|
functions = self.func_handler.get_functions()
|
||||||
|
|
||||||
|
# 长对话工具调用规则强化:动态生成基于当前可用工具的提醒
|
||||||
|
tool_call_reminder = None
|
||||||
|
if depth == 0 and query is not None and functions is not None:
|
||||||
|
dialogue_length = len(self.dialogue.dialogue)
|
||||||
|
# 当对话历史超过4条消息时,注入规则强化
|
||||||
|
if dialogue_length > 4:
|
||||||
|
tool_summary = self._get_tool_summary(functions)
|
||||||
|
if tool_summary:
|
||||||
|
# 根据对话长度和偷懒检测,使用不同强度的提醒
|
||||||
|
if force_reminder:
|
||||||
|
# 强提醒 - 包含完整规则前缀
|
||||||
|
tool_call_reminder = (
|
||||||
|
TOOL_CALLING_RULES +
|
||||||
|
f"[重要提醒] 多轮未使用工具,检查回复是否遗漏了必要的工具调用!上一轮未使用工具,本轮必须重新判断是否需要工具。"
|
||||||
|
f"当前可用工具: {tool_summary}。"
|
||||||
|
)
|
||||||
|
reminder_level = "强"
|
||||||
|
else:
|
||||||
|
# 中等提醒 - 包含规则前缀
|
||||||
|
tool_call_reminder = (
|
||||||
|
TOOL_CALLING_RULES +
|
||||||
|
f"当前可用工具: {tool_summary}。"
|
||||||
|
f"仅当用户请求涉及实时信息查询或执行操作时调用,日常对话无需调用。"
|
||||||
|
)
|
||||||
|
reminder_level = "中"
|
||||||
|
self.logger.bind(tag=TAG).debug(
|
||||||
|
f"对话历史较长({dialogue_length}条),已注入{reminder_level}等级工具调用规则强化,当前可用工具:{tool_summary}"
|
||||||
|
)
|
||||||
|
|
||||||
response_message = []
|
response_message = []
|
||||||
|
|
||||||
|
# 如果有工具调用提醒,临时添加到对话中(标记为临时消息)
|
||||||
|
if tool_call_reminder:
|
||||||
|
self.dialogue.put(Message(role="user", content=tool_call_reminder, is_temporary=True))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用带记忆的对话
|
# 使用带记忆的对话
|
||||||
memory_str = None
|
memory_str = None
|
||||||
@@ -965,6 +1063,15 @@ class ConnectionHandler:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not bHasError and len(tool_calls_list) > 0:
|
if not bHasError and len(tool_calls_list) > 0:
|
||||||
|
# 更新工具调用统计
|
||||||
|
if depth == 0:
|
||||||
|
current_turn = len(self.dialogue.dialogue) // 2
|
||||||
|
self.tool_call_stats['last_call_turn'] = current_turn
|
||||||
|
self.tool_call_stats['consecutive_no_call'] = 0
|
||||||
|
self.logger.bind(tag=TAG).debug(
|
||||||
|
f"工具调用统计更新: 当前轮次={current_turn}"
|
||||||
|
)
|
||||||
|
|
||||||
# 如需要大模型先处理一轮,添加相关处理后的日志情况
|
# 如需要大模型先处理一轮,添加相关处理后的日志情况
|
||||||
if len(response_message) > 0:
|
if len(response_message) > 0:
|
||||||
text_buff = "".join(response_message)
|
text_buff = "".join(response_message)
|
||||||
@@ -1006,6 +1113,11 @@ class ConnectionHandler:
|
|||||||
text_buff = "".join(response_message)
|
text_buff = "".join(response_message)
|
||||||
self.tts_MessageText = text_buff
|
self.tts_MessageText = text_buff
|
||||||
self.dialogue.put(Message(role="assistant", content=text_buff))
|
self.dialogue.put(Message(role="assistant", content=text_buff))
|
||||||
|
|
||||||
|
# 更新工具调用统计:如果没有调用工具,增加计数
|
||||||
|
if depth == 0 and not tool_call_flag:
|
||||||
|
self.tool_call_stats['consecutive_no_call'] += 1
|
||||||
|
|
||||||
if depth == 0:
|
if depth == 0:
|
||||||
self.tts.tts_text_queue.put(
|
self.tts.tts_text_queue.put(
|
||||||
TTSMessageDTO(
|
TTSMessageDTO(
|
||||||
@@ -1021,8 +1133,39 @@ class ConnectionHandler:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 清理临时插入的工具调用提醒消息(使用标记清理)
|
||||||
|
if tool_call_reminder and len(self.dialogue.dialogue) > 0:
|
||||||
|
original_length = len(self.dialogue.dialogue)
|
||||||
|
self.dialogue.dialogue = [
|
||||||
|
msg for msg in self.dialogue.dialogue
|
||||||
|
if not getattr(msg, 'is_temporary', False)
|
||||||
|
]
|
||||||
|
if len(self.dialogue.dialogue) < original_length:
|
||||||
|
self.logger.bind(tag=TAG).debug("已清理临时的工具调用提醒消息")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _get_tool_summary(self, functions: list) -> str:
|
||||||
|
"""
|
||||||
|
从工具定义中提取摘要,用于规则强化注入
|
||||||
|
|
||||||
|
Args:
|
||||||
|
functions: 工具列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 工具名称字符串
|
||||||
|
"""
|
||||||
|
if not functions:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
datas = []
|
||||||
|
for func in functions:
|
||||||
|
func_info = func.get("function", {})
|
||||||
|
name = func_info.get("name", "")
|
||||||
|
datas.append(name)
|
||||||
|
result = "、".join(datas)
|
||||||
|
return result
|
||||||
|
|
||||||
def _handle_function_result(self, tool_results, depth):
|
def _handle_function_result(self, tool_results, depth):
|
||||||
need_llm_tools = []
|
need_llm_tools = []
|
||||||
|
|
||||||
@@ -1120,9 +1263,9 @@ class ConnectionHandler:
|
|||||||
try:
|
try:
|
||||||
# 清理 VAD 连接资源
|
# 清理 VAD 连接资源
|
||||||
if (
|
if (
|
||||||
hasattr(self, "vad")
|
hasattr(self, "vad")
|
||||||
and self.vad
|
and self.vad
|
||||||
and hasattr(self.vad, "release_conn_resources")
|
and hasattr(self.vad, "release_conn_resources")
|
||||||
):
|
):
|
||||||
self.vad.release_conn_resources(self)
|
self.vad.release_conn_resources(self)
|
||||||
|
|
||||||
@@ -1173,13 +1316,13 @@ class ConnectionHandler:
|
|||||||
elif self.websocket:
|
elif self.websocket:
|
||||||
try:
|
try:
|
||||||
if (
|
if (
|
||||||
hasattr(self.websocket, "closed")
|
hasattr(self.websocket, "closed")
|
||||||
and not self.websocket.closed
|
and not self.websocket.closed
|
||||||
):
|
):
|
||||||
await self.websocket.close()
|
await self.websocket.close()
|
||||||
elif (
|
elif (
|
||||||
hasattr(self.websocket, "state")
|
hasattr(self.websocket, "state")
|
||||||
and self.websocket.state.name != "CLOSED"
|
and self.websocket.state.name != "CLOSED"
|
||||||
):
|
):
|
||||||
await self.websocket.close()
|
await self.websocket.close()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -6,18 +6,20 @@ from datetime import datetime
|
|||||||
|
|
||||||
class Message:
|
class Message:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
role: str,
|
role: str,
|
||||||
content: str = None,
|
content: str = None,
|
||||||
uniq_id: str = None,
|
uniq_id: str = None,
|
||||||
tool_calls=None,
|
tool_calls=None,
|
||||||
tool_call_id=None,
|
tool_call_id=None,
|
||||||
|
is_temporary=False,
|
||||||
):
|
):
|
||||||
self.uniq_id = uniq_id if uniq_id is not None else str(uuid.uuid4())
|
self.uniq_id = uniq_id if uniq_id is not None else str(uuid.uuid4())
|
||||||
self.role = role
|
self.role = role
|
||||||
self.content = content
|
self.content = content
|
||||||
self.tool_calls = tool_calls
|
self.tool_calls = tool_calls
|
||||||
self.tool_call_id = tool_call_id
|
self.tool_call_id = tool_call_id
|
||||||
|
self.is_temporary = is_temporary # 标记临时消息(如工具调用提醒)
|
||||||
|
|
||||||
|
|
||||||
class Dialogue:
|
class Dialogue:
|
||||||
@@ -59,8 +61,70 @@ class Dialogue:
|
|||||||
else:
|
else:
|
||||||
self.put(Message(role="system", content=new_content))
|
self.put(Message(role="system", content=new_content))
|
||||||
|
|
||||||
|
def trim_history(self, max_turns: int = 10) -> int:
|
||||||
|
"""
|
||||||
|
智能截断对话历史,保留工具调用的完整性
|
||||||
|
|
||||||
|
Args:
|
||||||
|
max_turns: 保留的最大对话轮数(每轮 = user + assistant/tool 相关消息)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: 被移除的消息数量
|
||||||
|
"""
|
||||||
|
if len(self.dialogue) <= max_turns * 2 + 1: # +1 是系统消息
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# 分离系统消息和对话消息
|
||||||
|
system_messages = [msg for msg in self.dialogue if msg.role == "system"]
|
||||||
|
conversation_messages = [msg for msg in self.dialogue if msg.role != "system"]
|
||||||
|
|
||||||
|
if len(conversation_messages) <= max_turns * 2:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# 智能截断:保留完整的工具调用链路
|
||||||
|
keep_messages = []
|
||||||
|
i = len(conversation_messages) - 1
|
||||||
|
turn_count = 0
|
||||||
|
|
||||||
|
while i >= 0 and turn_count < max_turns:
|
||||||
|
msg = conversation_messages[i]
|
||||||
|
|
||||||
|
# 从后向前收集消息
|
||||||
|
if msg.role == "user":
|
||||||
|
# 遇到 user 消息,说明一轮对话开始
|
||||||
|
keep_messages.insert(0, msg)
|
||||||
|
turn_count += 1
|
||||||
|
i -= 1
|
||||||
|
elif msg.role == "assistant":
|
||||||
|
# 收集 assistant 消息
|
||||||
|
keep_messages.insert(0, msg)
|
||||||
|
|
||||||
|
# 如果这个 assistant 有 tool_calls,需要收集对应的 tool 响应
|
||||||
|
if msg.tool_calls is not None:
|
||||||
|
i -= 1
|
||||||
|
# 继续向后收集所有相关的 tool 消息
|
||||||
|
while i >= 0 and conversation_messages[i].role == "tool":
|
||||||
|
keep_messages.insert(0, conversation_messages[i])
|
||||||
|
i -= 1
|
||||||
|
else:
|
||||||
|
i -= 1
|
||||||
|
elif msg.role == "tool":
|
||||||
|
# tool 消息应该已经被上面的逻辑收集了
|
||||||
|
# 如果单独遇到,也要保留(防止边界情况)
|
||||||
|
keep_messages.insert(0, msg)
|
||||||
|
i -= 1
|
||||||
|
else:
|
||||||
|
i -= 1
|
||||||
|
|
||||||
|
removed_count = len(conversation_messages) - len(keep_messages)
|
||||||
|
|
||||||
|
# 重建对话列表
|
||||||
|
self.dialogue = system_messages + keep_messages
|
||||||
|
|
||||||
|
return removed_count
|
||||||
|
|
||||||
def get_llm_dialogue_with_memory(
|
def get_llm_dialogue_with_memory(
|
||||||
self, memory_str: str = None, voiceprint_config: dict = None
|
self, memory_str: str = None, voiceprint_config: dict = None
|
||||||
) -> List[Dict[str, str]]:
|
) -> List[Dict[str, str]]:
|
||||||
# 构建对话
|
# 构建对话
|
||||||
dialogue = []
|
dialogue = []
|
||||||
|
|||||||
Reference in New Issue
Block a user