remove:无用代码
This commit is contained in:
@@ -1,15 +1,10 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import math
|
|
||||||
import random
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from json_repair import repair_json
|
|
||||||
|
|
||||||
from sqlmodel import col, select
|
from sqlmodel import col, select
|
||||||
|
|
||||||
from src.chat.message_receive.chat_manager import chat_manager as _chat_manager
|
from src.chat.message_receive.chat_manager import chat_manager as _chat_manager
|
||||||
@@ -19,15 +14,10 @@ from src.common.database.database_model import PersonInfo
|
|||||||
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
|
||||||
from src.services.memory_service import memory_service
|
from src.services.memory_service import memory_service
|
||||||
from src.services.llm_service import LLMServiceClient
|
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger("person_info")
|
logger = get_logger("person_info")
|
||||||
|
|
||||||
relation_selection_model = LLMServiceClient(
|
|
||||||
task_name="utils", request_type="relation_selection"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _to_group_cardname_records(group_cardname_json: Optional[str]) -> list[dict[str, str]]:
|
def _to_group_cardname_records(group_cardname_json: Optional[str]) -> list[dict[str, str]]:
|
||||||
"""将数据库中的群名片 JSON 转换为 `Person` 内部使用的结构。
|
"""将数据库中的群名片 JSON 转换为 `Person` 内部使用的结构。
|
||||||
@@ -133,50 +123,6 @@ def is_person_known(
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_category_from_memory(memory_point: str) -> Optional[str]:
|
|
||||||
"""从记忆点中获取分类"""
|
|
||||||
# 按照最左边的:符号进行分割,返回分割后的第一个部分作为分类
|
|
||||||
if not isinstance(memory_point, str):
|
|
||||||
return None
|
|
||||||
parts = memory_point.split(":", 1)
|
|
||||||
return parts[0].strip() if len(parts) > 1 else None
|
|
||||||
|
|
||||||
|
|
||||||
def get_weight_from_memory(memory_point: str) -> float:
|
|
||||||
"""从记忆点中获取权重"""
|
|
||||||
# 按照最右边的:符号进行分割,返回分割后的最后一个部分作为权重
|
|
||||||
if not isinstance(memory_point, str):
|
|
||||||
return -math.inf
|
|
||||||
parts = memory_point.rsplit(":", 1)
|
|
||||||
if len(parts) <= 1:
|
|
||||||
return -math.inf
|
|
||||||
try:
|
|
||||||
return float(parts[-1].strip())
|
|
||||||
except Exception:
|
|
||||||
return -math.inf
|
|
||||||
|
|
||||||
|
|
||||||
def get_memory_content_from_memory(memory_point: str) -> str:
|
|
||||||
"""从记忆点中获取记忆内容"""
|
|
||||||
# 按:进行分割,去掉第一段和最后一段,返回中间部分作为记忆内容
|
|
||||||
if not isinstance(memory_point, str):
|
|
||||||
return ""
|
|
||||||
parts = memory_point.split(":")
|
|
||||||
return ":".join(parts[1:-1]).strip() if len(parts) > 2 else ""
|
|
||||||
|
|
||||||
|
|
||||||
def extract_categories_from_response(response: str) -> list[str]:
|
|
||||||
"""从response中提取所有<>包裹的内容"""
|
|
||||||
if not isinstance(response, str):
|
|
||||||
return []
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
pattern = r"<([^<>]+)>"
|
|
||||||
matches = re.findall(pattern, response)
|
|
||||||
return matches
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_string_similarity(s1: str, s2: str) -> float:
|
def calculate_string_similarity(s1: str, s2: str) -> float:
|
||||||
"""
|
"""
|
||||||
计算两个字符串的相似度
|
计算两个字符串的相似度
|
||||||
@@ -430,31 +376,6 @@ class Person:
|
|||||||
|
|
||||||
return deleted_count
|
return deleted_count
|
||||||
|
|
||||||
def get_all_category(self):
|
|
||||||
category_list = []
|
|
||||||
for memory in self.memory_points:
|
|
||||||
if memory is None:
|
|
||||||
continue
|
|
||||||
category = get_category_from_memory(memory)
|
|
||||||
if category and category not in category_list:
|
|
||||||
category_list.append(category)
|
|
||||||
return category_list
|
|
||||||
|
|
||||||
def get_memory_list_by_category(self, category: str):
|
|
||||||
memory_list = []
|
|
||||||
for memory in self.memory_points:
|
|
||||||
if memory is None:
|
|
||||||
continue
|
|
||||||
if get_category_from_memory(memory) == category:
|
|
||||||
memory_list.append(memory)
|
|
||||||
return memory_list
|
|
||||||
|
|
||||||
def get_random_memory_by_category(self, category: str, num: int = 1):
|
|
||||||
memory_list = self.get_memory_list_by_category(category)
|
|
||||||
if len(memory_list) < num:
|
|
||||||
return memory_list
|
|
||||||
return random.sample(memory_list, num)
|
|
||||||
|
|
||||||
def add_group_nick_name(self, group_id: str, group_nick_name: str):
|
def add_group_nick_name(self, group_id: str, group_nick_name: str):
|
||||||
"""
|
"""
|
||||||
添加或更新群昵称
|
添加或更新群昵称
|
||||||
@@ -584,251 +505,6 @@ class Person:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"同步用户 {self.person_id} 信息到数据库时出错: {e}")
|
logger.error(f"同步用户 {self.person_id} 信息到数据库时出错: {e}")
|
||||||
|
|
||||||
async def build_relationship(self, chat_content: str = "", info_type=""):
|
|
||||||
if not self.is_known:
|
|
||||||
return ""
|
|
||||||
# 构建points文本
|
|
||||||
|
|
||||||
nickname_str = ""
|
|
||||||
if self.person_name != self.nickname:
|
|
||||||
nickname_str = f"(ta在{self.platform}上的昵称是{self.nickname})"
|
|
||||||
|
|
||||||
relation_info = ""
|
|
||||||
|
|
||||||
points_text = ""
|
|
||||||
category_list = self.get_all_category()
|
|
||||||
|
|
||||||
if chat_content:
|
|
||||||
prompt = f"""当前聊天内容:
|
|
||||||
{chat_content}
|
|
||||||
|
|
||||||
分类列表:
|
|
||||||
{category_list}
|
|
||||||
**要求**:请你根据当前聊天内容,从以下分类中选择一个与聊天内容相关的分类,并用<>包裹输出,不要输出其他内容,不要输出引号或[],严格用<>包裹:
|
|
||||||
例如:
|
|
||||||
<分类1><分类2><分类3>......
|
|
||||||
如果没有相关的分类,请输出<none>"""
|
|
||||||
|
|
||||||
generation_result = await relation_selection_model.generate_response(prompt)
|
|
||||||
response = generation_result.response
|
|
||||||
# print(prompt)
|
|
||||||
# print(response)
|
|
||||||
category_list = extract_categories_from_response(response)
|
|
||||||
if "none" not in category_list:
|
|
||||||
for category in category_list:
|
|
||||||
random_memory = self.get_random_memory_by_category(category, 2)
|
|
||||||
if random_memory:
|
|
||||||
random_memory_str = "\n".join(
|
|
||||||
[get_memory_content_from_memory(memory) for memory in random_memory]
|
|
||||||
)
|
|
||||||
points_text = f"有关 {category} 的内容:{random_memory_str}"
|
|
||||||
break
|
|
||||||
elif info_type:
|
|
||||||
prompt = f"""你需要获取用户{self.person_name}的 **{info_type}** 信息。
|
|
||||||
|
|
||||||
现有信息类别列表:
|
|
||||||
{category_list}
|
|
||||||
**要求**:请你根据**{info_type}**,从以下分类中选择一个与**{info_type}**相关的分类,并用<>包裹输出,不要输出其他内容,不要输出引号或[],严格用<>包裹:
|
|
||||||
例如:
|
|
||||||
<分类1><分类2><分类3>......
|
|
||||||
如果没有相关的分类,请输出<none>"""
|
|
||||||
generation_result = await relation_selection_model.generate_response(prompt)
|
|
||||||
response = generation_result.response
|
|
||||||
# print(prompt)
|
|
||||||
# print(response)
|
|
||||||
category_list = extract_categories_from_response(response)
|
|
||||||
if "none" not in category_list:
|
|
||||||
for category in category_list:
|
|
||||||
random_memory = self.get_random_memory_by_category(category, 3)
|
|
||||||
if random_memory:
|
|
||||||
random_memory_str = "\n".join(
|
|
||||||
[get_memory_content_from_memory(memory) for memory in random_memory]
|
|
||||||
)
|
|
||||||
points_text = f"有关 {category} 的内容:{random_memory_str}"
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
for category in category_list:
|
|
||||||
random_memory = self.get_random_memory_by_category(category, 1)[0]
|
|
||||||
if random_memory:
|
|
||||||
points_text = f"有关 {category} 的内容:{get_memory_content_from_memory(random_memory)}"
|
|
||||||
break
|
|
||||||
|
|
||||||
points_info = ""
|
|
||||||
if points_text:
|
|
||||||
points_info = f"你还记得有关{self.person_name}的内容:{points_text}"
|
|
||||||
|
|
||||||
if not (nickname_str or points_info):
|
|
||||||
return ""
|
|
||||||
relation_info = f"{self.person_name}:{nickname_str}{points_info}"
|
|
||||||
|
|
||||||
return relation_info
|
|
||||||
|
|
||||||
|
|
||||||
class PersonInfoManager:
|
|
||||||
def __init__(self):
|
|
||||||
self.person_name_list = {}
|
|
||||||
self.qv_name_llm = LLMServiceClient(
|
|
||||||
task_name="utils", request_type="relation.qv_name"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
with get_db_session() as _:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"数据库连接或 PersonInfo 表创建失败: {e}")
|
|
||||||
|
|
||||||
# 初始化时读取所有person_name
|
|
||||||
try:
|
|
||||||
with get_db_session() as session:
|
|
||||||
statement = select(PersonInfo.person_id, PersonInfo.person_name).where(
|
|
||||||
col(PersonInfo.person_name).is_not(None)
|
|
||||||
)
|
|
||||||
for person_id, person_name in session.exec(statement).all():
|
|
||||||
if person_name:
|
|
||||||
self.person_name_list[person_id] = person_name
|
|
||||||
logger.debug(f"已加载 {len(self.person_name_list)} 个用户名称")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"加载 person_name_list 失败: {e}")
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _extract_json_from_text(text: str) -> Dict[str, str]:
|
|
||||||
"""从文本中提取JSON数据的高容错方法"""
|
|
||||||
try:
|
|
||||||
fixed_json = repair_json(text)
|
|
||||||
if isinstance(fixed_json, str):
|
|
||||||
parsed_json = json.loads(fixed_json)
|
|
||||||
else:
|
|
||||||
parsed_json = fixed_json
|
|
||||||
|
|
||||||
if isinstance(parsed_json, list) and parsed_json:
|
|
||||||
parsed_json = parsed_json[0]
|
|
||||||
|
|
||||||
if isinstance(parsed_json, dict):
|
|
||||||
return parsed_json
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"JSON提取失败: {e}")
|
|
||||||
|
|
||||||
logger.warning(f"无法从文本中提取有效的JSON字典: {text}")
|
|
||||||
logger.info(f"文本: {text}")
|
|
||||||
return {"nickname": "", "reason": ""}
|
|
||||||
|
|
||||||
async def _generate_unique_person_name(self, base_name: str) -> str:
|
|
||||||
"""生成唯一的 person_name,如果存在重复则添加数字后缀"""
|
|
||||||
# 处理空昵称的情况
|
|
||||||
if not base_name or base_name.isspace():
|
|
||||||
base_name = "空格"
|
|
||||||
|
|
||||||
# 检查基础名称是否已存在
|
|
||||||
if base_name not in self.person_name_list.values():
|
|
||||||
return base_name
|
|
||||||
|
|
||||||
# 如果存在,添加数字后缀
|
|
||||||
counter = 1
|
|
||||||
while True:
|
|
||||||
new_name = f"{base_name}[{counter}]"
|
|
||||||
if new_name not in self.person_name_list.values():
|
|
||||||
return new_name
|
|
||||||
counter += 1
|
|
||||||
|
|
||||||
async def qv_person_name(
|
|
||||||
self, person_id: str, user_nickname: str, user_cardname: str, user_avatar: str, request: str = ""
|
|
||||||
):
|
|
||||||
"""给某个用户取名"""
|
|
||||||
if not person_id:
|
|
||||||
logger.debug("取名失败:person_id不能为空")
|
|
||||||
return None
|
|
||||||
|
|
||||||
person = Person(person_id=person_id)
|
|
||||||
old_name = person.person_name
|
|
||||||
old_reason = person.name_reason
|
|
||||||
|
|
||||||
max_retries = 8
|
|
||||||
current_try = 0
|
|
||||||
existing_names_str = ""
|
|
||||||
current_name_set = set(self.person_name_list.values())
|
|
||||||
|
|
||||||
while current_try < max_retries:
|
|
||||||
bot_name = global_config.bot.nickname
|
|
||||||
|
|
||||||
qv_name_prompt = f"你是{bot_name},一个伪装成人类的AI,你不能让别人发现这一点,"
|
|
||||||
qv_name_prompt += f"现在你想给一个用户取一个昵称,用户的昵称是{user_nickname},"
|
|
||||||
qv_name_prompt += f"用户的群昵称名是{user_cardname},"
|
|
||||||
if user_avatar:
|
|
||||||
qv_name_prompt += f"用户的头像是{user_avatar},"
|
|
||||||
if old_name:
|
|
||||||
qv_name_prompt += f"你之前叫他{old_name},是因为{old_reason},"
|
|
||||||
|
|
||||||
qv_name_prompt += f"\n其他取名的要求是:{request},不要太浮夸,简短,"
|
|
||||||
qv_name_prompt += "\n请根据以上用户信息,想想你叫他什么比较好,不要太浮夸,请最好使用用户的昵称或群昵称原文,可以稍作修改,优先使用原文。优先使用用户的昵称或者群昵称原文。"
|
|
||||||
|
|
||||||
if existing_names_str:
|
|
||||||
qv_name_prompt += f"\n请注意,以下名称已被你尝试过或已知存在,请避免:{existing_names_str}。\n"
|
|
||||||
|
|
||||||
if len(current_name_set) < 50 and current_name_set:
|
|
||||||
qv_name_prompt += f"已知的其他昵称有: {', '.join(list(current_name_set)[:10])}等。\n"
|
|
||||||
|
|
||||||
qv_name_prompt += "请用json给出你的想法,并给出理由,示例如下:"
|
|
||||||
qv_name_prompt += """{
|
|
||||||
"nickname": "昵称",
|
|
||||||
"reason": "理由"
|
|
||||||
}"""
|
|
||||||
generation_result = await self.qv_name_llm.generate_response(qv_name_prompt)
|
|
||||||
response = generation_result.response
|
|
||||||
# logger.info(f"取名提示词:{qv_name_prompt}\n取名回复:{response}")
|
|
||||||
result = self._extract_json_from_text(response)
|
|
||||||
|
|
||||||
if not result or not result.get("nickname"):
|
|
||||||
logger.error("生成的昵称为空或结果格式不正确,重试中...")
|
|
||||||
current_try += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
generated_nickname = result["nickname"]
|
|
||||||
|
|
||||||
is_duplicate = False
|
|
||||||
if generated_nickname in current_name_set:
|
|
||||||
is_duplicate = True
|
|
||||||
logger.info(f"尝试给用户{user_nickname} {person_id} 取名,但是 {generated_nickname} 已存在,重试中...")
|
|
||||||
else:
|
|
||||||
|
|
||||||
def _db_check_name_exists_sync(name_to_check):
|
|
||||||
with get_db_session() as session:
|
|
||||||
statement = select(PersonInfo.person_id).where(col(PersonInfo.person_name) == name_to_check)
|
|
||||||
return session.exec(statement).first() is not None
|
|
||||||
|
|
||||||
if await asyncio.to_thread(_db_check_name_exists_sync, generated_nickname):
|
|
||||||
is_duplicate = True
|
|
||||||
current_name_set.add(generated_nickname)
|
|
||||||
|
|
||||||
if not is_duplicate:
|
|
||||||
person.person_name = generated_nickname
|
|
||||||
person.name_reason = result.get("reason", "未提供理由")
|
|
||||||
person.sync_to_database()
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"成功给用户{user_nickname} {person_id} 取名 {generated_nickname},理由:{result.get('reason', '未提供理由')}"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.person_name_list[person_id] = generated_nickname
|
|
||||||
return result
|
|
||||||
else:
|
|
||||||
if existing_names_str:
|
|
||||||
existing_names_str += "、"
|
|
||||||
existing_names_str += generated_nickname
|
|
||||||
logger.debug(f"生成的昵称 {generated_nickname} 已存在,重试中...")
|
|
||||||
current_try += 1
|
|
||||||
|
|
||||||
# 如果多次尝试后仍未成功,使用唯一的 user_nickname 作为默认值
|
|
||||||
unique_nickname = await self._generate_unique_person_name(user_nickname)
|
|
||||||
logger.warning(f"在{max_retries}次尝试后未能生成唯一昵称,使用默认昵称 {unique_nickname}")
|
|
||||||
person.person_name = unique_nickname
|
|
||||||
person.name_reason = "使用用户原始昵称作为默认值"
|
|
||||||
person.sync_to_database()
|
|
||||||
self.person_name_list[person_id] = unique_nickname
|
|
||||||
return {"nickname": unique_nickname, "reason": "使用用户原始昵称作为默认值"}
|
|
||||||
|
|
||||||
|
|
||||||
person_info_manager = PersonInfoManager()
|
|
||||||
|
|
||||||
|
|
||||||
async def store_person_memory_from_answer(person_name: str, memory_content: str, chat_id: str) -> None:
|
async def store_person_memory_from_answer(person_name: str, memory_content: str, chat_id: str) -> None:
|
||||||
"""将人物事实写入长期记忆系统。
|
"""将人物事实写入长期记忆系统。
|
||||||
|
|||||||
Reference in New Issue
Block a user