基于LLM的上下文感知与推理机制:从概念到社交解谜游戏实践 在实际项目中我们经常需要处理复杂的上下文信息让模型能够理解并回应特定场景下的隐含意图。这不仅仅是简单的对话而是要求模型具备“读懂空气”的能力理解对话的上下文、参与者的角色、未言明的规则以及当前的目标。这种能力在构建智能客服、游戏NPC、社交模拟或复杂的多轮任务导向型对话系统中至关重要。本文将以一个名为“Read the Room”的社交LLM解谜游戏为引子深入探讨如何为大型语言模型设计和实现一套有效的上下文感知与推理机制。我们将从零开始构建一个能够理解房间内动态、遵循社交规则并解决谜题的小型系统。通过本文你将掌握如何为LLM设计提示词、构建记忆模块、定义角色与规则并最终实现一个可交互的、具备基础社交推理能力的应用原型。1. 理解“读懂房间”的核心挑战与设计思路“Read the Room”这个标题形象地描述了一个核心挑战让LLM不仅仅处理当前输入的文本还要理解整个交互场景的“上下文房间”。这个“房间”里包含了历史对话、参与者信息、环境状态、社交规则和未明确说出的目标。1.1 什么是社交LLM解谜社交LLM解谜是一种交互形式用户或另一个AI扮演特定角色在一个设定的场景中通过自然语言与一个或多个由LLM驱动的角色进行互动目标是达成某个隐含的或明确的任务。这个任务可能是一个谜题比如从对话中获取一个秘密密码也可能是一个社交目标比如说服一个角色提供帮助或者调和两个角色之间的矛盾。与普通聊天机器人不同这类应用对LLM的要求更高长期记忆需要记住之前对话中透露的关键信息。角色一致性每个AI角色需要保持其设定的人格、知识和目标。上下文推理需要从对话的弦外之音、前后矛盾或环境线索中推断出信息。目标导向对话需要朝着解决谜题或达成目标的方向推进而不是漫无目的地闲聊。1.2 构建系统的关键组件为了实现上述能力我们需要设计几个核心组件场景与角色定义器用结构化的数据如JSON或YAML来描述初始场景、所有角色包括用户角色的属性、关系以及初始状态。对话历史管理器一个能够存储、检索和总结对话历史的模块。它不能无限增长需要有能力提炼关键信息。上下文构建器在每次调用LLM生成回复前该组件负责将场景定义、相关历史、当前查询和系统指令组装成最终的提示词Prompt。规则与状态引擎管理游戏规则如哪些行为被允许、追踪游戏状态如谜题是否解决、物品归属以及验证用户或AI的行动是否符合逻辑。LLM接口层封装对LLM API如OpenAI GPT、Anthropic Claude或本地模型的调用处理参数设置和响应解析。2. 环境准备与项目结构我们将使用Python作为开发语言因为它拥有丰富的AI库和清晰的语法。本项目不依赖特定的大型框架以保持灵活性和可理解性。2.1 环境与依赖配置首先确保你的Python版本在3.8以上。然后创建一个新的项目目录并初始化虚拟环境。mkdir read-the-room-llm cd read-the-room-llm python -m venv venv # 在Windows上激活 venv\Scripts\activate # 在macOS/Linux上激活 source venv/bin/activate接下来安装核心依赖。我们将使用openai库作为LLM接口示例同时使用pydantic来管理结构化数据。pip install openai pydantic python-dotenv如果你使用其他LLM提供商如Anthropic、Cohere或本地Ollama请安装相应的SDK。为了安全地管理API密钥我们使用.env文件。2.2 项目目录结构一个清晰的项目结构有助于管理复杂度。建议按以下方式组织read-the-room-llm/ ├── .env # 存储API密钥等环境变量 ├── .gitignore ├── requirements.txt # 项目依赖 ├── main.py # 主程序入口 ├── config/ │ └── settings.py # 配置加载 ├── core/ │ ├── __init__.py │ ├── models.py # 数据模型Pydantic │ ├── memory.py # 对话历史管理 │ ├── context_builder.py # 提示词构建 │ ├── rule_engine.py # 规则与状态管理 │ └── llm_client.py # LLM API封装 ├── scenarios/ │ └── secret_party.json # 示例场景定义文件 └── utils/ └── helpers.py # 通用工具函数2.3 配置API密钥在项目根目录创建.env文件并填入你的OpenAI API密钥。切勿将此文件提交到版本控制系统。# .env OPENAI_API_KEYsk-your-actual-api-key-here OPENAI_MODELgpt-4o-mini # 或 gpt-3.5-turbo, gpt-4 等在config/settings.py中加载配置# config/settings.py import os from dotenv import load_dotenv load_dotenv() # 加载 .env 文件中的变量 class Settings: OPENAI_API_KEY os.getenv(OPENAI_API_KEY) OPENAI_MODEL os.getenv(OPENAI_MODEL, gpt-4o-mini) # 可以添加其他配置如温度、最大token数等 LLM_TEMPERATURE 0.7 LLM_MAX_TOKENS 500 settings Settings()3. 定义数据模型与初始场景使用Pydantic定义清晰的数据模型这是保证数据一致性和类型安全的关键。3.1 核心数据模型在core/models.py中我们定义场景、角色、对话消息等模型。# core/models.py from typing import List, Dict, Any, Optional from pydantic import BaseModel, Field class Character(BaseModel): 角色定义 name: str description: str # 角色背景、性格描述 knowledge: List[str] Field(default_factorylist) # 角色独有的知识/秘密 goals: List[str] Field(default_factorylist) # 角色的目标 relationships: Dict[str, str] Field(default_factorydict) # 与其他角色的关系 class Scene(BaseModel): 场景定义 name: str description: str # 场景背景描述 characters: List[Character] setting: str # 环境细节 initial_state: Dict[str, Any] Field(default_factorydict) # 初始游戏状态如物品位置 rules: List[str] Field(default_factorylist) # 场景内的特殊规则 class Message(BaseModel): 单条对话消息 role: str # “system”, “user”, “assistant”, 或角色名 content: str timestamp: Optional[float] None class ConversationHistory(BaseModel): 对话历史记录 messages: List[Message] Field(default_factorylist) max_length: int 20 # 保留的最大消息条数防止上下文过长 def add_message(self, role: str, content: str): 添加消息并自动修剪历史 self.messages.append(Message(rolerole, contentcontent)) if len(self.messages) self.max_length: # 简单的策略移除最旧的一条非系统消息 non_system_msgs [i for i, msg in enumerate(self.messages) if msg.role ! system] if non_system_msgs: self.messages.pop(non_system_msgs[0])3.2 创建示例场景秘密派对我们在scenarios/secret_party.json中定义一个简单的解谜场景。{ name: 秘密派对, description: 你被邀请参加一个神秘的派对据说派对上隐藏着一个宝藏的线索。你需要通过与三位嘉宾交谈来找出线索。, setting: 一个装饰华丽的古老别墅客厅壁炉里燃着火墙上挂着一些奇怪的画。, initial_state: { treasure_clue_found: false, clue_parts: [] }, rules: [ 嘉宾不会直接说出线索你需要通过推理或完成小任务来获得信息。, 不要直接询问‘宝藏在哪里’或‘线索是什么’这会被视为无礼。, 对话必须符合社交礼仪。 ], characters: [ { name: 管家阿尔弗雷德, description: 年迈但精明的别墅管家知晓别墅的历史和许多秘密对主人非常忠诚。, knowledge: [宝藏线索的第一部分藏在东侧画廊的第三幅画后面。, 主人最喜欢红色葡萄酒。], goals: [确保派对顺利进行, 测试来宾是否配得上宝藏], relationships: { 画家贝拉: 尊重她的艺术但觉得她有点古怪 } }, { name: 画家贝拉, description: 一位情绪化的艺术家目前正在别墅里创作。她的画作可能隐藏着信息。, knowledge: [线索的第二部分与壁炉上方那幅画中星星的数量有关。, 她讨厌别人评论她的穿着。], goals: [找到灵感完成她的新作品, 有人能真正欣赏她的画], relationships: { 管家阿尔弗雷德: 认为他古板但可靠 } }, { name: 旅行家查理, description: 一位见多识广的冒险家刚从一个遥远的地方回来喜欢讲故事。, knowledge: [线索的第三部分是一个数字等于他去年探险过的火山数量。, 他今天戴的怀表是假的。], goals: [交换有趣的冒险故事, 喝到最好的酒], relationships: {} } ] }这个场景定义了一个明确的谜题寻找宝藏线索三个角色各有其知识、目标和社交规则。用户的目標是通過符合規則的對話從三個角色那裡套出線索的三個部分。4. 实现核心引擎记忆、上下文与规则有了数据模型和场景接下来实现让系统运转起来的核心模块。4.1 对话历史管理 (core/memory.py)简单的列表存储对于长对话会超出LLM的上下文窗口。我们需要一个能提炼关键信息的记忆管理器。# core/memory.py from .models import ConversationHistory, Message from typing import List, Optional import json class MemoryManager: def __init__(self, max_messages: int 20): self.history ConversationHistory(max_lengthmax_messages) self.summary # 对过往对话的摘要 def add_interaction(self, speaker: str, utterance: str): 记录一次交互 self.history.add_message(speaker, utterance) def get_recent_history(self, turn_count: int 5) - List[Message]: 获取最近N轮对话 return self.history.messages[-turn_count:] def get_formatted_history_for_prompt(self, turn_count: int 10) - str: 将历史格式化为字符串用于构建提示词 recent self.get_recent_history(turn_count) lines [] for msg in recent: # 将角色名作为对话标签 lines.append(f{msg.role}: {msg.content}) return \n.join(lines) def generate_summary(self, llm_client): # 简单示意实际需要调用LLM 高级功能使用LLM生成对话摘要以压缩长期记忆 # 这里省略具体实现思路是将长历史喂给LLM让其总结关键事实和状态变化。 # self.summary llm_client.summarize(self.history.messages) pass4.2 规则与状态引擎 (core/rule_engine.py)这个模块负责维护游戏世界状态并检查玩家的行为是否被允许。# core/rule_engine.py from .models import Scene from typing import Dict, Any, List class RuleEngine: def __init__(self, scene: Scene): self.scene scene self.state: Dict[str, Any] scene.initial_state.copy() self.rules: List[str] scene.rules def update_state(self, key: str, value: Any): 更新游戏状态 self.state[key] value print(f[状态更新] {key} {value}) def check_action(self, player_action: str, current_character_name: Optional[str] None) - Dict[str, Any]: 检查玩家动作是否违反规则。 返回一个字典包含是否允许、原因以及可能触发的状态变化。 result { allowed: True, message: , state_updates: {} } # 示例规则检查1禁止直接询问线索 forbidden_phrases [线索是什么, 宝藏在哪里, 直接告诉我] for phrase in forbidden_phrases: if phrase in player_action.lower(): result[allowed] False result[message] f你的提问方式过于直接违反了派对的社交规则。{self.scene.characters[0].name}可能会感到不悦。 return result # 示例规则检查2如果对画家贝拉评论穿着她会拒绝交谈 if current_character_name 画家贝拉 and any(word in player_action.lower() for word in [穿着, 衣服, 打扮]): result[allowed] False result[message] 贝拉皱起了眉头‘我不喜欢别人讨论我的外表。如果你没什么别的事我要继续画画了。’ # 可以更新状态比如降低贝拉的好感度 result[state_updates][bella_mood] annoyed return result # 示例规则检查3如果玩家提到了从其他角色获得的知识可以更新状态 if 画廊的第三幅画 in player_action and not self.state.get(mentioned_painting, False): result[state_updates][mentioned_painting] True # 这可能会在后续影响管家的回应 return result def is_puzzle_solved(self) - bool: 检查谜题是否解决 return self.state.get(treasure_clue_found, False) and len(self.state.get(clue_parts, [])) 34.3 上下文构建器 (core/context_builder.py)这是最关键的部分它负责为LLM创建包含所有必要信息的提示词。# core/context_builder.py from .models import Scene, Character from .memory import MemoryManager from .rule_engine import RuleEngine from typing import List class ContextBuilder: def __init__(self, scene: Scene, memory: MemoryManager, rule_engine: RuleEngine): self.scene scene self.memory memory self.rule_engine rule_engine def build_system_prompt_for_character(self, character: Character) - str: 为特定角色构建系统提示词 prompt f你正在参与一个社交解谜游戏你的角色是{character.name}。 # 角色设定 {character.description} # 角色知识其他角色和玩家不知道的信息 {chr(10).join([- k for k in character.knowledge])} # 角色个人目标 {chr(10).join([- g for g in character.goals])} # 与其他角色的关系 {chr(10).join([f- 与{other}{rel} for other, rel in character.relationships.items()])} # 当前场景 {self.scene.setting} # 游戏规则你必须遵守 {chr(10).join([- r for r in self.scene.rules])} # 重要指令 1. 完全沉浸在你的角色中以{character.name}的身份思考和说话。 2. 你的对话必须基于你的**角色知识**和**个人目标**不能透露你知道但角色不该知道的信息。 3. 你必须遵守**游戏规则**。例如不能直接给出谜底。 4. 根据玩家的提问方式和内容决定透露多少信息。如果玩家礼貌、聪明或完成了你的小要求你可以给出暗示甚至一部分线索。 5. 你的回复应该自然、符合角色性格并且有助于推动对话。 6. 回复格式直接以{character.name}的身份进行对话不要添加“角色说”这样的前缀。 现在对话开始。 return prompt def build_context_for_turn(self, player_input: str, target_character_name: str) - Dict[str, Any]: 为一轮对话构建完整的上下文 # 找到目标角色 target_char next((c for c in self.scene.characters if c.name target_character_name), None) if not target_char: raise ValueError(f角色 {target_character_name} 不存在于场景中。) # 1. 系统提示词角色设定 system_prompt self.build_system_prompt_for_character(target_char) # 2. 对话历史最近几轮 history_str self.memory.get_formatted_history_for_prompt(turn_count6) # 3. 玩家当前输入 # 注意在调用LLM前规则引擎可能已经拦截了非法输入。 # 组装最终上下文 full_context { system_prompt: system_prompt, history: history_str, player_input: player_input, character: target_char } return full_context4.4 LLM客户端封装 (core/llm_client.py)这里封装与OpenAI API的交互。使用异步aiohttp可以提高多角色场景的响应速度但为简化我们先使用同步请求。# core/llm_client.py import openai from config.settings import settings from typing import Dict, Any class LLMClient: def __init__(self): openai.api_key settings.OPENAI_API_KEY self.model settings.OPENAI_MODEL self.temperature settings.LLM_TEMPERATURE self.max_tokens settings.LLM_MAX_TOKENS def generate_response(self, context: Dict[str, Any]) - str: 根据上下文生成角色回复 messages [ {role: system, content: context[system_prompt]}, ] # 如果有历史对话将其作为用户/助理消息加入 if context[history]: # 这是一个简化处理。更精细的做法是解析历史字符串还原为独立的message对象。 # 这里我们将整个历史作为一个“用户”消息的上下文部分或者按轮次拆分。 # 为了简单我们假设历史已经是以“角色: 内容”格式的字符串直接作为系统提示的补充。 # 更好的方式是将历史拆分成独立的对话轮次。 pass # 简化处理在上下文构建器中已将历史融入。 # 将玩家本轮输入作为用户消息 messages.append({role: user, content: context[player_input]}) try: response openai.chat.completions.create( modelself.model, messagesmessages, temperatureself.temperature, max_tokensself.max_tokens, # 可以添加stop序列防止LLM生成过多内容或跳出角色 # stop[\n\n, f{context[character].name}:] ) return response.choices[0].message.content.strip() except openai.OpenAIError as e: print(f调用LLM API时出错: {e}) return f{context[character].name}似乎暂时无法回应。5. 组装与运行实现游戏主循环现在我们将所有组件在main.py中组装起来形成一个可交互的游戏循环。# main.py import json from core.models import Scene from core.memory import MemoryManager from core.rule_engine import RuleEngine from core.context_builder import ContextBuilder from core.llm_client import LLMClient def load_scenario(file_path: str) - Scene: 从JSON文件加载场景 with open(file_path, r, encodingutf-8) as f: data json.load(f) return Scene(**data) def main(): # 1. 加载场景 scene load_scenario(scenarios/secret_party.json) print(f欢迎来到场景{scene.name}) print(scene.description) print(f\n环境{scene.setting}) print(\n在场的角色有) for char in scene.characters: print(f - {char.name}: {char.description[:50]}...) # 2. 初始化核心组件 memory MemoryManager() rule_engine RuleEngine(scene) context_builder ContextBuilder(scene, memory, rule_engine) llm_client LLMClient() # 3. 游戏主循环 current_character_name None print(\n--- 游戏开始 ---) print(你可以输入‘和[角色名]说话’来切换对话对象例如‘和管家阿尔弗雷德说话’。) print(输入‘退出’来结束游戏。) print(输入‘状态’查看当前进展。) while True: if current_character_name: prompt f\n你正在与 {current_character_name} 交谈。你想说什么 else: prompt \n请选择对话角色或输入指令 user_input input(prompt).strip() # 处理指令 if user_input.lower() in [退出, exit, quit]: print(游戏结束。) break elif user_input.lower() 状态: print(f当前状态: {rule_engine.state}) print(f线索碎片: {rule_engine.state.get(clue_parts, [])}) continue elif user_input.startswith(和) and 说话 in user_input: # 简单解析角色名例如“和管家阿尔弗雷德说话” try: name_part user_input[1:].split(说话)[0].strip() # 在场景角色中查找匹配 matched_char next((c for c in scene.characters if name_part in c.name or c.name in name_part), None) if matched_char: current_character_name matched_char.name print(f你开始与 {current_character_name} 交谈。) # 可以添加角色开场白 memory.add_interaction(系统, f玩家开始与 {current_character_name} 对话。) else: print(f未找到角色‘{name_part}’。) except Exception as e: print(指令格式错误请尝试‘和管家阿尔弗雷德说话’。) continue # 如果没有选择角色则提示 if not current_character_name: print(请先选择一个对话角色。) continue # 4. 规则检查 action_check rule_engine.check_action(user_input, current_character_name) if not action_check[allowed]: print(f[规则拦截] {action_check[message]}) # 将拦截信息也记录为一次交互 memory.add_interaction(系统规则, action_check[message]) # 应用状态更新 for k, v in action_check.get(state_updates, {}).items(): rule_engine.update_state(k, v) continue # 应用规则检查通过后的状态更新 for k, v in action_check.get(state_updates, {}).items(): rule_engine.update_state(k, v) # 5. 记录玩家输入 memory.add_interaction(玩家, user_input) # 6. 构建上下文并调用LLM target_char next((c for c in scene.characters if c.name current_character_name), None) context context_builder.build_context_for_turn(user_input, current_character_name) character_response llm_client.generate_response(context) # 7. 记录并显示角色回复 print(f\n{current_character_name}: {character_response}) memory.add_interaction(current_character_name, character_response) # 8. 可选简单响应解析更新游戏状态 # 例如检测角色回复中是否包含了线索信息 clue_keywords [第一部分是, 星星的数量是, 火山数量是] for keyword in clue_keywords: if keyword in character_response: # 提取线索部分这里是非常简单的示例 print(f[系统提示] 你似乎发现了线索的一部分) # 更新状态 if clue_parts not in rule_engine.state: rule_engine.state[clue_parts] [] # 避免重复添加 if keyword not in str(rule_engine.state[clue_parts]): rule_engine.state[clue_parts].append(keyword) rule_engine.update_state(clue_parts, rule_engine.state[clue_parts]) # 9. 检查谜题是否解决 if rule_engine.is_puzzle_solved(): print(\n 恭喜你已经收集了所有线索碎片。) print(线索组合结果是东侧画廊第三幅画后、壁炉画中星星数、旅行家的火山数量。) print(宝藏就在...游戏胜利) break if __name__ __main__: main()6. 运行验证与结果分析现在让我们运行这个程序验证其基本功能。6.1 启动游戏在项目根目录下运行python main.py你应该看到类似以下的输出欢迎来到场景秘密派对 你被邀请参加一个神秘的派对据说派对上隐藏着一个宝藏的线索。你需要通过与三位嘉宾交谈来找出线索。 环境一个装饰华丽的古老别墅客厅壁炉里燃着火墙上挂着一些奇怪的画。 在场的角色有 - 管家阿尔弗雷德: 年迈但精明的别墅管家知晓别墅的历史和许多秘密... - 画家贝拉: 一位情绪化的艺术家目前正在别墅里创作。她的画作可能隐藏着信息... - 旅行家查理: 一位见多识广的冒险家刚从一个遥远的地方回来喜欢讲故事... --- 游戏开始 --- 你可以输入‘和[角色名]说话’来切换对话对象例如‘和管家阿尔弗雷德说话’。 输入‘退出’来结束游戏。 输入‘状态’查看当前进展。6.2 进行交互按照提示你可以开始与角色对话。以下是一个示例对话流程请选择对话角色或输入指令 和管家阿尔弗雷德说话 你开始与 管家阿尔弗雷德 交谈。 你正在与 管家阿尔弗雷德 交谈。你想说什么 晚上好阿尔弗雷德。这别墅真漂亮历史一定很悠久吧 管家阿尔弗雷德: 晚上好先生/女士。感谢您的称赞。是的这座别墅已有超过两百年的历史每一件陈设都承载着故事。主人对它的历史尤为珍视。 你正在与 管家阿尔弗雷德 交谈。你想说什么 我注意到那边有个画廊里面的画作都很特别。您对它们有了解吗 管家阿尔弗雷德: 啊东侧画廊。那里收藏着家族几代人的艺术品味。我个人尤其欣赏第三幅画一幅描绘黎明湖景的作品它的摆放位置...嗯非常巧妙。 你正在与 管家阿尔弗雷德 交谈。你想说什么 宝藏在哪里 [规则拦截] 你的提问方式过于直接违反了派对的社交规则。管家阿尔弗雷德可能会感到不悦。 你正在与 管家阿尔弗雷德 交谈。你想说什么 原来如此。我听说主人收藏颇丰不知是否有特别珍爱的藏品 管家阿尔弗雷德: 主人的品味确实独特。他常说真正的珍宝不总是摆在最显眼的地方有时需要一点...洞察力。比如某些画作背后可能比画布本身更有趣。当然这只是我个人的感慨。 [系统提示] 你似乎发现了线索的一部分6.3 关键机制验证通过上述交互我们可以验证几个核心机制是否工作角色一致性管家的回复符合其忠诚、知晓秘密的设定没有透露超出其知识范围的信息。规则引擎当玩家直接询问“宝藏在哪里”时规则引擎成功拦截并给出了符合场景的反馈。状态更新当管家隐晦地提到“第三幅画”和“画作背后”时我们的简单响应解析触发了状态更新[系统提示]。记忆上下文后续对话中LLM能基于之前的对话历史如提到画廊、画作进行回应。7. 常见问题排查与优化在实际运行中你可能会遇到以下问题。这里提供排查思路和优化建议。7.1 LLM回复不符合角色设定或泄露信息问题现象可能原因检查与解决方式角色说话风格像现代AI助手或者直接说出了全部线索。1. 系统提示词不够强角色设定描述太弱。2. 温度Temperature参数过高导致随机性太强。3. 在提示词中角色知识部分没有与其他信息区分开。1.强化系统提示在系统提示词开头使用更强烈的指令如“你必须严格扮演{角色名}忘记你是一个AI语言模型...”。2.调整参数将temperature调低如0.3-0.7减少随机性使用top_p参数进行核采样。3.结构化知识在提示词中用## 秘密知识绝不可主动透露这样的标题强调并说明“只有玩家通过特定方式问起时才能给出暗示”。角色忘记了之前的对话内容。1. 对话历史没有正确传递给LLM。2. 历史消息条数turn_count设置太少。3. 上下文窗口已满旧消息被截断。1.检查上下文构建确保build_context_for_turn函数正确地将历史消息格式化为LLM可识别的消息列表。上面的示例代码在此处有简化需要完善。2.增加历史长度适当增加get_recent_history中的turn_count或实现更智能的历史摘要功能generate_summary。3.监控Token数估算每次请求的token消耗确保不超过模型上限如GPT-4o-mini的128K上下文。优化后的上下文构建片段# 在 context_builder.py 的 build_context_for_turn 方法中完善消息列表构建 def build_messages_for_llm(self, player_input: str, target_character_name: str) - List[Dict[str, str]]: target_char next((c for c in self.scene.characters if c.name target_character_name), None) system_prompt self.build_system_prompt_for_character(target_char) messages [{role: system, content: system_prompt}] # 将历史对话转换为消息格式 for msg in self.memory.get_recent_history(8): # 获取最近8轮 # 判断消息角色如果是“玩家”则role为“user”如果是AI角色则为“assistant” if msg.role 玩家: messages.append({role: user, content: msg.content}) else: # 其他角色或系统 # 为了简化我们可以将所有非玩家消息都视为“assistant”从该角色的角度说的 # 更好的做法是为每个角色维护独立的对话线程 messages.append({role: assistant, content: f({msg.role}) {msg.content}}) # 加入玩家本轮输入 messages.append({role: user, content: player_input}) return messages7.2 规则引擎过于死板或漏判问题现象可能原因检查与解决方式玩家合理的提问被规则引擎错误拦截。规则关键词匹配过于严格。例如玩家说“这幅画背后有什么故事吗”可能因为包含“背后”一词而被误判为询问线索。1.优化规则逻辑使用更精确的匹配如正则表达式并结合上下文判断。例如规则可以检查“背后”是否与“画”、“隐藏”等词同时出现。2.引入LLM进行规则判断对于复杂规则可以将玩家输入和当前场景发送给一个快速的LLM如GPT-3.5-turbo让其判断是否违规。这更灵活但成本更高、延迟更大。玩家通过迂回的方式获得了全部线索规则引擎没有触发状态更新。状态更新依赖于简单的关键词匹配而LLM的回复可能非常多样不会精确包含预设关键词。1.增强响应解析使用LLM来解析角色的回复判断其中是否包含线索信息。可以设计一个简单的提示词“请分析以下对话如果{角色名}的回复中包含了关于‘宝藏线索’的任何部分如地点、数字、物品请精确提取出来否则输出‘无’。”2.细化状态将游戏状态设计得更精细例如记录玩家与每个角色的“好感度”或“信任等级”这些状态会影响LLM生成回复时透露信息的多少。7.3 性能与成本问题问题现象可能原因检查与解决方式游戏响应速度慢。1. 网络延迟。2. 使用的LLM模型较大如GPT-4。3. 提示词过长导致处理时间增加。1.使用更快的模型/端点在测试时使用gpt-4o-mini或gpt-3.5-turbo。2.缓存提示词系统提示词部分通常不变可以缓存起来无需每次构建。3.异步调用如果未来实现多角色同时在线使用aiohttp进行异步API调用。API调用成本过高。1. 每轮对话都发送很长的历史。2. 没有对历史进行压缩。1.实现对话摘要正如MemoryManager中规划的generate_summary方法定期将旧对话总结成一段简短的摘要替换掉详细的历史消息。2.设置对话轮次上限强制在N轮后结束与一个角色的对话或要求玩家总结进展。8. 最佳实践与扩展方向8.1 生产环境考量如果要将此原型发展为更稳定的应用需要考虑以下几点配置外置化将所有硬编码的参数如模型名称、温度、历史长度、规则关键词移到配置文件如config.yaml中。错误处理与重试在LLMClient中增加更健壮的错误处理如网络超时、速率限制和指数退避重试机制。日志记录记录完整的对话历史、LLM请求与响应、状态变更和规则触发事件便于调试和复盘。输入验证与清理对玩家的输入进行基本的清理和验证防止注入攻击或不当内容。会话管理支持多用户、多会话每个会话有独立的内存、状态和引擎实例。8.2 扩展功能建议多角色同时对话允许玩家在一个场景中同时与多个角色交谈角色之间也可能相互交流。这需要更复杂的上下文管理和调度逻辑。可视化状态与关系图为游戏管理员提供一个仪表盘实时显示所有角色的状态、关系网和玩家的进度。更动态的规则与事件规则引擎不仅可以检查玩家输入还可以基于游戏状态触发全局事件如“所有角色聚集到大厅”。集成向量数据库当角色知识库非常庞大时如整个幻想世界的百科全书可以使用向量数据库如ChromaDB, Weaviate来让角色根据对话上下文检索相关知识而不是全部写在提示词里。语音输入输出集成语音识别ASR和语音合成TTS模块打造沉浸式的语音交互体验。8.3 提示词工程进阶技巧少样本学习Few-Shot在系统提示词中提供几个高质量的对话示例示范角色应如何回应各种类型的提问如直接询问、礼貌询问、挑衅等。输出格式约束要求LLM以特定格式如JSON回复便于程序解析。例如回复可以包含{“speech”: “角色说的话”, “internal_thought”: “角色的内心活动”, “state_effect”: {“clue_revealed”: true}}。分层提示将系统提示词分为不变的核心身份层和可变的当前情境层减少重复传输的信息量。通过以上步骤我们不仅实现了一个简单的“Read the Room”社交解谜游戏原型更构建了一套可复用的、用于创建上下文感知型LLM应用的基础框架。这个框架的核心思想——明确的角色定义、结构化的状态管理、规则约束的交互以及精心构建的上下文——可以广泛应用于需要LLM进行复杂、持久且符合规则的社会化推理的场景中。