move:修改人格文件结构

This commit is contained in:
SengokuCola
2025-04-06 00:58:46 +08:00
parent 0acad09dd7
commit 80753d95a5
15 changed files with 406 additions and 23 deletions

View File

@@ -0,0 +1,123 @@
import asyncio
import os
import time
from typing import Tuple, Union
import aiohttp
import requests
from src.common.logger import get_module_logger
logger = get_module_logger("offline_llm")
class LLM_request_off:
def __init__(self, model_name="Pro/deepseek-ai/DeepSeek-V3", **kwargs):
self.model_name = model_name
self.params = kwargs
self.api_key = os.getenv("SILICONFLOW_KEY")
self.base_url = os.getenv("SILICONFLOW_BASE_URL")
if not self.api_key or not self.base_url:
raise ValueError("环境变量未正确加载SILICONFLOW_KEY 或 SILICONFLOW_BASE_URL 未设置")
logger.info(f"API URL: {self.base_url}") # 使用 logger 记录 base_url
def generate_response(self, prompt: str) -> Union[str, Tuple[str, str]]:
"""根据输入的提示生成模型的响应"""
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
# 构建请求体
data = {
"model": self.model_name,
"messages": [{"role": "user", "content": prompt}],
"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 # 基础等待时间(秒)
for retry in range(max_retries):
try:
response = requests.post(api_url, headers=headers, json=data)
if response.status_code == 429:
wait_time = base_wait_time * (2**retry) # 指数退避
logger.warning(f"遇到请求限制(429),等待{wait_time}秒后重试...")
time.sleep(wait_time)
continue
response.raise_for_status() # 检查其他响应状态
result = 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", "")
return content, reasoning_content
return "没有返回结果", ""
except Exception as e:
if retry < max_retries - 1: # 如果还有重试机会
wait_time = base_wait_time * (2**retry)
logger.error(f"[回复]请求失败,等待{wait_time}秒后重试... 错误: {str(e)}")
time.sleep(wait_time)
else:
logger.error(f"请求失败: {str(e)}")
return f"请求失败: {str(e)}", ""
logger.error("达到最大重试次数,请求仍然失败")
return "达到最大重试次数,请求仍然失败", ""
async def generate_response_async(self, prompt: str) -> Union[str, Tuple[str, str]]:
"""异步方式根据输入的提示生成模型的响应"""
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
# 构建请求体
data = {
"model": self.model_name,
"messages": [{"role": "user", "content": prompt}],
"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:
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
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", "")
return content, reasoning_content
return "没有返回结果", ""
except Exception as e:
if retry < max_retries - 1: # 如果还有重试机会
wait_time = base_wait_time * (2**retry)
logger.error(f"[回复]请求失败,等待{wait_time}秒后重试... 错误: {str(e)}")
await asyncio.sleep(wait_time)
else:
logger.error(f"请求失败: {str(e)}")
return f"请求失败: {str(e)}", ""
logger.error("达到最大重试次数,请求仍然失败")
return "达到最大重试次数,请求仍然失败", ""

View File

@@ -0,0 +1,32 @@
from dataclasses import dataclass
from typing import Dict, List
@dataclass
class Personality:
"""人格特质类"""
openness: float # 开放性
conscientiousness: float # 尽责性
extraversion: float # 外向性
agreeableness: float # 宜人性
neuroticism: float # 神经质
bot_nickname: str # 机器人昵称
personality_core: str # 人格核心特点
personality_detail: List[str] # 人格细节描述
def to_dict(self) -> Dict:
"""将人格特质转换为字典格式"""
return {
"openness": self.openness,
"conscientiousness": self.conscientiousness,
"extraversion": self.extraversion,
"agreeableness": self.agreeableness,
"neuroticism": self.neuroticism,
"bot_nickname": self.bot_nickname,
"personality_core": self.personality_core,
"personality_detail": self.personality_detail
}
@classmethod
def from_dict(cls, data: Dict) -> 'Personality':
"""从字典创建人格特质实例"""
return cls(**data)

View File

@@ -0,0 +1,186 @@
import os
import json
import sys
from typing import Optional, List
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
from src.personality.offline_llm import LLM_request_off
from src.common.logger import get_module_logger
from src.personality.personality import Personality
logger = get_module_logger("personality_gen")
class PersonalityGenerator:
"""人格生成器类"""
def __init__(self, bot_nickname: str):
self.bot_nickname = bot_nickname
self.llm = LLM_request_off()
self.personality: Optional[Personality] = None
self.save_path = os.path.join("data", "personality")
# 确保保存目录存在
os.makedirs(self.save_path, exist_ok=True)
def personality_exists(self) -> bool:
"""检查是否已存在该机器人的人格文件"""
file_path = os.path.join(self.save_path, f"{self.bot_nickname}_personality.per")
return os.path.exists(file_path)
async def generate_personality(
self,
personality_core: str,
personality_detail: List[str],
height: int,
weight: int,
age: int,
gender: str,
appearance: str,
interests: List[str],
others: List[str]
) -> Optional[Personality]:
"""根据配置生成人格特质"""
# 检查是否已存在
if self.personality_exists():
logger.info(f"机器人 {self.bot_nickname} 的人格文件已存在,跳过生成")
return await self.load_personality()
# 构建提示文本
prompt = f"""你是一个心理学家,专职心理测量和大五人格研究。请根据以下信息分析并给出这个人的大五人格特质评分。
每个特质的分数范围是0到1之间的小数请确保返回标准的JSON格式。
机器人信息:
- 昵称:{self.bot_nickname}
- 性格核心的特质:{personality_core}
- 性格细节:{', '.join(personality_detail)}
- 身高:{height}cm
- 体重:{weight}kg
- 年龄:{age}
- 性别:{gender}
- 外貌:{appearance}
- 兴趣爱好:{', '.join(interests)}
- 其他信息:{', '.join(others)}
请只返回如下JSON格式数据不要包含任何其他文字
{{
"openness": 0.x,
"conscientiousness": 0.x,
"extraversion": 0.x,
"agreeableness": 0.x,
"neuroticism": 0.x
}}"""
response, _ = await self.llm.generate_response_async(prompt)
try:
# 尝试清理响应文本只保留JSON部分
json_str = response.strip()
if "```json" in json_str:
json_str = json_str.split("```json")[1].split("```")[0].strip()
elif "```" in json_str:
json_str = json_str.split("```")[1].strip()
traits = json.loads(json_str)
# 验证所有必需的字段是否存在
required_fields = ["openness", "conscientiousness", "extraversion", "agreeableness", "neuroticism"]
if not all(field in traits for field in required_fields):
raise ValueError("缺少必需的人格特质字段")
# 验证数值是否在合理范围内
for field in required_fields:
if not 0 <= traits[field] <= 1:
traits[field] = max(0, min(traits[field], 1))
self.personality = Personality(
**traits,
bot_nickname=self.bot_nickname
)
await self.save_personality()
return self.personality
except json.JSONDecodeError as e:
logger.error(f"JSON解析失败: {e}\n响应内容: {response}")
raise
except Exception as e:
logger.error(f"生成人格特质失败: {e}")
raise
async def save_personality(self) -> None:
"""保存人格特质到文件"""
if not self.personality:
raise ValueError("没有可保存的人格特质")
file_path = os.path.join(self.save_path, f"{self.bot_nickname}_personality.per")
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(self.personality.to_dict(), f, ensure_ascii=False, indent=4)
logger.info(f"人格特质已保存到: {file_path}")
except Exception as e:
logger.error(f"保存人格特质失败: {e}")
raise
async def load_personality(self) -> Optional[Personality]:
"""从文件加载人格特质"""
file_path = os.path.join(self.save_path, f"{self.bot_nickname}_personality.per")
try:
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
self.personality = Personality.from_dict(data)
return self.personality
except Exception as e:
logger.error(f"加载人格特质失败: {e}")
return None
async def main():
"""主函数,用于测试人格生成"""
# 创建人格生成器实例
generator = PersonalityGenerator("麦麦")
# 生成或加载人格
personality = await generator.generate_personality(
personality_core="对世界抱着善意和好奇,愿意尝试新奇事物",
personality_detail=[
"你会刷小红书",
"你会刷贴吧",
"学习心理学和脑科学",
"你会刷b站对ACG文化感兴趣",
"有时候有些搞怪",
],
height=160,
weight=45,
age=20,
gender="",
appearance="有着橙色短发",
interests=["摄影", "绘画"],
others=["是一个大二女大学生"]
)
if personality:
logger.info("人格特质生成成功:")
logger.info(f"开放性: {personality.openness}")
logger.info(f"尽责性: {personality.conscientiousness}")
logger.info(f"外向性: {personality.extraversion}")
logger.info(f"宜人性: {personality.agreeableness}")
logger.info(f"神经质: {personality.neuroticism}")
else:
logger.error("人格特质生成失败")
if __name__ == "__main__":
import asyncio
import platform
if platform.system() == 'Windows':
# Windows平台特殊处理
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
try:
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
finally:
# 确保所有待处理的任务都完成
pending = asyncio.all_tasks(loop)
for task in pending:
task.cancel()
# 运行一次以处理取消的任务
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
loop.close()

View File

@@ -148,14 +148,36 @@ class BotConfig:
ban_user_id = set() ban_user_id = set()
# personality # personality
PROMPT_PERSONALITY = [ personality_core = "用一句话或几句话描述人格的核心特点" # 建议20字以内谁再写3000字小作文敲谁脑袋
"用一句话或几句话描述性格特点和其他特征", personality_detail: List[str] = field(default_factory=lambda: [
"例如,是一个热爱国家热爱党的新时代好青年", "用一句话或几句话描述人格的一些细节",
"例如,曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧", "用一句话或几句话描述人格的一些细节",
] "用一句话或几句话描述人格的一些细节",
PERSONALITY_1: float = 0.6 # 第一种人格概率 "用一句话或几句话描述人格的一些细节",
PERSONALITY_2: float = 0.3 # 第二种人格概率 "用一句话或几句话描述人格的一些细节"
PERSONALITY_3: float = 0.1 # 第三种人格概率 ])
traits: List[str] = field(default_factory=lambda: [
"用一个词描述性格",
"用一个词描述性格",
"用一个词描述性格",
])
# identity
identity_detail: List[str] = field(default_factory=lambda: [
"身份特点",
"身份特点",
])
height: int = 170 # 身高 单位厘米
weight: int = 50 # 体重 单位千克
age: int = 20 # 年龄 单位岁
gender: str = "" # 性别
appearance: str = "用几句话描述外貌特征" # 外貌特征
interests: List[str] = field(default_factory=lambda: [
"兴趣爱好1",
"兴趣爱好2",
"兴趣爱好3"
])
# schedule # schedule
ENABLE_SCHEDULE_GEN: bool = False # 是否启用日程生成 ENABLE_SCHEDULE_GEN: bool = False # 是否启用日程生成
@@ -347,14 +369,20 @@ class BotConfig:
def personality(parent: dict): def personality(parent: dict):
personality_config = parent["personality"] personality_config = parent["personality"]
personality = personality_config.get("prompt_personality") if config.INNER_VERSION in SpecifierSet(">=1.2.4"):
if len(personality) >= 2: config.personality_core = personality_config.get("personality_core", config.personality_core)
logger.info(f"载入自定义人格:{personality}") config.personality_detail = personality_config.get("personality_detail", config.personality_detail)
config.PROMPT_PERSONALITY = personality_config.get("prompt_personality", config.PROMPT_PERSONALITY)
config.PERSONALITY_1 = personality_config.get("personality_1_probability", config.PERSONALITY_1) def identity(parent: dict):
config.PERSONALITY_2 = personality_config.get("personality_2_probability", config.PERSONALITY_2) identity_config = parent["identity"]
config.PERSONALITY_3 = personality_config.get("personality_3_probability", config.PERSONALITY_3) if config.INNER_VERSION in SpecifierSet(">=1.2.4"):
config.identity_detail = identity_config.get("identity_detail", config.identity_detail)
config.height = identity_config.get("height", config.height)
config.weight = identity_config.get("weight", config.weight)
config.age = identity_config.get("age", config.age)
config.gender = identity_config.get("gender", config.gender)
config.appearance = identity_config.get("appearance", config.appearance)
config.interests = identity_config.get("interests", config.interests)
def schedule(parent: dict): def schedule(parent: dict):
schedule_config = parent["schedule"] schedule_config = parent["schedule"]
@@ -611,6 +639,7 @@ class BotConfig:
"bot": {"func": bot, "support": ">=0.0.0"}, "bot": {"func": bot, "support": ">=0.0.0"},
"groups": {"func": groups, "support": ">=0.0.0"}, "groups": {"func": groups, "support": ">=0.0.0"},
"personality": {"func": personality, "support": ">=0.0.0"}, "personality": {"func": personality, "support": ">=0.0.0"},
"identity": {"func": identity, "support": ">=1.2.4"},
"schedule": {"func": schedule, "support": ">=0.0.11", "necessary": False}, "schedule": {"func": schedule, "support": ">=0.0.11", "necessary": False},
"message": {"func": message, "support": ">=0.0.0"}, "message": {"func": message, "support": ">=0.0.0"},
"willing": {"func": willing, "support": ">=0.0.9", "necessary": False}, "willing": {"func": willing, "support": ">=0.0.9", "necessary": False},

View File

@@ -34,14 +34,27 @@ talk_frequency_down = [] #降低回复频率的群号码
ban_user_id = [] #禁止回复和读取消息的QQ号 ban_user_id = [] #禁止回复和读取消息的QQ号
[personality] [personality]
prompt_personality = [ personality_core = "用一句话或几句话描述人格的核心特点" # 建议20字以内谁再写3000字小作文敲谁脑袋
"用一句话或几句话描述性格特点和其他特征", personality_detail = [
"例如,是一个热爱国家热爱党的新时代好青年", "用一句话或几句话描述人格的一些细节",
"例如,曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧" "用一句话或几句话描述人格的一些细节",
] "用一句话或几句话描述人格的一些细节",
personality_1_probability = 0.7 # 第一种人格出现概率 "用一句话或几句话描述人格的一些细节",
personality_2_probability = 0.2 # 第二种人格出现概率可以为0 "用一句话或几句话描述人格的一些细节",
personality_3_probability = 0.1 # 第三种人格出现概率请确保三个概率相加等于1 ]# 条数任意
[identity] #アイデンティティがない 生まれないらららら
# 兴趣爱好
identity_detail = [
"身份特点",
"身份特点",
]# 条数任意
#外貌特征
height = 170 # 身高 单位厘米
weight = 50 # 体重 单位千克
age = 20 # 年龄 单位岁
gender = "男" # 性别
appearance = "用几句话描述外貌特征" # 外貌特征
[schedule] [schedule]
enable_schedule_gen = true # 是否启用日程表(尚未完成) enable_schedule_gen = true # 是否启用日程表(尚未完成)