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

improve: chat logic

Browse files
Files changed (1) hide show
  1. modules/response_generator.py +213 -131
modules/response_generator.py CHANGED
@@ -1,154 +1,236 @@
1
  import json
2
  from .ai_model import AIModel
3
- from .session_manager import SessionState # 假设 session_state 是一个类
4
  from utils.logger import log
5
- import re
6
 
7
  class ResponseGenerator:
8
- def __init__(self, ai_model: AIModel):
9
  self.ai_model = ai_model
 
10
  self.personas = self._load_personas()
11
 
12
- def _load_personas(self) -> dict:
 
 
13
 
14
- with open("./config/personas.json", 'r', encoding='utf-8') as f:
15
  data = json.load(f)
16
- log.info(f"✅ 成功加载 {len(data.get('personas', {}))} 个 persona")
17
  return data.get('personas', {})
18
-
19
- def generate(self, session_state: SessionState) -> str:
20
- """
21
- 根据当前会话状态,生成一个完全符合选定 Persona 风格的智能回复。
22
- """
23
- try:
24
- persona_key = session_state.persona or "planner" # 如果未选择,则默认为 planner
25
- persona_config = self.personas.get(persona_key, self.personas.get("planner"))
26
 
27
- # 1. 检查信息完整性,并使用 persona 的风格来提问
28
- next_question = self._get_next_question(session_state, persona_config)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  if next_question:
30
- # 如果是第一个问题(目的地未知),直接返回问题
31
- if not session_state.destination:
32
- return next_question
33
- # 否则,先确认已知信息,再提问
34
  else:
35
- ack = self._generate_acknowledgement(session_state, persona_config)
36
- return f"{ack} {next_question}"
37
-
38
- # 2. 如果所有信息都齐全,则生成最终的旅行计划
39
- log.info(f"✅ 所有信息已集齐,开始为 persona '{persona_key}' 生成最终计划。")
40
- plan = self._generate_persona_enhanced_plan(session_state, persona_config)
41
- return plan
 
 
 
 
 
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: SessionState, persona_config: dict) -> str:
48
- """根据缺失信息,生成符合 persona 语气的下一个问题。"""
 
 
49
 
50
- persona_tone_style = f"语气({persona_config.get('tone', ['友好'])[0]})"
51
-
52
- if not session_state.destination:
53
- # 根据不同 persona 定制提问方式
54
- if persona_config.get('name', '').startswith("高效"):
55
- return "您好,为了开始规划,请首先提供您的目的地城市。"
56
- elif persona_config.get('name', '').startswith("社交"):
57
- return "哈喽!准备好开启一场超棒的旅行了吗?快告诉我你们想去哪里呀?目的地是哪里呀?🌍"
58
- else: # 默认或体验型
59
- return "您好!很高兴能为您服务。请问这次您渴望探索哪个目的地呢?"
60
-
61
- if not session_state.duration:
62
- dest_name = session_state.destination
63
- if persona_config.get('name', '').startswith("高效"):
64
- return f"目的地已确认为 {dest_name}。请提供计划的旅行总天数。"
65
- elif persona_config.get('name', '').startswith("社交"):
66
- return f"哇!{dest_name}!听起来就超赞的!你们打算一起玩几天呀?🥳"
67
- else:
68
- return f"{dest_name},一个充满故事的地方。您希望在这片土地上沉浸几天呢?"
69
-
70
- if not session_state.budget:
71
- if persona_config.get('name', '').startswith("高效"):
72
- return "为了进行精确的预算分配,请明确您的预算等级(例如:经济型、舒适型、豪华型)或大致的总金额。"
73
- elif persona_config.get('name', '').startswith("社交"):
74
- return "最后一个问题!这次出去玩的预算大概是多少呀?是小资轻奢还是尽情嗨皮呢?💰"
75
- else:
76
- return "为了让体验更贴合您的期望,可以分享一下这次旅行的预算范围吗?"
77
 
78
- return "" # 所有信息都已收集完毕
79
-
80
- def _generate_acknowledgement(self, session_state: SessionState, persona_config: dict) -> str:
81
- """对刚刚更新的信息,生成一个符合 persona 语气的确认。"""
82
- # 这个函数可以在 session_state 更新后被调用,以提供即时反馈。
83
- # 在当前简化流程中,它被整合到提问环节。
84
- dest = session_state.destination
85
- days = session_state.duration
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- if persona_config.get('name', '').startswith("高效"):
88
- if days:
89
- return f"已记录:行程共计 {days} 天。"
90
- return f"收到。目的地:{dest}。"
91
- elif persona_config.get('name', '').startswith("社交"):
92
- if days:
93
- return f"收到啦!{days} 天的行程,一定能玩得超开心!"
94
- return f"好嘞!目的地 {dest} 已锁定!"
95
- else:
96
- if days:
97
- return f"明白了,一段为期 {days} 天的旅程。"
98
- return f"好的,我们将围绕 {dest} 展开探索。"
99
-
100
- def _generate_persona_enhanced_plan(self, session_state: SessionState, persona_config: dict) -> str:
101
- """调用AI模型,使用 persona 模板生成最终的旅行计划。"""
102
- prompt = self._build_prompt(session_state, persona_config)
103
 
104
- log.info(f"🧠 正在使用 persona '{persona_config.get('name')}' 的模板生成计划...")
105
- # log.debug(f"最终生成的Prompt:\n---\n{prompt}\n---") # 如果需要调试,可以取消此行注释
106
-
107
- # 实际调用AI模型
108
- # plan = self.ai_model.generate(prompt)
109
- # return plan
110
-
111
- # --- 当前用于演示的临时返回 ---
112
- # 它会清晰地展示我们是如何根据persona构建prompt的
113
- return f"✨ **计划生成预览** ✨\n\n我将使用下面的指令为您生成计划:\n\n```prompt\n{prompt}\n```"
114
-
115
-
116
- def _build_prompt(self, session_state: SessionState, persona_config: dict) -> str:
117
- """
118
- 【核心升级】
119
- 一个更健壮、更灵活的 prompt 构建方法。
120
- 它会安全地填充模板,即使 session_state 中的信息不完整。
121
- """
122
- template = persona_config.get('prompt_template')
123
- if not template:
124
- log.warning(f"Persona '{persona_config.get('name')}' 缺少 'prompt_template',将使用通用模板。")
125
- # 此处可以返回一个通用的后备 prompt
126
- return f"请为我规划一个去 {session_state.destination} 的 {session_state.duration} 天行程。"
127
-
128
- # 1. 从 session_state 收集所有可能用到的信息
129
- context = {
130
- "location": session_state.destination,
131
- "days": session_state.duration,
132
- "budget": session_state.budget,
133
- "date": session_state.get_specific_info('date', '近期'), # 假设 session_state 有此方法
134
- "user_tags": ", ".join(session_state.get_specific_info('tags', [])),
135
- "commercial_preference": session_state.get_specific_info('commercial', '适中'),
136
- "group_description": session_state.get_specific_info('group', '个人'),
137
- "tags": ", ".join(session_state.get_specific_info('tags', []))
138
- }
139
 
140
- # 2. 使用正则表达式查找模板中所有的 {placeholder}
141
- placeholders = re.findall(r'\{(\w+)\}', template)
142
-
143
- # 3. 用 context 中的值替换模板中的占位符
144
- # 这种方法比 .format(**context) 更安全,因为它只替换存在的占位符,
145
- # 并且如果 context 中缺少某个键,它不会抛出 KeyError。
146
- for key in placeholders:
147
- value = context.get(key)
148
- if value is not None:
149
- template = template.replace(f'{{{key}}}', str(value))
150
- else:
151
- # 如果 context 中没有这个值,可以选择留空或使用默认值
152
- template = template.replace(f'{{{key}}}', '(未提供)')
153
 
154
- return template
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import json
2
  from .ai_model import AIModel
3
+ from .knowledge_base import KnowledgeBase
4
  from utils.logger import log
 
5
 
6
  class ResponseGenerator:
7
+ def __init__(self, ai_model: AIModel, knowledge_base: KnowledgeBase):
8
  self.ai_model = ai_model
9
+ self.kb = knowledge_base
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:
61
+ response_parts.append(plan)
62
+
63
+ return " ".join(response_parts)
64
 
65
  except Exception as e:
66
+ log.error(f"❌ 响应生成失败: {e}", exc_info=True)
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进行生成。")
149
+ return self.ai_model.generate(user_message, prompt)
150
+ else:
151
+ log.warning("⚠️ AI模型不可用,生成备用计划。")
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"
235
+ plan += "\n如需更详细的个性化规划,请告诉我您的具体需求!"
236
+ return plan