Merge branch 'debug' into feature
This commit is contained in:
@@ -15,7 +15,7 @@ from .message import Message_Thinking # 导入 Message_Thinking 类
|
||||
from .relationship_manager import relationship_manager
|
||||
from .willing_manager import willing_manager # 导入意愿管理器
|
||||
from .utils import is_mentioned_bot_in_txt, calculate_typing_time
|
||||
from ..memory_system.memory import memory_graph
|
||||
from ..memory_system.memory import memory_graph,hippocampus
|
||||
from loguru import logger
|
||||
|
||||
class ChatBot:
|
||||
@@ -70,24 +70,12 @@ class ChatBot:
|
||||
|
||||
|
||||
|
||||
topic=await topic_identifier.identify_topic_llm(message.processed_plain_text)
|
||||
|
||||
|
||||
# topic1 = topic_identifier.identify_topic_jieba(message.processed_plain_text)
|
||||
# topic2 = await topic_identifier.identify_topic_llm(message.processed_plain_text)
|
||||
# topic3 = topic_identifier.identify_topic_snownlp(message.processed_plain_text)
|
||||
logger.info(f"\033[1;32m[主题识别]\033[0m 使用{global_config.topic_extract}主题: {topic}")
|
||||
|
||||
all_num = 0
|
||||
interested_num = 0
|
||||
if topic:
|
||||
for current_topic in topic:
|
||||
all_num += 1
|
||||
first_layer_items, second_layer_items = memory_graph.get_related_item(current_topic, depth=2)
|
||||
if first_layer_items:
|
||||
interested_num += 1
|
||||
print(f"\033[1;32m[前额叶]\033[0m 对|{current_topic}|有印象")
|
||||
interested_rate = interested_num / all_num if all_num > 0 else 0
|
||||
# topic=await topic_identifier.identify_topic_llm(message.processed_plain_text)
|
||||
topic = ''
|
||||
interested_rate = 0
|
||||
interested_rate = await hippocampus.memory_activate_value(message.processed_plain_text)/100
|
||||
print(f"\033[1;32m[记忆激活]\033[0m 对{message.processed_plain_text}的激活度:---------------------------------------{interested_rate}\n")
|
||||
# logger.info(f"\033[1;32m[主题识别]\033[0m 使用{global_config.topic_extract}主题: {topic}")
|
||||
|
||||
await self.storage.store_message(message, topic[0] if topic else None)
|
||||
|
||||
@@ -134,7 +122,7 @@ class ChatBot:
|
||||
if isinstance(msg, Message_Thinking) and msg.message_id == think_id:
|
||||
thinking_message = msg
|
||||
container.messages.remove(msg)
|
||||
print(f"\033[1;32m[思考消息删除]\033[0m 已找到思考消息对象,开始删除")
|
||||
# print(f"\033[1;32m[思考消息删除]\033[0m 已找到思考消息对象,开始删除")
|
||||
break
|
||||
|
||||
#记录开始思考的时间,避免从思考到回复的时间太久
|
||||
@@ -167,7 +155,7 @@ class ChatBot:
|
||||
message_set.add_message(bot_message)
|
||||
|
||||
#message_set 可以直接加入 message_manager
|
||||
print(f"\033[1;32m[回复]\033[0m 将回复载入发送容器")
|
||||
# print(f"\033[1;32m[回复]\033[0m 将回复载入发送容器")
|
||||
message_manager.add_message(message_set)
|
||||
|
||||
bot_response_time = tinking_time_point
|
||||
@@ -205,7 +193,7 @@ class ChatBot:
|
||||
)
|
||||
message_manager.add_message(bot_message)
|
||||
|
||||
willing_manager.change_reply_willing_after_sent(event.group_id)
|
||||
# willing_manager.change_reply_willing_after_sent(event.group_id)
|
||||
|
||||
# 创建全局ChatBot实例
|
||||
chat_bot = ChatBot()
|
||||
@@ -40,6 +40,7 @@ class BotConfig:
|
||||
llm_normal_minor: Dict[str, str] = field(default_factory=lambda: {})
|
||||
embedding: Dict[str, str] = field(default_factory=lambda: {})
|
||||
vlm: Dict[str, str] = field(default_factory=lambda: {})
|
||||
rerank: Dict[str, str] = field(default_factory=lambda: {})
|
||||
|
||||
# 主题提取配置
|
||||
topic_extract: str = 'snownlp' # 只支持jieba,snownlp,llm
|
||||
@@ -136,6 +137,9 @@ class BotConfig:
|
||||
if "embedding" in model_config:
|
||||
config.embedding = model_config["embedding"]
|
||||
|
||||
if "rerank" in model_config:
|
||||
config.rerank = model_config["rerank"]
|
||||
|
||||
if 'topic' in toml_dict:
|
||||
topic_config=toml_dict['topic']
|
||||
if 'topic_extract' in topic_config:
|
||||
|
||||
@@ -64,10 +64,11 @@ class ResponseGenerator:
|
||||
# 获取关系值
|
||||
relationship_value = relationship_manager.get_relationship(message.user_id).relationship_value if relationship_manager.get_relationship(message.user_id) else 0.0
|
||||
if relationship_value != 0.0:
|
||||
print(f"\033[1;32m[关系管理]\033[0m 回复中_当前关系值: {relationship_value}")
|
||||
# print(f"\033[1;32m[关系管理]\033[0m 回复中_当前关系值: {relationship_value}")
|
||||
pass
|
||||
|
||||
# 构建prompt
|
||||
prompt, prompt_check = prompt_builder._build_prompt(
|
||||
prompt, prompt_check = await prompt_builder._build_prompt(
|
||||
message_txt=message.processed_plain_text,
|
||||
sender_name=sender_name,
|
||||
relationship_value=relationship_value,
|
||||
|
||||
@@ -103,7 +103,7 @@ class MessageContainer:
|
||||
|
||||
def add_message(self, message: Union[Message_Thinking, Message_Sending]) -> None:
|
||||
"""添加消息到队列"""
|
||||
print(f"\033[1;32m[添加消息]\033[0m 添加消息到对应群")
|
||||
# print(f"\033[1;32m[添加消息]\033[0m 添加消息到对应群")
|
||||
if isinstance(message, MessageSet):
|
||||
for single_message in message.messages:
|
||||
self.messages.append(single_message)
|
||||
@@ -156,17 +156,13 @@ class MessageManager:
|
||||
#最早的对象,可能是思考消息,也可能是发送消息
|
||||
message_earliest = container.get_earliest_message() #一个message_thinking or message_sending
|
||||
|
||||
#一个月后删了
|
||||
if not message_earliest:
|
||||
print(f"\033[1;34m[BUG,如果出现这个,说明有BUG,3月4日留]\033[0m ")
|
||||
return
|
||||
|
||||
#如果是思考消息
|
||||
if isinstance(message_earliest, Message_Thinking):
|
||||
#优先等待这条消息
|
||||
message_earliest.update_thinking_time()
|
||||
thinking_time = message_earliest.thinking_time
|
||||
print(f"\033[1;34m[调试]\033[0m 消息正在思考中,已思考{int(thinking_time)}秒")
|
||||
if thinking_time % 10 == 0:
|
||||
print(f"\033[1;34m[调试]\033[0m 消息正在思考中,已思考{int(thinking_time)}秒")
|
||||
else:# 如果不是message_thinking就只能是message_sending
|
||||
print(f"\033[1;34m[调试]\033[0m 消息'{message_earliest.processed_plain_text}'正在发送中")
|
||||
#直接发,等什么呢
|
||||
|
||||
@@ -2,13 +2,15 @@ import time
|
||||
import random
|
||||
from ..schedule.schedule_generator import bot_schedule
|
||||
import os
|
||||
from .utils import get_embedding, combine_messages, get_recent_group_detailed_plain_text
|
||||
from .utils import get_embedding, combine_messages, get_recent_group_detailed_plain_text,find_similar_topics
|
||||
from ...common.database import Database
|
||||
from .config import global_config
|
||||
from .topic_identifier import topic_identifier
|
||||
from ..memory_system.memory import memory_graph
|
||||
from ..memory_system.memory import memory_graph,hippocampus
|
||||
from random import choice
|
||||
|
||||
import numpy as np
|
||||
import jieba
|
||||
from collections import Counter
|
||||
|
||||
class PromptBuilder:
|
||||
def __init__(self):
|
||||
@@ -16,7 +18,9 @@ class PromptBuilder:
|
||||
self.activate_messages = ''
|
||||
self.db = Database.get_instance()
|
||||
|
||||
def _build_prompt(self,
|
||||
|
||||
|
||||
async def _build_prompt(self,
|
||||
message_txt: str,
|
||||
sender_name: str = "某人",
|
||||
relationship_value: float = 0.0,
|
||||
@@ -31,60 +35,7 @@ class PromptBuilder:
|
||||
|
||||
Returns:
|
||||
str: 构建好的prompt
|
||||
"""
|
||||
|
||||
|
||||
memory_prompt = ''
|
||||
start_time = time.time() # 记录开始时间
|
||||
# topic = await topic_identifier.identify_topic_llm(message_txt)
|
||||
topic = topic_identifier.identify_topic_snownlp(message_txt)
|
||||
|
||||
# print(f"\033[1;32m[pb主题识别]\033[0m 主题: {topic}")
|
||||
|
||||
all_first_layer_items = [] # 存储所有第一层记忆
|
||||
all_second_layer_items = {} # 用字典存储每个topic的第二层记忆
|
||||
overlapping_second_layer = set() # 存储重叠的第二层记忆
|
||||
|
||||
if topic:
|
||||
# 遍历所有topic
|
||||
for current_topic in topic:
|
||||
first_layer_items, second_layer_items = memory_graph.get_related_item(current_topic, depth=2)
|
||||
# if first_layer_items:
|
||||
# print(f"\033[1;32m[前额叶]\033[0m 主题 '{current_topic}' 的第一层记忆: {first_layer_items}")
|
||||
|
||||
# 记录第一层数据
|
||||
all_first_layer_items.extend(first_layer_items)
|
||||
|
||||
# 记录第二层数据
|
||||
all_second_layer_items[current_topic] = second_layer_items
|
||||
|
||||
# 检查是否有重叠的第二层数据
|
||||
for other_topic, other_second_layer in all_second_layer_items.items():
|
||||
if other_topic != current_topic:
|
||||
# 找到重叠的记忆
|
||||
overlap = set(second_layer_items) & set(other_second_layer)
|
||||
if overlap:
|
||||
# print(f"\033[1;32m[前额叶]\033[0m 发现主题 '{current_topic}' 和 '{other_topic}' 有共同的第二层记忆: {overlap}")
|
||||
overlapping_second_layer.update(overlap)
|
||||
|
||||
selected_first_layer = random.sample(all_first_layer_items, min(2, len(all_first_layer_items))) if all_first_layer_items else []
|
||||
selected_second_layer = random.sample(list(overlapping_second_layer), min(2, len(overlapping_second_layer))) if overlapping_second_layer else []
|
||||
|
||||
# 合并并去重
|
||||
all_memories = list(set(selected_first_layer + selected_second_layer))
|
||||
if all_memories:
|
||||
print(f"\033[1;32m[前额叶]\033[0m 合并所有需要的记忆: {all_memories}")
|
||||
random_item = " ".join(all_memories)
|
||||
memory_prompt = f"看到这些聊天,你想起来{random_item}\n"
|
||||
else:
|
||||
memory_prompt = "" # 如果没有记忆,则返回空字符串
|
||||
|
||||
end_time = time.time() # 记录结束时间
|
||||
print(f"\033[1;32m[回忆耗时]\033[0m 耗时: {(end_time - start_time):.3f}秒") # 输出耗时
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
#先禁用关系
|
||||
if 0 > 30:
|
||||
relation_prompt = "关系特别特别好,你很喜欢喜欢他"
|
||||
@@ -112,22 +63,48 @@ class PromptBuilder:
|
||||
prompt_info = self.get_prompt_info(message_txt,threshold=0.5)
|
||||
if prompt_info:
|
||||
prompt_info = f'''\n----------------------------------------------------\n你有以下这些[知识]:\n{prompt_info}\n请你记住上面的[知识],之后可能会用到\n----------------------------------------------------\n'''
|
||||
# promt_info_prompt = '你有一些[知识],在上面可以参考。'
|
||||
|
||||
end_time = time.time()
|
||||
print(f"\033[1;32m[知识检索]\033[0m 耗时: {(end_time - start_time):.3f}秒")
|
||||
# print(f"\033[1;34m[调试]\033[0m 获取知识库内容结果: {prompt_info}")
|
||||
|
||||
|
||||
# print(f"\033[1;34m[调试信息]\033[0m 正在构建聊天上下文")
|
||||
|
||||
# 获取聊天上下文
|
||||
chat_talking_prompt = ''
|
||||
if group_id:
|
||||
chat_talking_prompt = get_recent_group_detailed_plain_text(self.db, group_id, limit=global_config.MAX_CONTEXT_SIZE,combine = True)
|
||||
|
||||
chat_talking_prompt = f"以下是群里正在聊天的内容:\n{chat_talking_prompt}"
|
||||
# print(f"\033[1;34m[调试]\033[0m 已从数据库获取群 {group_id} 的消息记录:{chat_talking_prompt}")
|
||||
|
||||
|
||||
|
||||
# 使用新的记忆获取方法
|
||||
memory_prompt = ''
|
||||
start_time = time.time()
|
||||
|
||||
# 调用 hippocampus 的 get_relevant_memories 方法
|
||||
relevant_memories = await hippocampus.get_relevant_memories(
|
||||
text=message_txt,
|
||||
max_topics=5,
|
||||
similarity_threshold=0.4
|
||||
)
|
||||
|
||||
if relevant_memories:
|
||||
# 格式化记忆内容
|
||||
memory_items = []
|
||||
for memory in relevant_memories:
|
||||
memory_items.append(f"关于「{memory['topic']}」的记忆:{memory['content']}")
|
||||
|
||||
memory_prompt = f"看到这些聊天,你想起来:\n" + "\n".join(memory_items) + "\n"
|
||||
|
||||
# 打印调试信息
|
||||
print("\n\033[1;32m[记忆检索]\033[0m 找到以下相关记忆:")
|
||||
for memory in relevant_memories:
|
||||
print(f"- 主题「{memory['topic']}」[相似度: {memory['similarity']:.2f}]: {memory['content']}")
|
||||
|
||||
end_time = time.time()
|
||||
print(f"\033[1;32m[回忆耗时]\033[0m 耗时: {(end_time - start_time):.3f}秒")
|
||||
|
||||
|
||||
|
||||
#激活prompt构建
|
||||
activate_prompt = ''
|
||||
activate_prompt = f"以上是群里正在进行的聊天,{memory_prompt} 现在昵称为 '{sender_name}' 的用户说的:{message_txt}。引起了你的注意,你和他{relation_prompt},你想要{relation_prompt_2}。"
|
||||
@@ -162,29 +139,19 @@ class PromptBuilder:
|
||||
if random.random() < 0.01:
|
||||
prompt_ger += '你喜欢用文言文'
|
||||
|
||||
|
||||
#额外信息要求
|
||||
extra_info = '''但是记得回复平淡一些,简短一些,尤其注意在没明确提到时不要过多提及自身的背景, 记住不要输出多余内容(包括前后缀,冒号和引号,括号,表情等),只需要输出回复内容就好,不要输出其他任何内容'''
|
||||
|
||||
|
||||
|
||||
#合并prompt
|
||||
prompt = ""
|
||||
prompt += f"{prompt_info}\n"
|
||||
prompt += f"{prompt_date}\n"
|
||||
prompt += f"{chat_talking_prompt}\n"
|
||||
|
||||
# prompt += f"{memory_prompt}\n"
|
||||
|
||||
# prompt += f"{activate_prompt}\n"
|
||||
prompt += f"{prompt_personality}\n"
|
||||
prompt += f"{prompt_ger}\n"
|
||||
prompt += f"{extra_info}\n"
|
||||
|
||||
|
||||
|
||||
'''读空气prompt处理'''
|
||||
|
||||
activate_prompt_check=f"以上是群里正在进行的聊天,昵称为 '{sender_name}' 的用户说的:{message_txt}。引起了你的注意,你和他{relation_prompt},你想要{relation_prompt_2},但是这不一定是合适的时机,请你决定是否要回应这条消息。"
|
||||
prompt_personality_check = ''
|
||||
extra_check_info=f"请注意把握群里的聊天内容的基础上,综合群内的氛围,例如,和{global_config.BOT_NICKNAME}相关的话题要积极回复,如果是at自己的消息一定要回复,如果自己正在和别人聊天一定要回复,其他话题如果合适搭话也可以回复,如果认为应该回复请输出yes,否则输出no,请注意是决定是否需要回复,而不是编写回复内容,除了yes和no不要输出任何回复内容。"
|
||||
|
||||
@@ -42,7 +42,7 @@ class TopicIdentifier:
|
||||
print(f"\033[1;32m[主题识别]\033[0m 主题: {topic_list}")
|
||||
return topic_list if topic_list else None
|
||||
|
||||
def identify_topic_snownlp(self, text: str) -> Optional[List[str]]:
|
||||
def identify_topic_snownlp(self, text: str,num:int=5) -> Optional[List[str]]:
|
||||
"""使用 SnowNLP 进行主题识别
|
||||
|
||||
Args:
|
||||
@@ -57,7 +57,7 @@ class TopicIdentifier:
|
||||
try:
|
||||
s = SnowNLP(text)
|
||||
# 提取前3个关键词作为主题
|
||||
keywords = s.keywords(5)
|
||||
keywords = s.keywords(num)
|
||||
return keywords if keywords else None
|
||||
except Exception as e:
|
||||
print(f"\033[1;31m[错误]\033[0m SnowNLP 处理失败: {str(e)}")
|
||||
|
||||
@@ -11,6 +11,8 @@ from collections import Counter
|
||||
import math
|
||||
from nonebot import get_driver
|
||||
from ..models.utils_model import LLM_request
|
||||
import aiohttp
|
||||
import jieba
|
||||
|
||||
driver = get_driver()
|
||||
config = driver.config
|
||||
@@ -117,7 +119,7 @@ def get_cloest_chat_from_db(db, length: int, timestamp: str):
|
||||
chat_text += record["detailed_plain_text"]
|
||||
|
||||
return chat_text
|
||||
print(f"消息已读取3次,跳过")
|
||||
# print(f"消息已读取3次,跳过")
|
||||
return ''
|
||||
|
||||
def get_recent_group_messages(db, group_id: int, limit: int = 12) -> list:
|
||||
@@ -421,3 +423,62 @@ def calculate_typing_time(input_string: str, chinese_time: float = 0.2, english_
|
||||
return total_time
|
||||
|
||||
|
||||
def find_similar_topics(message_txt: str, all_memory_topic: list, top_k: int = 5) -> list:
|
||||
"""使用重排序API找出与输入文本最相似的话题
|
||||
|
||||
Args:
|
||||
message_txt: 输入文本
|
||||
all_memory_topic: 所有记忆主题列表
|
||||
top_k: 返回最相似的话题数量
|
||||
|
||||
Returns:
|
||||
list: 最相似话题列表及其相似度分数
|
||||
"""
|
||||
|
||||
if not all_memory_topic:
|
||||
return []
|
||||
|
||||
try:
|
||||
llm = LLM_request(model=global_config.rerank)
|
||||
return llm.rerank_sync(message_txt, all_memory_topic, top_k)
|
||||
except Exception as e:
|
||||
print(f"重排序API调用出错: {str(e)}")
|
||||
return []
|
||||
|
||||
def cosine_similarity(v1, v2):
|
||||
"""计算余弦相似度"""
|
||||
dot_product = np.dot(v1, v2)
|
||||
norm1 = np.linalg.norm(v1)
|
||||
norm2 = np.linalg.norm(v2)
|
||||
if norm1 == 0 or norm2 == 0:
|
||||
return 0
|
||||
return dot_product / (norm1 * norm2)
|
||||
|
||||
def text_to_vector(text):
|
||||
"""将文本转换为词频向量"""
|
||||
# 分词
|
||||
words = jieba.lcut(text)
|
||||
# 统计词频
|
||||
word_freq = Counter(words)
|
||||
return word_freq
|
||||
|
||||
def find_similar_topics_simple(text: str, topics: list, top_k: int = 5) -> list:
|
||||
"""使用简单的余弦相似度计算文本相似度"""
|
||||
# 将输入文本转换为词频向量
|
||||
text_vector = text_to_vector(text)
|
||||
|
||||
# 计算每个主题的相似度
|
||||
similarities = []
|
||||
for topic in topics:
|
||||
topic_vector = text_to_vector(topic)
|
||||
# 获取所有唯一词
|
||||
all_words = set(text_vector.keys()) | set(topic_vector.keys())
|
||||
# 构建向量
|
||||
v1 = [text_vector.get(word, 0) for word in all_words]
|
||||
v2 = [topic_vector.get(word, 0) for word in all_words]
|
||||
# 计算相似度
|
||||
similarity = cosine_similarity(v1, v2)
|
||||
similarities.append((topic, similarity))
|
||||
|
||||
# 按相似度降序排序并返回前k个
|
||||
return sorted(similarities, key=lambda x: x[1], reverse=True)[:top_k]
|
||||
@@ -37,13 +37,13 @@ class WillingManager:
|
||||
current_willing *= 0.15
|
||||
print(f"表情包, 当前意愿: {current_willing}")
|
||||
|
||||
if interested_rate > 0.65:
|
||||
if interested_rate > 0.4:
|
||||
print(f"兴趣度: {interested_rate}, 当前意愿: {current_willing}")
|
||||
current_willing += interested_rate-0.6
|
||||
current_willing += interested_rate-0.1
|
||||
|
||||
self.group_reply_willing[group_id] = min(current_willing, 3.0)
|
||||
|
||||
reply_probability = max((current_willing - 0.55) * 1.9, 0)
|
||||
reply_probability = max((current_willing - 0.45) * 2, 0)
|
||||
if group_id not in config.talk_allowed_groups:
|
||||
current_willing = 0
|
||||
reply_probability = 0
|
||||
|
||||
@@ -11,7 +11,7 @@ from ..chat.config import global_config
|
||||
from ...common.database import Database # 使用正确的导入语法
|
||||
from ..models.utils_model import LLM_request
|
||||
import math
|
||||
from ..chat.utils import calculate_information_content, get_cloest_chat_from_db
|
||||
from ..chat.utils import calculate_information_content, get_cloest_chat_from_db ,find_similar_topics,text_to_vector,cosine_similarity
|
||||
|
||||
|
||||
|
||||
@@ -135,6 +135,14 @@ class Hippocampus:
|
||||
self.llm_model_get_topic = LLM_request(model = global_config.llm_normal_minor,temperature=0.5)
|
||||
self.llm_model_summary = LLM_request(model = global_config.llm_normal,temperature=0.5)
|
||||
|
||||
def get_all_node_names(self) -> list:
|
||||
"""获取记忆图中所有节点的名字列表
|
||||
|
||||
Returns:
|
||||
list: 包含所有节点名字的列表
|
||||
"""
|
||||
return list(self.memory_graph.G.nodes())
|
||||
|
||||
def calculate_node_hash(self, concept, memory_items):
|
||||
"""计算节点的特征值"""
|
||||
if not isinstance(memory_items, list):
|
||||
@@ -483,6 +491,193 @@ class Hippocampus:
|
||||
prompt = f'这是一段文字:{text}。我想让你基于这段文字来概括"{topic}"这个概念,帮我总结成一句自然的话,可以包含时间和人物,以及具体的观点。只输出这句话就好'
|
||||
return prompt
|
||||
|
||||
async def _identify_topics(self, text: str) -> list:
|
||||
"""从文本中识别可能的主题
|
||||
|
||||
Args:
|
||||
text: 输入文本
|
||||
|
||||
Returns:
|
||||
list: 识别出的主题列表
|
||||
"""
|
||||
topics_response = await self.llm_model_get_topic.generate_response(self.find_topic_llm(text, 5))
|
||||
print(f"话题: {topics_response[0]}")
|
||||
topics = [topic.strip() for topic in topics_response[0].replace(",", ",").replace("、", ",").replace(" ", ",").split(",") if topic.strip()]
|
||||
print(f"话题: {topics}")
|
||||
|
||||
return topics
|
||||
|
||||
def _find_similar_topics(self, topics: list, similarity_threshold: float = 0.4, debug_info: str = "") -> list:
|
||||
"""查找与给定主题相似的记忆主题
|
||||
|
||||
Args:
|
||||
topics: 主题列表
|
||||
similarity_threshold: 相似度阈值
|
||||
debug_info: 调试信息前缀
|
||||
|
||||
Returns:
|
||||
list: (主题, 相似度) 元组列表
|
||||
"""
|
||||
all_memory_topics = self.get_all_node_names()
|
||||
all_similar_topics = []
|
||||
|
||||
# 计算每个识别出的主题与记忆主题的相似度
|
||||
for topic in topics:
|
||||
if debug_info:
|
||||
print(f"\033[1;32m[{debug_info}]\033[0m 正在思考有没有见过: {topic}")
|
||||
|
||||
topic_vector = text_to_vector(topic)
|
||||
has_similar_topic = False
|
||||
|
||||
for memory_topic in all_memory_topics:
|
||||
memory_vector = text_to_vector(memory_topic)
|
||||
# 获取所有唯一词
|
||||
all_words = set(topic_vector.keys()) | set(memory_vector.keys())
|
||||
# 构建向量
|
||||
v1 = [topic_vector.get(word, 0) for word in all_words]
|
||||
v2 = [memory_vector.get(word, 0) for word in all_words]
|
||||
# 计算相似度
|
||||
similarity = cosine_similarity(v1, v2)
|
||||
|
||||
if similarity >= similarity_threshold:
|
||||
has_similar_topic = True
|
||||
if debug_info:
|
||||
print(f"\033[1;32m[{debug_info}]\033[0m 找到相似主题: {topic} -> {memory_topic} (相似度: {similarity:.2f})")
|
||||
all_similar_topics.append((memory_topic, similarity))
|
||||
|
||||
if not has_similar_topic and debug_info:
|
||||
print(f"\033[1;31m[{debug_info}]\033[0m 没有见过: {topic} ,呃呃")
|
||||
|
||||
return all_similar_topics
|
||||
|
||||
def _get_top_topics(self, similar_topics: list, max_topics: int = 5) -> list:
|
||||
"""获取相似度最高的主题
|
||||
|
||||
Args:
|
||||
similar_topics: (主题, 相似度) 元组列表
|
||||
max_topics: 最大主题数量
|
||||
|
||||
Returns:
|
||||
list: (主题, 相似度) 元组列表
|
||||
"""
|
||||
seen_topics = set()
|
||||
top_topics = []
|
||||
|
||||
for topic, score in sorted(similar_topics, key=lambda x: x[1], reverse=True):
|
||||
if topic not in seen_topics and len(top_topics) < max_topics:
|
||||
seen_topics.add(topic)
|
||||
top_topics.append((topic, score))
|
||||
|
||||
return top_topics
|
||||
|
||||
async def memory_activate_value(self, text: str, max_topics: int = 5, similarity_threshold: float = 0.3) -> int:
|
||||
"""计算输入文本对记忆的激活程度"""
|
||||
print(f"\033[1;32m[记忆激活]\033[0m 开始计算文本的记忆激活度: {text}")
|
||||
|
||||
# 识别主题
|
||||
identified_topics = await self._identify_topics(text)
|
||||
print(f"\033[1;32m[记忆激活]\033[0m 识别出的主题: {identified_topics}")
|
||||
|
||||
if not identified_topics:
|
||||
print(f"\033[1;32m[记忆激活]\033[0m 未识别出主题,返回0")
|
||||
return 0
|
||||
|
||||
# 查找相似主题
|
||||
all_similar_topics = self._find_similar_topics(
|
||||
identified_topics,
|
||||
similarity_threshold=similarity_threshold,
|
||||
debug_info="记忆激活"
|
||||
)
|
||||
|
||||
if not all_similar_topics:
|
||||
print(f"\033[1;32m[记忆激活]\033[0m 未找到相似主题,返回0")
|
||||
return 0
|
||||
|
||||
# 获取最相关的主题
|
||||
top_topics = self._get_top_topics(all_similar_topics, max_topics)
|
||||
|
||||
# 如果只找到一个主题,进行惩罚
|
||||
if len(top_topics) == 1:
|
||||
topic, score = top_topics[0]
|
||||
activation = int(score * 50) # 单主题情况下,直接用相似度*50作为激活值
|
||||
print(f"\033[1;32m[记忆激活]\033[0m 只找到一个主题,进行惩罚:")
|
||||
print(f"\033[1;32m[记忆激活]\033[0m - 主题: {topic}")
|
||||
print(f"\033[1;32m[记忆激活]\033[0m - 相似度: {score:.3f}")
|
||||
print(f"\033[1;32m[记忆激活]\033[0m - 最终激活值: {activation}")
|
||||
return activation
|
||||
|
||||
# 计算关键词匹配率
|
||||
matched_topics = set()
|
||||
topic_similarities = {}
|
||||
|
||||
print(f"\033[1;32m[记忆激活]\033[0m 计算关键词匹配情况:")
|
||||
for memory_topic, similarity in top_topics:
|
||||
# 对每个记忆主题,检查它与哪些输入主题相似
|
||||
for input_topic in identified_topics:
|
||||
topic_vector = text_to_vector(input_topic)
|
||||
memory_vector = text_to_vector(memory_topic)
|
||||
all_words = set(topic_vector.keys()) | set(memory_vector.keys())
|
||||
v1 = [topic_vector.get(word, 0) for word in all_words]
|
||||
v2 = [memory_vector.get(word, 0) for word in all_words]
|
||||
sim = cosine_similarity(v1, v2)
|
||||
if sim >= similarity_threshold:
|
||||
matched_topics.add(input_topic)
|
||||
topic_similarities[input_topic] = max(topic_similarities.get(input_topic, 0), sim)
|
||||
print(f"\033[1;32m[记忆激活]\033[0m - 输入主题「{input_topic}」匹配到记忆「{memory_topic}」, 相似度: {sim:.3f}")
|
||||
|
||||
# 计算主题匹配率
|
||||
topic_match = len(matched_topics) / len(identified_topics)
|
||||
print(f"\033[1;32m[记忆激活]\033[0m 主题匹配率:")
|
||||
print(f"\033[1;32m[记忆激活]\033[0m - 匹配主题数: {len(matched_topics)}")
|
||||
print(f"\033[1;32m[记忆激活]\033[0m - 总主题数: {len(identified_topics)}")
|
||||
print(f"\033[1;32m[记忆激活]\033[0m - 匹配率: {topic_match:.3f}")
|
||||
|
||||
# 计算匹配主题的平均相似度
|
||||
average_similarities = sum(topic_similarities.values()) / len(topic_similarities) if topic_similarities else 0
|
||||
print(f"\033[1;32m[记忆激活]\033[0m 平均相似度:")
|
||||
print(f"\033[1;32m[记忆激活]\033[0m - 各主题相似度: {[f'{k}:{v:.3f}' for k,v in topic_similarities.items()]}")
|
||||
print(f"\033[1;32m[记忆激活]\033[0m - 平均相似度: {average_similarities:.3f}")
|
||||
|
||||
# 计算最终激活值
|
||||
activation = (topic_match + average_similarities) / 2 * 100
|
||||
print(f"\033[1;32m[记忆激活]\033[0m 最终激活值: {int(activation)}")
|
||||
|
||||
return int(activation)
|
||||
|
||||
async def get_relevant_memories(self, text: str, max_topics: int = 5, similarity_threshold: float = 0.4) -> list:
|
||||
"""根据输入文本获取相关的记忆内容"""
|
||||
# 识别主题
|
||||
identified_topics = await self._identify_topics(text)
|
||||
|
||||
# 查找相似主题
|
||||
all_similar_topics = self._find_similar_topics(
|
||||
identified_topics,
|
||||
similarity_threshold=similarity_threshold,
|
||||
debug_info="记忆检索"
|
||||
)
|
||||
|
||||
# 获取最相关的主题
|
||||
relevant_topics = self._get_top_topics(all_similar_topics, max_topics)
|
||||
|
||||
# 获取相关记忆内容
|
||||
relevant_memories = []
|
||||
for topic, score in relevant_topics:
|
||||
# 获取该主题的记忆内容
|
||||
first_layer, _ = self.memory_graph.get_related_item(topic, depth=1)
|
||||
if first_layer:
|
||||
# 为每条记忆添加来源主题和相似度信息
|
||||
for memory in first_layer:
|
||||
relevant_memories.append({
|
||||
'topic': topic,
|
||||
'similarity': score,
|
||||
'content': memory
|
||||
})
|
||||
|
||||
# 按相似度排序
|
||||
relevant_memories.sort(key=lambda x: x['similarity'], reverse=True)
|
||||
|
||||
return relevant_memories
|
||||
|
||||
|
||||
def segment_text(text):
|
||||
seg_text = list(jieba.cut(text))
|
||||
|
||||
@@ -55,7 +55,7 @@ class LLM_request:
|
||||
logger.warning(f"遇到请求限制(429),等待{wait_time}秒后重试...")
|
||||
await asyncio.sleep(wait_time)
|
||||
continue
|
||||
|
||||
|
||||
if response.status in [500, 503]:
|
||||
logger.error(f"服务器错误: {response.status}")
|
||||
raise RuntimeError("服务器负载过高,模型恢复失败QAQ")
|
||||
@@ -69,10 +69,10 @@ class LLM_request:
|
||||
think_match = None
|
||||
reasoning_content = message.get("reasoning_content", "")
|
||||
if not reasoning_content:
|
||||
think_match = re.search(r'<think>(.*?)</think>', content, re.DOTALL)
|
||||
think_match = re.search(r'(?:<think>)?(.*?)</think>', content, re.DOTALL)
|
||||
if think_match:
|
||||
reasoning_content = think_match.group(1).strip()
|
||||
content = re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL).strip()
|
||||
content = re.sub(r'(?:<think>)?.*?</think>', '', content, flags=re.DOTALL, count=1).strip()
|
||||
return content, reasoning_content
|
||||
return "没有返回结果", ""
|
||||
|
||||
@@ -83,6 +83,7 @@ class LLM_request:
|
||||
await asyncio.sleep(wait_time)
|
||||
else:
|
||||
logger.critical(f"请求失败: {str(e)}", exc_info=True)
|
||||
logger.critical(f"请求头: {headers} 请求体: {data}")
|
||||
raise RuntimeError(f"API请求失败: {str(e)}")
|
||||
|
||||
logger.error("达到最大重试次数,请求仍然失败")
|
||||
@@ -118,7 +119,7 @@ class LLM_request:
|
||||
],
|
||||
**self.params
|
||||
}
|
||||
|
||||
|
||||
|
||||
# 发送请求到完整的chat/completions端点
|
||||
api_url = f"{self.base_url.rstrip('/')}/chat/completions"
|
||||
@@ -126,10 +127,9 @@ class LLM_request:
|
||||
|
||||
max_retries = 3
|
||||
base_wait_time = 15
|
||||
|
||||
|
||||
current_image_base64 = image_base64
|
||||
current_image_base64 = compress_base64_image_by_scale(current_image_base64)
|
||||
|
||||
|
||||
for retry in range(max_retries):
|
||||
try:
|
||||
@@ -146,7 +146,7 @@ class LLM_request:
|
||||
logger.warning("图片太大(413),尝试压缩...")
|
||||
current_image_base64 = compress_base64_image_by_scale(current_image_base64)
|
||||
continue
|
||||
|
||||
|
||||
response.raise_for_status() # 检查其他响应状态
|
||||
|
||||
result = await response.json()
|
||||
@@ -156,10 +156,10 @@ class LLM_request:
|
||||
think_match = None
|
||||
reasoning_content = message.get("reasoning_content", "")
|
||||
if not reasoning_content:
|
||||
think_match = re.search(r'<think>(.*?)</think>', content, re.DOTALL)
|
||||
think_match = re.search(r'(?:<think>)?(.*?)</think>', content, re.DOTALL)
|
||||
if think_match:
|
||||
reasoning_content = think_match.group(1).strip()
|
||||
content = re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL).strip()
|
||||
content = re.sub(r'(?:<think>)?.*?</think>', '', content, flags=re.DOTALL, count=1).strip()
|
||||
return content, reasoning_content
|
||||
return "没有返回结果", ""
|
||||
|
||||
@@ -170,6 +170,7 @@ class LLM_request:
|
||||
await asyncio.sleep(wait_time)
|
||||
else:
|
||||
logger.critical(f"请求失败: {str(e)}", exc_info=True)
|
||||
logger.critical(f"请求头: {headers} 请求体: {data}")
|
||||
raise RuntimeError(f"API请求失败: {str(e)}")
|
||||
|
||||
logger.error("达到最大重试次数,请求仍然失败")
|
||||
@@ -181,7 +182,7 @@ class LLM_request:
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
|
||||
# 构建请求体
|
||||
data = {
|
||||
"model": self.model_name,
|
||||
@@ -189,14 +190,14 @@ class LLM_request:
|
||||
"temperature": 0.5,
|
||||
**self.params
|
||||
}
|
||||
|
||||
|
||||
# 发送请求到完整的 chat/completions 端点
|
||||
api_url = f"{self.base_url.rstrip('/')}/chat/completions"
|
||||
logger.info(f"Request URL: {api_url}") # 记录请求的 URL
|
||||
|
||||
|
||||
max_retries = 3
|
||||
base_wait_time = 15
|
||||
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
for retry in range(max_retries):
|
||||
try:
|
||||
@@ -206,16 +207,23 @@ class LLM_request:
|
||||
logger.warning(f"遇到请求限制(429),等待{wait_time}秒后重试...")
|
||||
await asyncio.sleep(wait_time)
|
||||
continue
|
||||
|
||||
|
||||
response.raise_for_status() # 检查其他响应状态
|
||||
|
||||
|
||||
result = await response.json()
|
||||
if "choices" in result and len(result["choices"]) > 0:
|
||||
content = result["choices"][0]["message"]["content"]
|
||||
reasoning_content = result["choices"][0]["message"].get("reasoning_content", "")
|
||||
message = result["choices"][0]["message"]
|
||||
content = message.get("content", "")
|
||||
think_match = None
|
||||
reasoning_content = message.get("reasoning_content", "")
|
||||
if not reasoning_content:
|
||||
think_match = re.search(r'(?:<think>)?(.*?)</think>', content, re.DOTALL)
|
||||
if think_match:
|
||||
reasoning_content = think_match.group(1).strip()
|
||||
content = re.sub(r'(?:<think>)?.*?</think>', '', content, flags=re.DOTALL, count=1).strip()
|
||||
return content, reasoning_content
|
||||
return "没有返回结果", ""
|
||||
|
||||
|
||||
except Exception as e:
|
||||
if retry < max_retries - 1: # 如果还有重试机会
|
||||
wait_time = base_wait_time * (2 ** retry)
|
||||
@@ -223,8 +231,9 @@ class LLM_request:
|
||||
await asyncio.sleep(wait_time)
|
||||
else:
|
||||
logger.error(f"请求失败: {str(e)}")
|
||||
logger.critical(f"请求头: {headers} 请求体: {data}")
|
||||
return f"请求失败: {str(e)}", ""
|
||||
|
||||
|
||||
logger.error("达到最大重试次数,请求仍然失败")
|
||||
return "达到最大重试次数,请求仍然失败", ""
|
||||
|
||||
@@ -288,10 +297,10 @@ class LLM_request:
|
||||
think_match = None
|
||||
reasoning_content = message.get("reasoning_content", "")
|
||||
if not reasoning_content:
|
||||
think_match = re.search(r'<think>(.*?)</think>', content, re.DOTALL)
|
||||
think_match = re.search(r'(?:<think>)?(.*?)</think>', content, re.DOTALL)
|
||||
if think_match:
|
||||
reasoning_content = think_match.group(1).strip()
|
||||
content = re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL).strip()
|
||||
content = re.sub(r'(?:<think>)?.*?</think>', '', content, flags=re.DOTALL, count=1).strip()
|
||||
return content, reasoning_content
|
||||
return "没有返回结果", ""
|
||||
|
||||
@@ -302,6 +311,7 @@ class LLM_request:
|
||||
time.sleep(wait_time)
|
||||
else:
|
||||
logger.critical(f"请求失败: {str(e)}", exc_info=True)
|
||||
logger.critical(f"请求头: {headers} 请求体: {data}")
|
||||
raise RuntimeError(f"API请求失败: {str(e)}")
|
||||
|
||||
logger.error("达到最大重试次数,请求仍然失败")
|
||||
@@ -358,6 +368,7 @@ class LLM_request:
|
||||
time.sleep(wait_time)
|
||||
else:
|
||||
logger.critical(f"embedding请求失败: {str(e)}", exc_info=True)
|
||||
logger.critical(f"请求头: {headers} 请求体: {data}")
|
||||
return None
|
||||
|
||||
logger.error("达到最大重试次数,embedding请求仍然失败")
|
||||
@@ -414,7 +425,204 @@ class LLM_request:
|
||||
await asyncio.sleep(wait_time)
|
||||
else:
|
||||
logger.critical(f"embedding请求失败: {str(e)}", exc_info=True)
|
||||
logger.critical(f"请求头: {headers} 请求体: {data}")
|
||||
return None
|
||||
|
||||
logger.error("达到最大重试次数,embedding请求仍然失败")
|
||||
return None
|
||||
|
||||
def rerank_sync(self, query: str, documents: list, top_k: int = 5) -> list:
|
||||
"""同步方法:使用重排序API对文档进行排序
|
||||
|
||||
Args:
|
||||
query: 查询文本
|
||||
documents: 待排序的文档列表
|
||||
top_k: 返回前k个结果
|
||||
|
||||
Returns:
|
||||
list: [(document, score), ...] 格式的结果列表
|
||||
"""
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
data = {
|
||||
"model": self.model_name,
|
||||
"query": query,
|
||||
"documents": documents,
|
||||
"top_n": top_k,
|
||||
"return_documents": True,
|
||||
}
|
||||
|
||||
api_url = f"{self.base_url.rstrip('/')}/rerank"
|
||||
logger.info(f"发送请求到URL: {api_url}")
|
||||
|
||||
max_retries = 2
|
||||
base_wait_time = 6
|
||||
|
||||
for retry in range(max_retries):
|
||||
try:
|
||||
response = requests.post(api_url, headers=headers, json=data, timeout=30)
|
||||
|
||||
if response.status_code == 429:
|
||||
wait_time = base_wait_time * (2 ** retry)
|
||||
logger.warning(f"遇到请求限制(429),等待{wait_time}秒后重试...")
|
||||
time.sleep(wait_time)
|
||||
continue
|
||||
|
||||
if response.status_code in [500, 503]:
|
||||
wait_time = base_wait_time * (2 ** retry)
|
||||
logger.error(f"服务器错误({response.status_code}),等待{wait_time}秒后重试...")
|
||||
if retry < max_retries - 1:
|
||||
time.sleep(wait_time)
|
||||
continue
|
||||
else:
|
||||
# 如果是最后一次重试,尝试使用chat/completions作为备选方案
|
||||
return self._fallback_rerank_with_chat(query, documents, top_k)
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
if 'results' in result:
|
||||
return [(item["document"], item["score"]) for item in result["results"]]
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
if retry < max_retries - 1:
|
||||
wait_time = base_wait_time * (2 ** retry)
|
||||
logger.error(f"[rerank_sync]请求失败,等待{wait_time}秒后重试... 错误: {str(e)}", exc_info=True)
|
||||
time.sleep(wait_time)
|
||||
else:
|
||||
logger.critical(f"重排序请求失败: {str(e)}", exc_info=True)
|
||||
|
||||
logger.error("达到最大重试次数,重排序请求仍然失败")
|
||||
return []
|
||||
|
||||
async def rerank(self, query: str, documents: list, top_k: int = 5) -> list:
|
||||
"""异步方法:使用重排序API对文档进行排序
|
||||
|
||||
Args:
|
||||
query: 查询文本
|
||||
documents: 待排序的文档列表
|
||||
top_k: 返回前k个结果
|
||||
|
||||
Returns:
|
||||
list: [(document, score), ...] 格式的结果列表
|
||||
"""
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
data = {
|
||||
"model": self.model_name,
|
||||
"query": query,
|
||||
"documents": documents,
|
||||
"top_n": top_k,
|
||||
"return_documents": True,
|
||||
}
|
||||
|
||||
api_url = f"{self.base_url.rstrip('/')}/v1/rerank"
|
||||
logger.info(f"发送请求到URL: {api_url}")
|
||||
|
||||
max_retries = 3
|
||||
base_wait_time = 15
|
||||
|
||||
for retry in range(max_retries):
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(api_url, headers=headers, json=data) as response:
|
||||
if response.status == 429:
|
||||
wait_time = base_wait_time * (2 ** retry)
|
||||
logger.warning(f"遇到请求限制(429),等待{wait_time}秒后重试...")
|
||||
await asyncio.sleep(wait_time)
|
||||
continue
|
||||
|
||||
if response.status in [500, 503]:
|
||||
wait_time = base_wait_time * (2 ** retry)
|
||||
logger.error(f"服务器错误({response.status}),等待{wait_time}秒后重试...")
|
||||
if retry < max_retries - 1:
|
||||
await asyncio.sleep(wait_time)
|
||||
continue
|
||||
else:
|
||||
# 如果是最后一次重试,尝试使用chat/completions作为备选方案
|
||||
return await self._fallback_rerank_with_chat_async(query, documents, top_k)
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
result = await response.json()
|
||||
if 'results' in result:
|
||||
return [(item["document"], item["score"]) for item in result["results"]]
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
if retry < max_retries - 1:
|
||||
wait_time = base_wait_time * (2 ** retry)
|
||||
logger.error(f"[rerank]请求失败,等待{wait_time}秒后重试... 错误: {str(e)}", exc_info=True)
|
||||
await asyncio.sleep(wait_time)
|
||||
else:
|
||||
logger.critical(f"重排序请求失败: {str(e)}", exc_info=True)
|
||||
# 作为最后的备选方案,尝试使用chat/completions
|
||||
return await self._fallback_rerank_with_chat_async(query, documents, top_k)
|
||||
|
||||
logger.error("达到最大重试次数,重排序请求仍然失败")
|
||||
return []
|
||||
|
||||
async def _fallback_rerank_with_chat_async(self, query: str, documents: list, top_k: int = 5) -> list:
|
||||
"""当rerank API失败时的备选方案,使用chat/completions异步实现重排序
|
||||
|
||||
Args:
|
||||
query: 查询文本
|
||||
documents: 待排序的文档列表
|
||||
top_k: 返回前k个结果
|
||||
|
||||
Returns:
|
||||
list: [(document, score), ...] 格式的结果列表
|
||||
"""
|
||||
try:
|
||||
logger.info("使用chat/completions作为重排序的备选方案")
|
||||
|
||||
# 构建提示词
|
||||
prompt = f"""请对以下文档列表进行重排序,按照与查询的相关性从高到低排序。
|
||||
查询: {query}
|
||||
|
||||
文档列表:
|
||||
{documents}
|
||||
|
||||
请以JSON格式返回排序结果,格式为:
|
||||
[{{"document": "文档内容", "score": 相关性分数}}, ...]
|
||||
只返回JSON,不要其他任何文字。"""
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
data = {
|
||||
"model": self.model_name,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
**self.params
|
||||
}
|
||||
|
||||
api_url = f"{self.base_url.rstrip('/')}/v1/chat/completions"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(api_url, headers=headers, json=data) as response:
|
||||
response.raise_for_status()
|
||||
result = await response.json()
|
||||
|
||||
if "choices" in result and len(result["choices"]) > 0:
|
||||
message = result["choices"][0]["message"]
|
||||
content = message.get("content", "")
|
||||
try:
|
||||
import json
|
||||
parsed_content = json.loads(content)
|
||||
if isinstance(parsed_content, list):
|
||||
return [(item["document"], item["score"]) for item in parsed_content]
|
||||
except:
|
||||
pass
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"备选方案也失败了: {str(e)}")
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user