feat:动态麦麦人格测评工具
This commit is contained in:
@@ -20,7 +20,7 @@ class LLM_request_off:
|
|||||||
if not self.api_key or not self.base_url:
|
if not self.api_key or not self.base_url:
|
||||||
raise ValueError("环境变量未正确加载:SILICONFLOW_KEY 或 SILICONFLOW_BASE_URL 未设置")
|
raise ValueError("环境变量未正确加载:SILICONFLOW_KEY 或 SILICONFLOW_BASE_URL 未设置")
|
||||||
|
|
||||||
logger.info(f"API URL: {self.base_url}") # 使用 logger 记录 base_url
|
# logger.info(f"API URL: {self.base_url}") # 使用 logger 记录 base_url
|
||||||
|
|
||||||
def generate_response(self, prompt: str) -> Union[str, Tuple[str, str]]:
|
def generate_response(self, prompt: str) -> Union[str, Tuple[str, str]]:
|
||||||
"""根据输入的提示生成模型的响应"""
|
"""根据输入的提示生成模型的响应"""
|
||||||
|
|||||||
@@ -1,36 +1,30 @@
|
|||||||
"""
|
|
||||||
The definition of artificial personality in this paper follows the dispositional para-digm and adapts a definition of
|
|
||||||
personality developed for humans [17]:
|
|
||||||
Personality for a human is the "whole and organisation of relatively stable tendencies and patterns of experience and
|
|
||||||
behaviour within one person (distinguishing it from other persons)". This definition is modified for artificial
|
|
||||||
personality:
|
|
||||||
Artificial personality describes the relatively stable tendencies and patterns of behav-iour of an AI-based machine that
|
|
||||||
can be designed by developers and designers via different modalities, such as language, creating the impression
|
|
||||||
of individuality of a humanized social agent when users interact with the machine."""
|
|
||||||
|
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import sys
|
import sys
|
||||||
|
import toml
|
||||||
|
import random
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
"""
|
# 添加项目根目录到 Python 路径
|
||||||
第一种方案:基于情景评估的人格测定
|
root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
|
||||||
"""
|
|
||||||
current_dir = Path(__file__).resolve().parent
|
|
||||||
project_root = current_dir.parent.parent.parent
|
|
||||||
env_path = project_root / ".env"
|
|
||||||
|
|
||||||
root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
|
|
||||||
sys.path.append(root_path)
|
sys.path.append(root_path)
|
||||||
|
|
||||||
from src.individuality.scene import get_scene_by_factor, PERSONALITY_SCENES # noqa: E402
|
# 加载配置文件
|
||||||
from src.individuality.questionnaire import FACTOR_DESCRIPTIONS # noqa: E402
|
config_path = os.path.join(root_path, "config", "bot_config.toml")
|
||||||
from src.individuality.offline_llm import LLMModel # noqa: E402
|
with open(config_path, "r", encoding="utf-8") as f:
|
||||||
|
config = toml.load(f)
|
||||||
|
|
||||||
|
# 现在可以导入src模块
|
||||||
|
from src.individuality.scene import get_scene_by_factor, PERSONALITY_SCENES
|
||||||
|
from src.individuality.questionnaire import FACTOR_DESCRIPTIONS
|
||||||
|
from src.individuality.offline_llm import LLM_request_off
|
||||||
|
|
||||||
# 加载环境变量
|
# 加载环境变量
|
||||||
if env_path.exists():
|
env_path = os.path.join(root_path, ".env")
|
||||||
|
if os.path.exists(env_path):
|
||||||
print(f"从 {env_path} 加载环境变量")
|
print(f"从 {env_path} 加载环境变量")
|
||||||
load_dotenv(env_path)
|
load_dotenv(env_path)
|
||||||
else:
|
else:
|
||||||
@@ -38,10 +32,60 @@ else:
|
|||||||
print("将使用默认配置")
|
print("将使用默认配置")
|
||||||
|
|
||||||
|
|
||||||
|
def adapt_scene(scene: str) -> str:
|
||||||
|
|
||||||
|
personality_core = config['personality']['personality_core']
|
||||||
|
personality_sides = config['personality']['personality_sides']
|
||||||
|
personality_side = random.choice(personality_sides)
|
||||||
|
identity_details = config['identity']['identity_detail']
|
||||||
|
identity_detail = random.choice(identity_details)
|
||||||
|
|
||||||
|
"""
|
||||||
|
根据config中的属性,改编场景使其更适合当前角色
|
||||||
|
|
||||||
|
Args:
|
||||||
|
scene: 原始场景描述
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 改编后的场景描述
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
prompt = f"""
|
||||||
|
这是一个参与人格测评的角色形象:
|
||||||
|
- 昵称: {config['bot']['nickname']}
|
||||||
|
- 性别: {config['identity']['gender']}
|
||||||
|
- 年龄: {config['identity']['age']}岁
|
||||||
|
- 外貌: {config['identity']['appearance']}
|
||||||
|
- 性格核心: {personality_core}
|
||||||
|
- 性格侧面: {personality_side}
|
||||||
|
- 身份细节: {identity_detail}
|
||||||
|
|
||||||
|
请根据上述形象,改编以下原始场景,在测评中,用户将根据该场景给出上述角色形象的反应:
|
||||||
|
{scene}
|
||||||
|
|
||||||
|
保持场景的本质不变,但最好贴近生活且具体,并且让它更适合这个角色。
|
||||||
|
改编后的场景应该自然、连贯,并考虑角色的年龄、身份和性格特点。只返回改编后的场景描述,不要包含其他说明。注意{config['bot']['nickname']}是面对这个情景的人,而不是场景的其他人。"""
|
||||||
|
|
||||||
|
llm = LLM_request_off(model_name=config['model']['llm_normal']['name'])
|
||||||
|
adapted_scene, _ = llm.generate_response(prompt)
|
||||||
|
|
||||||
|
# 检查返回的场景是否为空或错误信息
|
||||||
|
if not adapted_scene or "错误" in adapted_scene or "失败" in adapted_scene:
|
||||||
|
print("场景改编失败,将使用原始场景")
|
||||||
|
return scene
|
||||||
|
|
||||||
|
return adapted_scene
|
||||||
|
except Exception as e:
|
||||||
|
print(f"场景改编过程出错:{str(e)},将使用原始场景")
|
||||||
|
return scene
|
||||||
|
|
||||||
|
|
||||||
class PersonalityEvaluator_direct:
|
class PersonalityEvaluator_direct:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.personality_traits = {"开放性": 0, "严谨性": 0, "外向性": 0, "宜人性": 0, "神经质": 0}
|
self.personality_traits = {"开放性": 0, "严谨性": 0, "外向性": 0, "宜人性": 0, "神经质": 0}
|
||||||
self.scenarios = []
|
self.scenarios = []
|
||||||
|
self.final_scores = {"开放性": 0, "严谨性": 0, "外向性": 0, "宜人性": 0, "神经质": 0}
|
||||||
|
self.dimension_counts = {trait: 0 for trait in self.final_scores.keys()}
|
||||||
|
|
||||||
# 为每个人格特质获取对应的场景
|
# 为每个人格特质获取对应的场景
|
||||||
for trait in PERSONALITY_SCENES:
|
for trait in PERSONALITY_SCENES:
|
||||||
@@ -67,7 +111,7 @@ class PersonalityEvaluator_direct:
|
|||||||
{"场景": scene["scenario"], "评估维度": [trait, secondary_trait], "场景编号": scene_key}
|
{"场景": scene["scenario"], "评估维度": [trait, secondary_trait], "场景编号": scene_key}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.llm = LLMModel()
|
self.llm = LLM_request_off()
|
||||||
|
|
||||||
def evaluate_response(self, scenario: str, response: str, dimensions: List[str]) -> Dict[str, float]:
|
def evaluate_response(self, scenario: str, response: str, dimensions: List[str]) -> Dict[str, float]:
|
||||||
"""
|
"""
|
||||||
@@ -126,24 +170,35 @@ class PersonalityEvaluator_direct:
|
|||||||
print(f"评估过程出错:{str(e)}")
|
print(f"评估过程出错:{str(e)}")
|
||||||
return {dim: 3.5 for dim in dimensions}
|
return {dim: 3.5 for dim in dimensions}
|
||||||
|
|
||||||
|
def run_evaluation(self):
|
||||||
def main():
|
"""
|
||||||
print("欢迎使用人格形象创建程序!")
|
运行整个评估过程
|
||||||
print("接下来,您将面对一系列场景(共15个)。请根据您想要创建的角色形象,描述在该场景下可能的反应。")
|
"""
|
||||||
print("每个场景都会评估不同的人格维度,最终得出完整的人格特征评估。")
|
print(f"欢迎使用{config['bot']['nickname']}形象创建程序!")
|
||||||
print("评分标准:1=非常不符合,2=比较不符合,3=有点不符合,4=有点符合,5=比较符合,6=非常符合")
|
print("接下来,将给您呈现一系列有关您bot的场景(共15个)。")
|
||||||
|
print("请想象您的bot在以下场景下会做什么,并描述您的bot的反应。")
|
||||||
|
print("每个场景都会进行不同方面的评估。")
|
||||||
|
print("\n角色基本信息:")
|
||||||
|
print(f"- 昵称:{config['bot']['nickname']}")
|
||||||
|
print(f"- 性格核心:{config['personality']['personality_core']}")
|
||||||
|
print(f"- 性格侧面:{config['personality']['personality_sides']}")
|
||||||
|
print(f"- 身份细节:{config['identity']['identity_detail']}")
|
||||||
print("\n准备好了吗?按回车键开始...")
|
print("\n准备好了吗?按回车键开始...")
|
||||||
input()
|
input()
|
||||||
|
|
||||||
evaluator = PersonalityEvaluator_direct()
|
total_scenarios = len(self.scenarios)
|
||||||
final_scores = {"开放性": 0, "严谨性": 0, "外向性": 0, "宜人性": 0, "神经质": 0}
|
progress_bar = tqdm(total=total_scenarios, desc="场景进度", ncols=100, bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]')
|
||||||
dimension_counts = {trait: 0 for trait in final_scores.keys()}
|
|
||||||
|
|
||||||
for i, scenario_data in enumerate(evaluator.scenarios, 1):
|
for i, scenario_data in enumerate(self.scenarios, 1):
|
||||||
print(f"\n场景 {i}/{len(evaluator.scenarios)} - {scenario_data['场景编号']}:")
|
# print(f"\n{'-' * 20} 场景 {i}/{total_scenarios} - {scenario_data['场景编号']} {'-' * 20}")
|
||||||
print("-" * 50)
|
|
||||||
print(scenario_data["场景"])
|
# 改编场景,使其更适合当前角色
|
||||||
print("\n请描述您的角色在这种情况下会如何反应:")
|
print(f"{config['bot']['nickname']}祈祷中...")
|
||||||
|
adapted_scene = adapt_scene(scenario_data["场景"])
|
||||||
|
scenario_data["改编场景"] = adapted_scene
|
||||||
|
|
||||||
|
print(adapted_scene)
|
||||||
|
print(f"\n请描述{config['bot']['nickname']}在这种情况下会如何反应:")
|
||||||
response = input().strip()
|
response = input().strip()
|
||||||
|
|
||||||
if not response:
|
if not response:
|
||||||
@@ -151,45 +206,102 @@ def main():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
print("\n正在评估您的描述...")
|
print("\n正在评估您的描述...")
|
||||||
scores = evaluator.evaluate_response(scenario_data["场景"], response, scenario_data["评估维度"])
|
scores = self.evaluate_response(adapted_scene, response, scenario_data["评估维度"])
|
||||||
|
|
||||||
# 更新最终分数
|
# 更新最终分数
|
||||||
for dimension, score in scores.items():
|
for dimension, score in scores.items():
|
||||||
final_scores[dimension] += score
|
self.final_scores[dimension] += score
|
||||||
dimension_counts[dimension] += 1
|
self.dimension_counts[dimension] += 1
|
||||||
|
|
||||||
print("\n当前评估结果:")
|
print("\n当前评估结果:")
|
||||||
print("-" * 30)
|
print("-" * 30)
|
||||||
for dimension, score in scores.items():
|
for dimension, score in scores.items():
|
||||||
print(f"{dimension}: {score}/6")
|
print(f"{dimension}: {score}/6")
|
||||||
|
|
||||||
if i < len(evaluator.scenarios):
|
# 更新进度条
|
||||||
print("\n按回车键继续下一个场景...")
|
progress_bar.update(1)
|
||||||
input()
|
|
||||||
|
# if i < total_scenarios:
|
||||||
|
# print("\n按回车键继续下一个场景...")
|
||||||
|
# input()
|
||||||
|
|
||||||
|
progress_bar.close()
|
||||||
|
|
||||||
# 计算平均分
|
# 计算平均分
|
||||||
for dimension in final_scores:
|
for dimension in self.final_scores:
|
||||||
if dimension_counts[dimension] > 0:
|
if self.dimension_counts[dimension] > 0:
|
||||||
final_scores[dimension] = round(final_scores[dimension] / dimension_counts[dimension], 2)
|
self.final_scores[dimension] = round(self.final_scores[dimension] / self.dimension_counts[dimension], 2)
|
||||||
|
|
||||||
print("\n最终人格特征评估结果:")
|
print("\n" + "=" * 50)
|
||||||
print("-" * 30)
|
print(f" {config['bot']['nickname']}的人格特征评估结果 ".center(50))
|
||||||
for trait, score in final_scores.items():
|
print("=" * 50)
|
||||||
print(f"{trait}: {score}/6")
|
for trait, score in self.final_scores.items():
|
||||||
print(f"测试场景数:{dimension_counts[trait]}")
|
print(f"{trait}: {score}/6".ljust(20) + f"测试场景数:{self.dimension_counts[trait]}".rjust(30))
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
# 保存结果
|
# 返回评估结果
|
||||||
result = {"final_scores": final_scores, "dimension_counts": dimension_counts, "scenarios": evaluator.scenarios}
|
return self.get_result()
|
||||||
|
|
||||||
|
def get_result(self):
|
||||||
|
"""
|
||||||
|
获取评估结果
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"final_scores": self.final_scores,
|
||||||
|
"dimension_counts": self.dimension_counts,
|
||||||
|
"scenarios": self.scenarios,
|
||||||
|
"bot_info": {
|
||||||
|
"nickname": config['bot']['nickname'],
|
||||||
|
"gender": config['identity']['gender'],
|
||||||
|
"age": config['identity']['age'],
|
||||||
|
"height": config['identity']['height'],
|
||||||
|
"weight": config['identity']['weight'],
|
||||||
|
"appearance": config['identity']['appearance'],
|
||||||
|
"personality_core": config['personality']['personality_core'],
|
||||||
|
"personality_sides": config['personality']['personality_sides'],
|
||||||
|
"identity_detail": config['identity']['identity_detail']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
evaluator = PersonalityEvaluator_direct()
|
||||||
|
result = evaluator.run_evaluation()
|
||||||
|
|
||||||
|
# 准备简化的结果数据
|
||||||
|
simplified_result = {
|
||||||
|
"openness": round(result["final_scores"]["开放性"] / 6, 1), # 转换为0-1范围
|
||||||
|
"conscientiousness": round(result["final_scores"]["严谨性"] / 6, 1),
|
||||||
|
"extraversion": round(result["final_scores"]["外向性"] / 6, 1),
|
||||||
|
"agreeableness": round(result["final_scores"]["宜人性"] / 6, 1),
|
||||||
|
"neuroticism": round(result["final_scores"]["神经质"] / 6, 1),
|
||||||
|
"bot_nickname": config['bot']['nickname']
|
||||||
|
}
|
||||||
|
|
||||||
# 确保目录存在
|
# 确保目录存在
|
||||||
os.makedirs("results", exist_ok=True)
|
save_dir = os.path.join(root_path, "data", "personality")
|
||||||
|
os.makedirs(save_dir, exist_ok=True)
|
||||||
|
|
||||||
# 保存到文件
|
# 创建文件名,替换可能的非法字符
|
||||||
|
bot_name = config['bot']['nickname']
|
||||||
|
# 替换Windows文件名中不允许的字符
|
||||||
|
for char in ['\\', '/', ':', '*', '?', '"', '<', '>', '|']:
|
||||||
|
bot_name = bot_name.replace(char, '_')
|
||||||
|
|
||||||
|
file_name = f"{bot_name}_personality.per"
|
||||||
|
save_path = os.path.join(save_dir, file_name)
|
||||||
|
|
||||||
|
# 保存简化的结果
|
||||||
|
with open(save_path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(simplified_result, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
print(f"\n结果已保存到 {save_path}")
|
||||||
|
|
||||||
|
# 同时保存完整结果到results目录
|
||||||
|
os.makedirs("results", exist_ok=True)
|
||||||
with open("results/personality_result.json", "w", encoding="utf-8") as f:
|
with open("results/personality_result.json", "w", encoding="utf-8") as f:
|
||||||
json.dump(result, f, ensure_ascii=False, indent=2)
|
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
print("\n结果已保存到 results/personality_result.json")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
56
(测试版)麦麦生成人格.bat
Normal file
56
(测试版)麦麦生成人格.bat
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 > nul
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
cd /d %~dp0
|
||||||
|
|
||||||
|
title 麦麦人格生成
|
||||||
|
|
||||||
|
cls
|
||||||
|
echo ======================================
|
||||||
|
echo 警告提示
|
||||||
|
echo ======================================
|
||||||
|
echo 1.这是一个demo系统,仅供体验,特性可能会在将来移除
|
||||||
|
echo ======================================
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ======================================
|
||||||
|
echo 请选择Python环境:
|
||||||
|
echo 1 - venv (推荐)
|
||||||
|
echo 2 - conda
|
||||||
|
echo ======================================
|
||||||
|
choice /c 12 /n /m "请输入数字选择(1或2): "
|
||||||
|
|
||||||
|
if errorlevel 2 (
|
||||||
|
echo ======================================
|
||||||
|
set "CONDA_ENV="
|
||||||
|
set /p CONDA_ENV="请输入要激活的 conda 环境名称: "
|
||||||
|
|
||||||
|
:: 检查输入是否为空
|
||||||
|
if "!CONDA_ENV!"=="" (
|
||||||
|
echo 错误:环境名称不能为空
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
call conda activate !CONDA_ENV!
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo 激活 conda 环境失败
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Conda 环境 "!CONDA_ENV!" 激活成功
|
||||||
|
python src/individuality/per_bf_gen.py
|
||||||
|
) else (
|
||||||
|
if exist "venv\Scripts\python.exe" (
|
||||||
|
venv\Scripts\python src/individuality/per_bf_gen.py
|
||||||
|
) else (
|
||||||
|
echo ======================================
|
||||||
|
echo 错误: venv环境不存在,请先创建虚拟环境
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
endlocal
|
||||||
|
pause
|
||||||
Reference in New Issue
Block a user