
在實際項目中我們經常需要處理復雜的上下文信息讓模型能夠理解并回應特定場景下的隱含意圖。這不僅僅是簡單的對話而是要求模型具備“讀懂空氣”的能力理解對話的上下文、參與者的角色、未言明的規則以及當前的目標。這種能力在構建智能客服、游戲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進行復雜、持久且符合規則的社會化推理的場景中。