三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

基于LLM的上下文感知与推理机制:从概念到社交解谜游戏实践

基于LLM的上下文感知与推理机制:从概念到社交解谜游戏实践

在实际项目中,我们经常需要处理复杂的上下文信息,让模型能够理解并回应特定场景下的隐含意图。这不仅仅是简单的对话,而是要求模型具备“读懂空气”的能力,理解对话的上下文、参与者的角色、未言明的规则以及当前的目标。这种能力在构建智能客服、游戏NPC、社交模拟或复杂的多轮任务导向型对话系统中至关重要。本文将以一个名为“Read the Room”的社交LLM解谜游戏为引子,深入探讨如何为大型语言模型设计和实现一套有效的上下文感知与推理机制。我们将从零开始,构建一个能够理解房间内动态、遵循社交规则并解决谜题的小型系统。通过本文,你将掌握如何为LLM设计提示词、构建记忆模块、定义角色与规则,并最终实现一个可交互的、具备基础社交推理能力的应用原型。

1. 理解“读懂房间”的核心挑战与设计思路

“Read the Room”这个标题形象地描述了一个核心挑战:让LLM不仅仅处理当前输入的文本,还要理解整个交互场景的“上下文房间”。这个“房间”里包含了历史对话、参与者信息、环境状态、社交规则和未明确说出的目标。

1.1 什么是社交LLM解谜?

社交LLM解谜是一种交互形式,用户(或另一个AI)扮演特定角色,在一个设定的场景中,通过自然语言与一个或多个由LLM驱动的角色进行互动,目标是达成某个隐含的或明确的任务。这个任务可能是一个谜题,比如从对话中获取一个秘密密码;也可能是一个社交目标,比如说服一个角色提供帮助,或者调和两个角色之间的矛盾。

与普通聊天机器人不同,这类应用对LLM的要求更高:

  1. 长期记忆:需要记住之前对话中透露的关键信息。
  2. 角色一致性:每个AI角色需要保持其设定的人格、知识和目标。
  3. 上下文推理:需要从对话的弦外之音、前后矛盾或环境线索中推断出信息。
  4. 目标导向:对话需要朝着解决谜题或达成目标的方向推进,而不是漫无目的地闲聊。

1.2 构建系统的关键组件

为了实现上述能力,我们需要设计几个核心组件:

  1. 场景与角色定义器:用结构化的数据(如JSON或YAML)来描述初始场景、所有角色(包括用户角色)的属性、关系以及初始状态。
  2. 对话历史管理器:一个能够存储、检索和总结对话历史的模块。它不能无限增长,需要有能力提炼关键信息。
  3. 上下文构建器:在每次调用LLM生成回复前,该组件负责将场景定义、相关历史、当前查询和系统指令组装成最终的提示词(Prompt)。
  4. 规则与状态引擎:管理游戏规则(如哪些行为被允许)、追踪游戏状态(如谜题是否解决、物品归属)以及验证用户或AI的行动是否符合逻辑。
  5. 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_KEY=sk-your-actual-api-key-here OPENAI_MODEL=gpt-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_factory=list) # 角色独有的知识/秘密 goals: List[str] = Field(default_factory=list) # 角色的目标 relationships: Dict[str, str] = Field(default_factory=dict) # 与其他角色的关系 class Scene(BaseModel): """场景定义""" name: str description: str # 场景背景描述 characters: List[Character] setting: str # 环境细节 initial_state: Dict[str, Any] = Field(default_factory=dict) # 初始游戏状态,如物品位置 rules: List[str] = Field(default_factory=list) # 场景内的特殊规则 class Message(BaseModel): """单条对话消息""" role: str # “system”, “user”, “assistant”, 或角色名 content: str timestamp: Optional[float] = None class ConversationHistory(BaseModel): """对话历史记录""" messages: List[Message] = Field(default_factory=list) max_length: int = 20 # 保留的最大消息条数,防止上下文过长 def add_message(self, role: str, content: str): """添加消息,并自动修剪历史""" self.messages.append(Message(role=role, content=content)) 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_length=max_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) pass

4.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", [])) >= 3

4.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_count=6) # 3. 玩家当前输入 # 注意:在调用LLM前,规则引擎可能已经拦截了非法输入。 # 组装最终上下文 full_context = { "system_prompt": system_prompt, "history": history_str, "player_input": player_input, "character": target_char } return full_context

4.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( model=self.model, messages=messages, temperature=self.temperature, max_tokens=self.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', encoding='utf-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 关键机制验证

通过上述交互,我们可以验证几个核心机制是否工作:

  1. 角色一致性:管家的回复符合其忠诚、知晓秘密的设定,没有透露超出其知识范围的信息。
  2. 规则引擎:当玩家直接询问“宝藏在哪里”时,规则引擎成功拦截,并给出了符合场景的反馈。
  3. 状态更新:当管家隐晦地提到“第三幅画”和“画作背后”时,我们的简单响应解析触发了状态更新([系统提示])。
  4. 记忆上下文:后续对话中,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 messages

7.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-minigpt-3.5-turbo
2.缓存提示词:系统提示词部分通常不变,可以缓存起来,无需每次构建。
3.异步调用:如果未来实现多角色同时在线,使用aiohttp进行异步API调用。
API调用成本过高。1. 每轮对话都发送很长的历史。
2. 没有对历史进行压缩。
1.实现对话摘要:正如MemoryManager中规划的generate_summary方法,定期将旧对话总结成一段简短的摘要,替换掉详细的历史消息。
2.设置对话轮次上限:强制在N轮后结束与一个角色的对话,或要求玩家总结进展。

8. 最佳实践与扩展方向

8.1 生产环境考量

如果要将此原型发展为更稳定的应用,需要考虑以下几点:

  1. 配置外置化:将所有硬编码的参数(如模型名称、温度、历史长度、规则关键词)移到配置文件(如config.yaml)中。
  2. 错误处理与重试:在LLMClient中增加更健壮的错误处理(如网络超时、速率限制)和指数退避重试机制。
  3. 日志记录:记录完整的对话历史、LLM请求与响应、状态变更和规则触发事件,便于调试和复盘。
  4. 输入验证与清理:对玩家的输入进行基本的清理和验证,防止注入攻击或不当内容。
  5. 会话管理:支持多用户、多会话,每个会话有独立的内存、状态和引擎实例。

8.2 扩展功能建议

  1. 多角色同时对话:允许玩家在一个场景中同时与多个角色交谈,角色之间也可能相互交流。这需要更复杂的上下文管理和调度逻辑。
  2. 可视化状态与关系图:为游戏管理员提供一个仪表盘,实时显示所有角色的状态、关系网和玩家的进度。
  3. 更动态的规则与事件:规则引擎不仅可以检查玩家输入,还可以基于游戏状态触发全局事件(如“所有角色聚集到大厅”)。
  4. 集成向量数据库:当角色知识库非常庞大时(如整个幻想世界的百科全书),可以使用向量数据库(如ChromaDB, Weaviate)来让角色根据对话上下文检索相关知识,而不是全部写在提示词里。
  5. 语音输入输出:集成语音识别(ASR)和语音合成(TTS)模块,打造沉浸式的语音交互体验。

8.3 提示词工程进阶技巧

  1. 少样本学习(Few-Shot):在系统提示词中提供几个高质量的对话示例,示范角色应如何回应各种类型的提问(如直接询问、礼貌询问、挑衅等)。
  2. 输出格式约束:要求LLM以特定格式(如JSON)回复,便于程序解析。例如,回复可以包含{“speech”: “角色说的话”, “internal_thought”: “角色的内心活动”, “state_effect”: {“clue_revealed”: true}}
  3. 分层提示:将系统提示词分为不变的核心身份层和可变的当前情境层,减少重复传输的信息量。

通过以上步骤,我们不仅实现了一个简单的“Read the Room”社交解谜游戏原型,更构建了一套可复用的、用于创建上下文感知型LLM应用的基础框架。这个框架的核心思想——明确的角色定义、结构化的状态管理、规则约束的交互以及精心构建的上下文——可以广泛应用于需要LLM进行复杂、持久且符合规则的社会化推理的场景中。

← 返回列表