Eliot0110 commited on
Commit
e53ed5b
·
1 Parent(s): cd4408f

🚀 fix: 修复导入错误并集成persona系统

Browse files

✨ 修复内容:
- 修正 travel_assistant.py 文件内容错误
- 添加预算识别功能
- 集成persona系统
- 更新API验证器

🐛 解决问题:
- ImportError: cannot import TravelAssistant
- 文件路径配置问题

modules/info_extractor.py CHANGED
@@ -1,119 +1,145 @@
 
1
  import re
2
- from .config_loader import ConfigLoader
3
  from utils.logger import log
4
 
5
  class InfoExtractor:
6
- def __init__(self, config_loader: ConfigLoader):
7
- self.configs = config_loader
8
-
9
- def extract(self, user_input: str) -> dict:
10
- """从用户输入中提取目的地、天数和旅行风格"""
11
  extracted_info = {}
12
- user_lower = user_input.lower()
13
-
14
- try:
15
- # 提取目的地
16
- destination = self._extract_destination(user_lower)
17
- if destination:
18
- extracted_info["destination"] = destination
19
-
20
- # 提取天数
21
- duration = self._extract_duration(user_input)
22
- if duration:
23
- extracted_info["duration"] = duration
24
-
25
- # 提取旅行风格 (persona)
26
- persona = self._extract_persona(user_input)
27
- if persona:
28
- extracted_info["persona"] = persona
29
-
30
- return extracted_info
31
-
32
- except Exception as e:
33
- log.error(f"❌ 信息提取失败: {e}", exc_info=True)
34
- return {}
35
-
36
- def _extract_destination(self, user_lower: str) -> dict:
37
  """提取目的地信息"""
38
- for alias, city_info in self.configs.cities.items():
39
- if alias in user_lower:
40
- return city_info
 
 
 
 
 
 
 
 
 
 
41
  return None
42
-
43
- def _extract_duration(self, user_input: str) -> dict:
44
- """提取天数信息 - 改进版本"""
 
45
  patterns = [
46
- (r'(\d+)\s*天', lambda x: int(x)),
47
- (r'(\d+)\s*日', lambda x: int(x)),
48
- (r'一周|7天|七天', lambda x: 7),
49
- (r'两周|14天|十四天', lambda x: 14),
50
- (r'周末', lambda x: 2),
51
- (r'三天|3天', lambda x: 3),
52
- (r'五天|5天', lambda x: 5),
53
- (r'十天|10天', lambda x: 10),
54
  ]
55
 
56
- for pattern, converter in patterns:
57
- match = re.search(pattern, user_input)
58
  if match:
59
- try:
60
- if match.groups():
61
- days = converter(match.group(1))
62
- else:
63
- days = converter(None)
64
-
65
- if 1 <= days <= 30:
66
- return {"days": days, "description": f"{days}天旅行"}
67
- except (ValueError, TypeError):
68
- continue
69
  return None
70
-
71
- def _extract_persona(self, user_input: str) -> dict:
72
- """提取用户偏好 - 根据实际personas.json结构"""
73
- user_lower = user_input.lower()
74
-
75
- # 检查personas配置
76
- for persona_key, persona_info in self.configs.personas.items():
77
- # 检查persona名称中的关键词
78
- persona_name = persona_info.get('name', '').lower()
79
- # 移除emoji并检查关键词
80
- clean_name = ''.join(char for char in persona_name if char.isalpha() or char.isspace())
81
-
82
- if any(keyword in user_lower for keyword in clean_name.split() if len(keyword) > 1):
83
- return {**persona_info, "key": persona_key} # 添加key方便后续使用
84
-
85
- # 检查风格关键词
86
- style = persona_info.get('style', '').lower()
87
- if any(keyword in user_lower for keyword in style.split('、') if len(keyword) > 1):
88
- return {**persona_info, "key": persona_key}
89
-
90
- # 检查特征描述
91
- characteristics = persona_info.get('characteristics', [])
92
- for char in characteristics:
93
- char_keywords = char.lower().split()[:3] # 取前3个词作为关键词
94
- if any(keyword in user_lower for keyword in char_keywords if len(keyword) > 1):
95
- return {**persona_info, "key": persona_key}
96
 
97
- # 基于关键词的简单映射
98
- keyword_persona_map = {
99
- '规划': 'planner',
100
- '计划': 'planner',
101
- '效率': 'planner',
102
- '预算': 'planner',
103
- '分享': 'social',
104
- '朋友': 'social',
105
- '拍照': 'social',
106
- '打卡': 'social',
107
- '深度': 'experiential',
108
- '文化': 'experiential',
109
- '地道': 'experiential',
110
- '当地': 'experiential'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  }
 
 
 
 
 
 
 
 
 
 
112
 
113
- for keyword, persona_key in keyword_persona_map.items():
114
- if keyword in user_input:
115
- persona_info = self.configs.personas.get(persona_key, {})
116
- if persona_info:
117
- return {**persona_info, "key": persona_key}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
- return None
 
 
 
 
 
 
 
 
1
+ # modules/info_extractor.py - 增强版本(添加预算识别)
2
  import re
 
3
  from utils.logger import log
4
 
5
  class InfoExtractor:
6
+ def __init__(self, config):
7
+ self.config = config
8
+
9
+ def extract(self, message: str) -> dict:
10
+ """从用户消息中提取信息"""
11
  extracted_info = {}
12
+
13
+ # 1. 提取目的地信息
14
+ destination = self._extract_destination(message)
15
+ if destination:
16
+ extracted_info['destination'] = destination
17
+
18
+ # 2. 提取天数信息
19
+ duration = self._extract_duration(message)
20
+ if duration:
21
+ extracted_info['duration'] = duration
22
+
23
+ # 3. 提取预算信息(新增)
24
+ budget = self._extract_budget(message)
25
+ if budget:
26
+ extracted_info['budget'] = budget
27
+
28
+ return extracted_info
29
+
30
+ def _extract_destination(self, message: str) -> dict:
 
 
 
 
 
 
31
  """提取目的地信息"""
32
+ message_lower = message.lower()
33
+
34
+ # 从配置中的城市列表匹配
35
+ for city_key, city_info in self.config.cities.items():
36
+ if city_key in message_lower:
37
+ return {
38
+ 'name': city_info['name'],
39
+ 'country': city_info.get('country', ''),
40
+ 'highlights': city_info.get('highlights', ''),
41
+ 'best_season': city_info.get('best_season', ''),
42
+ 'avg_daily_budget': city_info.get('avg_daily_budget', 100)
43
+ }
44
+
45
  return None
46
+
47
+ def _extract_duration(self, message: str) -> dict:
48
+ """提取旅行天数"""
49
+ # 匹配各种天数表达方式
50
  patterns = [
51
+ r'(\d+)\s*天',
52
+ r'(\d+)\s*日',
53
+ r'(\d+)\s*days?',
54
+ r'玩\s*(\d+)\s*天',
55
+ r'待\s*(\d+)\s*天',
56
+ r'(\d+)\s*个天',
 
 
57
  ]
58
 
59
+ for pattern in patterns:
60
+ match = re.search(pattern, message, re.IGNORECASE)
61
  if match:
62
+ days = int(match.group(1))
63
+ if 1 <= days <= 30: # 合理的天数范围
64
+ return {
65
+ 'days': days,
66
+ 'description': f"{days}天"
67
+ }
68
+
 
 
 
69
  return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ def _extract_budget(self, message: str) -> dict:
72
+ """提取预算信息(新增功能)"""
73
+ message_lower = message.lower()
74
+
75
+ # 1. 匹配具体金额(支持多种货币)
76
+ money_patterns = [
77
+ # 欧元
78
+ (r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:欧元|euros?|eur|€)', 'EUR'),
79
+ # 美元
80
+ (r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:美元|dollars?|usd|\$)', 'USD'),
81
+ # 人民币
82
+ (r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:元|人民币|rmb|¥)', 'CNY'),
83
+ # 英镑
84
+ (r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:英镑|pounds?|gbp|£)', 'GBP'),
85
+ ]
86
+
87
+ for pattern, currency in money_patterns:
88
+ match = re.search(pattern, message_lower)
89
+ if match:
90
+ amount_str = match.group(1).replace(',', '')
91
+ amount = float(amount_str)
92
+ return {
93
+ 'amount': amount,
94
+ 'currency': currency,
95
+ 'type': self._categorize_budget_amount(amount, currency),
96
+ 'description': f"{amount}{currency}"
97
+ }
98
+
99
+ # 2. 匹配预算类型关键词
100
+ budget_keywords = {
101
+ 'economy': ['经济', '便宜', '省钱', '穷游', '节约', '预算有限', '学生'],
102
+ 'comfortable': ['舒适', '中等', '适中', '一般', '普通', '正常'],
103
+ 'luxury': ['豪华', '奢华', '高端', '贵', '不差钱', '任性', '土豪']
104
  }
105
+
106
+ for budget_type, keywords in budget_keywords.items():
107
+ for keyword in keywords:
108
+ if keyword in message_lower:
109
+ return {
110
+ 'type': budget_type,
111
+ 'description': self._get_budget_type_description(budget_type)
112
+ }
113
+
114
+ return None
115
 
116
+ def _categorize_budget_amount(self, amount: float, currency: str) -> str:
117
+ """根据金额和货币分类预算类型"""
118
+ # 将所有货币转换为欧元基准进行分类
119
+ eur_rates = {
120
+ 'EUR': 1.0,
121
+ 'USD': 0.85, # 1 USD ≈ 0.85 EUR
122
+ 'CNY': 0.13, # 1 CNY ≈ 0.13 EUR
123
+ 'GBP': 1.15 # 1 GBP ≈ 1.15 EUR
124
+ }
125
+
126
+ eur_amount = amount * eur_rates.get(currency, 1.0)
127
+
128
+ # 按每日预算分类(假设7天行程)
129
+ daily_budget = eur_amount / 7
130
+
131
+ if daily_budget < 50:
132
+ return 'economy'
133
+ elif daily_budget < 150:
134
+ return 'comfortable'
135
+ else:
136
+ return 'luxury'
137
 
138
+ def _get_budget_type_description(self, budget_type: str) -> str:
139
+ """获取预算类型的描述"""
140
+ descriptions = {
141
+ 'economy': '经济型预算',
142
+ 'comfortable': '舒适型预算',
143
+ 'luxury': '豪华型预算'
144
+ }
145
+ return descriptions.get(budget_type, '中等预算')
modules/response_generator.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import json
2
  import os
3
  from .ai_model import AIModel
@@ -27,7 +28,7 @@ class ResponseGenerator:
27
 
28
  # 1. 检查是否需要收集目的地
29
  if not session_state.get("destination"):
30
- return "听起来很棒!你想去欧洲的哪个城市呢?比如巴黎, 罗马, 巴塞罗那?"
31
 
32
  # 2. 检查是否需要收集天数
33
  if not session_state.get("duration"):
@@ -40,7 +41,18 @@ class ResponseGenerator:
40
  # 4. persona由前端传入,这里检查是否存在
41
  # 注意:如果前端没传persona,可以询问或使用默认值
42
  if not session_state.get("persona"):
43
- return "请告诉我您更偏向哪种旅行风格:高效规划型、社交分享型,还是深度体验型?"
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  # 【增强阶段】信息收集完毕,现在用知识库和persona增强回答质量
46
  return self._generate_persona_enhanced_plan(user_message, session_state)
 
1
+ # modules/response_generator.py - 完整版本(含persona+预算)
2
  import json
3
  import os
4
  from .ai_model import AIModel
 
28
 
29
  # 1. 检查是否需要收集目的地
30
  if not session_state.get("destination"):
31
+ return "Hi!你想去欧洲的哪个城市呢?比如巴黎, 罗马, 巴塞罗那?"
32
 
33
  # 2. 检查是否需要收集天数
34
  if not session_state.get("duration"):
 
41
  # 4. persona由前端传入,这里检查是否存在
42
  # 注意:如果前端没传persona,可以询问或使用默认值
43
  if not session_state.get("persona"):
44
+ separator = '*' * 60
45
+
46
+ persona_selection_message = f"""请告诉我您更偏向哪种旅行风格:高效规划型、社交分享型,还是深度体验型?
47
+
48
+ {separator}
49
+
50
+ 🗓️ **高效规划型** - 注重时间安排和预算控制,喜欢详细的行程规划
51
+
52
+ 🤝 **社交分享型** - 重视与朋友分享,喜欢拍照打卡和热闹的体验
53
+
54
+ 🎨 **深度体验型** - 追求地道文化体验,避开商业化景点"""
55
+ return persona_selection_message
56
 
57
  # 【增强阶段】信息收集完毕,现在用知识库和persona增强回答质量
58
  return self._generate_persona_enhanced_plan(user_message, session_state)
modules/travel_assistant.py CHANGED
@@ -1,323 +1,57 @@
1
- # modules/response_generator.py - 完整版本(含persona+预算)
2
- import json
3
- import os
4
  from .ai_model import AIModel
5
  from .knowledge_base import KnowledgeBase
 
 
 
6
  from utils.logger import log
7
 
8
- class ResponseGenerator:
9
- def __init__(self, ai_model: AIModel, knowledge_base: KnowledgeBase):
10
- self.ai_model = ai_model
11
- self.kb = knowledge_base
12
- self.personas = self._load_personas()
13
-
14
- def _load_personas(self):
15
- """加载personas配置"""
16
- try:
17
- personas_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'personas.json')
18
- with open(personas_path, 'r', encoding='utf-8') as f:
19
- data = json.load(f)
20
- return data.get('personas', {})
21
- except Exception as e:
22
- log.error(f"❌ 加载personas.json失败: {e}")
23
- return {}
24
-
25
- def generate(self, user_message: str, session_state: dict) -> str:
26
- try:
27
- # 【主流程】按顺序收集信息:目的地 → 天数 → 预算 → persona(可选)
28
-
29
- # 1. 检查是否需要收集目的地
30
- if not session_state.get("destination"):
31
- return "Hi!你想去欧洲的哪个城市呢?比如巴黎, 罗马, 巴塞罗那?"
32
-
33
- # 2. 检查是否需要收集天数
34
- if not session_state.get("duration"):
35
- return f"好的,{session_state['destination']['name']}是个很棒的选择!你计划玩几天呢?"
36
-
37
- # 3. 检查是否需要收集预算
38
- if not session_state.get("budget"):
39
- return f"了解!{session_state['duration']['days']}天的行程。为了给您更合适的建议,请问您的预算大概是多少呢?(比如:2000欧元,或者经济型/舒适型/豪华型)"
40
-
41
- # 4. persona由前端传入,这里检查是否存在
42
- # 注意:如果前端没传persona,可以询问或使用默认值
43
- if not session_state.get("persona"):
44
- separator = '*' * 60
45
-
46
- persona_selection_message = f"""请告诉我您更偏向哪种旅行风格:高效规划型、社交分享型,还是深度体验型?
47
-
48
- {separator}
49
-
50
- 🗓️ **高效规划型** - 注重时间安排和预算控制,喜欢详细的行程规划
51
-
52
- 🤝 **社交分享型** - 重视与朋友分享,喜欢拍照打卡和热闹的体验
53
-
54
- 🎨 **深度体验型** - 追求地道文化体验,避开商业化景点"""
55
- return persona_selection_message
56
-
57
- # 【增强阶段】信息收集完毕,现在用知识库和persona增强回答质量
58
- return self._generate_persona_enhanced_plan(user_message, session_state)
59
-
60
- except Exception as e:
61
- log.error(f"❌ 响应生成失败: {e}", exc_info=True)
62
- return "抱歉,我在处理您的请求时遇到了问题,请稍后再试。"
63
-
64
- def _generate_persona_enhanced_plan(self, user_message: str, session_state: dict) -> str:
65
- """根据persona和完整信息生成定制化的旅行计划"""
66
-
67
- # 1. 获取知识库上下文
68
- search_query = self._build_search_query(session_state)
69
- relevant_knowledge = self.kb.search(search_query)
70
- knowledge_context = self._format_knowledge_context(relevant_knowledge)
71
-
72
- # 2. 根据persona构建定制化prompt
73
- persona_prompt = self._build_persona_enhanced_prompt(session_state, knowledge_context)
74
-
75
- # 3. 使用AI生成回答
76
- if self.ai_model.is_available():
77
- return self.ai_model.generate(user_message, persona_prompt)
78
- else:
79
- return self._generate_fallback_plan(session_state, knowledge_context)
80
-
81
- def _build_persona_enhanced_prompt(self, session_state: dict, knowledge_context: str = "") -> str:
82
- """根据persona构建增强的prompt"""
83
- destination = session_state.get("destination", {})
84
- duration = session_state.get("duration", {})
85
- budget = session_state.get("budget", {})
86
- persona = session_state.get("persona", {})
87
-
88
- # 基础信息
89
- location = destination.get('name', '目的地')
90
- days = duration.get('days', '几')
91
- budget_info = self._format_budget_info(budget)
92
-
93
- # 获取persona配置
94
- persona_key = persona.get('key')
95
- if not persona_key or persona_key not in self.personas:
96
- # 如果没有有效persona,使用通用prompt
97
- return self._build_generic_prompt(session_state, knowledge_context)
98
-
99
- persona_config = self.personas[persona_key]
100
-
101
- # 使用personas.json中的prompt_template
102
- persona_template = persona_config.get('prompt_template', '')
103
-
104
- # 替换模板中的变量
105
- enhanced_prompt = persona_template.format(
106
- location=location,
107
- days=days,
108
- date="近期", # 可以从session中获取更具体的日期
109
- user_tags="", # 可以从用户偏好中提取
110
- commercial_preference="适中", # 可以从session中获取
111
- group_description="个人/朋友", # 可以从session中获取
112
- budget=budget_info, # 使用格式化的预算信息
113
- tags="" # 可以从session中获取
114
- )
115
-
116
- # 添加知识库上下文
117
- if knowledge_context:
118
- enhanced_prompt += f"\n\n【背景知识】\n{knowledge_context}"
119
-
120
- # 添加预算约束信息
121
- enhanced_prompt += f"\n\n【预算约束】\n用户预算:{budget_info},请确保所有推荐都在预算范围内。"
122
-
123
- # 添加persona特定的指导原则
124
- characteristics = persona_config.get('characteristics', [])
125
- if characteristics:
126
- enhanced_prompt += f"\n\n【用户特征】\n" + "\n".join([f"- {char}" for char in characteristics])
127
-
128
- recommendation_strategy = persona_config.get('recommendation_strategy', [])
129
- if recommendation_strategy:
130
- enhanced_prompt += f"\n\n【推荐策略】\n" + "\n".join([f"- {strategy}" for strategy in recommendation_strategy])
131
-
132
- tone_guidelines = persona_config.get('tone', [])
133
- if tone_guidelines:
134
- enhanced_prompt += f"\n\n【语言风格】\n" + "\n".join([f"- {tone}" for tone in tone_guidelines])
135
-
136
- return enhanced_prompt
137
-
138
- def _format_budget_info(self, budget: dict) -> str:
139
- """格式化预算信息"""
140
- if not budget:
141
- return "中等预算"
142
-
143
- # 如果有具体金额
144
- if budget.get('amount') and budget.get('currency'):
145
- return f"{budget['amount']}{budget['currency']}"
146
-
147
- # 如果有预算类型
148
- if budget.get('type'):
149
- budget_type_map = {
150
- 'economy': '经济型预算',
151
- 'comfortable': '舒适型预算',
152
- 'luxury': '豪华型预算'
153
  }
154
- return budget_type_map.get(budget['type'], budget['type'])
155
-
156
- # 如果有预算范围
157
- if budget.get('range'):
158
- return budget['range']
159
-
160
- return "中等预算"
161
-
162
- def _build_generic_prompt(self, session_state: dict, knowledge_context: str = "") -> str:
163
- """构建通用prompt(当没有persona时使用)"""
164
- destination = session_state.get("destination", {})
165
- duration = session_state.get("duration", {})
166
- budget = session_state.get("budget", {})
167
-
168
- location = destination.get('name', '目的地')
169
- days = duration.get('days', '几')
170
- budget_info = self._format_budget_info(budget)
171
-
172
- prompt = f"""你是一个专业的旅游助手。请为用户生成一个详细的旅行计划。
173
 
174
- 【基本信息】
175
- - 目的地:{location}
176
- - 旅行天数:{days}天
177
- - 预算:{budget_info}
178
-
179
- 【要求】
180
- - 提供具体的景点推荐和路线安排
181
- - 包含交通、住宿、餐饮建议
182
- - 确保所有推荐都在预算范围内
183
- - 提供实用的旅行贴士"""
184
-
185
- if knowledge_context:
186
- prompt += f"\n\n【背景信息】\n{knowledge_context}"
187
-
188
- prompt += "\n\n请生成一份实用���详细的旅行计划。"
189
-
190
- return prompt
191
-
192
- def _build_search_query(self, session_state: dict) -> str:
193
- """构建知识库搜索查询"""
194
- destination = session_state.get("destination", {})
195
- persona = session_state.get("persona", {})
196
- budget = session_state.get("budget", {})
197
-
198
- query_parts = []
199
-
200
- # 添加目的地
201
- if destination.get('name'):
202
- query_parts.append(destination['name'])
203
-
204
- # 添加persona类型
205
- if persona.get('key'):
206
- query_parts.append(persona['key'])
207
-
208
- # 添加预算类型
209
- if budget.get('type'):
210
- query_parts.append(budget['type'])
211
 
212
- return " ".join(query_parts) if query_parts else "欧洲旅行"
 
 
 
 
213
 
214
- def _generate_fallback_plan(self, session_state: dict, knowledge_context: str = "") -> str:
215
- """AI不可用时的备用计划生成(现在包含预算和persona信息)"""
216
- destination = session_state.get("destination", {})
217
- duration = session_state.get("duration", {})
218
- budget = session_state.get("budget", {})
219
- persona = session_state.get("persona", {})
220
 
221
- location = destination.get('name', '目的地')
222
- days = duration.get('days', '几')
223
- budget_info = self._format_budget_info(budget)
224
- persona_name = persona.get('name', '旅行者')
225
 
226
- plan = f"为您推荐 {location} {days}天旅行计划:\n\n"
 
227
 
228
- # 用户信息概述
229
- plan += f"👤 旅行者类型:{persona_name}\n"
230
- plan += f"💰 预算范围:{budget_info}\n\n"
231
-
232
- # 优先使用知识库信息
233
- if knowledge_context and "相关旅游知识" in knowledge_context:
234
- plan += f"📚 {knowledge_context}\n\n"
235
-
236
- # 使用cities.json中的基础信息
237
- highlights = destination.get('highlights', '精彩景点等待您的探索')
238
- plan += f"🎯 主要景点:{highlights}\n\n"
239
-
240
- best_season = destination.get('best_season', '全年适宜')
241
- plan += f"🌤️ 最佳旅行时间:{best_season}\n\n"
242
-
243
- avg_budget = destination.get('avg_daily_budget', 100)
244
- plan += f"💰 日均预算参考:约{avg_budget}欧元/天\n\n"
245
-
246
- # 根据persona调整建议风格
247
- if persona.get('key') == 'planner':
248
- plan += "📋 建议制定详细的每日行程表,包含具体时间安排和备选方案。\n\n"
249
- elif persona.get('key') == 'social':
250
- plan += "📸 推荐寻找热门打卡点,准备好相机记录美好时光!\n\n"
251
- elif persona.get('key') == 'experiential':
252
- plan += "🎨 建议深入当地社区,寻找地道的文化体验和小众景点。\n\n"
253
-
254
- # 预算相关建议
255
- if budget.get('type') == 'economy':
256
- plan += "💡 经济型贴士:考虑使用公共交通,选择青旅或民宿,寻找当地平价美食。\n\n"
257
- elif budget.get('type') == 'luxury':
258
- plan += "✨ 豪华体验:可以考虑五星酒店、私人导览和米其林餐厅。\n\n"
259
-
260
- plan += "如需更详细的个性化规划,请告诉我您的具体需求!"
261
- return plan
262
-
263
- def _format_knowledge_context(self, knowledge_items: list) -> str:
264
- """格式化知识库内容作为上下文"""
265
- if not knowledge_items:
266
- return "基于一般旅游建议"
267
-
268
- try:
269
- # 只使用最相关的一条知识
270
- item = knowledge_items[0]
271
- knowledge = item.get('knowledge', {})
272
- travel_knowledge = knowledge.get('travel_knowledge', {})
273
-
274
- context_parts = []
275
-
276
- # 1. 获取目的地信息
277
- destination_info = travel_knowledge.get('destination_info', {})
278
- if destination_info:
279
- primary_destinations = destination_info.get('primary_destinations', [])
280
- if primary_destinations:
281
- context_parts.append(f"目的地: {', '.join(primary_destinations)}")
282
-
283
- recommended_duration = destination_info.get('recommended_duration')
284
- if recommended_duration:
285
- context_parts.append(f"推荐天数: {recommended_duration}天")
286
-
287
- # 2. 获取预算信息
288
- budget_analysis = travel_knowledge.get('budget_analysis', {})
289
- if budget_analysis:
290
- total_budget = budget_analysis.get('total_budget_range')
291
- if total_budget:
292
- context_parts.append(f"预算参考: {total_budget}")
293
-
294
- daily_average = budget_analysis.get('daily_average')
295
- if daily_average:
296
- context_parts.append(f"日均预算: {daily_average}")
297
-
298
- # 3. 获取专业见解
299
- professional_insights = travel_knowledge.get('professional_insights', {})
300
- if professional_insights:
301
- common_mistakes = professional_insights.get('common_mistakes', [])
302
- if common_mistakes:
303
- context_parts.append(f"避免误区: {', '.join(common_mistakes[:2])}")
304
-
305
- insider_secrets = professional_insights.get('insider_secrets', [])
306
- if insider_secrets:
307
- context_parts.append(f"内行贴士: {', '.join(insider_secrets[:2])}")
308
-
309
- seasonal_info = professional_insights.get('seasonal_considerations', {})
310
- if seasonal_info:
311
- best_months = seasonal_info.get('best_months', [])
312
- if best_months:
313
- context_parts.append(f"最佳时间: {', '.join(best_months)}")
314
-
315
- # 4. 拼接结果
316
- if context_parts:
317
- return "相关旅游知识:\n- " + "\n- ".join(context_parts)
318
- else:
319
- return "基于专业旅游建议"
320
-
321
- except Exception as e:
322
- log.error(f"❌ 格式化知识库上下文失败: {e}", exc_info=True)
323
- return "基于一般旅游建议"
 
1
+ # modules/travel_assistant.py - 正确版本
2
+ from .config_loader import ConfigLoader
 
3
  from .ai_model import AIModel
4
  from .knowledge_base import KnowledgeBase
5
+ from .info_extractor import InfoExtractor
6
+ from .session_manager import SessionManager
7
+ from .response_generator import ResponseGenerator
8
  from utils.logger import log
9
 
10
+ class TravelAssistant:
11
+ def __init__(self):
12
+ # 依赖注入:在这里实例化所有需要的模块
13
+ log.info("开始初始化 Travel Assistant 核心模块...")
14
+ self.config = ConfigLoader()
15
+ self.kb = KnowledgeBase()
16
+ self.ai_model = AIModel()
17
+ self.session_manager = SessionManager()
18
+ self.info_extractor = InfoExtractor(self.config)
19
+ self.response_generator = ResponseGenerator(self.ai_model, self.kb)
20
+ log.info("✅ Travel Assistant 核心模块全部初始化完成!")
21
+
22
+ def chat(self, message: str, session_id: str, history: list, persona_key: str = None):
23
+ # 1. 获取或创建会话
24
+ session_state = self.session_manager.get_or_create_session(session_id)
25
+ current_session_id = session_state['session_id']
26
+
27
+ # 2. 如果前端传来了persona,直接设置到session中
28
+ if persona_key and persona_key in self.config.personas:
29
+ persona_info = {
30
+ 'key': persona_key,
31
+ 'name': self.config.personas[persona_key]['name'],
32
+ 'style': self.config.personas[persona_key]['style'],
33
+ 'source': 'frontend_selection' # 标记来源
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  }
35
+ self.session_manager.update_session(current_session_id, {'persona': persona_info})
36
+ session_state = self.session_manager.get_or_create_session(current_session_id)
37
+ log.info(f"设置用户persona: {persona_info['name']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ # 3. 从用户输入中提取其他信息(目的地、天数、预算)
40
+ extracted_info = self.info_extractor.extract(message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ # 4. 更新会话状态
43
+ if extracted_info:
44
+ self.session_manager.update_session(current_session_id, extracted_info)
45
+ # 重新获取更新后的状态
46
+ session_state = self.session_manager.get_or_create_session(current_session_id)
47
 
48
+ # 5. 生成回复
49
+ bot_response = self.response_generator.generate(message, session_state)
 
 
 
 
50
 
51
+ # 6. 格式化状态信息用于前端显示
52
+ status_info = self.session_manager.format_session_info(session_state)
 
 
53
 
54
+ # 7. 更新对话历史
55
+ new_history = history + [[message, bot_response]]
56
 
57
+ return bot_response, current_session_id, status_info, new_history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/validators.py CHANGED
@@ -1,3 +1,4 @@
 
1
  from pydantic import BaseModel
2
  from typing import List, Optional, Dict, Any
3
 
 
1
+ # utils/validators.py - 更新版本
2
  from pydantic import BaseModel
3
  from typing import List, Optional, Dict, Any
4