feat(chatter): 整合工具使用并增强主聊天流程中的上下文

此提交为聊天系统引入了若干重大增强,以提高回应质量、上下文感知能力和角色一致性。

**Kokoro 流程中的工具整合:**
- 在主聊天提示中新增了一个 `tool_info` 上下文块。
- `KFCContextBuilder` 现在通过 `ToolExecutor` 执行工具后再生成回复,使聊天模型能够感知实时信息和操作。

**以角色为驱动的用户画像:**
- `update_user_profile` 工具被重构为两阶段过程。工具使用模型提供一个简单的“印象提示”,专门的 `relationship_tracker` 模型将其扩展为丰富且符合角色的一致印象。
- 工具使用提示已更新,明确指示模型在生成工具参数时保持机器人的角色。

**记忆系统改进:**
- 记忆检索判定提示已更新,更加主动地获取长期记忆,目标是为了提供更丰富的上下文。- 记忆查询现在从最近的一组消息中生成,而不是单条消息,从而提高检索的相关性。**其他更改:** - 在上下文构建过程中添加了性能时间日志,以识别瓶颈。- 在响应过滤中进行了小修复,以清理特定前缀,如“,说:”。
This commit is contained in:
tt-P607
2025-12-03 14:04:29 +08:00
parent 9a5ae357b5
commit 39c52490d9
9 changed files with 345 additions and 48 deletions

View File

@@ -957,6 +957,8 @@ def filter_system_format_content(content: str | None) -> str:
last_bracket_index = cleaned_content.rfind("]") last_bracket_index = cleaned_content.rfind("]")
if last_bracket_index != -1: if last_bracket_index != -1:
cleaned_content = cleaned_content[last_bracket_index + 1 :].strip() cleaned_content = cleaned_content[last_bracket_index + 1 :].strip()
# 专门清理 ",说:" 或 "说:"
cleaned_content = re.sub(r"^(|,)说:", "", cleaned_content).strip()
# 在处理完回复格式后,再清理其他简单的格式 # 在处理完回复格式后,再清理其他简单的格式
# 新增:移除所有残余的 [...] 格式,例如 [at=...] 等 # 新增:移除所有残余的 [...] 格式,例如 [at=...] 等

View File

@@ -333,8 +333,9 @@ class UnifiedMemoryManager:
prompt = f"""你是一个记忆检索评估专家。你的任务是判断当前检索到的“感知记忆”(即时对话)和“短期记忆”(结构化信息)是否足以支撑一次有深度、有上下文的回复。 prompt = f"""你是一个记忆检索评估专家。你的任务是判断当前检索到的“感知记忆”(即时对话)和“短期记忆”(结构化信息)是否足以支撑一次有深度、有上下文的回复。
**核心原则:** **核心原则:**
- **不要轻易检索长期记忆** 只有在当前对话需要深入探讨、回忆过去复杂事件或需要特定背景知识时,才认为记忆不足 - **适当检索长期记忆有助于提升回复质量。** 当对话涉及到特定话题、人物、事件或需要回忆过去的经历时,应该检索长期记忆
- **闲聊、简单问候、表情互动或无特定主题的对话,现有记忆通常是充足的。** 频繁检索长期记忆会拖慢响应速度 - **简单的闲聊和打招呼通常不需要长期记忆。** 如"你好""哈哈"、纯表情互动等
- **如果用户在讨论某个具体话题,即使现有记忆有一些相关信息,也可以检索长期记忆来补充更多背景。**
**用户查询:** **用户查询:**
{query} {query}
@@ -346,13 +347,12 @@ class UnifiedMemoryManager:
{short_term_desc or '(无)'} {short_term_desc or '(无)'}
**评估指南:** **评估指南:**
1. **分析用户意图**:用户在闲聊还是在讨论一个需要深入挖掘的话题? 1. **分析用户意图**:用户在聊什么?是简单闲聊还是有具体话题?
2. **检查现有记忆**当前的感知和短期记忆是否已经包含了足够的信息来回应用户的查询? 2. **检查现有记忆**
- 对于闲聊(如“你好”、“哈哈”、“[表情]”),现有记忆总是充足的 (`"is_sufficient": true`)。 - 如果是纯闲聊("你好""哈哈"、表情)→ 现有记忆充足
- 对于需要回忆具体细节、深入探讨个人经历或专业知识的查询,如果现有记忆中没有相关信息,则可能不充足。 - 如果涉及具体话题(人物、事件、知识)→ 考虑检索长期记忆
3. **决策** - 如果用户提到过去的经历或需要回忆 → 需要检索长期记忆
- 如果记忆充足,设置 `"is_sufficient": true` 3. **倾向于检索**:当不确定时,倾向于设置 `is_sufficient: false`,让系统检索长期记忆以提供更丰富的上下文
- 如果确实需要更多信息才能进行有意义的对话,设置 `"is_sufficient": false`,并提供具体的补充查询。
**输出格式JSON** **输出格式JSON**
```json ```json
@@ -365,7 +365,7 @@ class UnifiedMemoryManager:
}} }}
``` ```
严格按照上述原则进行判断,并输出JSON""" 请输出JSON"""
# 调用记忆裁判模型 # 调用记忆裁判模型
from src.config.config import model_config from src.config.config import model_config

View File

@@ -65,6 +65,12 @@ def init_tool_executor_prompt():
- 避免重复调用历史记录中已执行的工具(除非参数不同) - 避免重复调用历史记录中已执行的工具(除非参数不同)
- 优先考虑使用已有的缓存结果,避免重复调用 - 优先考虑使用已有的缓存结果,避免重复调用
**🎭 重要:保持人设一致性**
你在填写任何工具参数时,都要以"{bot_name}"的身份和视角来写:
- 涉及"印象""感受""评价"类的参数,要用符合你人设的语气和表达方式
- 不要用客观冷漠的AI口吻要像一个真实的人在记录对朋友的感受
- 你的人格特质会影响你对他人的看法,请体现出来
**历史记录说明:** **历史记录说明:**
- 上方显示的是**之前**的工具调用记录 - 上方显示的是**之前**的工具调用记录
- 请参考历史记录避免重复调用相同参数的工具 - 请参考历史记录避免重复调用相同参数的工具
@@ -220,8 +226,9 @@ class ToolExecutor:
all_tools = get_llm_available_tool_definitions(self.chat_id) all_tools = get_llm_available_tool_definitions(self.chat_id)
# 获取基础工具定义(包括二步工具的第一步) # 获取基础工具定义(包括二步工具的第一步)
# 工具定义格式为 {"name": ..., "description": ..., "parameters": ...}
tool_definitions = [ tool_definitions = [
definition for definition in all_tools if definition.get("function", {}).get("name") definition for definition in all_tools if definition.get("name")
] ]
# 检查是否有待处理的二步工具第二步调用 # 检查是否有待处理的二步工具第二步调用
@@ -230,6 +237,10 @@ class ToolExecutor:
# 添加第二步工具定义 # 添加第二步工具定义
tool_definitions.extend(list(pending_step_two.values())) tool_definitions.extend(list(pending_step_two.values()))
# 打印可用的工具名称,方便调试
tool_names = [d.get("name") for d in tool_definitions]
logger.debug(f"{self.log_prefix}当前可用工具 ({len(tool_names)}个): {tool_names}")
return tool_definitions return tool_definitions

View File

@@ -1,8 +1,9 @@
""" """
用户画像更新工具 用户画像更新工具
直接更新用户画像信息,包括别名、主观印象、偏好关键词和好感分数 采用两阶段设计:
现在依赖工具调用历史记录LLM可以看到之前的调用结果因此直接覆盖更新即可 1. 工具调用模型(tool_use)负责判断是否需要更新,传入基本信息
2. 关系追踪模型(relationship_tracker)负责生成高质量的、有人设特色的印象内容
""" """
import time import time
@@ -13,7 +14,7 @@ from sqlalchemy import select
from src.common.database.compatibility import get_db_session from src.common.database.compatibility import get_db_session
from src.common.database.core.models import UserRelationships from src.common.database.core.models import UserRelationships
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.config import global_config from src.config.config import global_config, model_config
from src.plugin_system import BaseTool, ToolParamType from src.plugin_system import BaseTool, ToolParamType
logger = get_logger("user_profile_tool") logger = get_logger("user_profile_tool")
@@ -22,18 +23,22 @@ logger = get_logger("user_profile_tool")
class UserProfileTool(BaseTool): class UserProfileTool(BaseTool):
"""用户画像更新工具 """用户画像更新工具
直接使用LLM传入的参数更新用户画像。 两阶段设计:
由于工具执行器现在支持历史记录LLM可以看到之前的调用结果因此无需再次调用LLM进行合并。 - 第一阶段tool_use模型判断是否更新传入简要信息
- 第二阶段relationship_tracker模型生成有人设特色的印象描述
""" """
name = "update_user_profile" name = "update_user_profile"
description = "当你通过聊天记录对某个用户产生了新的认识或印象时使用此工具更新该用户的画像信息。包括用户别名、你对TA的主观印象、TA的偏好兴趣、你对TA的好感程度。调用时机当你发现用户透露了新的个人信息、展现了性格特点、表达了兴趣偏好或者你们的互动让你对TA的看法发生变化时" description = """当你通过聊天对某个产生了新的认识或印象时使用此工具。
调用时机当你发现TA透露了新信息、展现了性格特点、表达了兴趣爱好或你们的互动让你对TA有了新感受时。
注意impression_hint只需要简单描述你观察到的要点系统会自动用你的人设风格来润色生成最终印象。"""
parameters = [ parameters = [
("target_user_id", ToolParamType.STRING, "目标用户的ID必须", True, None), ("target_user_id", ToolParamType.STRING, "目标用户的ID必须", True, None),
("user_aliases", ToolParamType.STRING, "用户的昵称或别名,如果发现用户自称或被他人称呼的其他名字时填写,多个别名用逗号分隔(可选", False, None), ("target_user_name", ToolParamType.STRING, "目标用户的名字/昵称(必须,用于生成印象时称呼", True, None),
("impression_description", ToolParamType.STRING, "你对该用户的整体印象和性格感受,例如'这个用户很幽默开朗''TA对技术很有热情'等。当你通过对话了解到用户的性格、态度、行为特点时填写(可选)", False, None), ("user_aliases", ToolParamType.STRING, "TA的其他昵称或别名多个用逗号分隔(可选)", False, None),
("preference_keywords", ToolParamType.STRING, "该用户表现出的兴趣爱好或偏好,如'编程,游戏,动漫'。当用户谈论自己喜欢的事物时填写,多个关键词用逗号分隔(可选)", False, None), ("impression_hint", ToolParamType.STRING, "【简要描述】你观察到的关于TA的要点'很健谈,喜欢聊游戏,有点害羞'。系统会用你的人设风格润色(可选)", False, None),
("affection_score", ToolParamType.FLOAT, "你对该用户的好感程度0.0(陌生/不喜欢)到1.0(很喜欢/爱人)。当你们的互动让你对TA的感觉发生变化时更新【注意0.6分已经是一个很高的分数,打分一定要保守谨慎】(可选)", False, None), ("preference_keywords", ToolParamType.STRING, "TA的兴趣爱好关键词'编程,游戏,音乐',用逗号分隔(可选)", False, None),
("affection_score", ToolParamType.FLOAT, "你对TA的好感度(0.0-1.0)。0.3=普通认识0.5=还不错的朋友0.7=很喜欢0.9=非常亲密。打分要保守(可选)", False, None),
] ]
available_for_llm = True available_for_llm = True
history_ttl = 5 history_ttl = 5
@@ -50,6 +55,7 @@ class UserProfileTool(BaseTool):
try: try:
# 提取参数 # 提取参数
target_user_id = function_args.get("target_user_id") target_user_id = function_args.get("target_user_id")
target_user_name = function_args.get("target_user_name", target_user_id)
if not target_user_id: if not target_user_id:
return { return {
"type": "error", "type": "error",
@@ -59,25 +65,35 @@ class UserProfileTool(BaseTool):
# 从LLM传入的参数 # 从LLM传入的参数
new_aliases = function_args.get("user_aliases", "") new_aliases = function_args.get("user_aliases", "")
new_impression = function_args.get("impression_description", "") impression_hint = function_args.get("impression_hint", "")
new_keywords = function_args.get("preference_keywords", "") new_keywords = function_args.get("preference_keywords", "")
new_score = function_args.get("affection_score") new_score = function_args.get("affection_score")
# 从数据库获取现有用户画像(用于返回信息) # 从数据库获取现有用户画像
existing_profile = await self._get_user_profile(target_user_id) existing_profile = await self._get_user_profile(target_user_id)
# 如果LLM没有传入任何有效参数返回提示 # 如果LLM没有传入任何有效参数返回提示
if not any([new_aliases, new_impression, new_keywords, new_score is not None]): if not any([new_aliases, impression_hint, new_keywords, new_score is not None]):
return { return {
"type": "info", "type": "info",
"id": target_user_id, "id": target_user_id,
"content": "提示:需要提供至少一项更新内容(别名、印象描述、偏好关键词或好感分数)" "content": "提示:需要提供至少一项更新内容(别名、印象描述、偏好关键词或好感分数)"
} }
# 直接使用LLM传入的值进行覆盖更新保留未更新的字段 # 🎯 核心使用relationship_tracker模型生成高质量印象
final_impression = existing_profile.get("relationship_text", "")
if impression_hint:
final_impression = await self._generate_impression_with_personality(
target_user_name=str(target_user_name) if target_user_name else str(target_user_id),
impression_hint=str(impression_hint),
existing_impression=str(existing_profile.get("relationship_text", "")),
preference_keywords=str(new_keywords or existing_profile.get("preference_keywords", "")),
)
# 构建最终画像
final_profile = { final_profile = {
"user_aliases": new_aliases if new_aliases else existing_profile.get("user_aliases", ""), "user_aliases": new_aliases if new_aliases else existing_profile.get("user_aliases", ""),
"relationship_text": new_impression if new_impression else existing_profile.get("relationship_text", ""), "relationship_text": final_impression,
"preference_keywords": new_keywords if new_keywords else existing_profile.get("preference_keywords", ""), "preference_keywords": new_keywords if new_keywords else existing_profile.get("preference_keywords", ""),
"relationship_score": new_score if new_score is not None else existing_profile.get("relationship_score", global_config.affinity_flow.base_relationship_score), "relationship_score": new_score if new_score is not None else existing_profile.get("relationship_score", global_config.affinity_flow.base_relationship_score),
} }
@@ -93,13 +109,13 @@ class UserProfileTool(BaseTool):
if final_profile.get("user_aliases"): if final_profile.get("user_aliases"):
updates.append(f"别名: {final_profile['user_aliases']}") updates.append(f"别名: {final_profile['user_aliases']}")
if final_profile.get("relationship_text"): if final_profile.get("relationship_text"):
updates.append(f"印象: {final_profile['relationship_text'][:50]}...") updates.append(f"印象: {final_profile['relationship_text'][:80]}...")
if final_profile.get("preference_keywords"): if final_profile.get("preference_keywords"):
updates.append(f"偏好: {final_profile['preference_keywords']}") updates.append(f"偏好: {final_profile['preference_keywords']}")
if final_profile.get("relationship_score") is not None: if final_profile.get("relationship_score") is not None:
updates.append(f"好感分: {final_profile['relationship_score']:.2f}") updates.append(f"好感分: {final_profile['relationship_score']:.2f}")
result_text = f"已更新用户 {target_user_id} 的画像:\n" + "\n".join(updates) result_text = f"已更新用户 {target_user_name} 的画像:\n" + "\n".join(updates)
logger.info(f"用户画像更新成功: {target_user_id}") logger.info(f"用户画像更新成功: {target_user_id}")
return { return {
@@ -116,6 +132,90 @@ class UserProfileTool(BaseTool):
"content": f"用户画像更新失败: {e!s}" "content": f"用户画像更新失败: {e!s}"
} }
async def _generate_impression_with_personality(
self,
target_user_name: str,
impression_hint: str,
existing_impression: str,
preference_keywords: str,
) -> str:
"""使用relationship_tracker模型生成有人设特色的印象描述
Args:
target_user_name: 目标用户的名字
impression_hint: 工具调用模型传入的简要观察
existing_impression: 现有的印象描述
preference_keywords: 用户的兴趣偏好
Returns:
str: 生成的印象描述
"""
try:
from src.llm_models.utils_model import LLMRequest
# 获取人设信息
bot_name = global_config.bot.nickname
personality_core = global_config.personality.personality_core
personality_side = global_config.personality.personality_side
# 构建提示词
prompt = f"""你是{bot_name},现在要记录你对一个人的印象。
## 你的人设
{personality_core}
## 你的性格特点
{personality_side}
## 任务
根据下面的观察要点,用你自己的语气和视角,写一段对"{target_user_name}"的印象描述。
## 观察到的要点
{impression_hint}
## TA的兴趣爱好
{preference_keywords if preference_keywords else "暂未了解"}
## 之前对TA的印象如果有
{existing_impression if existing_impression else "这是第一次记录对TA的印象"}
## 写作要求
1. 用第一人称""来写,就像在写日记或者跟朋友聊天时描述一个人
2. 用"{target_user_name}""TA"来称呼对方,不要用"该用户""此人"
3. 写出你真实的、主观的感受,可以带情绪和直觉判断
4. 如果有之前的印象,可以结合新观察进行补充或修正
5. 长度控制在50-150字自然流畅
请直接输出印象描述,不要加任何前缀或解释:"""
# 使用relationship_tracker模型
llm = LLMRequest(
model_set=model_config.model_task_config.relationship_tracker,
request_type="user_profile.impression_generator"
)
response, _ = await llm.generate_response_async(
prompt=prompt,
temperature=0.7,
max_tokens=300,
)
# 清理响应
impression = response.strip()
# 如果响应为空或太短回退到原始hint
if not impression or len(impression) < 10:
logger.warning(f"印象生成结果过短使用原始hint: {impression_hint}")
return impression_hint
logger.info(f"成功生成有人设特色的印象描述,长度: {len(impression)}")
return impression
except Exception as e:
logger.error(f"生成印象描述失败回退到原始hint: {e}")
# 失败时回退到工具调用模型传入的hint
return impression_hint
async def _get_user_profile(self, user_id: str) -> dict[str, Any]: async def _get_user_profile(self, user_id: str) -> dict[str, Any]:
"""从数据库获取用户现有画像 """从数据库获取用户现有画像

View File

@@ -5,6 +5,7 @@ Kokoro Flow Chatter 上下文构建器
包含: 包含:
- 关系信息 (relation_info) - 关系信息 (relation_info)
- 记忆块 (memory_block) - 记忆块 (memory_block)
- 工具调用 (tool_info)
- 表达习惯 (expression_habits) - 表达习惯 (expression_habits)
- 日程信息 (schedule) - 日程信息 (schedule)
- 时间信息 (time) - 时间信息 (time)
@@ -51,6 +52,7 @@ class KFCContextBuilder:
target_message: str, target_message: str,
context: Optional["StreamContext"] = None, context: Optional["StreamContext"] = None,
user_id: Optional[str] = None, user_id: Optional[str] = None,
enable_tool: bool = True,
) -> dict[str, str]: ) -> dict[str, str]:
""" """
并行构建所有上下文模块 并行构建所有上下文模块
@@ -60,46 +62,73 @@ class KFCContextBuilder:
target_message: 目标消息内容 target_message: 目标消息内容
context: 聊天流上下文(可选) context: 聊天流上下文(可选)
user_id: 用户ID可选用于精确查找关系信息 user_id: 用户ID可选用于精确查找关系信息
enable_tool: 是否启用工具调用
Returns: Returns:
dict: 包含所有上下文块的字典 dict: 包含所有上下文块的字典
""" """
logger.debug(f"[KFC上下文] 开始构建上下文: sender={sender_name}, target={target_message[:50] if target_message else '(空)'}...")
chat_history = await self._get_chat_history_text(context) chat_history = await self._get_chat_history_text(context)
tasks = { tasks = {
"relation_info": self._build_relation_info(sender_name, target_message, user_id), "relation_info": self._build_relation_info(sender_name, target_message, user_id),
"memory_block": self._build_memory_block(chat_history, target_message), "memory_block": self._build_memory_block(chat_history, target_message, context),
"tool_info": self._build_tool_info(chat_history, sender_name, target_message, enable_tool),
"expression_habits": self._build_expression_habits(chat_history, target_message), "expression_habits": self._build_expression_habits(chat_history, target_message),
"schedule": self._build_schedule_block(), "schedule": self._build_schedule_block(),
"time": self._build_time_block(), "time": self._build_time_block(),
} }
results = {} results = {}
timing_logs = []
# 任务名称中英文映射
task_name_mapping = {
"relation_info": "感受关系",
"memory_block": "回忆",
"tool_info": "使用工具",
"expression_habits": "选取表达方式",
"schedule": "日程",
"time": "时间",
}
try: try:
task_results = await asyncio.gather( task_results = await asyncio.gather(
*[self._wrap_task(name, coro) for name, coro in tasks.items()], *[self._wrap_task_with_timing(name, coro) for name, coro in tasks.items()],
return_exceptions=True return_exceptions=True
) )
for result in task_results: for result in task_results:
if isinstance(result, tuple): if isinstance(result, tuple) and len(result) == 3:
name, value = result name, value, duration = result
results[name] = value results[name] = value
else: chinese_name = task_name_mapping.get(name, name)
timing_logs.append(f"{chinese_name}: {duration:.1f}s")
if duration > 8:
logger.warning(f"KFC 上下文构建耗时过长: {chinese_name} 耗时: {duration:.1f}s")
elif isinstance(result, Exception):
logger.warning(f"上下文构建任务异常: {result}") logger.warning(f"上下文构建任务异常: {result}")
except Exception as e: except Exception as e:
logger.error(f"并行构建上下文失败: {e}") logger.error(f"并行构建上下文失败: {e}")
# 输出耗时日志
if timing_logs:
logger.info(f"在回复前的步骤耗时: {'; '.join(timing_logs)}")
return results return results
async def _wrap_task(self, name: str, coro) -> tuple[str, str]: async def _wrap_task_with_timing(self, name: str, coro) -> tuple[str, str, float]:
"""包装任务以返回名称结果""" """包装任务以返回名称结果和耗时"""
start_time = time.time()
try: try:
result = await coro result = await coro
return (name, result or "") duration = time.time() - start_time
return (name, result or "", duration)
except Exception as e: except Exception as e:
duration = time.time() - start_time
logger.error(f"构建 {name} 失败: {e}") logger.error(f"构建 {name} 失败: {e}")
return (name, "") return (name, "", duration)
async def _get_chat_history_text( async def _get_chat_history_text(
self, self,
@@ -176,11 +205,17 @@ class KFCContextBuilder:
logger.error(f"获取关系信息失败: {e}") logger.error(f"获取关系信息失败: {e}")
return f"你与{sender_name}是普通朋友关系。" return f"你与{sender_name}是普通朋友关系。"
async def _build_memory_block(self, chat_history: str, target_message: str) -> str: async def _build_memory_block(
self,
chat_history: str,
target_message: str,
context: Optional["StreamContext"] = None,
) -> str:
"""构建记忆块(使用三层记忆系统)""" """构建记忆块(使用三层记忆系统)"""
config = _get_config() config = _get_config()
if not (config.memory and config.memory.enable): if not (config.memory and config.memory.enable):
logger.debug("[KFC记忆] 记忆系统未启用")
return "" return ""
try: try:
@@ -189,16 +224,21 @@ class KFCContextBuilder:
unified_manager = get_unified_memory_manager() unified_manager = get_unified_memory_manager()
if not unified_manager: if not unified_manager:
logger.debug("[三层记忆] 管理器未初始化") logger.warning("[KFC记忆] 管理器未初始化,跳过记忆检索")
return "" return ""
# 构建查询文本(使用最近多条消息的组合块)
query_text = self._build_memory_query_text(target_message, context)
logger.debug(f"[KFC记忆] 开始检索,查询文本: {query_text[:100]}...")
search_result = await unified_manager.search_memories( search_result = await unified_manager.search_memories(
query_text=target_message, query_text=query_text,
use_judge=True, use_judge=True,
recent_chat_history=chat_history, recent_chat_history=chat_history,
) )
if not search_result: if not search_result:
logger.debug("[KFC记忆] 未找到相关记忆")
return "" return ""
perceptual_blocks = search_result.get("perceptual_blocks", []) perceptual_blocks = search_result.get("perceptual_blocks", [])
@@ -214,15 +254,126 @@ class KFCContextBuilder:
total_count = len(perceptual_blocks) + len(short_term_memories) + len(long_term_memories) total_count = len(perceptual_blocks) + len(short_term_memories) + len(long_term_memories)
if total_count > 0 and formatted_memories.strip(): if total_count > 0 and formatted_memories.strip():
logger.info( logger.info(
f"[三层记忆] 检索到 {total_count} 条记忆 " f"[KFC记忆] 检索到 {total_count} 条记忆 "
f"(感知:{len(perceptual_blocks)}, 短期:{len(short_term_memories)}, 长期:{len(long_term_memories)})" f"(感知:{len(perceptual_blocks)}, 短期:{len(short_term_memories)}, 长期:{len(long_term_memories)})"
) )
return f"### 🧠 相关记忆\n\n{formatted_memories}" return f"### 🧠 相关记忆\n\n{formatted_memories}"
logger.debug("[KFC记忆] 记忆为空")
return "" return ""
except Exception as e: except Exception as e:
logger.error(f"[三层记忆] 检索失败: {e}") logger.error(f"[KFC记忆] 检索失败: {e}")
import traceback
traceback.print_exc()
return ""
def _build_memory_query_text(
self,
fallback_text: str,
context: Optional["StreamContext"] = None,
block_size: int = 5,
) -> str:
"""
将最近若干条消息拼接为一个查询块,用于生成语义向量。
Args:
fallback_text: 如果无法拼接消息块时使用的后备文本
context: 聊天流上下文
block_size: 组合的消息数量
Returns:
str: 用于检索的查询文本
"""
if not context:
return fallback_text
try:
messages = context.get_messages(limit=block_size, include_unread=True)
if not messages:
return fallback_text
lines = []
for msg in messages:
sender = ""
if msg.user_info:
sender = msg.user_info.user_nickname or msg.user_info.user_cardname or ""
content = msg.processed_plain_text or msg.display_message or ""
if sender and content:
lines.append(f"{sender}: {content}")
elif content:
lines.append(content)
return "\n".join(lines) if lines else fallback_text
except Exception:
return fallback_text
async def _build_tool_info(
self,
chat_history: str,
sender_name: str,
target_message: str,
enable_tool: bool = True,
) -> str:
"""构建工具信息块
Args:
chat_history: 聊天历史记录
sender_name: 发送者名称
target_message: 目标消息内容
enable_tool: 是否启用工具调用
Returns:
str: 工具信息字符串
"""
if not enable_tool:
return ""
try:
from src.plugin_system.core.tool_use import ToolExecutor
tool_executor = ToolExecutor(chat_id=self.chat_id)
# 首先获取当前的历史记录(在执行新工具调用之前)
tool_history_str = tool_executor.history_manager.format_for_prompt(
max_records=3, include_results=True
)
# 然后执行工具调用
tool_results, _, _ = await tool_executor.execute_from_chat_message(
sender=sender_name,
target_message=target_message,
chat_history=chat_history,
return_details=False,
)
info_parts = []
# 显示之前的工具调用历史(不包括当前这次调用)
if tool_history_str:
info_parts.append(tool_history_str)
# 显示当前工具调用的结果(简要信息)
if tool_results:
current_results_parts = ["### 🔧 刚获取的工具信息"]
for tool_result in tool_results:
tool_name = tool_result.get("tool_name", "unknown")
content = tool_result.get("content", "")
# 不进行截断,让工具自己处理结果长度
current_results_parts.append(f"- **{tool_name}**: {content}")
info_parts.append("\n".join(current_results_parts))
logger.info(f"[工具调用] 获取到 {len(tool_results)} 个工具结果")
# 如果没有任何信息,返回空字符串
if not info_parts:
logger.debug("[工具调用] 未获取到任何工具结果或历史记录")
return ""
return "\n\n".join(info_parts)
except Exception as e:
logger.error(f"[工具调用] 工具信息获取失败: {e}")
return "" return ""
async def _build_expression_habits(self, chat_history: str, target_message: str) -> str: async def _build_expression_habits(self, chat_history: str, target_message: str) -> str:

View File

@@ -69,10 +69,11 @@ class PromptBuilder:
# 1. 构建人设块 # 1. 构建人设块
persona_block = self._build_persona_block() persona_block = self._build_persona_block()
# 2. 使用 context_builder 获取关系、记忆、表达习惯等 # 2. 使用 context_builder 获取关系、记忆、工具、表达习惯等
context_data = await self._build_context_data(user_name, chat_stream, user_id) context_data = await self._build_context_data(user_name, chat_stream, user_id)
relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。") relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。")
memory_block = context_data.get("memory_block", "") memory_block = context_data.get("memory_block", "")
tool_info = context_data.get("tool_info", "")
expression_habits = self._build_combined_expression_block(context_data.get("expression_habits", "")) expression_habits = self._build_combined_expression_block(context_data.get("expression_habits", ""))
# 3. 构建活动流 # 3. 构建活动流
@@ -99,6 +100,7 @@ class PromptBuilder:
persona_block=persona_block, persona_block=persona_block,
relation_block=relation_block, relation_block=relation_block,
memory_block=memory_block or "(暂无相关记忆)", memory_block=memory_block or "(暂无相关记忆)",
tool_info=tool_info or "(暂无工具信息)",
expression_habits=expression_habits or "(根据自然对话风格回复即可)", expression_habits=expression_habits or "(根据自然对话风格回复即可)",
activity_stream=activity_stream or "(这是你们第一次聊天)", activity_stream=activity_stream or "(这是你们第一次聊天)",
current_situation=current_situation, current_situation=current_situation,
@@ -140,10 +142,11 @@ class PromptBuilder:
# 1. 构建人设块 # 1. 构建人设块
persona_block = self._build_persona_block() persona_block = self._build_persona_block()
# 2. 使用 context_builder 获取关系、记忆、表达习惯等 # 2. 使用 context_builder 获取关系、记忆、工具、表达习惯等
context_data = await self._build_context_data(user_name, chat_stream, user_id) context_data = await self._build_context_data(user_name, chat_stream, user_id)
relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。") relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。")
memory_block = context_data.get("memory_block", "") memory_block = context_data.get("memory_block", "")
tool_info = context_data.get("tool_info", "")
expression_habits = self._build_combined_expression_block(context_data.get("expression_habits", "")) expression_habits = self._build_combined_expression_block(context_data.get("expression_habits", ""))
# 3. 构建活动流 # 3. 构建活动流
@@ -169,6 +172,7 @@ class PromptBuilder:
persona_block=persona_block, persona_block=persona_block,
relation_block=relation_block, relation_block=relation_block,
memory_block=memory_block or "(暂无相关记忆)", memory_block=memory_block or "(暂无相关记忆)",
tool_info=tool_info or "(暂无工具信息)",
activity_stream=activity_stream or "(这是你们第一次聊天)", activity_stream=activity_stream or "(这是你们第一次聊天)",
current_situation=current_situation, current_situation=current_situation,
chat_history_block=chat_history_block, chat_history_block=chat_history_block,
@@ -237,12 +241,13 @@ class PromptBuilder:
""" """
使用 KFCContextBuilder 构建完整的上下文数据 使用 KFCContextBuilder 构建完整的上下文数据
包括:关系信息、记忆、表达习惯等 包括:关系信息、记忆、工具、表达习惯等
""" """
if not chat_stream: if not chat_stream:
return { return {
"relation_info": f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。", "relation_info": f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。",
"memory_block": "", "memory_block": "",
"tool_info": "",
"expression_habits": "", "expression_habits": "",
} }
@@ -275,6 +280,7 @@ class PromptBuilder:
return { return {
"relation_info": f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。", "relation_info": f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。",
"memory_block": "", "memory_block": "",
"tool_info": "",
"expression_habits": "", "expression_habits": "",
} }
@@ -881,10 +887,11 @@ class PromptBuilder:
# 1. 构建人设块 # 1. 构建人设块
persona_block = self._build_persona_block() persona_block = self._build_persona_block()
# 2. 使用 context_builder 获取关系、记忆、表达习惯等 # 2. 使用 context_builder 获取关系、记忆、工具、表达习惯等
context_data = await self._build_context_data(user_name, chat_stream, user_id) context_data = await self._build_context_data(user_name, chat_stream, user_id)
relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。") relation_block = context_data.get("relation_info", f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。")
memory_block = context_data.get("memory_block", "") memory_block = context_data.get("memory_block", "")
tool_info = context_data.get("tool_info", "")
expression_habits = self._build_combined_expression_block(context_data.get("expression_habits", "")) expression_habits = self._build_combined_expression_block(context_data.get("expression_habits", ""))
# 3. 构建活动流 # 3. 构建活动流
@@ -911,6 +918,7 @@ class PromptBuilder:
persona_block=persona_block, persona_block=persona_block,
relation_block=relation_block, relation_block=relation_block,
memory_block=memory_block or "(暂无相关记忆)", memory_block=memory_block or "(暂无相关记忆)",
tool_info=tool_info or "(暂无工具信息)",
expression_habits=expression_habits or "(根据自然对话风格回复即可)", expression_habits=expression_habits or "(根据自然对话风格回复即可)",
activity_stream=activity_stream or "(这是你们第一次聊天)", activity_stream=activity_stream or "(这是你们第一次聊天)",
current_situation=current_situation, current_situation=current_situation,

View File

@@ -23,6 +23,9 @@ kfc_MAIN_PROMPT = Prompt(
## 相关记忆 ## 相关记忆
{memory_block} {memory_block}
## 工具信息
{tool_info}
## 你们之间最近的活动记录 ## 你们之间最近的活动记录
以下是你和 {user_name} 最近的互动历史,按时间顺序记录了你们的对话和你的心理活动: 以下是你和 {user_name} 最近的互动历史,按时间顺序记录了你们的对话和你的心理活动:
@@ -278,6 +281,9 @@ kfc_REPLYER_PROMPT = Prompt(
## 相关记忆 ## 相关记忆
{memory_block} {memory_block}
## 工具信息
{tool_info}
## 你们之间发生的事(活动流) ## 你们之间发生的事(活动流)
以下是你和 {user_name} 最近的互动历史,按时间顺序记录了你们的对话和你的心理活动: 以下是你和 {user_name} 最近的互动历史,按时间顺序记录了你们的对话和你的心理活动:

View File

@@ -139,6 +139,9 @@ def build_context_module(
# 记忆 # 记忆
memory_block = context_data.get("memory_block", "") memory_block = context_data.get("memory_block", "")
# 工具调用结果
tool_info = context_data.get("tool_info", "")
parts = [] parts = []
# 时间和场景 # 时间和场景
@@ -160,6 +163,10 @@ def build_context_module(
if memory_block: if memory_block:
parts.append(f"\n{memory_block}") parts.append(f"\n{memory_block}")
# 工具调用结果
if tool_info:
parts.append(f"\n{tool_info}")
return "\n".join(parts) return "\n".join(parts)

View File

@@ -416,12 +416,16 @@ async def _build_context_data(
user_id: Optional[str] = None, user_id: Optional[str] = None,
) -> dict[str, str]: ) -> dict[str, str]:
""" """
构建上下文数据(关系、记忆、表达习惯等) 构建上下文数据(关系、记忆、工具、表达习惯等)
""" """
logger.debug(f"[KFC Unified] 开始构建上下文数据: user={user_name}")
if not chat_stream: if not chat_stream:
logger.warning("[KFC Unified] 无 chat_stream返回默认上下文")
return { return {
"relation_info": f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。", "relation_info": f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。",
"memory_block": "", "memory_block": "",
"tool_info": "",
"expression_habits": "", "expression_habits": "",
"schedule": "", "schedule": "",
} }
@@ -445,13 +449,21 @@ async def _build_context_data(
user_id=user_id, user_id=user_id,
) )
# 打印关键信息
memory_len = len(context_data.get("memory_block", ""))
tool_len = len(context_data.get("tool_info", ""))
logger.debug(f"[KFC Unified] 上下文构建完成: memory_block={memory_len}字符, tool_info={tool_len}字符")
return context_data return context_data
except Exception as e: except Exception as e:
logger.warning(f"构建上下文数据失败: {e}") logger.error(f"[KFC Unified] 构建上下文数据失败: {e}")
import traceback
traceback.print_exc()
return { return {
"relation_info": f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。", "relation_info": f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。",
"memory_block": "", "memory_block": "",
"tool_info": "",
"expression_habits": "", "expression_habits": "",
"schedule": "", "schedule": "",
} }