ciyidogan commited on
Commit
7798bfb
Β·
verified Β·
1 Parent(s): 1c091c0

Update prompt_builder.py

Browse files
Files changed (1) hide show
  1. prompt_builder.py +168 -148
prompt_builder.py CHANGED
@@ -1,45 +1,93 @@
1
  """
2
- Flare – Prompt Builder
 
3
  """
4
 
5
- from typing import Dict, List, Optional
6
- from datetime import datetime
7
- import json
8
- import re
9
-
10
  from config_provider import ConfigProvider
11
- from locale_manager import LocaleManager
12
  from utils import log
 
13
 
14
- # ─────────────────────────────────────────────────────────────────────────────
15
- # DATE CONTEXT
16
- # ─────────────────────────────────────────────────────────────────────────────
17
  def _get_date_context(locale_code: str = "tr") -> Dict[str, str]:
18
- """Get today/tomorrow dates with weekday names in target locale"""
19
- from datetime import timedelta
20
-
21
- today = datetime.now()
22
- tomorrow = today + timedelta(days=1)
23
 
 
24
  locale_data = LocaleManager.get_locale(locale_code)
25
- weekday_names = locale_data.get("weekdays", {
26
- "0": "Monday", "1": "Tuesday", "2": "Wednesday", "3": "Thursday",
27
- "4": "Friday", "5": "Saturday", "6": "Sunday"
28
- })
29
 
30
- # Get localized date format
31
- date_format = locale_data.get("date_format", "%Y-%m-%d")
32
 
 
33
  dates = {
34
- "today": today.strftime(date_format),
35
- "tomorrow": tomorrow.strftime(date_format),
36
- "today_weekday": weekday_names.get(str(today.weekday()), ""),
37
- "tomorrow_weekday": weekday_names.get(str(tomorrow.weekday()), ""),
 
 
 
 
 
 
 
38
  "locale_code": locale_code
39
  }
40
 
41
  return dates
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  # ─────────────────────────────────────────────────────────────────────────────
44
  # INTENT PROMPT
45
  # ─────────────────────────────────────────────────────────────────────────────
@@ -47,9 +95,10 @@ def build_intent_prompt(general_prompt: str,
47
  conversation: List[Dict[str, str]],
48
  user_input: str,
49
  intents: List,
 
50
  project_locale: str = "tr") -> str:
51
 
52
- # Get config when needed
53
  cfg = ConfigProvider.get()
54
 
55
  # Get internal prompt from LLM provider settings
@@ -61,10 +110,9 @@ def build_intent_prompt(general_prompt: str,
61
  intent_names = [it.name for it in intents]
62
  intent_captions = [it.caption or it.name for it in intents]
63
 
64
- # Get locale info
65
- from locale_manager import LocaleManager
66
  locale_data = LocaleManager.get_locale(project_locale)
67
- project_language = locale_data.get("name", "Turkish") if locale_data else "Turkish"
68
 
69
  # Replace placeholders in internal prompt
70
  if internal_prompt:
@@ -110,7 +158,7 @@ def build_intent_prompt(general_prompt: str,
110
  f"Conversation so far:\n{history_block}\n\n"
111
  f"USER: {user_input.strip()}"
112
  )
113
- log("βœ… Intent prompt built (with internal prompt)")
114
  return prompt
115
 
116
  # ─────────────────────────────────────────────────────────────────────────────
@@ -122,18 +170,11 @@ def build_parameter_prompt(intent_cfg,
122
  missing_params: List[str],
123
  user_input: str,
124
  conversation: List[Dict[str, str]],
125
- locale_code: str = None) -> str:
126
 
127
- # Use project locale if not specified
128
- if not locale_code:
129
- locale_code = "tr" # Default
130
-
131
- # Get locale info
132
- from locale_manager import LocaleManager
133
  locale_data = LocaleManager.get_locale(locale_code)
134
 
135
- date_ctx = _get_date_context()
136
-
137
  parts: List[str] = [
138
  f"You are extracting parameters from user messages in {locale_data.get('name', 'the target language')}.",
139
  f"Today is {date_ctx['today']} ({date_ctx['today_weekday']}). Tomorrow is {date_ctx['tomorrow']}.",
@@ -154,7 +195,7 @@ def build_parameter_prompt(intent_cfg,
154
  parts.append("Parameters to extract:")
155
  for p in intent_cfg.parameters:
156
  if p.name in missing_params:
157
- # Get caption for locale
158
  caption = p.get_caption_for_locale(locale_code)
159
 
160
  # Special handling for date type parameters
@@ -164,15 +205,15 @@ def build_parameter_prompt(intent_cfg,
164
  )
165
  parts.append(date_prompt)
166
  else:
167
- parts.append(f"β€’ {p.name}: {p.extraction_prompt}")
168
- parts.append(f" Caption: {caption}")
169
 
170
- # Rest of the function remains the same...
171
  parts.append("")
172
  parts.append("IMPORTANT: Your response must start with '#PARAMETERS:' followed by the JSON.")
173
  parts.append("Return ONLY this format with no extra text before or after:")
174
  parts.append(_FMT)
175
 
 
176
  history_block = "\n".join(
177
  f"{m['role'].upper()}: {m['content']}" for m in conversation[-10:]
178
  )
@@ -183,134 +224,113 @@ def build_parameter_prompt(intent_cfg,
183
  "\n\nUSER: " + user_input.strip()
184
  )
185
 
186
- log(f"πŸ“ Parameter prompt built for missing: {missing_params}")
187
  return prompt
188
 
189
- def _build_locale_aware_date_prompt(param, date_ctx: Dict, locale_data: Dict, locale_code: str) -> str:
190
- """Build date extraction prompt with locale awareness"""
191
- caption = param.get_caption_for_locale(locale_code)
 
 
 
 
 
 
 
 
 
192
 
193
- # Get locale-specific date info
194
- month_names = locale_data.get("months", {})
195
- relative_dates = locale_data.get("relative_dates", {
196
- "today": "today", "tomorrow": "tomorrow",
197
- "yesterday": "yesterday", "this_week": "this week"
198
- })
199
 
 
 
 
 
 
 
 
 
 
 
200
  parts = [
201
- f"β€’ {param.name} ({caption}): Extract date in YYYY-MM-DD format.",
202
- f" - Today is {date_ctx['today']} ({date_ctx['today_weekday']})",
203
- f" - '{relative_dates.get('today', 'today')}' β†’ {date_ctx['today']}",
204
- f" - '{relative_dates.get('tomorrow', 'tomorrow')}' β†’ {date_ctx['tomorrow']}"
 
 
205
  ]
206
 
207
- if param.extraction_prompt:
208
- parts.append(f" - {param.extraction_prompt}")
 
209
 
210
  return "\n".join(parts)
211
 
212
  # ─────────────────────────────────────────────────────────────────────────────
213
- # SMART PARAMETER QUESTION
214
  # ─────────────────────────────────────────────────────────────────────────────
215
- def build_smart_parameter_question_prompt(
216
- collection_config,
217
- intent_config,
218
- missing_params: List[str],
219
- session,
220
- project_language: str = "Turkish",
221
- locale_code: str = None
222
- ) -> str:
223
- """Build prompt for smart parameter collection"""
224
-
225
- # Get parameter collection config from LLM provider settings
226
- cfg = ConfigProvider.get()
227
- if cfg.global_config.llm_provider and cfg.global_config.llm_provider.settings:
228
- collection_config = cfg.global_config.llm_provider.settings.get("parameter_collection_config", collection_config)
229
 
230
- # Use locale if specified
231
- if not locale_code:
232
- locale_code = "tr"
233
 
234
- # Get locale info
235
- from locale_manager import LocaleManager
236
- locale_data = LocaleManager.get_locale(locale_code)
237
- if locale_data:
238
- project_language = locale_data.get("name", project_language)
239
-
240
- # Rest of the function implementation...
241
- template = collection_config.get("collection_prompt", "")
242
-
243
- # Format conversation history
244
- conversation_history = _format_conversation_history(session.chat_history)
245
-
246
- # Format collected parameters with locale-aware captions
247
- collected_params = ""
248
- if session.variables:
249
- params_list = []
250
- for param_name, value in session.variables.items():
251
- param = next((p for p in intent_config.parameters if p.name == param_name), None)
252
- if param:
253
- caption = param.get_caption_for_locale(locale_code)
254
- params_list.append(f"- {caption}: {value}")
255
- collected_params = "\n".join(params_list)
256
-
257
- # Format missing parameters with locale-aware captions
258
- missing_params_list = []
259
- for param_name in missing_params:
260
- param = next((p for p in intent_config.parameters if p.name == param_name), None)
261
- if param:
262
- caption = param.get_caption_for_locale(locale_code)
263
- missing_params_list.append(f"- {caption} ({param.name})")
264
- missing_params_str = "\n".join(missing_params_list)
265
-
266
- # Rest of template replacement...
267
- prompt = template.replace("{{conversation_history}}", conversation_history)
268
- prompt = prompt.replace("{{intent_name}}", intent_config.name)
269
- prompt = prompt.replace("{{intent_caption}}", intent_config.caption)
270
- prompt = prompt.replace("{{collected_params}}", collected_params)
271
- prompt = prompt.replace("{{missing_params}}", missing_params_str)
272
- prompt = prompt.replace("{{max_params}}", str(collection_config.get("max_params_per_question", 2)))
273
- prompt = prompt.replace("{{project_language}}", project_language)
274
 
275
- log(f"πŸ“ Smart parameter question prompt built for {len(missing_params)} params in {locale_code}")
276
- return prompt
277
-
278
- # ─────────────────────────────────────────────────────────────────────────────
279
- # PARAMETER EXTRACTION FROM QUESTION
280
- # ─────────────────────────────────────────────────────────────────────────────
281
- def extract_params_from_question(question: str, intent_config, project_locale: str = "tr") -> List[str]:
282
- """Extract which parameters are being asked in the question"""
283
- asked_params = []
284
- question_lower = question.lower()
285
-
286
- # Check each missing parameter
287
- for param in intent_config.parameters:
288
- # Check all locale captions
289
- for caption_obj in param.caption:
290
- caption = caption_obj.caption.lower()
291
- # Check if caption appears in question
292
- if caption in question_lower:
293
- asked_params.append(param.name)
294
- break
295
-
296
- # Also check parameter name
297
- if param.name.lower() in question_lower:
298
- if param.name not in asked_params:
299
- asked_params.append(param.name)
300
 
301
- return asked_params
302
 
303
  # ─────────────────────────────────────────────────────────────────────────────
304
  # API RESPONSE PROMPT
305
  # ─────────────────────────────────────────────────────────────────────────────
306
  def build_api_response_prompt(api_config, api_response: Dict) -> str:
307
- """Build prompt for API response humanization"""
308
 
309
  response_prompt = api_config.response_prompt
310
  if not response_prompt:
311
- response_prompt = "Convert this API response to a friendly message: {{api_response}}"
 
 
 
 
 
 
 
 
 
 
 
 
312
 
313
- # Replace placeholders
314
- response_prompt = response_prompt.replace("{{api_response}}", json.dumps(api_response, ensure_ascii=False))
 
 
 
 
 
315
 
316
- return response_prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ Flare – Prompt Builder (Refactored with Multi-language Support)
3
+ ==============================================================
4
  """
5
 
6
+ from typing import List, Dict, Any
7
+ from datetime import datetime, timedelta
 
 
 
8
  from config_provider import ConfigProvider
 
9
  from utils import log
10
+ from locale_manager import LocaleManager
11
 
12
+ # Date helper for locale-aware date expressions
 
 
13
  def _get_date_context(locale_code: str = "tr") -> Dict[str, str]:
14
+ """Generate date context for date expressions"""
15
+ now = datetime.now()
 
 
 
16
 
17
+ # Get locale data
18
  locale_data = LocaleManager.get_locale(locale_code)
19
+ weekdays = locale_data.get("weekdays", ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"])
 
 
 
20
 
21
+ today_weekday = weekdays[now.weekday()]
 
22
 
23
+ # Calculate various dates
24
  dates = {
25
+ "today": now.strftime("%Y-%m-%d"),
26
+ "tomorrow": (now + timedelta(days=1)).strftime("%Y-%m-%d"),
27
+ "day_after_tomorrow": (now + timedelta(days=2)).strftime("%Y-%m-%d"),
28
+ "this_weekend_saturday": (now + timedelta(days=(5-now.weekday())%7)).strftime("%Y-%m-%d"),
29
+ "this_weekend_sunday": (now + timedelta(days=(6-now.weekday())%7)).strftime("%Y-%m-%d"),
30
+ "next_week_same_day": (now + timedelta(days=7)).strftime("%Y-%m-%d"),
31
+ "two_weeks_later": (now + timedelta(days=14)).strftime("%Y-%m-%d"),
32
+ "today_weekday": today_weekday,
33
+ "today_day": now.day,
34
+ "today_month": now.month,
35
+ "today_year": now.year,
36
  "locale_code": locale_code
37
  }
38
 
39
  return dates
40
 
41
+ def _build_locale_aware_date_prompt(param, date_ctx: Dict, locale_data: Dict, locale_code: str) -> str:
42
+ """Build locale-aware date extraction prompt"""
43
+
44
+ # Get locale-specific date patterns
45
+ date_patterns = locale_data.get("date_patterns", {})
46
+ months = locale_data.get("months", [])
47
+
48
+ prompt_parts = [f"β€’ {param.name}: {param.extraction_prompt}"]
49
+ prompt_parts.append(f" IMPORTANT DATE RULES for {locale_data.get('name', 'this language')}:")
50
+
51
+ # Add locale-specific patterns
52
+ if locale_code == "tr":
53
+ prompt_parts.extend([
54
+ f" - 'bugΓΌn' = {date_ctx['today']}",
55
+ f" - 'yarΔ±n' = {date_ctx['tomorrow']}",
56
+ f" - 'ΓΆbΓΌr gΓΌn' = {date_ctx['day_after_tomorrow']}",
57
+ f" - 'bu hafta sonu' = {date_ctx['this_weekend_saturday']} or {date_ctx['this_weekend_sunday']}",
58
+ f" - 'bu cumartesi' = {date_ctx['this_weekend_saturday']}",
59
+ f" - 'bu pazar' = {date_ctx['this_weekend_sunday']}",
60
+ f" - 'haftaya' or 'gelecek hafta' = add 7 days to current date",
61
+ f" - 'haftaya bugΓΌn' = {date_ctx['next_week_same_day']}",
62
+ f" - 'iki hafta sonra' = {date_ctx['two_weeks_later']}",
63
+ f" - '15 gΓΌn sonra' = add 15 days to today",
64
+ f" - '10 Temmuz' = {date_ctx['today_year']}-07-10",
65
+ f" - Turkish months: Ocak=01, Şubat=02, Mart=03, Nisan=04, Mayıs=05, Haziran=06, "
66
+ f"Temmuz=07, Ağustos=08, Eylül=09, Ekim=10, Kasım=11, Aralık=12"
67
+ ])
68
+ elif locale_code == "en":
69
+ prompt_parts.extend([
70
+ f" - 'today' = {date_ctx['today']}",
71
+ f" - 'tomorrow' = {date_ctx['tomorrow']}",
72
+ f" - 'day after tomorrow' = {date_ctx['day_after_tomorrow']}",
73
+ f" - 'this weekend' = {date_ctx['this_weekend_saturday']} or {date_ctx['this_weekend_sunday']}",
74
+ f" - 'this Saturday' = {date_ctx['this_weekend_saturday']}",
75
+ f" - 'this Sunday' = {date_ctx['this_weekend_sunday']}",
76
+ f" - 'next week' = add 7 days to current date",
77
+ f" - 'next {date_ctx['today_weekday']}' = {date_ctx['next_week_same_day']}",
78
+ f" - 'in two weeks' = {date_ctx['two_weeks_later']}",
79
+ f" - 'July 10' or '10th of July' = {date_ctx['today_year']}-07-10",
80
+ f" - English months: January=01, February=02, March=03, April=04, May=05, June=06, "
81
+ f"July=07, August=08, September=09, October=10, November=11, December=12"
82
+ ])
83
+
84
+ # Add month mappings if available
85
+ if months:
86
+ month_mapping = [f"{month}={i+1:02d}" for i, month in enumerate(months)]
87
+ prompt_parts.append(f" - Month names: {', '.join(month_mapping)}")
88
+
89
+ return "\n".join(prompt_parts)
90
+
91
  # ─────────────────────────────────────────────────────────────────────────────
92
  # INTENT PROMPT
93
  # ─────────────────────────────────────────────────────────────────────────────
 
95
  conversation: List[Dict[str, str]],
96
  user_input: str,
97
  intents: List,
98
+ project_name: str = None,
99
  project_locale: str = "tr") -> str:
100
 
101
+ # Get config
102
  cfg = ConfigProvider.get()
103
 
104
  # Get internal prompt from LLM provider settings
 
110
  intent_names = [it.name for it in intents]
111
  intent_captions = [it.caption or it.name for it in intents]
112
 
113
+ # Get project language name
 
114
  locale_data = LocaleManager.get_locale(project_locale)
115
+ project_language = locale_data.get("name", "Turkish")
116
 
117
  # Replace placeholders in internal prompt
118
  if internal_prompt:
 
158
  f"Conversation so far:\n{history_block}\n\n"
159
  f"USER: {user_input.strip()}"
160
  )
161
+ log("βœ… Intent prompt built (with internal prompt and locale support)")
162
  return prompt
163
 
164
  # ─────────────────────────────────────────────────────────────────────────────
 
170
  missing_params: List[str],
171
  user_input: str,
172
  conversation: List[Dict[str, str]],
173
+ locale_code: str = "tr") -> str:
174
 
175
+ date_ctx = _get_date_context(locale_code)
 
 
 
 
 
176
  locale_data = LocaleManager.get_locale(locale_code)
177
 
 
 
178
  parts: List[str] = [
179
  f"You are extracting parameters from user messages in {locale_data.get('name', 'the target language')}.",
180
  f"Today is {date_ctx['today']} ({date_ctx['today_weekday']}). Tomorrow is {date_ctx['tomorrow']}.",
 
195
  parts.append("Parameters to extract:")
196
  for p in intent_cfg.parameters:
197
  if p.name in missing_params:
198
+ # Get localized caption
199
  caption = p.get_caption_for_locale(locale_code)
200
 
201
  # Special handling for date type parameters
 
205
  )
206
  parts.append(date_prompt)
207
  else:
208
+ parts.append(f"β€’ {p.name}: {p.extraction_prompt} (Display: {caption})")
 
209
 
210
+ # Add format instruction
211
  parts.append("")
212
  parts.append("IMPORTANT: Your response must start with '#PARAMETERS:' followed by the JSON.")
213
  parts.append("Return ONLY this format with no extra text before or after:")
214
  parts.append(_FMT)
215
 
216
+ # Add conversation history
217
  history_block = "\n".join(
218
  f"{m['role'].upper()}: {m['content']}" for m in conversation[-10:]
219
  )
 
224
  "\n\nUSER: " + user_input.strip()
225
  )
226
 
227
+ log(f"πŸ“ Parameter prompt built for missing: {missing_params} in locale: {locale_code}")
228
  return prompt
229
 
230
+ # ─────────────────────────────────────────────────────────────────────────────
231
+ # SMART PARAMETER COLLECTION PROMPT
232
+ # ─────────────────────────────────────────────────────────────────────────────
233
+ def build_smart_parameter_question_prompt(intent_cfg,
234
+ missing_params: List[str],
235
+ conversation: List[Dict[str, str]],
236
+ collection_config: Dict[str, Any],
237
+ project_language: str) -> str:
238
+ """Build prompt for smart parameter collection"""
239
+
240
+ # Get the collection prompt template
241
+ template = collection_config.get("collection_prompt", "Ask for the missing parameters.")
242
 
243
+ # Replace placeholders
244
+ prompt = template.replace("{{max_params}}", str(collection_config.get("max_params_per_question", 2)))
245
+ prompt = prompt.replace("{{project_language}}", project_language)
 
 
 
246
 
247
+ # Add parameter information
248
+ param_info = []
249
+ for param_name in missing_params[:collection_config.get("max_params_per_question", 2)]:
250
+ param = next((p for p in intent_cfg.parameters if p.name == param_name), None)
251
+ if param:
252
+ # Get caption for default locale (first supported locale)
253
+ caption = param.get_caption_for_locale("tr") # Default to Turkish
254
+ param_info.append(f"- {param_name}: {caption}")
255
+
256
+ # Add context
257
  parts = [
258
+ prompt,
259
+ "",
260
+ "Missing parameters:",
261
+ "\n".join(param_info),
262
+ "",
263
+ "Recent conversation:"
264
  ]
265
 
266
+ # Add recent conversation
267
+ for msg in conversation[-5:]:
268
+ parts.append(f"{msg['role'].upper()}: {msg['content']}")
269
 
270
  return "\n".join(parts)
271
 
272
  # ─────────────────────────────────────────────────────────────────────────────
273
+ # PARAMETER EXTRACTION FROM QUESTION
274
  # ─────────────────────────────────────────────────────────────────────────────
275
+ def extract_params_from_question(question: str, param_names: List[str]) -> Dict[str, str]:
276
+ """Extract parameters that might be embedded in the question itself"""
277
+ extracted = {}
 
 
 
 
 
 
 
 
 
 
 
278
 
279
+ # Simple pattern matching for common cases
280
+ # This is a basic implementation - can be enhanced with NLP
 
281
 
282
+ # Example: "Δ°stanbul'dan Ankara'ya ne zaman gitmek istiyorsunuz?"
283
+ # Could extract origin=Δ°stanbul, destination=Ankara
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
 
285
+ # For now, return empty dict
286
+ # This function can be enhanced based on specific use cases
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
+ return extracted
289
 
290
  # ─────────────────────────────────────────────────────────────────────────────
291
  # API RESPONSE PROMPT
292
  # ─────────────────────────────────────────────────────────────────────────────
293
  def build_api_response_prompt(api_config, api_response: Dict) -> str:
294
+ """Build prompt for API response with mappings"""
295
 
296
  response_prompt = api_config.response_prompt
297
  if not response_prompt:
298
+ return "İşlem başarıyla tamamlandı."
299
+
300
+ # Apply response mappings if available
301
+ mapped_data = {}
302
+ if hasattr(api_config, 'response_mappings'):
303
+ for mapping in api_config.response_mappings:
304
+ field_path = mapping.get('field_path', '')
305
+ display_name = mapping.get('display_name', field_path)
306
+
307
+ # Extract value from response
308
+ value = _extract_value_from_path(api_response, field_path)
309
+ if value is not None:
310
+ mapped_data[display_name] = value
311
 
312
+ # Replace placeholders in response prompt
313
+ for key, value in mapped_data.items():
314
+ response_prompt = response_prompt.replace(f"{{{key}}}", str(value))
315
+
316
+ # Also try direct field replacement
317
+ for key, value in api_response.items():
318
+ response_prompt = response_prompt.replace(f"{{{key}}}", str(value))
319
 
320
+ return response_prompt
321
+
322
+ def _extract_value_from_path(data: Dict, path: str) -> Any:
323
+ """Extract value from nested dict using dot notation"""
324
+ try:
325
+ parts = path.split('.')
326
+ value = data
327
+ for part in parts:
328
+ if isinstance(value, dict):
329
+ value = value.get(part)
330
+ elif isinstance(value, list) and part.isdigit():
331
+ value = value[int(part)]
332
+ else:
333
+ return None
334
+ return value
335
+ except:
336
+ return None