news 2026/9/18 16:32:48

hello-agents 赛博小镇 NPC 好感度系统实现指南:基于 LLM 情感分析的动态关系与对话风格引擎

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
hello-agents 赛博小镇 NPC 好感度系统实现指南:基于 LLM 情感分析的动态关系与对话风格引擎

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 做"情感裁判",把一段自然语言对话转化为结构化的数值决策,再由数值驱动另一套对话生成逻辑

四大核心能力

  1. 自动情感分析:使用 LLM Agent 分析对话情感,从四个维度评估——玩家态度(友好/中立/不友好)、对话内容(积极/中立/消极)、互动质量(深入/一般/敷衍)、情感倾向(赞美/批评/中性)。
  2. 好感度动态调整:友好对话提升好感度(+1 到 +10),批评对话降低好感度(-3 到 -15),更新后自动收敛在 0-100 范围内。
  3. 关系等级系统:将连续的好感度数值映射为五档离散关系等级。
  4. 对话风格调整:好感度等级和修饰词被注入 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 到 -15

4. 输出格式(强制 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):

  1. 直接解析:先用json.loads(response)尝试整体解析;
  2. 截取解析:若失败,用response.find('{')response.rfind('}')截取首尾花括号之间的内容再解析;
  3. 正则兜底:若仍失败,用正则分别匹配should_changechange_amountreasonsentiment四个字段:
    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中的affinityaffinity_changesentiment字段),实现"好感度 → 记忆 → 后续对话"的长期闭环。这也解释了 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 测试(推荐)

  1. 启动后端服务(配置步骤详见 SETUP_GUIDE.md):
    cd backend python main.py
  2. 访问 API 文档:http://localhost:8000/docs
  3. 依次测试:
    • 对话: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 系统中的四个高复用设计模式:

  1. LLM 情感分析实战:如何设计结构化分析提示词、如何用 few-shot 稳定输出、如何对 JSON 响应做三级容错解析;
  2. 数值状态机设计:如何把连续数值(0-100)映射为离散等级,并用等级驱动另一套生成逻辑(对话风格修饰词);
  3. 多系统协同集成:好感度如何与记忆系统(保存元数据)、日志系统(记录变化轨迹)、API 层(暴露查询与设置)联动;
  4. 用户体验设计:如何让 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 16:24:24

Win10+CUDA环境配置:硬件-驱动-编译器协同原理与实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 16:24:21

Flutter列表跳动问题排查与修复:身份、位置、尺寸对齐指南

你正在调试一个 Flutter 项目,列表在底部加载新数据后瞬间“跳”回顶部;你只是往聊天列表里插一条新消息,结果已经读过的历史内容像被推了一把;你又怀疑是图片加载问题,于是把网络图全部改成固定高度,滚到一…

作者头像 李华
网站建设 2026/9/18 16:24:14

CNN与Transformer混合模型在测井孔隙度预测中的应用与代码实现

简介:面向石油勘探开发与地质建模领域研究人员的CNN-Transformer测井孔隙度预测复现资料,对应学术论文《Porosity prediction through well logging data: A combined approach of convolutional neural network and transformer model (CNN-transformer…

作者头像 李华
网站建设 2026/9/18 16:23:38

用Python将CFA词汇PDF转为结构化词库:解析清洗与SQLite存储实践

简介:CFA核心词汇.pdf是一份面向CFA考生及金融从业者的专业术语整理文档,系统收录金融、会计、投资、证券、保险等领域的核心词汇,内容覆盖从基础概念到实务应用。文档按字母顺序编排,每个词条均附中文对照与详细解释,…

作者头像 李华