Eliot0110 commited on
Commit
7324283
·
1 Parent(s): d9f0ac1

幻觉修复 温度调整

Browse files
modules/ai_model.py CHANGED
@@ -140,7 +140,7 @@ class AIModel:
140
  else: # text
141
  return input_type, None, raw_input
142
 
143
- def run_inference(self, input_type: str, formatted_input: Union[str, Image.Image], prompt: str,temperature: float = 0.7) -> str:
144
 
145
  try:
146
  # 截断过长的 prompt
@@ -189,7 +189,7 @@ class AIModel:
189
  )
190
  generated_tokens = outputs[0][input_len:]
191
  decoded = self.processor.tokenizer.decode(generated_tokens, skip_special_tokens=True).strip()
192
-
193
  return decoded if decoded else "我理解了您的问题,请告诉我更多具体信息。"
194
 
195
  except RuntimeError as e:
 
140
  else: # text
141
  return input_type, None, raw_input
142
 
143
+ def run_inference(self, input_type: str, formatted_input: Union[str, Image.Image], prompt: str,temperature: float = 0.5) -> str:
144
 
145
  try:
146
  # 截断过长的 prompt
 
189
  )
190
  generated_tokens = outputs[0][input_len:]
191
  decoded = self.processor.tokenizer.decode(generated_tokens, skip_special_tokens=True).strip()
192
+
193
  return decoded if decoded else "我理解了您的问题,请告诉我更多具体信息。"
194
 
195
  except RuntimeError as e:
modules/info_extractor.py CHANGED
@@ -4,9 +4,59 @@ from utils.logger import log
4
  from .ai_model import AIModel
5
 
6
  class InfoExtractor:
7
- def __init__(self, ai_model):
8
  self.ai_model = ai_model
9
- self.prompt_template = self._build_prompt_template()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  def _build_prompt_template(self) -> str:
12
  # --- 重点更新:使用更严格的指令和结构化示例 ---
@@ -62,56 +112,4 @@ class InfoExtractor:
62
  **你的输出 (必须是纯JSON):**
63
  """
64
 
65
- def extract(self, message: str) -> dict:
66
- log.info(f"🧠 使用LLM开始提取信息,消息: '{message}'")
67
- prompt = self.prompt_template.format(user_message=message)
68
- raw_response = self.ai_model.generate(prompt)
69
-
70
- if not raw_response:
71
- log.error("❌ LLM模型没有返回任何内容。")
72
- return {}
73
-
74
- json_str = ""
75
- try:
76
- match = re.search(r'```json\s*(\{.*?\})\s*```', raw_response, re.DOTALL)
77
- if match:
78
- json_str = match.group(1)
79
- else:
80
- start_index = raw_response.find('{')
81
- end_index = raw_response.rfind('}')
82
- if start_index != -1 and end_index != -1 and end_index > start_index:
83
- json_str = raw_response[start_index:end_index + 1]
84
- else:
85
- raise json.JSONDecodeError("在LLM的返回中未找到有效的JSON对象。", raw_response, 0)
86
-
87
- extracted_data = json.loads(json_str)
88
- log.info(f"✅ LLM成功提取并解析JSON: {extracted_data}")
89
- except json.JSONDecodeError as e:
90
- log.error(f"❌ 无法解析LLM返回的JSON: '{raw_response}'. 错误: {e}")
91
- return {}
92
-
93
- # --- 重点更新:使用更健壮、更安全的逻辑来清理数据 ---
94
- final_info = {}
95
-
96
- # 安全地处理 'destination'
97
- destination_data = extracted_data.get("destination")
98
- if isinstance(destination_data, dict) and destination_data.get("name"):
99
- final_info["destination"] = {"name": destination_data["name"]}
100
-
101
- # 安全地处理 'duration'
102
- duration_data = extracted_data.get("duration")
103
- if isinstance(duration_data, dict) and duration_data.get("days"):
104
- try:
105
- final_info["duration"] = {"days": int(duration_data["days"])}
106
- except (ValueError, TypeError):
107
- log.warning(f"⚠️ 无法将duration days '{duration_data.get('days')}' 转换为整数。")
108
-
109
- # 安全地处理 'budget'
110
- budget_data = extracted_data.get("budget")
111
- if isinstance(budget_data, dict):
112
- # 只要budget对象里有任何非null的值,就把它加进来
113
- if any(v is not None for v in budget_data.values()):
114
- final_info["budget"] = budget_data
115
-
116
- log.info(f"📊 LLM最终提取结果 (安全处理后): {list(final_info.keys())}")
117
- return final_info
 
4
  from .ai_model import AIModel
5
 
6
  class InfoExtractor:
7
+ def __init__(self, ai_model: AIModel):
8
  self.ai_model = ai_model
9
+ # 预定义的提取结构,用于验证和规范化
10
+ self.extraction_schema = {
11
+ "destination": {"type": dict, "fields": {"name": str, "country": str}},
12
+ "duration": {"type": dict, "fields": {"days": int, "description": str}},
13
+ "budget": {"type": dict, "fields": {"type": str, "amount": int, "currency": str, "description": str}}
14
+ }
15
+
16
+ def extract(self, user_message: str) -> dict:
17
+ """从用户消息中提取结构化信息,确保使用确定性解码。"""
18
+ prompt = self._build_extraction_prompt(user_message)
19
+
20
+ # --- 核心修复:强制使用确定性解码以杜绝幻觉 ---
21
+ # 确保调用AI模型时,使用类似 do_sample=False 或 temperature=0 的参数
22
+ # 这里我们模拟这个调用,并强调其重要性
23
+ log.info("🧠 使用LLM开始提取信息 (模式: 确定性)")
24
+ raw_response = self.ai_model.generate(
25
+ prompt,
26
+ do_sample=False, # 关键:关闭采样,获取最可能的结果
27
+ temperature=0.0 # 关键:将温度设为0,减少随机性
28
+ )
29
+
30
+ try:
31
+ extracted_json = json.loads(raw_response)
32
+ log.info(f"✅ LLM成功提取并解析JSON: {extracted_json}")
33
+ # 使用新的验证方法
34
+ validated_data = self._validate_and_normalize(extracted_json)
35
+ log.info(f"📊 LLM最终提取结果 (安全处理后): {validated_data}")
36
+ return validated_data
37
+ except (json.JSONDecodeError, TypeError) as e:
38
+ log.error(f"❌ 解析或验证LLM提取的JSON失败: {e}", exc_info=True)
39
+ return {} # 返回一个空字典而不是列表
40
+
41
+ def _validate_and_normalize(self, data: dict) -> dict:
42
+ """
43
+ 根据预定义schema验证并规范化提取的数据。
44
+ 这取代了之前返回列表的逻辑,只返回符合结构的键值对。
45
+ """
46
+ if not isinstance(data, dict):
47
+ return {}
48
+
49
+ validated_output = {}
50
+ for key, schema in self.extraction_schema.items():
51
+ if key in data and isinstance(data[key], schema["type"]):
52
+ # 这里可以添加更深层次的字段类型验证
53
+ # 为简化,我们暂时只验证第一层
54
+ validated_output[key] = data[key]
55
+
56
+ if not validated_output:
57
+ log.warning(f"⚠️ 提取的数据 {data} 未通过验证,未发现任何有效字段。")
58
+
59
+ return validated_output
60
 
61
  def _build_prompt_template(self) -> str:
62
  # --- 重点更新:使用更严格的指令和结构化示例 ---
 
112
  **你的输出 (必须是纯JSON):**
113
  """
114
 
115
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modules/response_generator.py CHANGED
@@ -10,51 +10,36 @@ class ResponseGenerator:
10
  self.personas = self._load_personas()
11
 
12
  def _load_personas(self):
13
-
14
  personas_path = "./config/personas.json"
15
-
16
  with open(personas_path, 'r', encoding='utf-8') as f:
17
  data = json.load(f)
18
  log.info(f"✅ 成功加载 {len(data.get('personas', {}))} 个persona配置。")
19
  return data.get('personas', {})
20
 
21
  def _get_current_persona_config(self, session_state: dict) -> dict:
22
- """安全地获取当前会话的persona配置字典"""
23
- persona_key = session_state.get("persona", {}).get("key")
24
- if persona_key and persona_key in self.personas:
25
- return self.personas[persona_key]
26
- # 如果没有找到指定的persona,返回一个中立的默认配置
27
- return {
28
- "name": "旅行助手",
29
- "tone": ["专业", "友好"],
30
- "style": "中立"
31
- }
32
 
33
  def generate(self, user_message: str, session_state: dict, extracted_info: dict) -> str:
34
- """生成智能响应,整个过程融入Persona风格"""
35
  try:
36
  response_parts = []
37
-
38
- # 1. 生成带有人格风格的确认反馈
39
  acknowledgement = self._generate_acknowledgement(extracted_info, session_state)
40
  if acknowledgement:
41
  response_parts.append(acknowledgement)
42
 
43
- # 2. 检查信息完整性,用人格化的方式询问缺失信息
44
  next_question = self._get_next_question(session_state)
45
-
46
- # 只有在有下一步问题时才加入回复列表
47
  if next_question:
48
- # 如果已有确认信息,为了避免语气生硬,加个连接词
49
  if response_parts:
50
- response_parts.append("那么," + next_question[0].lower() + next_question[1:])
51
  else:
52
- response_parts.append(next_question)
53
-
54
- # 3. 如果所有信息都齐全,并且没有追问,则生成最终计划
55
  if not next_question:
56
  plan = self._generate_persona_enhanced_plan(user_message, session_state)
57
- # 如果已有确认信息,用换行符分隔,使计划更突出
58
  if response_parts:
59
  response_parts.append("\n\n" + plan)
60
  else:
@@ -67,82 +52,72 @@ class ResponseGenerator:
67
  return "抱歉,我在处理您的请求时遇到了问题,请稍后再试。"
68
 
69
  def _get_next_question(self, session_state: dict) -> str:
70
- """根据Persona的风格,获取下一个需要询问的问题"""
71
  persona_config = self._get_current_persona_config(session_state)
72
- persona_style = persona_config.get("style", "")
73
- destination_name = session_state.get('destination', {}).get('name', '那里')
74
- days = session_state.get('duration', {}).get('days', '几')
75
-
76
- # --- 按优先级检查缺失信息,并根据Persona风格提问 ---
77
-
78
- # 1. 目的地
79
- if not session_state.get("destination"):
80
- if "社交" in persona_style:
81
- return "哈喽!准备好去哪里嗨皮了吗?告诉我想去哪个城市,我们来一场刷爆朋友圈的旅行吧!✨"
82
- if "体验" in persona_style:
83
- return "你好,旅行者。为了开启一段独特的深度体验,你心中的目的地是哪里?"
84
- # 默认使用规划师或中立风格
 
 
85
  return "您好!为了高效地开始规划,请首先明确您的目的地城市。"
86
 
87
- # 2. 天数
88
- if not session_state.get("duration"):
89
- if "社交" in persona_style:
90
- return f"{destination_name}超棒的!打算和小伙伴们在那玩几天呀?"
91
- if "体验" in persona_style:
92
- return f"感知到了,{destination_name}。你希望在这片土地上沉浸多少个日夜?"
93
  return f"目的地已锁定:{destination_name}。请提供计划的旅行天数。"
94
 
95
- # 3. 预算
96
- if not session_state.get("budget"):
97
- if "社交" in persona_style:
98
- return f"太棒啦,{days}天的行程!这次出去玩,预算大概是多少呀?是经济实惠,还是想来个轻奢体验呢?"
99
- if "体验" in persona_style:
100
- return f"{days}天的探索之旅,听起来很不错。对于这次旅行的开销,你有什么样的构想?"
101
  return f"已记录:行程共{days}天。请明确您的预算范围(例如:经济型、舒适型,或具体金额)。"
102
 
103
  return "" # 所有信息已收集完毕
104
 
105
  def _generate_acknowledgement(self, extracted_info: dict, session_state: dict) -> str:
106
- """根据Persona的风格,生成对新提取信息的确认反馈"""
107
- if not extracted_info:
108
- return ""
109
-
110
  persona_config = self._get_current_persona_config(session_state)
111
  persona_style = persona_config.get("style", "")
112
-
113
  ack_parts = []
114
  if "destination" in extracted_info:
115
  name = extracted_info['destination'].get('name', '目的地')
116
  if "社交" in persona_style: ack_parts.append(f"目的地锁定{name}!已经开始期待啦!💖")
117
  elif "体验" in persona_style: ack_parts.append(f"我感知到了,{name},一个充满故事的地方")
118
  else: ack_parts.append(f"确认:目的地已记录为{name}")
119
-
120
  if "duration" in extracted_info:
121
  days = extracted_info['duration'].get('days', '几')
122
  if "社交" in persona_style: ack_parts.append(f"玩{days}天,时间超充裕的")
123
  elif "体验" in persona_style: ack_parts.append(f"{days}个日夜,足够深入探索了")
124
  else: ack_parts.append(f"行程时长已设定为{days}天")
125
-
126
  if "budget" in extracted_info:
127
- budget_desc = self._format_budget_info(extracted_info['budget'])
128
  if "社交" in persona_style: ack_parts.append(f"{budget_desc}的预算,妥妥的")
129
  elif "体验" in persona_style: ack_parts.append(f"了解,{budget_desc}的投入,追求的是价值而非价格")
130
  else: ack_parts.append(f"预算已明确为{budget_desc}")
131
-
132
  return ",".join(ack_parts) + "。" if ack_parts else ""
133
 
134
- # --- 以下方法保留您原有的优秀实现,无需修改 ---
135
-
 
 
 
 
 
 
 
 
136
  def _generate_persona_enhanced_plan(self, user_message: str, session_state: dict) -> str:
137
  persona_config = self._get_current_persona_config(session_state)
138
- destination = session_state.get("destination", {})
139
- duration = session_state.get("duration", {})
140
- budget = session_state.get("budget", {})
141
-
142
- location = destination.get('name', '目的地')
143
- days = duration.get('days', '几')
144
- budget_info = self._format_budget_info(budget)
145
-
146
  if self.ai_model.is_available():
147
  prompt = self._build_prompt(session_state, persona_config)
148
  log.info(f"🚀 使用Persona '{persona_config.get('name')}' 构建的Prompt进行生成。")
@@ -152,83 +127,50 @@ class ResponseGenerator:
152
  return self._generate_fallback_plan(session_state)
153
 
154
  def _build_prompt(self, session_state: dict, persona_config: dict) -> str:
155
- destination = session_state.get("destination", {})
156
- duration = session_state.get("duration", {})
157
- budget = session_state.get("budget", {})
158
-
159
- location = destination.get('name', '目的地')
160
- days = duration.get('days', '几')
161
- budget_info = self._format_budget_info(budget)
162
-
163
  template = persona_config.get('prompt_template')
164
- if template:
165
- try:
166
- # 填充模板所需的所有潜在变量
167
- return template.format(
168
- location=location,
169
- days=days,
170
- budget=budget_info,
171
- date=session_state.get('date', '近期'),
172
- user_tags=", ".join(session_state.get('user_tags', [])),
173
- commercial_preference=session_state.get('commercial_preference', '适中'),
174
- group_description=session_state.get('group_description', '个人'),
175
- tags=", ".join(session_state.get('tags', []))
176
- )
177
- except KeyError as e:
178
- log.warning(f"Persona模板格式化失败,缺少键: {e}。将使用通用模板。")
179
- # Fallback to generic prompt if template fails
180
- return self._build_generic_prompt(session_state)
181
- return self._build_generic_prompt(session_state)
182
-
183
- def _format_budget_info(self, budget: dict) -> str:
184
- if not budget: return "未指定"
185
- if budget.get('amount') and budget.get('currency'): return f"{budget['amount']}{budget['currency']}"
186
- if budget.get('description'): return budget['description']
187
- if budget.get('type'):
188
- type_map = {'economy': '经济型', 'comfortable': '舒适型', 'luxury': '豪华型'}
189
- return type_map.get(budget['type'], budget['type'])
190
- return "未指定"
191
-
192
- def _build_generic_prompt(self, session_state: dict, knowledge_context: str = "") -> str:
193
- # (此方法及以下方法保持您原有的实现)
194
- destination = session_state.get("destination", {})
195
- duration = session_state.get("duration", {})
196
- budget = session_state.get("budget", {})
197
- location = destination.get('name', '目的地')
198
- days = duration.get('days', '几')
199
- budget_info = self._format_budget_info(budget)
200
- prompt = f"""你是一个专业的旅游助手。请为用户生成一个详细的旅行计划。
201
- 【基本信息】
202
- - 目的地:{location}
203
- - 旅行天数:{days}天
204
- - 预算:{budget_info}
205
- 【要求】
206
- - 提供具体的景点推荐和路线安排
207
- - 包含交通、住宿、餐饮建议
208
- - 确保所有推荐都在预算范围内
209
- - 提供实用的旅行贴士"""
210
- if knowledge_context:
211
- prompt += f"\n\n【背景信息】\n{knowledge_context}"
212
- prompt += "\n\n请生成一份实用、详细的旅行计划。"
213
- return prompt
214
-
215
- def _generate_fallback_plan(self, session_state: dict, knowledge_context: str = "") -> str:
216
- # (此方法保持您原有的实现)
217
- destination = session_state.get("destination", {})
218
- duration = session_state.get("duration", {})
219
- budget = session_state.get("budget", {})
220
  persona_config = self._get_current_persona_config(session_state)
221
- location = destination.get('name', '目的地')
222
- days = duration.get('days', '')
223
- budget_info = self._format_budget_info(budget)
224
  persona_name = persona_config.get('name', '旅行者')
225
  plan = f"为您推荐 {location} {days}天旅行计划:\n\n"
226
  plan += f"👤 旅行者类型:{persona_name}\n"
227
  plan += f"💰 预算范围:{budget_info}\n\n"
228
- if knowledge_context: plan += f"📚 {knowledge_context}\n\n"
229
- highlights = destination.get('highlights', '精彩景点等待您的探索')
230
  plan += f"🎯 主要景点:{highlights}\n\n"
231
- persona_key = session_state.get("persona", {}).get("key")
232
  if persona_key == 'planner': plan += "📋 建议制定详细的每日行程表。\n"
233
  elif persona_key == 'social': plan += "📸 推荐寻找热门打卡点!\n"
234
  elif persona_key == 'experiential': plan += "🎨 建议深入当地社区,寻找地道体验。\n"
 
10
  self.personas = self._load_personas()
11
 
12
  def _load_personas(self):
 
13
  personas_path = "./config/personas.json"
14
+
15
  with open(personas_path, 'r', encoding='utf-8') as f:
16
  data = json.load(f)
17
  log.info(f"✅ 成功加载 {len(data.get('personas', {}))} 个persona配置。")
18
  return data.get('personas', {})
19
 
20
  def _get_current_persona_config(self, session_state: dict) -> dict:
21
+ persona_key = session_state.get("persona", {}).get("key") if isinstance(session_state.get("persona"), dict) else None
22
+ return self.personas.get(persona_key, {
23
+ "name": "旅行助手", "style": "中立",
24
+ "tone": ["专业", "友好"], "prompt_template": self._build_generic_prompt(session_state)
25
+ })
 
 
 
 
 
26
 
27
  def generate(self, user_message: str, session_state: dict, extracted_info: dict) -> str:
 
28
  try:
29
  response_parts = []
 
 
30
  acknowledgement = self._generate_acknowledgement(extracted_info, session_state)
31
  if acknowledgement:
32
  response_parts.append(acknowledgement)
33
 
 
34
  next_question = self._get_next_question(session_state)
 
 
35
  if next_question:
 
36
  if response_parts:
37
+ response_parts.append("那么," + next_question[0].lower() + next_question[1:])
38
  else:
39
+ response_parts.append(next_question)
40
+
 
41
  if not next_question:
42
  plan = self._generate_persona_enhanced_plan(user_message, session_state)
 
43
  if response_parts:
44
  response_parts.append("\n\n" + plan)
45
  else:
 
52
  return "抱歉,我在处理您的请求时遇到了问题,请稍后再试。"
53
 
54
  def _get_next_question(self, session_state: dict) -> str:
55
+ """根据Persona风格,健壮地获取下一个问题,绝不崩溃。"""
56
  persona_config = self._get_current_persona_config(session_state)
57
+ persona_style = persona_config.get("style", "中立")
58
+
59
+ # --- 核心修复:借鉴session_manager的健壮性检查逻辑 ---
60
+ destination_info = session_state.get("destination") # 可能为 None
61
+ duration_info = session_state.get("duration") # 可能为 None
62
+ budget_info = session_state.get("budget") # 可能为 None
63
+
64
+ # 只有当info是字典时才尝试获取深层数据,否则使用安全默认值
65
+ destination_name = destination_info.get('name', '那里') if isinstance(destination_info, dict) else '那里'
66
+ days = duration_info.get('days', '几') if isinstance(duration_info, dict) else '几'
67
+
68
+ # 依次检查核心信息是否缺失
69
+ if not destination_info:
70
+ if "社交" in persona_style: return "哈喽!准备好去哪里嗨皮了吗?告诉我想去哪个城市,我们来一场刷爆朋友圈的旅行吧!✨"
71
+ if "体验" in persona_style: return "你好,旅行者。为了开启一段独特的深度体验,你心中的目的地是哪里?"
72
  return "您好!为了高效地开始规划,请首先明确您的目的地城市。"
73
 
74
+ if not duration_info:
75
+ if "社交" in persona_style: return f"{destination_name}超棒的!打算和小伙伴们在那玩几天呀?"
76
+ if "体验" in persona_style: return f"感知到了,{destination_name}。你希望在这片土地上沉浸多少个日夜?"
 
 
 
77
  return f"目的地已锁定:{destination_name}。请提供计划的旅行天数。"
78
 
79
+ if not budget_info:
80
+ if "社交" in persona_style: return f"太棒啦,{days}天的行程!这次出去玩,预算大概是多少呀?是经济实惠,还是想来个轻奢体验呢?"
81
+ if "体验" in persona_style: return f"{days}天的探索之旅,听起来很不错。对于这次旅行的开销,你有什么样的构想?"
 
 
 
82
  return f"已记录:行程共{days}天。请明确您的预算范围(例如:经济型、舒适型,或具体金额)。"
83
 
84
  return "" # 所有信息已收集完毕
85
 
86
  def _generate_acknowledgement(self, extracted_info: dict, session_state: dict) -> str:
87
+ # ... (此部分及以下方法与上一版健壮版本相同,无需修改) ...
88
+ if not extracted_info: return ""
 
 
89
  persona_config = self._get_current_persona_config(session_state)
90
  persona_style = persona_config.get("style", "")
 
91
  ack_parts = []
92
  if "destination" in extracted_info:
93
  name = extracted_info['destination'].get('name', '目的地')
94
  if "社交" in persona_style: ack_parts.append(f"目的地锁定{name}!已经开始期待啦!💖")
95
  elif "体验" in persona_style: ack_parts.append(f"我感知到了,{name},一个充满故事的地方")
96
  else: ack_parts.append(f"确认:目的地已记录为{name}")
 
97
  if "duration" in extracted_info:
98
  days = extracted_info['duration'].get('days', '几')
99
  if "社交" in persona_style: ack_parts.append(f"玩{days}天,时间超充裕的")
100
  elif "体验" in persona_style: ack_parts.append(f"{days}个日夜,足够深入探索了")
101
  else: ack_parts.append(f"行程时长已设定为{days}天")
 
102
  if "budget" in extracted_info:
103
+ budget_desc = self._format_budget_info(extracted_info.get('budget'))
104
  if "社交" in persona_style: ack_parts.append(f"{budget_desc}的预算,妥妥的")
105
  elif "体验" in persona_style: ack_parts.append(f"了解,{budget_desc}的投入,追求的是价值而非价格")
106
  else: ack_parts.append(f"预算已明确为{budget_desc}")
 
107
  return ",".join(ack_parts) + "。" if ack_parts else ""
108
 
109
+ def _format_budget_info(self, budget: dict) -> str:
110
+ if not budget or not isinstance(budget, dict): return "未指定"
111
+ if budget.get('amount') and budget.get('currency'): return f"{budget['amount']}{budget['currency']}"
112
+ if budget.get('description'): return budget['description']
113
+ if budget.get('type'):
114
+ type_map = {'economy': '经济型', 'comfortable': '舒适型', 'luxury': '豪华型'}
115
+ return type_map.get(budget['type'], budget['type'])
116
+ return "未指定"
117
+
118
+ # --- 以下方法保持不变 ---
119
  def _generate_persona_enhanced_plan(self, user_message: str, session_state: dict) -> str:
120
  persona_config = self._get_current_persona_config(session_state)
 
 
 
 
 
 
 
 
121
  if self.ai_model.is_available():
122
  prompt = self._build_prompt(session_state, persona_config)
123
  log.info(f"🚀 使用Persona '{persona_config.get('name')}' 构建的Prompt进行生成。")
 
127
  return self._generate_fallback_plan(session_state)
128
 
129
  def _build_prompt(self, session_state: dict, persona_config: dict) -> str:
 
 
 
 
 
 
 
 
130
  template = persona_config.get('prompt_template')
131
+ if not template:
132
+ return self._build_generic_prompt(session_state)
133
+ try:
134
+ format_args = {
135
+ "location": self._safe_get_session_value(session_state, "destination", "name", "未指定"),
136
+ "days": self._safe_get_session_value(session_state, "duration", "days", "未指定"),
137
+ "budget": self._format_budget_info(session_state.get("budget")),
138
+ "date": session_state.get('date', '近期'),
139
+ "user_tags": ", ".join(session_state.get('user_tags', [])),
140
+ "commercial_preference": session_state.get('commercial_preference', '适中'),
141
+ "group_description": session_state.get('group_description', '个人'),
142
+ "tags": ", ".join(session_state.get('tags', []))
143
+ }
144
+ return template.format(**format_args)
145
+ except KeyError as e:
146
+ log.warning(f"Persona模板格式化失败,缺少键: {e}。将使用通用模板。")
147
+ return self._build_generic_prompt(session_state)
148
+
149
+ def _safe_get_session_value(self, session, key1, key2, default):
150
+ """安全地从嵌套的session字典中取值"""
151
+ level1 = session.get(key1)
152
+ if isinstance(level1, dict):
153
+ return level1.get(key2, default)
154
+ return default
155
+
156
+ def _build_generic_prompt(self, session_state: dict) -> str:
157
+ location = self._safe_get_session_value(session_state, "destination", "name", "目的地")
158
+ days = self._safe_get_session_value(session_state, "duration", "days", "几")
159
+ budget_info = self._format_budget_info(session_state.get("budget"))
160
+ return f"你是一个专业的旅游助手。请为用户生成一个详细的旅行计划。\n【基本信息】\n- 目的地:{location}\n- 旅行天数:{days}天\n- 预算:{budget_info}\n【要求】\n- 提供具体的景点推荐和路线安排\n- 包含交通、住宿、餐饮建议\n- 确保所有推荐都在预算范围内\n- 提供实用的旅行贴士\n\n请生成一份实用、详细的旅行计划。"
161
+
162
+ def _generate_fallback_plan(self, session_state: dict) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  persona_config = self._get_current_persona_config(session_state)
164
+ location = self._safe_get_session_value(session_state, "destination", "name", "目的地")
165
+ days = self._safe_get_session_value(session_state, "duration", "days", "")
166
+ budget_info = self._format_budget_info(session_state.get("budget"))
167
  persona_name = persona_config.get('name', '旅行者')
168
  plan = f"为您推荐 {location} {days}天旅行计划:\n\n"
169
  plan += f"👤 旅行者类型:{persona_name}\n"
170
  plan += f"💰 预算范围:{budget_info}\n\n"
171
+ highlights = self._safe_get_session_value(session_state, "destination", "highlights", "精彩景点等待您的探索")
 
172
  plan += f"🎯 主要景点:{highlights}\n\n"
173
+ persona_key = self._safe_get_session_value(session_state, "persona", "key", None)
174
  if persona_key == 'planner': plan += "📋 建议制定详细的每日行程表。\n"
175
  elif persona_key == 'social': plan += "📸 推荐寻找热门打卡点!\n"
176
  elif persona_key == 'experiential': plan += "🎨 建议深入当地社区,寻找地道体验。\n"