move:修改人格文件结构
This commit is contained in:
123
src/personality/offline_llm.py
Normal file
123
src/personality/offline_llm.py
Normal 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 "达到最大重试次数,请求仍然失败", ""
|
||||
32
src/personality/personality.py
Normal file
32
src/personality/personality.py
Normal 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)
|
||||
186
src/personality/personality_gen.py
Normal file
186
src/personality/personality_gen.py
Normal 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()
|
||||
@@ -148,14 +148,36 @@ class BotConfig:
|
||||
ban_user_id = set()
|
||||
|
||||
# personality
|
||||
PROMPT_PERSONALITY = [
|
||||
"用一句话或几句话描述性格特点和其他特征",
|
||||
"例如,是一个热爱国家热爱党的新时代好青年",
|
||||
"例如,曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧",
|
||||
]
|
||||
PERSONALITY_1: float = 0.6 # 第一种人格概率
|
||||
PERSONALITY_2: float = 0.3 # 第二种人格概率
|
||||
PERSONALITY_3: float = 0.1 # 第三种人格概率
|
||||
personality_core = "用一句话或几句话描述人格的核心特点" # 建议20字以内,谁再写3000字小作文敲谁脑袋
|
||||
personality_detail: List[str] = field(default_factory=lambda: [
|
||||
"用一句话或几句话描述人格的一些细节",
|
||||
"用一句话或几句话描述人格的一些细节",
|
||||
"用一句话或几句话描述人格的一些细节",
|
||||
"用一句话或几句话描述人格的一些细节",
|
||||
"用一句话或几句话描述人格的一些细节"
|
||||
])
|
||||
|
||||
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
|
||||
ENABLE_SCHEDULE_GEN: bool = False # 是否启用日程生成
|
||||
@@ -347,14 +369,20 @@ class BotConfig:
|
||||
|
||||
def personality(parent: dict):
|
||||
personality_config = parent["personality"]
|
||||
personality = personality_config.get("prompt_personality")
|
||||
if len(personality) >= 2:
|
||||
logger.info(f"载入自定义人格:{personality}")
|
||||
config.PROMPT_PERSONALITY = personality_config.get("prompt_personality", config.PROMPT_PERSONALITY)
|
||||
if config.INNER_VERSION in SpecifierSet(">=1.2.4"):
|
||||
config.personality_core = personality_config.get("personality_core", config.personality_core)
|
||||
config.personality_detail = personality_config.get("personality_detail", config.personality_detail)
|
||||
|
||||
config.PERSONALITY_1 = personality_config.get("personality_1_probability", config.PERSONALITY_1)
|
||||
config.PERSONALITY_2 = personality_config.get("personality_2_probability", config.PERSONALITY_2)
|
||||
config.PERSONALITY_3 = personality_config.get("personality_3_probability", config.PERSONALITY_3)
|
||||
def identity(parent: dict):
|
||||
identity_config = parent["identity"]
|
||||
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):
|
||||
schedule_config = parent["schedule"]
|
||||
@@ -611,6 +639,7 @@ class BotConfig:
|
||||
"bot": {"func": bot, "support": ">=0.0.0"},
|
||||
"groups": {"func": groups, "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},
|
||||
"message": {"func": message, "support": ">=0.0.0"},
|
||||
"willing": {"func": willing, "support": ">=0.0.9", "necessary": False},
|
||||
|
||||
@@ -34,14 +34,27 @@ talk_frequency_down = [] #降低回复频率的群号码
|
||||
ban_user_id = [] #禁止回复和读取消息的QQ号
|
||||
|
||||
[personality]
|
||||
prompt_personality = [
|
||||
"用一句话或几句话描述性格特点和其他特征",
|
||||
"例如,是一个热爱国家热爱党的新时代好青年",
|
||||
"例如,曾经是一个学习地质的女大学生,现在学习心理学和脑科学,你会刷贴吧"
|
||||
]
|
||||
personality_1_probability = 0.7 # 第一种人格出现概率
|
||||
personality_2_probability = 0.2 # 第二种人格出现概率,可以为0
|
||||
personality_3_probability = 0.1 # 第三种人格出现概率,请确保三个概率相加等于1
|
||||
personality_core = "用一句话或几句话描述人格的核心特点" # 建议20字以内,谁再写3000字小作文敲谁脑袋
|
||||
personality_detail = [
|
||||
"用一句话或几句话描述人格的一些细节",
|
||||
"用一句话或几句话描述人格的一些细节",
|
||||
"用一句话或几句话描述人格的一些细节",
|
||||
"用一句话或几句话描述人格的一些细节",
|
||||
"用一句话或几句话描述人格的一些细节",
|
||||
]# 条数任意
|
||||
|
||||
[identity] #アイデンティティがない 生まれないらららら
|
||||
# 兴趣爱好
|
||||
identity_detail = [
|
||||
"身份特点",
|
||||
"身份特点",
|
||||
]# 条数任意
|
||||
#外貌特征
|
||||
height = 170 # 身高 单位厘米
|
||||
weight = 50 # 体重 单位千克
|
||||
age = 20 # 年龄 单位岁
|
||||
gender = "男" # 性别
|
||||
appearance = "用几句话描述外貌特征" # 外貌特征
|
||||
|
||||
[schedule]
|
||||
enable_schedule_gen = true # 是否启用日程表(尚未完成)
|
||||
|
||||
Reference in New Issue
Block a user