feat:动态麦麦人格测评工具

This commit is contained in:
SengokuCola
2025-04-06 15:31:23 +08:00
parent 88a4db8894
commit 2d40b01ba8
4 changed files with 246 additions and 78 deletions

View File

@@ -20,7 +20,7 @@ class LLM_request_off:
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
# logger.info(f"API URL: {self.base_url}") # 使用 logger 记录 base_url
def generate_response(self, prompt: str) -> Union[str, Tuple[str, str]]:
"""根据输入的提示生成模型的响应"""

View File

@@ -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
import json
import os
from pathlib import Path
from dotenv import load_dotenv
import sys
import toml
import random
from tqdm import tqdm
"""
第一种方案:基于情景评估的人格测定
"""
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__), "../../.."))
# 添加项目根目录到 Python 路径
root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
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
from src.individuality.offline_llm import LLMModel # noqa: E402
# 加载配置文件
config_path = os.path.join(root_path, "config", "bot_config.toml")
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} 加载环境变量")
load_dotenv(env_path)
else:
@@ -38,10 +32,60 @@ else:
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:
def __init__(self):
self.personality_traits = {"开放性": 0, "严谨性": 0, "外向性": 0, "宜人性": 0, "神经质": 0}
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:
@@ -67,7 +111,7 @@ class PersonalityEvaluator_direct:
{"场景": 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]:
"""
@@ -126,24 +170,35 @@ class PersonalityEvaluator_direct:
print(f"评估过程出错:{str(e)}")
return {dim: 3.5 for dim in dimensions}
def main():
print("欢迎使用人格形象创建程序!")
print("接下来您将面对一系列场景共15个。请根据您想要创建的角色形象描述在该场景下可能的反应。")
print("每个场景都会评估不同的人格维度,最终得出完整的人格特征评估。")
print("评分标准1=非常不符合2=比较不符合3=有点不符合4=有点符合5=比较符合6=非常符合")
def run_evaluation(self):
"""
运行整个评估过程
"""
print(f"欢迎使用{config['bot']['nickname']}形象创建程序!")
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准备好了吗?按回车键开始...")
input()
evaluator = PersonalityEvaluator_direct()
final_scores = {"开放性": 0, "严谨性": 0, "外向性": 0, "宜人性": 0, "神经质": 0}
dimension_counts = {trait: 0 for trait in final_scores.keys()}
total_scenarios = len(self.scenarios)
progress_bar = tqdm(total=total_scenarios, desc="场景进度", ncols=100, bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]')
for i, scenario_data in enumerate(evaluator.scenarios, 1):
print(f"\n场景 {i}/{len(evaluator.scenarios)} - {scenario_data['场景编号']}:")
print("-" * 50)
print(scenario_data["场景"])
print("\n请描述您的角色在这种情况下会如何反应:")
for i, scenario_data in enumerate(self.scenarios, 1):
# print(f"\n{'-' * 20} 场景 {i}/{total_scenarios} - {scenario_data['场景编号']} {'-' * 20}")
# 改编场景,使其更适合当前角色
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()
if not response:
@@ -151,45 +206,102 @@ def main():
continue
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():
final_scores[dimension] += score
dimension_counts[dimension] += 1
self.final_scores[dimension] += score
self.dimension_counts[dimension] += 1
print("\n当前评估结果:")
print("-" * 30)
for dimension, score in scores.items():
print(f"{dimension}: {score}/6")
if i < len(evaluator.scenarios):
print("\n按回车键继续下一个场景...")
input()
# 更新进度条
progress_bar.update(1)
# if i < total_scenarios:
# print("\n按回车键继续下一个场景...")
# input()
progress_bar.close()
# 计算平均分
for dimension in final_scores:
if dimension_counts[dimension] > 0:
final_scores[dimension] = round(final_scores[dimension] / dimension_counts[dimension], 2)
for dimension in self.final_scores:
if self.dimension_counts[dimension] > 0:
self.final_scores[dimension] = round(self.final_scores[dimension] / self.dimension_counts[dimension], 2)
print("\n最终人格特征评估结果:")
print("-" * 30)
for trait, score in final_scores.items():
print(f"{trait}: {score}/6")
print(f"测试场景数:{dimension_counts[trait]}")
print("\n" + "=" * 50)
print(f" {config['bot']['nickname']}的人格特征评估结果 ".center(50))
print("=" * 50)
for trait, score in self.final_scores.items():
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:
json.dump(result, f, ensure_ascii=False, indent=2)
print("\n结果已保存到 results/personality_result.json")
if __name__ == "__main__":
main()

View 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