Spaces:
Sleeping
Sleeping
fix: chat logic
Browse files- modules/info_extractor.py +42 -37
- modules/response_generator.py +110 -122
modules/info_extractor.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
# modules/info_extractor.py - 增强版本(添加预算识别)
|
| 2 |
import re
|
| 3 |
from utils.logger import log
|
| 4 |
|
|
@@ -7,33 +6,42 @@ class InfoExtractor:
|
|
| 7 |
self.config = config
|
| 8 |
|
| 9 |
def extract(self, message: str) -> dict:
|
| 10 |
-
"""从用户消息中提取信息"""
|
| 11 |
extracted_info = {}
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
# 1. 提取目的地信息
|
| 14 |
-
destination = self._extract_destination(
|
| 15 |
if destination:
|
| 16 |
extracted_info['destination'] = destination
|
|
|
|
| 17 |
|
| 18 |
# 2. 提取天数信息
|
| 19 |
-
duration = self._extract_duration(
|
| 20 |
if duration:
|
| 21 |
extracted_info['duration'] = duration
|
|
|
|
| 22 |
|
| 23 |
-
# 3.
|
| 24 |
-
budget = self._extract_budget(
|
| 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', ''),
|
|
@@ -42,46 +50,50 @@ class InfoExtractor:
|
|
| 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'
|
| 52 |
-
r'(\d+)\s
|
| 53 |
-
r'(\d+)\s
|
| 54 |
-
r'(\d+)\s
|
| 55 |
-
r'
|
| 56 |
-
r'
|
| 57 |
-
r'(\d+)\s*个天',
|
| 58 |
]
|
| 59 |
|
| 60 |
-
for pattern in patterns:
|
| 61 |
-
match = re.search(pattern,
|
| 62 |
if match:
|
| 63 |
days = int(match.group(1))
|
| 64 |
if 1 <= days <= 30: # 合理的天数范围
|
|
|
|
| 65 |
return {
|
| 66 |
'days': days,
|
| 67 |
'description': f"{days}天"
|
| 68 |
}
|
|
|
|
|
|
|
| 69 |
|
|
|
|
| 70 |
return None
|
| 71 |
|
| 72 |
def _extract_budget(self, message: str) -> dict:
|
| 73 |
-
"""
|
| 74 |
message_lower = message.lower()
|
|
|
|
| 75 |
|
| 76 |
-
# 1.
|
| 77 |
money_patterns = [
|
| 78 |
-
# 欧元
|
| 79 |
(r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:欧元|euros?|eur|€)', 'EUR'),
|
| 80 |
-
# 美元
|
| 81 |
(r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:美元|dollars?|usd|\$)', 'USD'),
|
| 82 |
-
# 人民币
|
| 83 |
(r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:元|人民币|rmb|¥)', 'CNY'),
|
| 84 |
-
# 英镑
|
| 85 |
(r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:英镑|pounds?|gbp|£)', 'GBP'),
|
| 86 |
]
|
| 87 |
|
|
@@ -90,11 +102,12 @@ class InfoExtractor:
|
|
| 90 |
if match:
|
| 91 |
amount_str = match.group(1).replace(',', '')
|
| 92 |
amount = float(amount_str)
|
|
|
|
| 93 |
return {
|
| 94 |
'amount': amount,
|
| 95 |
'currency': currency,
|
| 96 |
'type': self._categorize_budget_amount(amount, currency),
|
| 97 |
-
'description': f"{amount}{currency}"
|
| 98 |
}
|
| 99 |
|
| 100 |
# 2. 匹配预算类型关键词
|
|
@@ -107,26 +120,19 @@ class InfoExtractor:
|
|
| 107 |
for budget_type, keywords in budget_keywords.items():
|
| 108 |
for keyword in keywords:
|
| 109 |
if keyword in message_lower:
|
|
|
|
| 110 |
return {
|
| 111 |
'type': budget_type,
|
| 112 |
'description': self._get_budget_type_description(budget_type)
|
| 113 |
}
|
| 114 |
|
|
|
|
| 115 |
return None
|
| 116 |
|
|
|
|
| 117 |
def _categorize_budget_amount(self, amount: float, currency: str) -> str:
|
| 118 |
-
|
| 119 |
-
# 将所有货币转换为欧元基准进行分类
|
| 120 |
-
eur_rates = {
|
| 121 |
-
'EUR': 1.0,
|
| 122 |
-
'USD': 0.85, # 1 USD ≈ 0.85 EUR
|
| 123 |
-
'CNY': 0.13, # 1 CNY ≈ 0.13 EUR
|
| 124 |
-
'GBP': 1.15 # 1 GBP ≈ 1.15 EUR
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
eur_amount = amount * eur_rates.get(currency, 1.0)
|
| 128 |
-
|
| 129 |
-
# 按每日预算分类(假设7天行程)
|
| 130 |
daily_budget = eur_amount / 7
|
| 131 |
|
| 132 |
if daily_budget < 50:
|
|
@@ -137,7 +143,6 @@ class InfoExtractor:
|
|
| 137 |
return 'luxury'
|
| 138 |
|
| 139 |
def _get_budget_type_description(self, budget_type: str) -> str:
|
| 140 |
-
"""获取预算类型的描述"""
|
| 141 |
descriptions = {
|
| 142 |
'economy': '经济型预算',
|
| 143 |
'comfortable': '舒适型预算',
|
|
|
|
|
|
|
| 1 |
import re
|
| 2 |
from utils.logger import log
|
| 3 |
|
|
|
|
| 6 |
self.config = config
|
| 7 |
|
| 8 |
def extract(self, message: str) -> dict:
|
| 9 |
+
"""从用户消息中提取信息 - 调试版"""
|
| 10 |
extracted_info = {}
|
| 11 |
+
message_clean = message.strip()
|
| 12 |
+
|
| 13 |
+
log.info(f"🔍 开始提取信息,消息: '{message_clean}'")
|
| 14 |
|
| 15 |
# 1. 提取目的地信息
|
| 16 |
+
destination = self._extract_destination(message_clean)
|
| 17 |
if destination:
|
| 18 |
extracted_info['destination'] = destination
|
| 19 |
+
log.info(f"✅ 提取到目的地: {destination['name']}")
|
| 20 |
|
| 21 |
# 2. 提取天数信息
|
| 22 |
+
duration = self._extract_duration(message_clean)
|
| 23 |
if duration:
|
| 24 |
extracted_info['duration'] = duration
|
| 25 |
+
log.info(f"✅ 提取到天数: {duration['days']}天")
|
| 26 |
|
| 27 |
+
# 3. 提取预算信息
|
| 28 |
+
budget = self._extract_budget(message_clean)
|
| 29 |
if budget:
|
| 30 |
extracted_info['budget'] = budget
|
| 31 |
+
log.info(f"✅ 提取到预算: {budget.get('description', '未知')}")
|
| 32 |
|
| 33 |
+
log.info(f"📋 最终提取结果: {list(extracted_info.keys())}")
|
| 34 |
return extracted_info
|
| 35 |
|
| 36 |
def _extract_destination(self, message: str) -> dict:
|
| 37 |
+
"""提取目的地信息 - 增强日志"""
|
| 38 |
message_lower = message.lower()
|
| 39 |
+
log.info(f"🏙️ 检查目的地,消息: '{message_lower}'")
|
| 40 |
|
| 41 |
# 从配置中的城市列表匹配
|
| 42 |
for city_key, city_info in self.config.cities.items():
|
| 43 |
if city_key in message_lower:
|
| 44 |
+
log.info(f"✅ 匹配到城市key: '{city_key}' -> {city_info['name']}")
|
| 45 |
return {
|
| 46 |
'name': city_info['name'],
|
| 47 |
'country': city_info.get('country', ''),
|
|
|
|
| 50 |
'avg_daily_budget': city_info.get('avg_daily_budget', 100)
|
| 51 |
}
|
| 52 |
|
| 53 |
+
log.info("❌ 未匹配到任何目的地")
|
| 54 |
return None
|
| 55 |
|
| 56 |
def _extract_duration(self, message: str) -> dict:
|
| 57 |
+
"""修复天数提取逻辑"""
|
| 58 |
+
message_clean = message.strip()
|
| 59 |
+
log.info(f"⏰ 检查天数,消息: '{message_clean}'")
|
| 60 |
+
|
| 61 |
+
# 修复正则表达式顺序和逻辑
|
| 62 |
patterns = [
|
| 63 |
+
r'(\d+)\s*天', # "3天", "5 天"
|
| 64 |
+
r'(\d+)\s*日', # "3日", "5 日"
|
| 65 |
+
r'(\d+)\s*days?', # "3day", "5days"
|
| 66 |
+
r'玩\s*(\d+)\s*天', # "玩3天"
|
| 67 |
+
r'待\s*(\d+)\s*天', # "待3天"
|
| 68 |
+
r'^(\d+)$', # 纯数字 "3" (放在最后)
|
|
|
|
| 69 |
]
|
| 70 |
|
| 71 |
+
for i, pattern in enumerate(patterns):
|
| 72 |
+
match = re.search(pattern, message_clean, re.IGNORECASE)
|
| 73 |
if match:
|
| 74 |
days = int(match.group(1))
|
| 75 |
if 1 <= days <= 30: # 合理的天数范围
|
| 76 |
+
log.info(f"✅ 匹配模式{i+1}: '{pattern}' -> {days}天")
|
| 77 |
return {
|
| 78 |
'days': days,
|
| 79 |
'description': f"{days}天"
|
| 80 |
}
|
| 81 |
+
else:
|
| 82 |
+
log.info(f"❌ 天数超出范围: {days}")
|
| 83 |
|
| 84 |
+
log.info("❌ 未匹配到任何天数")
|
| 85 |
return None
|
| 86 |
|
| 87 |
def _extract_budget(self, message: str) -> dict:
|
| 88 |
+
"""提取预算信息 - 增强日志"""
|
| 89 |
message_lower = message.lower()
|
| 90 |
+
log.info(f"💰 检查预算,消息: '{message_lower}'")
|
| 91 |
|
| 92 |
+
# 1. 匹配具体金额
|
| 93 |
money_patterns = [
|
|
|
|
| 94 |
(r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:欧元|euros?|eur|€)', 'EUR'),
|
|
|
|
| 95 |
(r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:美元|dollars?|usd|\$)', 'USD'),
|
|
|
|
| 96 |
(r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:元|人民币|rmb|¥)', 'CNY'),
|
|
|
|
| 97 |
(r'(\d+(?:,\d{3})*(?:\.\d{2})?)\s*(?:英镑|pounds?|gbp|£)', 'GBP'),
|
| 98 |
]
|
| 99 |
|
|
|
|
| 102 |
if match:
|
| 103 |
amount_str = match.group(1).replace(',', '')
|
| 104 |
amount = float(amount_str)
|
| 105 |
+
log.info(f"✅ 匹配到金额: {amount}{currency}")
|
| 106 |
return {
|
| 107 |
'amount': amount,
|
| 108 |
'currency': currency,
|
| 109 |
'type': self._categorize_budget_amount(amount, currency),
|
| 110 |
+
'description': f"{int(amount)}{currency}"
|
| 111 |
}
|
| 112 |
|
| 113 |
# 2. 匹配预算类型关键词
|
|
|
|
| 120 |
for budget_type, keywords in budget_keywords.items():
|
| 121 |
for keyword in keywords:
|
| 122 |
if keyword in message_lower:
|
| 123 |
+
log.info(f"✅ 匹配到预算类型: '{keyword}' -> {budget_type}")
|
| 124 |
return {
|
| 125 |
'type': budget_type,
|
| 126 |
'description': self._get_budget_type_description(budget_type)
|
| 127 |
}
|
| 128 |
|
| 129 |
+
log.info("❌ 未匹配到任何预算信息")
|
| 130 |
return None
|
| 131 |
|
| 132 |
+
# 其他方法保持不变
|
| 133 |
def _categorize_budget_amount(self, amount: float, currency: str) -> str:
|
| 134 |
+
eur_rates = {'EUR': 1.0, 'USD': 0.85, 'CNY': 0.13, 'GBP': 1.15}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
eur_amount = amount * eur_rates.get(currency, 1.0)
|
|
|
|
|
|
|
| 136 |
daily_budget = eur_amount / 7
|
| 137 |
|
| 138 |
if daily_budget < 50:
|
|
|
|
| 143 |
return 'luxury'
|
| 144 |
|
| 145 |
def _get_budget_type_description(self, budget_type: str) -> str:
|
|
|
|
| 146 |
descriptions = {
|
| 147 |
'economy': '经济型预算',
|
| 148 |
'comfortable': '舒适型预算',
|
modules/response_generator.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
| 1 |
-
# modules/response_generator.py - 完整版本(含persona+预算)
|
| 2 |
import json
|
| 3 |
import os
|
| 4 |
from .ai_model import AIModel
|
| 5 |
-
from pathlib import Path
|
| 6 |
from .knowledge_base import KnowledgeBase
|
| 7 |
from utils.logger import log
|
| 8 |
|
|
@@ -14,184 +12,174 @@ class ResponseGenerator:
|
|
| 14 |
|
| 15 |
def _load_personas(self):
|
| 16 |
"""加载personas配置"""
|
| 17 |
-
personas_path =
|
| 18 |
with open(personas_path, 'r', encoding='utf-8') as f:
|
| 19 |
data = json.load(f)
|
| 20 |
return data.get('personas', {})
|
| 21 |
|
| 22 |
def generate(self, user_message: str, session_state: dict, extracted_info: dict) -> str:
|
| 23 |
-
"""
|
| 24 |
-
生成智能响应。
|
| 25 |
-
这个方法现在更加灵活,能够处理信息的修改,并按优先级提问。
|
| 26 |
-
|
| 27 |
-
Args:
|
| 28 |
-
user_message (str): 用户的原始消息。
|
| 29 |
-
session_state (dict): 当前完整的会话状态。
|
| 30 |
-
extracted_info (dict): 从当前用户消息中新提取的信息。
|
| 31 |
-
"""
|
| 32 |
try:
|
| 33 |
response_parts = []
|
| 34 |
|
| 35 |
-
# 1.
|
| 36 |
-
# 这是解决“修改信息”问题的关键。
|
| 37 |
acknowledgement = self._generate_acknowledgement(extracted_info)
|
| 38 |
if acknowledgement:
|
| 39 |
response_parts.append(acknowledgement)
|
| 40 |
|
| 41 |
-
# 2.
|
| 42 |
-
|
| 43 |
-
if
|
| 44 |
-
|
| 45 |
-
response_parts.append(question)
|
| 46 |
-
|
| 47 |
-
elif not session_state.get("duration"):
|
| 48 |
-
question = f"好的,{session_state['destination']['name']}是个很棒的选择!你计划玩几天呢?"
|
| 49 |
-
response_parts.append(question)
|
| 50 |
-
|
| 51 |
-
elif not session_state.get("budget"):
|
| 52 |
-
question = f"了解!{session_state['duration']['days']}天的行程。为了给您更合适的建议,请问您的预算大概是多少呢?(比如:2000欧元,或经济型/舒适型/豪华型)"
|
| 53 |
-
response_parts.append(question)
|
| 54 |
-
|
| 55 |
-
elif not session_state.get("persona"):
|
| 56 |
-
separator = '*' * 60
|
| 57 |
-
|
| 58 |
-
question = f"""请告诉我您更偏向哪种旅行风格:高效规划型、社交分享型,还是深度体验型?
|
| 59 |
-
|
| 60 |
-
{separator}
|
| 61 |
-
|
| 62 |
-
🗓️ **高效规划型** - 注重时间安排和预算控制,喜欢详细的行程规划
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
🎨 **深度体验型** - 追求地道文化体验,避开商业化景点"""
|
| 67 |
-
response_parts.append(question)
|
| 68 |
-
|
| 69 |
-
else:
|
| 70 |
-
# 3. 如果所有信息都已收集完毕,则生成最终的旅行计划
|
| 71 |
-
# 如果刚才有信息修改,确认信息会和最终计划一起发出。
|
| 72 |
plan = self._generate_persona_enhanced_plan(user_message, session_state)
|
| 73 |
response_parts.append(plan)
|
| 74 |
|
| 75 |
-
#
|
| 76 |
-
return " ".join(response_parts)
|
| 77 |
|
| 78 |
except Exception as e:
|
| 79 |
log.error(f"❌ 响应生成失败: {e}", exc_info=True)
|
| 80 |
return "抱歉,我在处理您的请求时遇到了问题,请稍后再试。"
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
def _generate_acknowledgement(self, extracted_info: dict) -> str:
|
| 83 |
-
"""
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
ack_parts = []
|
|
|
|
| 88 |
if "destination" in extracted_info:
|
| 89 |
-
|
|
|
|
|
|
|
| 90 |
if "duration" in extracted_info:
|
| 91 |
-
|
|
|
|
|
|
|
| 92 |
if "budget" in extracted_info:
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
if not ack_parts:
|
| 98 |
return ""
|
| 99 |
|
| 100 |
return "好的," + ",".join(ack_parts) + "。"
|
| 101 |
|
| 102 |
def _generate_persona_enhanced_plan(self, user_message: str, session_state: dict) -> str:
|
| 103 |
-
"""
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
|
| 111 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
if self.ai_model.is_available():
|
| 113 |
-
|
|
|
|
| 114 |
else:
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
-
|
| 118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
destination = session_state.get("destination", {})
|
| 120 |
duration = session_state.get("duration", {})
|
| 121 |
budget = session_state.get("budget", {})
|
| 122 |
persona = session_state.get("persona", {})
|
| 123 |
|
| 124 |
-
# 基础信息
|
| 125 |
location = destination.get('name', '目的地')
|
| 126 |
days = duration.get('days', '几')
|
| 127 |
budget_info = self._format_budget_info(budget)
|
| 128 |
|
| 129 |
-
#
|
| 130 |
-
|
| 131 |
-
if not persona_key or persona_key not in self.personas:
|
| 132 |
-
# 如果没有有效persona,使用通用prompt
|
| 133 |
-
return self._build_generic_prompt(session_state, knowledge_context)
|
| 134 |
-
|
| 135 |
-
persona_config = self.personas[persona_key]
|
| 136 |
-
|
| 137 |
-
# 使用personas.json中的prompt_template
|
| 138 |
-
persona_template = persona_config.get('prompt_template', '')
|
| 139 |
-
|
| 140 |
-
# 替换模板中的变量
|
| 141 |
-
enhanced_prompt = persona_template.format(
|
| 142 |
-
location=location,
|
| 143 |
-
days=days,
|
| 144 |
-
date="近期", # 可以从session中获取更具体的日期
|
| 145 |
-
user_tags="", # 可以从用户偏好中提取
|
| 146 |
-
commercial_preference="适中", # 可以从session中获取
|
| 147 |
-
group_description="个人/朋友", # 可以从session中获取
|
| 148 |
-
budget=budget_info, # 使用格式化的预算信息
|
| 149 |
-
tags="" # 可以从session中获取
|
| 150 |
-
)
|
| 151 |
-
|
| 152 |
-
# 添加知识库上下文
|
| 153 |
-
if knowledge_context:
|
| 154 |
-
enhanced_prompt += f"\n\n【背景知识】\n{knowledge_context}"
|
| 155 |
-
|
| 156 |
-
# 添加预算约束信息
|
| 157 |
-
enhanced_prompt += f"\n\n【预算约束】\n用户预算:{budget_info},请确保所有推荐都在预算范围内。"
|
| 158 |
|
| 159 |
-
# 添加persona
|
| 160 |
-
|
| 161 |
-
if
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
-
return
|
| 173 |
|
| 174 |
def _format_budget_info(self, budget: dict) -> str:
|
| 175 |
"""格式化预算信息"""
|
| 176 |
if not budget:
|
| 177 |
return "中等预算"
|
| 178 |
|
| 179 |
-
# 如果有具体金额
|
| 180 |
if budget.get('amount') and budget.get('currency'):
|
| 181 |
return f"{budget['amount']}{budget['currency']}"
|
| 182 |
|
| 183 |
-
|
|
|
|
|
|
|
| 184 |
if budget.get('type'):
|
| 185 |
-
|
| 186 |
-
'economy': '
|
| 187 |
-
'comfortable': '
|
| 188 |
-
'luxury': '
|
| 189 |
}
|
| 190 |
-
return
|
| 191 |
-
|
| 192 |
-
# 如果有预算范围
|
| 193 |
-
if budget.get('range'):
|
| 194 |
-
return budget['range']
|
| 195 |
|
| 196 |
return "中等预算"
|
| 197 |
|
|
|
|
|
|
|
| 1 |
import json
|
| 2 |
import os
|
| 3 |
from .ai_model import AIModel
|
|
|
|
| 4 |
from .knowledge_base import KnowledgeBase
|
| 5 |
from utils.logger import log
|
| 6 |
|
|
|
|
| 12 |
|
| 13 |
def _load_personas(self):
|
| 14 |
"""加载personas配置"""
|
| 15 |
+
personas_path = "./config/personas.json" # 简化路径
|
| 16 |
with open(personas_path, 'r', encoding='utf-8') as f:
|
| 17 |
data = json.load(f)
|
| 18 |
return data.get('personas', {})
|
| 19 |
|
| 20 |
def generate(self, user_message: str, session_state: dict, extracted_info: dict) -> str:
|
| 21 |
+
"""生成智能响应 - 修复重复询问问题"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
try:
|
| 23 |
response_parts = []
|
| 24 |
|
| 25 |
+
# 1. 生成对新提取信息的确认反馈
|
|
|
|
| 26 |
acknowledgement = self._generate_acknowledgement(extracted_info)
|
| 27 |
if acknowledgement:
|
| 28 |
response_parts.append(acknowledgement)
|
| 29 |
|
| 30 |
+
# 2. 检查信息完整性,按优先级询问缺失信息
|
| 31 |
+
next_question = self._get_next_question(session_state)
|
| 32 |
+
if next_question:
|
| 33 |
+
response_parts.append(next_question)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
# 3. 如果所有信息都齐全,生成旅行计划
|
| 36 |
+
if not next_question:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
plan = self._generate_persona_enhanced_plan(user_message, session_state)
|
| 38 |
response_parts.append(plan)
|
| 39 |
|
| 40 |
+
# 组合回复
|
| 41 |
+
return " ".join(response_parts) if response_parts else "我理解了,请继续告诉我您的需求。"
|
| 42 |
|
| 43 |
except Exception as e:
|
| 44 |
log.error(f"❌ 响应生成失败: {e}", exc_info=True)
|
| 45 |
return "抱歉,我在处理您的请求时遇到了问题,请稍后再试。"
|
| 46 |
|
| 47 |
+
def _get_next_question(self, session_state: dict) -> str:
|
| 48 |
+
"""获取下一个需要询问的问题 - 修复版"""
|
| 49 |
+
|
| 50 |
+
# 按顺序检查缺失信息
|
| 51 |
+
if not session_state.get("destination"):
|
| 52 |
+
return "你想去欧洲的哪个城市呢?比如巴黎、罗马、巴塞罗那?"
|
| 53 |
+
|
| 54 |
+
if not session_state.get("duration"):
|
| 55 |
+
destination_name = session_state.get('destination', {}).get('name', '目的地')
|
| 56 |
+
return f"好的,{destination_name}是个很棒的选择!你计划玩几天呢?"
|
| 57 |
+
|
| 58 |
+
if not session_state.get("budget"):
|
| 59 |
+
days = session_state.get('duration', {}).get('days', '几')
|
| 60 |
+
return f"了解!{days}天的行程。预算大概多少呢?(比如:2000欧元,或经��型/舒适型/豪华型)"
|
| 61 |
+
|
| 62 |
+
if not session_state.get("persona"):
|
| 63 |
+
return """请选择您的旅行风格:
|
| 64 |
+
|
| 65 |
+
🗓️ **高效规划型** - 注重时间安排和预算控制
|
| 66 |
+
🤝 **社交分享型** - 重视拍照打卡和分享体验
|
| 67 |
+
🎨 **深度体验型** - 追求地道文化和小众探索"""
|
| 68 |
+
|
| 69 |
+
# 所有信息都已收集完毕
|
| 70 |
+
return ""
|
| 71 |
+
|
| 72 |
def _generate_acknowledgement(self, extracted_info: dict) -> str:
|
| 73 |
+
"""生成确认信息 - 简化版"""
|
| 74 |
+
if not extracted_info:
|
| 75 |
+
return ""
|
| 76 |
+
|
| 77 |
ack_parts = []
|
| 78 |
+
|
| 79 |
if "destination" in extracted_info:
|
| 80 |
+
name = extracted_info['destination'].get('name', '目的地')
|
| 81 |
+
ack_parts.append(f"目的地已设置为{name}")
|
| 82 |
+
|
| 83 |
if "duration" in extracted_info:
|
| 84 |
+
days = extracted_info['duration'].get('days', '几')
|
| 85 |
+
ack_parts.append(f"行程天数已设置为{days}天")
|
| 86 |
+
|
| 87 |
if "budget" in extracted_info:
|
| 88 |
+
budget_desc = self._format_budget_info(extracted_info['budget'])
|
| 89 |
+
ack_parts.append(f"预算已设置为{budget_desc}")
|
| 90 |
+
|
|
|
|
| 91 |
if not ack_parts:
|
| 92 |
return ""
|
| 93 |
|
| 94 |
return "好的," + ",".join(ack_parts) + "。"
|
| 95 |
|
| 96 |
def _generate_persona_enhanced_plan(self, user_message: str, session_state: dict) -> str:
|
| 97 |
+
"""生成最终旅行计划"""
|
| 98 |
+
|
| 99 |
+
# 获取基本信息
|
| 100 |
+
destination = session_state.get("destination", {})
|
| 101 |
+
duration = session_state.get("duration", {})
|
| 102 |
+
budget = session_state.get("budget", {})
|
| 103 |
+
persona = session_state.get("persona", {})
|
| 104 |
|
| 105 |
+
# 构建计划
|
| 106 |
+
location = destination.get('name', '目的地')
|
| 107 |
+
days = duration.get('days', '几')
|
| 108 |
+
budget_info = self._format_budget_info(budget)
|
| 109 |
+
persona_name = persona.get('name', '旅行者')
|
| 110 |
+
|
| 111 |
+
# 如果AI可用,生成详细计划
|
| 112 |
if self.ai_model.is_available():
|
| 113 |
+
prompt = self._build_prompt(session_state)
|
| 114 |
+
return self.ai_model.generate(user_message, prompt)
|
| 115 |
else:
|
| 116 |
+
# 备用计划
|
| 117 |
+
return f"""✈️ 为您制定{location}{days}天旅行计划
|
| 118 |
+
|
| 119 |
+
👤 旅行风格:{persona_name}
|
| 120 |
+
💰 预算:{budget_info}
|
| 121 |
|
| 122 |
+
🎯 主要景点:{destination.get('highlights', '经典景点等您探索')}
|
| 123 |
+
|
| 124 |
+
📍 建议路线:根据您的{persona_name}风格,为您推荐最适合的行程安排。
|
| 125 |
+
|
| 126 |
+
如需详细规划,请稍后重试或告诉我具体需求!"""
|
| 127 |
+
|
| 128 |
+
def _build_prompt(self, session_state: dict) -> str:
|
| 129 |
+
"""构建AI提示词"""
|
| 130 |
destination = session_state.get("destination", {})
|
| 131 |
duration = session_state.get("duration", {})
|
| 132 |
budget = session_state.get("budget", {})
|
| 133 |
persona = session_state.get("persona", {})
|
| 134 |
|
|
|
|
| 135 |
location = destination.get('name', '目的地')
|
| 136 |
days = duration.get('days', '几')
|
| 137 |
budget_info = self._format_budget_info(budget)
|
| 138 |
|
| 139 |
+
# 基础prompt
|
| 140 |
+
prompt = f"请为{location}制定{days}天旅行计划,预算{budget_info}。"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
|
| 142 |
+
# 添加persona风格
|
| 143 |
+
persona_key = persona.get('key')
|
| 144 |
+
if persona_key in self.personas:
|
| 145 |
+
persona_config = self.personas[persona_key]
|
| 146 |
+
template = persona_config.get('prompt_template', '')
|
| 147 |
+
if template:
|
| 148 |
+
try:
|
| 149 |
+
prompt = template.format(
|
| 150 |
+
location=location,
|
| 151 |
+
days=days,
|
| 152 |
+
budget=budget_info,
|
| 153 |
+
date="近期",
|
| 154 |
+
user_tags="",
|
| 155 |
+
commercial_preference="适中",
|
| 156 |
+
group_description="个人",
|
| 157 |
+
tags=""
|
| 158 |
+
)
|
| 159 |
+
except:
|
| 160 |
+
# 如果模板格式化失败,使用基础prompt
|
| 161 |
+
pass
|
| 162 |
|
| 163 |
+
return prompt
|
| 164 |
|
| 165 |
def _format_budget_info(self, budget: dict) -> str:
|
| 166 |
"""格式化预算信息"""
|
| 167 |
if not budget:
|
| 168 |
return "中等预算"
|
| 169 |
|
|
|
|
| 170 |
if budget.get('amount') and budget.get('currency'):
|
| 171 |
return f"{budget['amount']}{budget['currency']}"
|
| 172 |
|
| 173 |
+
if budget.get('description'):
|
| 174 |
+
return budget['description']
|
| 175 |
+
|
| 176 |
if budget.get('type'):
|
| 177 |
+
type_map = {
|
| 178 |
+
'economy': '经济型',
|
| 179 |
+
'comfortable': '舒适型',
|
| 180 |
+
'luxury': '豪华型'
|
| 181 |
}
|
| 182 |
+
return type_map.get(budget['type'], budget['type'])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
return "中等预算"
|
| 185 |
|