hello-agents 赛博小镇 NPC 好感度系统实现指南:基于 LLM 情感分析的动态关系与对话风格引擎
【免费下载链接】hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/datawhalechina/hello-agents
在《从零开始构建智能体》(hello-agents) 的赛博小镇项目中,NPC 不再是只会机械应答的对话机器人:它们拥有 0-100 的好感度数值、五档关系等级,会根据玩家与它们的每一次对话自动调整态度,并实时改变回复的语气与详细程度。本篇指南以 AFFINITY_SYSTEM_GUIDE.md 为核心主体,结合仓库中的RelationshipManager源码、NPCAgentManager集成逻辑与 FastAPI 接口实现,完整讲解好感度系统的架构设计、情感分析提示词、动态更新规则、REST API 与调试方法。读完本文,你将掌握如何在多智能体系统中用 LLM 实现"对话 → 情感分析 → 数值更新 → 风格调整"的完整闭环,并能直接复刻到自己的 Agent 游戏或角色扮演应用中。
一、系统概述:让 NPC 拥有"人情味"
赛博小镇(AI Town)是一个基于 HelloAgents 框架的 AI NPC 对话系统,包含张三(Python 工程师)、李四(产品经理)、王五(UI 设计师)三位 AI 居民。好感度系统的核心目标是:根据玩家与 NPC 的对话内容自动调整好感度,并让好感度反过来影响后续对话的风格和态度。
从 README.md 可知,该项目将好感度系统列为五大核心功能之一(智能对话、记忆系统、好感度系统、NPC 自主行为、日志系统),并作为教材第 15 章的配套案例。好感度系统的本质是:用一个 LLM Agent 做"情感裁判",把一段自然语言对话转化为结构化的数值决策,再由数值驱动另一套对话生成逻辑。
四大核心能力
- 自动情感分析:使用 LLM Agent 分析对话情感,从四个维度评估——玩家态度(友好/中立/不友好)、对话内容(积极/中立/消极)、互动质量(深入/一般/敷衍)、情感倾向(赞美/批评/中性)。
- 好感度动态调整:友好对话提升好感度(+1 到 +10),批评对话降低好感度(-3 到 -15),更新后自动收敛在 0-100 范围内。
- 关系等级系统:将连续的好感度数值映射为五档离散关系等级。
- 对话风格调整:好感度等级和修饰词被注入 NPC 的上下文提示词,实时改变回复语气。
二、架构设计:RelationshipManager 的核心结构
好感度系统的核心类位于 relationship_manager.py,其类结构如下:
RelationshipManager ├── affinity_scores: Dict[str, Dict[str, float]] # NPC好感度存储 {npc: {player: score}} ├── analyzer_agent: SimpleAgent # 情感分析Agent ├── get_affinity(npc_name, player_id) # 获取好感度 ├── set_affinity(npc_name, affinity, player_id) # 设置好感度 ├── analyze_and_update_affinity(...) # 分析并更新好感度 ├── get_affinity_level(affinity) # 获取关系等级 ├── get_affinity_modifier(affinity) # 获取对话风格修饰词 └── get_all_affinities(player_id) # 获取所有NPC好感度数据存储结构
好感度使用嵌套字典存储(relationship_manager.py):
# 格式: {npc_name: {player_id: affinity_score}} self.affinity_scores: Dict[str, Dict[str, float]] = {}第一层以 NPC 名称为键,第二层以玩家 ID 为键——这意味着同一 NPC 可以对不同玩家持有不同好感度,天然支持多人游戏场景。未初始化时的默认好感度为50.0(见get_affinity方法)。
分析 Agent 的创建
在__init__中,RelationshipManager基于 HelloAgents 框架的SimpleAgent创建了一个名为AffinityAnalyzer的专职分析 Agent(relationship_manager.py):
self.analyzer_agent = SimpleAgent( name="AffinityAnalyzer", llm=llm, system_prompt=self._create_analyzer_prompt() )这种"用 Agent 做子任务"的设计是 HelloAgents 多智能体思想的典型体现:对话生成由各 NPC 自己的 Agent 负责,情感分析则交给独立的专业 Agent,两者通过 LLM 接口解耦,互不干扰。
三、情感分析提示词设计:LLM 如何"读懂"情绪
情感分析效果的好坏,几乎完全取决于系统提示词的设计。RelationshipManager中_create_analyzer_prompt()方法构造了一套完整的分析框架(relationship_manager.py),其关键设计可拆解为四层:
1. 角色定位
你是一个情感分析专家,负责分析对话中的情感倾向,判断是否应该改变NPC对玩家的好感度。2. 分析维度(结构化)
【分析维度】 1. 玩家态度: 友好/中立/不友好 2. 对话内容: 积极/中立/消极 3. 互动质量: 深入/一般/敷衍 4. 情感倾向: 赞美/批评/中性3. 变化规则(量化约束)
【好感度变化规则】 - 赞美、感谢、请教: +3 到 +8 - 友好问候、正常交流: +1 到 +3 - 普通闲聊、中性话题: 0 - 批评、质疑、不耐烦: -3 到 -8 - 侮辱、攻击、恶意: -8 到 -154. 输出格式(强制 JSON)
【输出格式】(严格遵守JSON格式,不要添加任何其他文字) { "should_change": true/false, "change_amount": -15到+10之间的整数, "reason": "简短说明原因(10字以内)", "sentiment": "positive/neutral/negative" }提示词中还内置了 5 个 few-shot 示例(友好问候、批评工作、普通闲聊、赞美工作、请教学习各一例),并反复强调三条硬性约束:"只输出 JSON"、"change_amount 必须是整数"、"reason 必须在 10 字以内"。这种"角色 + 维度 + 规则 + 示例 + 约束"的提示词结构,是保证 LLM 输出稳定、可解析的关键,值得在任意 LLM 结构化输出场景中复用。
三级容错解析:不信任裸 JSON
LLM 并不总能保证输出合法 JSON,因此_parse_analysis方法实现了三级递进式解析策略(relationship_manager.py):
- 直接解析:先用
json.loads(response)尝试整体解析; - 截取解析:若失败,用
response.find('{')和response.rfind('}')截取首尾花括号之间的内容再解析; - 正则兜底:若仍失败,用正则分别匹配
should_change、change_amount、reason、sentiment四个字段:should_change_match = re.search(r'"should_change"\s*:\s*(true|false)', response, re.IGNORECASE) change_amount_match = re.search(r'"change_amount"\s*:\s*(-?\d+)', response)
全部失败时才返回默认值{"should_change": False, "change_amount": 0, ...},并打印警告日志。这一设计保证了系统即使在 LLM 输出不规范时也不会崩溃,是生产级 JSON 解析的示范代码。
四、好感度更新核心流程
analyze_and_update_affinity是系统的核心方法(relationship_manager.py),完整流程如下:
1. 玩家发送消息 ↓ 2. NPC生成回复 ↓ 3. 情感分析Agent分析对话 ├── 分析玩家态度 ├── 评估对话内容 ├── 判断情感倾向 └── 计算好感度变化量 ↓ 4. 更新好感度 ├── 当前好感度 + 变化量 ├── 限制在0-100范围 └── 检查等级变化 ↓ 5. 保存到记忆系统 └── 记录好感度和情感信息更新逻辑要点
方法先构造分析提示(拼接玩家消息与 NPC 回复),调用分析 Agent,然后根据 JSON 解析结果处理:
if analysis["should_change"]: current_affinity = self.get_affinity(npc_name, player_id) new_affinity = current_affinity + analysis["change_amount"] new_affinity = max(0.0, min(100.0, new_affinity)) # 限制在0-100 self.set_affinity(npc_name, new_affinity, player_id)值得注意的两处工程细节:
- 范围钳制:
max(0.0, min(100.0, affinity))出现在set_affinity中,确保任何路径写入的好感度都不会越界(relationship_manager.py); - 等级前后对比:更新前后分别调用
get_affinity_level,据此判断是否发生关系等级跨越,供上层日志记录"🎉 关系等级提升"事件; - 异常兜底:整个流程被 try/except 包裹,分析失败时返回
changed: False且保持原好感度,不会中断主对话。
与 NPC Agent 的集成:chat 方法六步流水线
好感度并不是孤立运行的,它被深度集成进NPCAgentManager.chat方法(agents.py)的完整对话流水线:
① 记录对话开始(日志系统) ② 读取当前好感度 → 构造【当前关系】上下文(等级+修饰词) ③ 检索 NPC 记忆 → 构造【之前的对话记忆】上下文 ④ 拼接增强提示词 → 调用 NPC Agent 生成回复 ⑤ 分析并更新好感度 → 记录变化详情到日志 ⑥ 保存对话到记忆(携带好感度与情感元数据)其中第 ② 步是关键:好感度被格式化为一段注入 NPC 提示词的上下文(agents.py):
affinity_context = f"""【当前关系】 你与玩家的关系: {affinity_level} (好感度: {affinity:.0f}/100) 【对话风格】{affinity_modifier} """这段上下文与检索到的记忆一起拼接成enhanced_message后交给 NPC 的SimpleAgent。第 ⑥ 步则将好感度、变化量、情感倾向写入记忆元数据(metadata中的affinity、affinity_change、sentiment字段),实现"好感度 → 记忆 → 后续对话"的长期闭环。这也解释了 AFFINITY_SYSTEM_GUIDE.md 中"与记忆系统协同工作"的教学要点。
五、关系等级系统与对话风格修饰词
连续的好感度数值被映射为五档离散等级,映射逻辑集中在get_affinity_level(relationship_manager.py):
| 好感度区间 | 关系等级 | 对话风格修饰词(get_affinity_modifier) |
|---|---|---|
| 0-20 | 陌生 | 冷淡疏离,不太愿意多说,回答简短 |
| 20-40 | 熟悉 | 礼貌但略显生疏,回答简洁 |
| 40-60 | 友好 | 礼貌友善,正常交流,保持专业 |
| 60-80 | 亲密 | 友好热情,愿意多聊,会主动关心对方 |
| 80-100 | 挚友 | 非常热情友好,像老朋友一样亲切,愿意分享私人话题 |
等级与修饰词是两套独立的映射函数,等级用于显示与 UI,修饰词用于驱动对话风格。从源码可以看到两个函数均按>= 80 / >= 60 / >= 40 / >= 20的阈值级联判断,区间下界由get_affinity_level中的比较符(>=)决定,例如好感度恰为 80 时属于"挚友"而非"亲密"。
对话风格变化的直观对比
同一句问候,在不同的好感度下,NPC 的回复风格差异明显(见 AFFINITY_SYSTEM_GUIDE.md 示例 3):
| 好感度/等级 | 玩家:"你好,最近怎么样?" | NPC 李四的反应 |
|---|---|---|
| 30(熟悉) | "还行吧。"(简短回答) | |
| 70(亲密) | "挺好的!最近在做一个很有意思的项目,你要不要听听?"(热情详细) | |
| 90(挚友) | "哈哈,老朋友!最近忙得不行,但很充实。对了,上次你问的那个问题,我找到答案了!"(亲切主动) |
六、好感度变化规则与两个完整示例
变化规则速查表
| 对话类型 | 变化量 | 示例 |
|---|---|---|
| 赞美、感谢、请教 | +3 到 +8 | "你真棒!" "谢谢你!" "能教教我吗?" |
| 友好问候、正常交流 | +1 到 +3 | "你好!" "最近怎么样?" |
| 普通闲聊、中性话题 | 0 | "今天天气不错" |
| 批评、质疑、不耐烦 | -3 到 -8 | "这个不太好" "真的吗?" |
| 侮辱、攻击、恶意 | -8 到 -15 | "你太烂了!" |
注意:该表格与提示词中的规则严格一致,但需指出变化量最终由 LLM 自行判断,表格是提示词中的"指导规则"而非硬编码逻辑——这正是系统的灵活性所在,也意味着实际变化可能存在合理浮动。
示例 1:好感度提升(含等级跨越)
初始好感度: 50 (友好) 第一次对话: 玩家: "你好,很高兴认识你!" 张三: "你好!我也很高兴认识你。" 📈 好感度: 50 -> 55 (友好问候) 第二次对话: 玩家: "你的代码写得真棒!" 张三: "谢谢!我最近在研究新技术,你对这个感兴趣吗?" 📈 好感度: 55 -> 63 (赞美工作) → 关系等级提升: 友好 -> 亲密 第三次对话: 玩家: "能教教我吗?" 张三: "当然可以!我很乐意分享。你想从哪里开始?" 📈 好感度: 63 -> 69 (请教学习)示例 2:好感度降低(含等级下降)
当前好感度: 69 (亲密) 批评对话: 玩家: "你这个代码写得太烂了!" 张三: "抱歉,我会改进的..." 📉 好感度: 69 -> 61 (批评工作) → 关系等级降低: 亲密 -> 友好这些示例与提示词中的 few-shot 示例一一对应,如果你在本地复现时发现数值与示例不完全一致,属于 LLM 判断的正常波动,可通过调整提示词规则收紧。
七、REST API 接口:如何把好感度暴露给前端
好感度系统通过 FastAPI 暴露为三个核心接口,全部实现在 main.py 中。启动后端后(python main.py),即可通过http://localhost:8000/docs访问 Swagger 文档在线调试。
1. 获取单个 NPC 好感度
GET /npcs/张三/affinity?player_id=player响应:
{ "npc_name": "张三", "player_id": "player", "affinity": 65.0, "level": "亲密", "modifier": "友好热情,愿意多聊,会主动关心对方" }实现上,该接口先校验 NPC 是否存在(不存在返回 404),再委托npc_mgr.get_npc_affinity,内部依次调用get_affinity/get_affinity_level/get_affinity_modifier。
2. 获取所有 NPC 好感度
GET /affinities?player_id=player响应:
{ "player_id": "player", "affinities": { "张三": { "affinity": 65.0, "level": "亲密", "modifier": "友好热情,愿意多聊,会主动关心对方" }, "李四": { "affinity": 50.0, "level": "友好", "modifier": "礼貌友善,正常交流,保持专业" }, "王五": { "affinity": 72.0, "level": "亲密", "modifier": "友好热情,愿意多聊,会主动关心对方" } } }该接口直接透传RelationshipManager.get_all_affinities的遍历结果,便于游戏 UI 一次性展示全镇 NPC 的态度。
3. 设置 NPC 好感度(测试/初始化用)
PUT /npcs/张三/affinity?affinity=80&player_id=player响应:
{ "message": "已设置张三对玩家的好感度", "npc_name": "张三", "player_id": "player", "affinity": 80.0, "level": "挚友", "modifier": "非常热情友好,像老朋友一样亲切,愿意分享私人话题" }该接口在 main.py 中额外做了参数校验:好感度必须在 0-100 之间,否则返回 400 错误。适合测试时快速把某位 NPC 的关系调到指定等级。
对话接口
POST /chat Content-Type: application/json {"npc_name": "张三", "message": "你好,你在做什么?"}对话接口本身不返回好感度,但每次调用都会触发一次情感分析与好感度更新,随后通过GET /npcs/张三/affinity即可看到变化。
八、测试方法:验证系统的完整手段
方法 1:测试脚本
AFFINITY_SYSTEM_GUIDE.md 描述了一个test_affinity.py测试脚本的用法:
cd backend python test_affinity.py测试覆盖内容:基本好感度功能、好感度提升/降低、关系等级变化、对话风格调整、好感度渐进提升。需要说明的是,当前仓库的backend目录中尚未包含该脚本文件,指南中给出了其核心测试思路——构造三类消息(friendly_messages = ["你好!", "你真棒!", "能教教我吗?"]、critical_messages = ["这个不好", "你太烂了"]、neutral_messages = ["今天天气不错", "嗯"])并调用analyze_and_update_affinity断言结果,你可以参考指南自行编写或直接改用下面的 API 方式验证。
方法 2:API 测试(推荐)
- 启动后端服务(配置步骤详见 SETUP_GUIDE.md):
cd backend python main.py - 访问 API 文档:
http://localhost:8000/docs; - 依次测试:
- 对话:
POST /chat - 查看好感度:
GET /npcs/张三/affinity - 查看所有好感度:
GET /affinities - 手动调整(可选):
PUT /npcs/张三/affinity?affinity=80
- 对话:
运行环境说明
从 config.py 可以看到,好感度系统与整个赛博小镇后端共用一套配置:默认使用 ModelScope 推理服务(LLM_BASE_URL默认https://api-inference.modelscope.cn/v1/,模型默认Qwen/Qwen2.5-72B-Instruct),通过.env文件中的LLM_API_KEY注入密钥。若未配置密钥,Settings.validate()会给出警告,NPCAgentManager将退化为模拟模式——此时好感度管理器不会被初始化(见 agents.py 的if self.llm:判断),因此完整体验好感度系统必须配置可用的 LLM 密钥。
九、调试技巧:观察好感度的每一步变化
1. 查看好感度变化日志
系统内置了完整的对话日志体系(logger.py),每次对话会按时间戳记录到backend/logs/dialogue_YYYY-MM-DD.log。与好感度相关的关键日志片段:
💖 当前好感度: 50.0/100 (友好) 📊 正在分析好感度变化... 📈 好感度变化: 50.0 -> 55.0 (+5.0) 原因: 友好问候 情感: positive 🎉 关系等级变化: 友好 -> 亲密日志中会自动为正向变化添加 📈、负向变化添加 📉 符号(log_affinity_change中依据change_amount正负选择),等级跨越时额外记录 🎉 事件。使用python view_logs.py tail可实时查看最新日志。
2. 检查情感分析结果
如需查看 LLM 的原始分析输出,可在 relationship_manager.py 中添加调试输出:
print(f"情感分析结果: {analysis}")3. 观察日志级别判断问题
结合"好感度为什么没有变化"的排查思路,优先检查日志中analyze_and_update_affinity返回的reason字段——若频繁出现"解析失败",说明 LLM 输出 JSON 不稳定,应优先检查提示词约束与 API 密钥对应的模型能力。
十、常见问题(FAQ)
Q1:好感度为什么没有变化?
可能原因:
- 对话内容过于中性(如"嗯"、"今天天气不错"),分析 Agent 判定
should_change: false; - LLM 响应解析失败,
_parse_analysis返回默认值(可在日志中看到"JSON解析失败"警告); - 处于模拟模式(未配置 LLM_API_KEY),好感度管理器未初始化。
解决方法:使用更明确的情感表达;检查日志中的情感分析结果;调整情感分析提示词。
Q2:好感度变化太快/太慢?
解决方法:
- 修改 relationship_manager.py 中提示词的变化量范围;
- 调整情感分析提示词中的规则描述;
- 使用
PUT /npcs/{npc_name}/affinity接口(对应set_npc_affinity)手动设置初始值。
Q3:对话风格没有明显变化?
可能原因:
- 好感度差异不够大(40 与 60 之间的修饰词差异确实不如 20 与 80 明显);
- NPC 的 system_prompt 没有充分利用好感度修饰词——从 agents.py 可以看到,修饰词是通过"【对话风格】"段落注入的,如果自定义了 NPC 提示词模板,需要保留该注入点。
解决方法:增大好感度差异(例如对比 20 vs 80);在 system_prompt 中强调对话风格的重要性。
十一、调优建议与扩展方向
敏感度调节
好感度变化的敏感度完全由提示词控制,可按需调整(修改 relationship_manager.py 中_create_analyzer_prompt):
- 更敏感:增大变化量范围(例如 -20 到 +15);
- 更保守:减小变化量范围(例如 -5 到 +5);
- 更细腻:添加更多分析维度(如幽默感、亲密度、话题相关性);
- 更简单:简化分析规则,减少 few-shot 示例。
建议的扩展方向
从指南的"下一步"与教学价值章节出发,可扩展的方向包括:在 Godot 前端显示好感度 UI(配合helloagents-ai-town项目中的对话 UI 脚本)、基于好感度解锁特殊对话与任务、加入随时间衰减的好感度机制、以及用 Embedding 相似度替代纯 LLM 判断使成本更低。
十二、教学价值总结
好感度系统虽然只是赛博小镇的一个模块,但它完整演示了 LLM 在 Agent 系统中的四个高复用设计模式:
- LLM 情感分析实战:如何设计结构化分析提示词、如何用 few-shot 稳定输出、如何对 JSON 响应做三级容错解析;
- 数值状态机设计:如何把连续数值(0-100)映射为离散等级,并用等级驱动另一套生成逻辑(对话风格修饰词);
- 多系统协同集成:好感度如何与记忆系统(保存元数据)、日志系统(记录变化轨迹)、API 层(暴露查询与设置)联动;
- 用户体验设计:如何让 NPC 更有"人情味"——从冷冰冰的问答变成会记仇、会亲近、会寒暄的虚拟居民。
这套"LLM 分析器 + 数值状态 + 上下文注入"的架构与具体实现(核心代码集中在 relationship_manager.py、agents.py 与 main.py 三个文件中)可以直接迁移到任意需要"关系模拟"的 Agent 场景:游戏 NPC、虚拟伴侣、客服情绪管理、教育陪练等。结合 SETUP_GUIDE.md 完成环境配置后,用几条"赞美"与"批评"消息,你就能亲眼看到一位 NPC 从陌生走向挚友的全过程。
【免费下载链接】hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程项目地址: https://gitcode.com/datawhalechina/hello-agents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考