一.引言
检索增强生成系统------RAG系统已成为构建长期对话Agent的主流架构。然而,现有RAG系统的记忆编码遵循"统一化"原则------所有对话片段经由同一个嵌入模型映射为等长向量,无论其情绪强度如何。这种"扁平编码"忽视了认知科学中的一个基本事实:情绪唤醒事件在人类记忆系统中享有编码优先级。情绪增强记忆效应(EEM)表明,高唤醒事件触发杏仁核-海马体协同信号,实现更深度的记忆编码与更持久的巩固(Cahill & McGaugh, 1995; McGaugh, 2004)。近年来,Emotional RAG(Huang et al., 2024)将情绪引入检索阶段,LUFY(2025)将情绪唤醒度作为记忆重要性的指标之一,但情绪对编码阶段的影响尚未被系统性探索------高情绪记忆是否应在编码时就获得更深度的表示,仍然是一个开放问题。
本文将EEM理论应用于RAG系统的编码阶段,提出情绪驱动的深度编码机制:当用户输入的情绪唤醒度超过阈值时,系统执行多维信息提取------除事实外,同步捕获用户的深层动机、语境线索及Agent的回应策略,将单维的"原话记录"转化为四维的"事件描述"。我们在EmpatheticDialogues多轮对话数据集上评估该机制,结果表明:深度编码使Agent的共情力提升4.0%,生动度呈现一致提升趋势。
1.将EEM效应可计算化地引入RAG编码阶段,尝试研究"情绪影响编码"这一设想;
2.设计并验证四维深度编码框架的有效性;
3.证明在多轮对话场景下系统编码深度对共情能力的正向作用。
二.基本原理
本实验整体框架分为三个核心步骤**:情绪分类、编码决策、向量存储与检索。**
本系统的核心任务是替换 RAG 的编码模块,其余部分保持不变,以隔离深度编码的独立贡献。

2.2情绪分类
2.2.1分类维度
我们使用 deepseek-v4-flash对每条用户输入进行情绪分类。分类器输出三个维度:
|------|---------|-------|-------------------------------------------------------|
| 维度 | 符号 | 范围 | 含义 |
| 效价 | valence | -1~1 | 情绪的积极或消极程度。1=极度积极,-1=极度消极,0=中性 |
| 唤醒度 | arousal | 0~1 | 情绪的生理激活强度。0=极度平静,1=极度兴奋、紧张和愤怒等情绪 |
| 情绪标签 | label | 枚举 | joy, sadness, anger, fear, surprise, love, neutral 之一 |
EEM效应由唤醒度驱动。杏仁核对唤醒度敏感,而非效价------无论是狂喜还是暴怒,只要是高唤醒状态,都会触发杏仁核-海马体的增强编码信号。因此我们以 arousal 作为编码决策的依据,valence仅作为情绪的积极或消极程度的参考依据。
2.2.2分类提示词
情绪分类使用以下 Prompt:
系统提示:
你是情绪分析专家,只输出JSON。
用户提示:
分析以下用户消息的情绪,只输出JSON格式:
{
"valence": 浮点数,范围-1到1(-1极度消极,0中性,1极度积极),
"arousal": 浮点数,范围0到1(0极度平静,1极度兴奋/紧张/愤怒),
"label": 字符串,从 joy|sadness|anger|fear|surprise|love|neutral 中选择
}
用户消息: {text}
2.2.3实现代码
查看代码
import re
def classify_emotion(text: str) -> Dict[str, Any]:
prompt = f"""分析以下用户消息的情绪,只输出JSON:
{{"valence": -1~1, "arousal": 0~1, "label": "joy|sadness|anger|fear|surprise|love|neutral"}}
消息: {text}"""
try:
resp = deepseek_client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": prompt}],
temperature=0.1
)
content = resp.choices[0].message.content.strip()
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
json_match = re.search(r'\{.*\}', content, re.DOTALL)
if json_match:
content = json_match.group(0)
content = re.sub(r',\s*}', '}', content)
content = re.sub(r',\s*]', ']', content)
return json.loads(content)
except Exception as e:
print(f"情绪分类失败: {e}")
return {"valence": 0.0, "arousal": 0.0, "label": "neutral"}
异常处理:当 API 调用失败或返回格式异常时,默认返回中性情绪(valence=0, arousal=0, label=neutral),确保系统稳定性。
2.3编码方式
2.3.1标准编码Baseline
标准编码是现有 RAG 系统的默认方式:直接将用户原话作为记忆文本存入。
def standard_encode(text: str, emotion: Dict) -> str:
return f"用户说:{text}"
2.3.2深度编码组
深度编码在满足arousal值条件时触发,除了原话外,额外调用 LLM 提取四个维度的信息:
查看代码
def deep_encode(text: str, emotion: Dict) -> str:
arousal = emotion.get("arousal", 0)
# 低情绪 → 标准编码
if arousal <= AROUSAL_THRESHOLD:
return standard_encode(text, emotion)
# 高情绪 → 深度编码
prompt = f"""用户说:"{text}"
情绪标签:{emotion.get('label', 'unknown')}
情绪唤醒度:{emotion.get('arousal', 0):.2f}
请提取深度记忆,输出JSON格式(只输出JSON):
{{
"fact": "用户说了什么(概括,一句话)",
"underlying_motive": "用户的深层需求或动机(推测)",
"contextual_cues": "语境线索(语气、标点、用词特点)",
"agent_strategy": "Agent应该如何回应(策略建议)"
}}
"""
try:
resp = deepseek_client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "你是记忆提取专家,只输出JSON。"},
{"role": "user", "content": prompt}
],
temperature=0.3
)
content = resp.choices[0].message.content.strip()
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
deep_data = json.loads(content)
return f"[深度记忆] {json.dumps(deep_data, ensure_ascii=False)}"
except Exception as e:
print(f"深度编码失败,回退标准编码: {e}")
return standard_encode(text, emotion)
2.3.3两种编码存入内容对比
Baseline组:
用户说:我拿到offer了!太开心了!
深度编码组:
[深度记忆] {
"fact": "用户拿到了梦寐以求的工作offer",
"underlying_motive": "希望被认可、分享喜悦",
"contextual_cues": "语气激动,用了感叹号",
"agent_strategy": "给予真诚祝贺,强化成就感"
}
两者的区别在于,标准编码只是单纯将信息照搬存入知识库,而深度编码会先通过LLM对信息进行分析,然后将信息分析出来的四个维度进行系统性存储。
2.4四维信息
|-------------------|----------------|--------------------|
| 维度 | 定义 | 检索时的作用 |
| fact | 概括用户说了什么 | 提供基础语义匹配 |
| underlying_motive | 用户的深层需求或动机 | 匹配用户意图,而非仅字面匹配 |
| contextual_cues | 语境线索(语气、标点、用词) | 提供情感色彩,区分"愤怒"和"委屈" |
| agent_strategy | Agent 应如何回应 | 指导回复风格,提升共情质量 |
其中,underlying_motive最为重要,用户的深层需求是最有价值的检索特征。
2.5对唤醒度arousal做阈值选择
2.5.1做阈值选择的原因
深度编码的触发条件是------"arousal>AROUSAL_THRESHOLD"。如果说阈值过高导致触发样本太少,无法验证深度编码的有效性;但是阈值过低则可能对中性情绪也进行深度编码,稀释高情绪样本的区分度。因此需要基于数据分布选择合理的阈值。
2.5.2数据分析求arousal分布
本文用一下代码对arousal分布进行分析:
from datasets import load_dataset
import random
dataset = load_dataset("empathetic_dialogues", split="test", trust_remote_code=True)
sample_data = random.sample(list(dataset), 100)
arousals = []
for sample in sample_data:
text = sample["utterance"]
emotion = classify_emotion(text)
arousals.append(emotion["arousal"])
得到分析结果:
Arousal 分布统计
==================================================
样本数: 100
最大值: 0.90
最小值: 0.00
平均值: 0.50
不同阈值触发率:
----------------------------------------
阈值 0.1: 触发 94/100 条 (94%)
阈值 0.2: 触发 91/100 条 (91%)
阈值 0.3: 触发 73/100 条 (73%)
阈值 0.4: 触发 53/100 条 (53%)
阈值 0.5: 触发 53/100 条 (53%)
2.5.3本实验阈值选择
本实验选择 τ = 0.1,原因如下:
1.样本充足:94%的触发率确保绝大多数样本进入深度编码组,使实验具有统计意义。
2.避免零触发:在前期实验中,阈值设为0.35时触发率仅8%,深度编码组几乎等同于Baseline,无法验证深度编码的有效性。τ = 0.1解决了这一问题。
3.宽松验证策略:阈值低相当于"宽口径"验证------如果深度编码在几乎全部样本都触发的条件下仍然有效,说明机制本身是鲁棒的,而非仅对极端情绪有效。
三.实验设计
3.1实验设置
3.1.1数据集
本实验使用 EmpatheticDialogues (Rashkin et al., 2019),这是共情对话领域最广泛使用的基准数据集。
|------|---------------------------------|
| 属性 | 说明 |
| 规模 | 25,000段对话,约100,000轮 |
| 情绪标签 | 32种情绪(如joy、sadness、anger、fear等) |
| 结构 | 每段对话4-8轮,由"倾诉者"和"倾听者"两名众包工人完成 |
我们使用测试集(test split)进行实验,共10,943条样本。
数据格式:
{
"conv_id": "hit:0_conv:1",
"utterance_idx": 1,
"utterance": "I remember going to see the fireworks with my best friend...",
"context": "sentimental",
"speaker_idx": 1
}
3.1.2对话分组
EmpatheticDialogues的每条样本包含 conv_id(对话ID)和 utterance_idx(轮次索引)。我们按conv_id分组,将同一段对话的所有轮次按utterance_idx排序,形成完整的多轮对话序列。
3.2评估指标
本实验由 StepFun的模型step-3.7-flash对每条回复进行严格评分(1-5分),评估两个维度:
|----------------|--------------------|----------------------------------------|
| 指标 | 定义 | 评估标准 |
| 共情力(Empathy) | Agent是否准确识别并回应用户情绪 | 5分:像资深心理咨询师,100条中不超过5条;3分:合格;1分:完全失败 |
| 生动度(Vividness) | Agent回复是否有温度、有细节 | 5分:有感官细节或情感画面,100条中不超过5条;3分:合格;1分:完全失败 |
评估严谨性:本实验生成模型用deepseek-v4-flash,评估模型用step-3.7-flash,二者来自不同厂商,避免"自我打分"的偏差。评估为盲评,评估模型不知道回复所属组别。
评估Prompt模板:
系统提示:你是严格的对话质量评估专家。5分极其罕见,平均分应在2.5-3.5之间。只输出JSON。
用户消息:{user_text}
用户情绪:{emotion_label}
Agent回复:{agent_reply}
输出:{"empathy": 整数, "vividness": 整数}
3.3对照实验设计
|-----------|------------------------|-----|
| 组别 | 编码方式 | 用途 |
| Baseline组 | 标准编码(只存原话) | 对照组 |
| 深度编码组 | 深度编码(arousal > 0.1触发) | 实验组 |
控制变量 :两组在情绪分类、向量存储、检索方式(纯语义检索,Top-3)、生成模型、评估模型上完全相同。唯一变量是编码方式。
完整实验参数:
|-------|-----------------------------------------|
| 参数 | 值 |
| 数据集 | EmpatheticDialogues(test split,10,943条) |
| 对话数 | 50段完整对话(每段4-8轮) |
| 评估轮数 | 220轮(只评估第2轮及以后) |
| 阈值τ | 0.1 |
| 生成模型 | deepseek-v4-flash |
| 嵌入模型 | all-MiniLM-L6-v2 |
| 向量数据库 | ChromaDB |
| 评估模型 | step-3.7-flash |
| 检索方式 | 纯语义检索或Top-3 |
3.4实验结果
|-----------|----------|------|-------|
| 指标 | Baseline | 深度编码 | 提升 |
| 共情力(1~5) | 3.52 | 3.66 | +4.0% |
| 生动度(1~5) | 3.20 | 3.21 | +0.5% |
| 评估轮数 | 220 | 220 | - |
下图是实验结果:

3.5结果分析
3.5.1 共情力提升4.0%
深度编码组的共情力显著高于Baseline,验证了本文的核心假设:高情绪时存得更详细 → 后续检索到更丰富的记忆 → 回复更有温度 。
Baseline 的回复通常停留在当前轮次的内容层面,而深度编码组的 Agent 能够跨轮次关联用户的情绪线索(如第1轮的"恐惧"与第5轮的"自责"),从而使回复更有针对性。
3.5.2 生动度提升0.5%
生动度提升幅度较小,可能有以下原因:
1**.生成模型能力** :生动度可能更多取决于生成模型(DeepSeek)本身的表达能力,而非记忆内容的丰富度。
2**.数据集特性** :EmpatheticDialogues的句子较短,深度编码提取的四维信息有限。
3**.阈值较低** :τ = 0.1 使94%的样本触发深度编码,稀释了"高情绪"的区分度。
3.5.3实验总结
我们根据实验结果可以知道,深度编码的共情力与生动度相较于标准编码的而言是有提升的,这正好符合我们本次实验的方向。
3.5.4衍生实验方向
我们还能根据这个实验做一个消融实验,证明四个维度中的underlying_motive最为重要,也可以做一个共情力的数据统计,证明深度编码的效果会随着对话轮次增加而放大**。**
四.结论
本文针对现有RAG系统对高情绪与低情绪记忆"一视同仁"的扁平编码问题,提出了情绪驱动的深度编码机制。该机制的核心思想是:当用户输入的情绪唤醒度超过阈值时,系统在执行标准编码(存储用户原话)的基础上,额外提取事实、动机、语境、策略四个维度的信息,将一条"用户说了什么"的扁平记录,扩展为包含深层语义信息的高密度记忆块。
我们在EmpatheticDialogues数据集上进行了多轮对话实验,50段对话、220轮有效评估的实验结果表明:深度编码能够显著提升Agent的共情能力。在共情力指标上,深度编码组(3.66分)相比标准编码Baseline(3.52分)提升了4.0%,验证了"高情绪记忆存得更详细 → 后续检索到更丰富的线索 → 回复更有温度"这一核心假设。在生动度指标上,深度编码组(3.21分)相比Baseline(3.20分)提升了0.5%,呈现一致提升趋势。
五.代码附录
5.1安装依赖
pip install openai==1.12.0 chromadb==0.5.0 sentence-transformers==2.7.0 datasets==2.21.0
5.2主程序
查看代码
import os
import json
import time
import uuid
import shutil
from typing import List, Dict, Any
from datetime import datetime
from collections import defaultdict
from openai import OpenAI
import chromadb
from chromadb.config import Settings
from sentence_transformers import SentenceTransformer
from datasets import load_dataset
# ============================================================
# 配置(用户可自行替换)
# ============================================================
# ---------- 生成模型配置 ----------
# 支持 OpenAI、DeepSeek、Azure OpenAI 等兼容 OpenAI SDK 的接口
# 只需替换以下三个变量即可
LLM_API_KEY = "your-api-key-here"
LLM_BASE_URL = "https://api.your-provider.com/v1"
LLM_MODEL = "your-model-name"
# ---------- 评估模型配置 ----------
# 建议使用与生成模型不同的厂商,避免自评偏差
EVAL_API_KEY = "your-eval-api-key-here"
EVAL_BASE_URL = "https://api.your-eval-provider.com/v1"
EVAL_MODEL = "your-eval-model-name"
# ---------- 实验配置 ----------
MAX_CONVERSATIONS = 50 # 跑多少段完整对话(每段平均4-8轮)
AROUSAL_THRESHOLD = 0.1 # 深度编码触发阈值
OUTPUT_DIR = "./experiment_results"
# ============================================================
# 客户端初始化
# ============================================================
llm_client = OpenAI(
api_key=LLM_API_KEY,
base_url=LLM_BASE_URL
)
eval_client = OpenAI(
api_key=EVAL_API_KEY,
base_url=EVAL_BASE_URL
)
os.makedirs(OUTPUT_DIR, exist_ok=True)
# ============================================================
# 情绪分类
# ============================================================
def classify_emotion(text: str) -> Dict[str, Any]:
"""
调用LLM对用户消息进行情绪分类
返回: {valence, arousal, label}
"""
prompt = f"""分析以下用户消息的情绪,只输出JSON:
{{"valence": -1~1, "arousal": 0~1, "label": "joy|sadness|anger|fear|surprise|love|neutral"}}
消息: {text}"""
try:
resp = llm_client.chat.completions.create(
model=LLM_MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=0.1
)
content = resp.choices[0].message.content.strip()
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
return json.loads(content)
except Exception as e:
print(f"情绪分类失败: {e}")
return {"valence": 0.0, "arousal": 0.0, "label": "neutral"}
# ============================================================
# 两种编码方式
# ============================================================
def standard_encode(text: str, emotion: Dict) -> str:
"""标准编码:只存原话"""
return f"用户说:{text}"
def deep_encode(text: str, emotion: Dict) -> str:
"""
深度编码:高情绪时提取四维信息
如果情绪唤醒度 <= 阈值,退化为标准编码
"""
arousal = emotion.get("arousal", 0)
if arousal <= AROUSAL_THRESHOLD:
return standard_encode(text, emotion)
prompt = f"""提取深度记忆(JSON):
用户说:"{text}" 情绪:{emotion.get('label')} 唤醒度:{emotion.get('arousal')}
{{"fact":"概括","motive":"动机","cues":"语境","strategy":"策略"}}"""
try:
resp = llm_client.chat.completions.create(
model=LLM_MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=0.3
)
content = resp.choices[0].message.content.strip()
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
data = json.loads(content)
return f"[深度记忆] {json.dumps(data, ensure_ascii=False)}"
except Exception as e:
print(f"深度编码失败: {e}")
return standard_encode(text, emotion)
# ============================================================
# 评估(严格评分)
# ============================================================
def evaluate_response(user_text: str, reply: str, emotion_label: str) -> Dict[str, float]:
"""
使用评估模型对单条回复进行严格评分
返回: {empathy_score, vividness_score}
"""
prompt = f"""评分(1-5):
用户:{user_text} 情绪:{emotion_label}
回复:{reply}
标准:empathy(共情力)和 vividness(生动度),5分极罕见,平均分2.5-3.5
输出JSON:{{"empathy":整数, "vividness":整数}}"""
try:
resp = eval_client.chat.completions.create(
model=EVAL_MODEL,
messages=[
{"role": "system", "content": "严格评分,5分极罕见。"},
{"role": "user", "content": prompt}
],
temperature=0.1
)
content = resp.choices[0].message.content.strip()
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
return json.loads(content)
except Exception as e:
print(f"评估失败: {e}")
return {"empathy": 3, "vividness": 3}
# ============================================================
# Agent
# ============================================================
class EmotionalAgent:
def __init__(self, encode_fn, agent_id="default"):
self.encode_fn = encode_fn
self.agent_id = agent_id
chroma_path = f"./chroma_db_{agent_id}"
if os.path.exists(chroma_path):
shutil.rmtree(chroma_path)
self.chroma_client = chromadb.PersistentClient(
path=chroma_path,
settings=Settings(anonymized_telemetry=False)
)
try:
self.collection = self.chroma_client.get_collection("memories")
except:
self.collection = self.chroma_client.create_collection("memories")
self.embedder = SentenceTransformer('all-MiniLM-L6-v2')
self.history = []
self.current_emotion = {"valence": 0.0, "arousal": 0.0, "label": "neutral"}
def reset(self):
try:
self.chroma_client.delete_collection("memories")
except:
pass
self.collection = self.chroma_client.create_collection("memories")
self.history = []
self.current_emotion = {"valence": 0.0, "arousal": 0.0, "label": "neutral"}
def chat(self, user_text: str) -> Dict[str, Any]:
self.current_emotion = classify_emotion(user_text)
memory_text = self.encode_fn(user_text, self.current_emotion)
memory_id = str(uuid.uuid4())
embedding = self.embedder.encode(memory_text).tolist()
self.collection.add(
ids=[memory_id],
embeddings=[embedding],
documents=[memory_text],
metadatas=[{
"timestamp": time.time(),
"arousal": self.current_emotion.get("arousal", 0),
"encoding_type": "deep" if "深度记忆" in memory_text else "standard"
}]
)
query_embedding = self.embedder.encode(user_text).tolist()
results = self.collection.query(
query_embeddings=[query_embedding],
n_results=3,
include=["documents"]
)
retrieved_docs = results['documents'][0] if results['documents'] else []
context = "\n".join(f"- {doc}" for doc in retrieved_docs) if retrieved_docs else ""
prompt = f"""你是共情对话Agent。
用户:{user_text}(情绪:{self.current_emotion.get('label')})
{context if context else "无历史记忆"}
回复:"""
try:
resp = llm_client.chat.completions.create(
model=LLM_MODEL,
messages=[{"role": "user", "content": prompt}],
temperature=0.7
)
reply = resp.choices[0].message.content.strip()
except Exception as e:
print(f"生成回复失败: {e}")
reply = f"(我理解你的情绪是{self.current_emotion.get('label')})"
turn_data = {
"user": user_text,
"reply": reply,
"emotion": self.current_emotion,
"retrieved_docs": retrieved_docs,
"encoding_type": "deep" if "深度记忆" in memory_text else "standard"
}
self.history.append(turn_data)
return turn_data
# ============================================================
# 多轮对话实验
# ============================================================
def run_multi_turn_experiment(dataset, encode_fn, agent_id, max_conversations=50):
agent = EmotionalAgent(encode_fn, agent_id)
conv_groups = defaultdict(list)
for sample in dataset:
conv_groups[sample["conv_id"]].append(sample)
print(f"共 {len(conv_groups)} 段对话")
results = []
conv_count = 0
for conv_id, turns in conv_groups.items():
if conv_count >= max_conversations:
break
turns_sorted = sorted(turns, key=lambda x: x.get("utterance_idx", 0))
if len(turns_sorted) < 2:
continue
agent.reset()
print(f"\n对话 {conv_count+1}/{max_conversations} (共{len(turns_sorted)}轮)")
all_turns = []
for idx, turn in enumerate(turns_sorted):
user_text = turn.get("utterance", "")
if not user_text:
continue
result = agent.chat(user_text)
all_turns.append(result)
print(f" 轮{idx+1}: {user_text[:40]}... -> {result['encoding_type']}")
for idx, turn_data in enumerate(all_turns):
if idx == 0:
continue
score = evaluate_response(
turn_data["user"],
turn_data["reply"],
turn_data["emotion"].get("label", "neutral")
)
turn_data["scores"] = score
results.extend(all_turns)
conv_count += 1
print(f"\n{agent_id} 完成,共 {len(results)} 轮记录")
output_file = os.path.join(OUTPUT_DIR, f"{agent_id}_results.json")
with open(output_file, "w", encoding="utf-8") as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(f"保存至: {output_file}")
return results
def evaluate_all(results_file: str):
with open(results_file, "r", encoding="utf-8") as f:
results = json.load(f)
empathy = []
vividness = []
for item in results:
scores = item.get("scores", {})
empathy.append(scores.get("empathy", 3))
vividness.append(scores.get("vividness", 3))
return {
"avg_empathy": sum(empathy) / len(empathy) if empathy else 0,
"avg_vividness": sum(vividness) / len(vividness) if vividness else 0,
"count": len(results)
}
# ============================================================
# 快速测试
# ============================================================
def quick_test():
print("快速测试模式")
print(f"生成模型: {LLM_MODEL}")
print(f"评估模型: {EVAL_MODEL}(严格评分)")
agent = EmotionalAgent(deep_encode, "test")
test_messages = [
"我今天拿到梦寐以求的offer了!太开心了!",
"但是合同里有些条款让我有点担心...",
"我决定先冷静一下,明天再仔细看。"
]
for msg in test_messages:
print(f"\n用户: {msg}")
result = agent.chat(msg)
print(f"情绪: {result['emotion']}")
print(f"编码类型: {result['encoding_type']}")
print(f"Agent: {result['reply']}")
if result.get('retrieved_docs'):
print(f"检索到: {result['retrieved_docs'][0][:60]}...")
print("\n评估最后一条回复:")
if agent.history:
last = agent.history[-1]
score = evaluate_response(
last.get("user", ""),
last.get("reply", ""),
last.get("emotion", {}).get("label", "neutral")
)
print(f" 共情力: {score.get('empathy', 0)}/5")
print(f" 生动度: {score.get('vividness', 0)}/5")
print("\n测试完成")
# ============================================================
# 主程序
# ============================================================
def main():
print("=" * 60)
print("情绪驱动深度编码 - 多轮对话实验")
print("数据集: empathetic_dialogues(按 conv_id 分组)")
print(f"生成模型: {LLM_MODEL}")
print(f"评估模型: {EVAL_MODEL}(严格评分)")
print(f"最大对话数: {MAX_CONVERSATIONS}")
print("=" * 60)
print("\n加载数据集...")
dataset = load_dataset("empathetic_dialogues", split="test", trust_remote_code=True)
print(f" 总样本数: {len(dataset)}")
print("\n" + "-" * 40)
print("运行 Baseline(标准编码)...")
run_multi_turn_experiment(dataset, standard_encode, "baseline", MAX_CONVERSATIONS)
print("\n" + "-" * 40)
print("运行 深度编码...")
run_multi_turn_experiment(dataset, deep_encode, "deep_encoding", MAX_CONVERSATIONS)
print("\n" + "-" * 40)
print("开始评估...")
baseline_scores = evaluate_all(os.path.join(OUTPUT_DIR, "baseline_results.json"))
deep_scores = evaluate_all(os.path.join(OUTPUT_DIR, "deep_encoding_results.json"))
print("\n" + "=" * 60)
print("实验结果对比")
print("=" * 60)
empathy_improve = (deep_scores['avg_empathy'] - baseline_scores['avg_empathy']) / baseline_scores['avg_empathy'] * 100 if baseline_scores['avg_empathy'] > 0 else 0
vividness_improve = (deep_scores['avg_vividness'] - baseline_scores['avg_vividness']) / baseline_scores['avg_vividness'] * 100 if baseline_scores['avg_vividness'] > 0 else 0
print(f"\n{'指标':<20} {'Baseline':<15} {'深度编码':<15} {'提升':<15}")
print("-" * 65)
print(f"{'共情力 (1-5)':<20} {baseline_scores['avg_empathy']:<15.2f} {deep_scores['avg_empathy']:<15.2f} {empathy_improve:+.1f}%")
print(f"{'生动度 (1-5)':<20} {baseline_scores['avg_vividness']:<15.2f} {deep_scores['avg_vividness']:<15.2f} {vividness_improve:+.1f}%")
print(f"{'评估轮数':<20} {baseline_scores['count']:<15} {deep_scores['count']:<15}")
summary = {
"experiment_time": datetime.now().isoformat(),
"dataset": "empathetic_dialogues",
"experiment_mode": "multi_turn_by_conv_id",
"generation_model": LLM_MODEL,
"evaluation_model": EVAL_MODEL,
"config": {"max_conversations": MAX_CONVERSATIONS, "arousal_threshold": AROUSAL_THRESHOLD},
"baseline": baseline_scores,
"deep_encoding": deep_scores,
"improvement": {"empathy": f"{empathy_improve:+.1f}%", "vividness": f"{vividness_improve:+.1f}%"}
}
with open(os.path.join(OUTPUT_DIR, "experiment_summary.json"), "w", encoding="utf-8") as f:
json.dump(summary, f, ensure_ascii=False, indent=2)
print(f"\n汇总已保存至: {os.path.join(OUTPUT_DIR, 'experiment_summary.json')}")
print("\n实验完成")
# ============================================================
# 运行入口
# ============================================================
if __name__ == "__main__":
# 检查配置是否已替换
if LLM_API_KEY == "your-api-key-here":
print("请先在代码顶部的配置区域填入你的 LLM API Key!")
print("需要配置: LLM_API_KEY, LLM_BASE_URL, LLM_MODEL")
print("以及: EVAL_API_KEY, EVAL_BASE_URL, EVAL_MODEL")
exit(1)
print("\n选择运行模式:")
print("1. 快速测试 (3条对话,验证能否跑通)")
print("2. 完整实验 (跑50段多轮对话,生成对比结果)")
choice = input("\n请输入 1 或 2: ").strip()
if choice == "1":
quick_test()
elif choice == "2":
main()
else:
print("无效选择")
5.3求阈值程序
查看代码
import json
from datasets import load_dataset
from openai import OpenAI
# ===== 配置(用户可自行替换) =====
LLM_API_KEY = "your-api-key-here"
LLM_BASE_URL = "https://api.your-provider.com/v1"
LLM_MODEL = "your-model-name"
client = OpenAI(
api_key=LLM_API_KEY,
base_url=LLM_BASE_URL
)
def classify_emotion(text: str):
"""调用LLM对用户消息进行情绪分类"""
prompt = f"""分析以下用户消息的情绪,只输出JSON格式:
{{
"valence": 浮点数,范围-1到1(-1极度消极,0中性,1极度积极),
"arousal": 浮点数,范围0到1(0极度平静,1极度兴奋/紧张/愤怒),
"label": 字符串,从 joy|sadness|anger|fear|surprise|love|neutral 中选择
}}
用户消息: {text}
"""
try:
response = client.chat.completions.create(
model=LLM_MODEL,
messages=[
{"role": "system", "content": "你是情绪分析专家,只输出JSON。"},
{"role": "user", "content": prompt}
],
temperature=0.1
)
content = response.choices[0].message.content.strip()
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
return json.loads(content)
except Exception as e:
print(f"情绪分类失败: {e}")
return {"valence": 0.0, "arousal": 0.0, "label": "neutral"}
if __name__ == "__main__":
# 检查配置是否已替换
if LLM_API_KEY == "your-api-key-here":
print("请先在代码顶部的配置区域填入你的 API Key!")
print("需要配置: LLM_API_KEY, LLM_BASE_URL, LLM_MODEL")
exit(1)
# 加载数据集
print("加载 empathetic_dialogues 数据集...")
dataset = load_dataset("empathetic_dialogues", split="test", trust_remote_code=True)
print(f" 总样本数: {len(dataset)}")
# 分析前 100 条
sample_size = 100
print(f"\n分析前 {sample_size} 条样本的 arousal 分布...")
arousals = []
for i in range(sample_size):
text = dataset[i]["utterance"]
emotion = classify_emotion(text)
arousals.append(emotion["arousal"])
print(f" [{i+1}/{sample_size}] {text[:40]}... -> arousal: {emotion['arousal']:.2f}")
# 统计
print("\n" + "=" * 50)
print("Arousal 分布统计")
print("=" * 50)
print(f"样本数: {len(arousals)}")
print(f"最大值: {max(arousals):.2f}")
print(f"最小值: {min(arousals):.2f}")
print(f"平均值: {sum(arousals)/len(arousals):.2f}")
# 各阈值触发率
print("\n不同阈值触发率:")
print("-" * 40)
thresholds = [0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5]
for threshold in thresholds:
triggered = sum(1 for a in arousals if a > threshold)
print(f"阈值 {threshold}: 触发 {triggered}/{len(arousals)} 条 ({triggered/len(arousals)*100:.0f}%)")
print("\n分析完成!根据上面的数据选择合适阈值。")
print("建议:选择触发率在 30%-70% 之间的阈值。")
5.4注意事项
遇到网络问题可以使用镜像或者其他方式,遇到版本问题可以试着换一下版本