Spaces:
Building
Building
""" | |
Flare β Prompt Builder (v6 Β· date handling) | |
============================================================== | |
""" | |
from typing import List, Dict | |
from datetime import datetime, timedelta | |
from utils import log | |
from config_provider import ConfigProvider | |
from locale_manager import LocaleManager | |
# Date helper for date expressions | |
def _get_date_context(locale_code: str = "tr-TR") -> Dict[str, str]: | |
"""Generate date context based on locale""" | |
now = datetime.now() | |
# Locale verilerini yΓΌkle | |
locale_data = LocaleManager.get_locale(locale_code) | |
# Weekday names from locale | |
days = locale_data.get("days", {}) | |
weekdays = [days.get(str(i), f"Day{i}") for i in range(7)] | |
# Monday is 0 in Python, Sunday is 6, but in locale Sunday is 0 | |
python_weekday = now.weekday() # 0=Monday, 6=Sunday | |
locale_weekday = (python_weekday + 1) % 7 # Convert to locale format where 0=Sunday | |
today_weekday = days.get(str(locale_weekday), "") | |
# Calculate various dates | |
dates = { | |
"today": now.strftime("%Y-%m-%d"), | |
"tomorrow": (now + timedelta(days=1)).strftime("%Y-%m-%d"), | |
"day_after_tomorrow": (now + timedelta(days=2)).strftime("%Y-%m-%d"), | |
"this_weekend_saturday": (now + timedelta(days=(5-now.weekday())%7)).strftime("%Y-%m-%d"), | |
"this_weekend_sunday": (now + timedelta(days=(6-now.weekday())%7)).strftime("%Y-%m-%d"), | |
"next_week_same_day": (now + timedelta(days=7)).strftime("%Y-%m-%d"), | |
"two_weeks_later": (now + timedelta(days=14)).strftime("%Y-%m-%d"), | |
"today_weekday": today_weekday, | |
"today_day": now.day, | |
"today_month": now.month, | |
"today_year": now.year, | |
"locale_code": locale_code | |
} | |
return dates | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
# INTENT PROMPT | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
def build_intent_prompt(general_prompt: str, | |
conversation: List[Dict[str, str]], | |
user_input: str, | |
intents: List, | |
project_name: str = None) -> str: | |
# Get config when needed | |
cfg = ConfigProvider.get() | |
# Get internal prompt from config | |
internal_prompt = cfg.global_config.internal_prompt or "" | |
# Extract intent names and captions | |
intent_names = [it.name for it in intents] | |
intent_captions = [it.caption or it.name for it in intents] | |
# Get project language | |
project_language = "Turkish" # Default | |
if project_name: | |
project = next((p for p in cfg.projects if p.name == project_name), None) | |
if project: | |
# Language code'u language name'e Γ§evir | |
lang_map = { | |
"tr": "Turkish", | |
"en": "English", | |
"de": "German", | |
"fr": "French", | |
"es": "Spanish" | |
} | |
project_language = lang_map.get(project.default_language, "Turkish") | |
# Replace placeholders in internal prompt | |
if internal_prompt: | |
# Intent names - tΔ±rnak iΓ§inde ve virgΓΌlle ayrΔ±lmΔ±Ε | |
intent_names_str = ', '.join([f'"{name}"' for name in intent_names]) | |
internal_prompt = internal_prompt.replace("<intent names>", intent_names_str) | |
# Intent captions - tΔ±rnak iΓ§inde ve virgΓΌlle ayrΔ±lmΔ±Ε | |
intent_captions_str = ', '.join([f'"{caption}"' for caption in intent_captions]) | |
internal_prompt = internal_prompt.replace("<intent captions>", intent_captions_str) | |
# Project language | |
internal_prompt = internal_prompt.replace("<project language>", project_language) | |
# === INTENT INDEX === | |
lines = ["### INTENT INDEX ###"] | |
for it in intents: | |
# IntentConfig object attribute access | |
det = it.detection_prompt.strip() if it.detection_prompt else "" | |
det_part = f' β’ detection_prompt β "{det}"' if det else "" | |
exs = " | ".join(it.examples) if it.examples else "" | |
ex_part = f" β’ examples β {exs}" if exs else "" | |
newline_between = "\n" if det_part and ex_part else "" | |
lines.append(f"{it.name}:{det_part}{newline_between}{ex_part}") | |
intent_index = "\n".join(lines) | |
# === HISTORY === | |
history_block = "\n".join( | |
f"{m['role'].upper()}: {m['content']}" for m in conversation[-10:] | |
) | |
# Combine prompts | |
combined_prompt = internal_prompt + "\n\n" + general_prompt if internal_prompt else general_prompt | |
prompt = ( | |
f"{combined_prompt}\n\n" | |
f"{intent_index}\n\n" | |
f"Conversation so far:\n{history_block}\n\n" | |
f"USER: {user_input.strip()}" | |
) | |
log("β Intent prompt built (with internal prompt)") | |
return prompt | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
# RESPONSE PROMPT | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
def build_api_response_prompt(api_config, api_response: Dict) -> str: | |
"""Build prompt for API response with mappings""" | |
response_prompt = api_config.response_prompt or "API yanΔ±tΔ±nΔ± kullanΔ±cΔ±ya aΓ§Δ±kla:" | |
# Response mappings varsa, mapping bilgilerini ekle | |
if api_config.response_mappings: | |
mapping_info = [] | |
for mapping in api_config.response_mappings: | |
# JSON path'e gΓΆre deΔeri bul | |
value = extract_value_from_json_path(api_response, mapping.json_path) | |
if value is not None: | |
# Type'a gΓΆre formatlama | |
if mapping.type == "date": | |
# ISO date'i TΓΌrkΓ§e formata Γ§evir | |
try: | |
dt = datetime.fromisoformat(value.replace('Z', '+00:00')) | |
value = dt.strftime("%d %B %Y %H:%M") | |
except: | |
pass | |
elif mapping.type == "float": | |
try: | |
value = f"{float(value):,.2f}" | |
except: | |
pass | |
mapping_info.append(f"{mapping.caption}: {value}") | |
if mapping_info: | |
# Response prompt'a mapping bilgilerini ekle | |
mapping_text = "\n\nΓnemli Bilgiler:\n" + "\n".join(f"β’ {info}" for info in mapping_info) | |
response_prompt = response_prompt.replace("{{api_response}}", f"{{{{api_response}}}}{mapping_text}") | |
# API response'u JSON string olarak ekle | |
response_json = json.dumps(api_response, ensure_ascii=False, indent=2) | |
final_prompt = response_prompt.replace("{{api_response}}", response_json) | |
return final_prompt | |
def extract_value_from_json_path(data: Dict, path: str): | |
"""Extract value from JSON using dot notation path""" | |
try: | |
parts = path.split('.') | |
value = data | |
for part in parts: | |
if isinstance(value, dict): | |
value = value.get(part) | |
elif isinstance(value, list) and part.isdigit(): | |
value = value[int(part)] | |
else: | |
return None | |
return value | |
except: | |
return None | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
# PARAMETER PROMPT | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
_FMT = """#PARAMETERS:{"extracted":[{"name":"<param>","value":"<val>"},...],"missing":["<param>",...]}""" | |
def build_parameter_prompt(intent_cfg, | |
missing_params: List[str], | |
user_input: str, | |
conversation: List[Dict[str, str]], | |
locale_code: str = None) -> str: | |
# Intent'in locale'ini kullan, yoksa default | |
if not locale_code: | |
locale_code = getattr(intent_cfg, 'locale', 'tr-TR') | |
date_ctx = _get_date_context() | |
locale_data = LocaleManager.get_locale(locale_code) | |
parts: List[str] = [ | |
f"You are extracting parameters from user messages in {locale_data.get('name', 'the target language')}.", | |
f"Today is {date_ctx['today']} ({date_ctx['today_weekday']}). Tomorrow is {date_ctx['tomorrow']}.", | |
"Extract ONLY the parameters listed below from the conversation.", | |
"Look at BOTH the current message AND previous messages to find parameter values.", | |
"If a parameter cannot be found, is invalid, or wasn't provided, keep it in the \"missing\" list.", | |
"Never guess or make up values. Only extract values explicitly given by the user.", | |
"", | |
"IMPORTANT: If the user is NOT providing the requested parameter but instead:", | |
"- Asking for recommendations or advice (e.g. 'nereye gitsem?', 'ΓΆnerin var mΔ±?')", | |
"- Expressing uncertainty (e.g. 'tam net deΔil', 'emin deΔilim', 'bilmiyorum')", | |
"- Changing the subject or asking something else", | |
"Then DO NOT extract any value for that parameter. Keep it in the 'missing' list.", | |
"" | |
] | |
# Add parameter descriptions | |
parts.append("Parameters to extract:") | |
for p in intent_cfg.parameters: | |
if p.name in missing_params: | |
# Special handling for date type parameters | |
if p.type == "date": | |
date_prompt = _build_locale_aware_date_prompt( | |
p, date_ctx, locale_data, locale_code | |
) | |
parts.append(date_prompt) | |
else: | |
parts.append(f"β’ {p.name}: {p.extraction_prompt}") | |
# Add format instruction | |
parts.append("") | |
parts.append("IMPORTANT: Your response must start with '#PARAMETERS:' followed by the JSON.") | |
parts.append("Return ONLY this format with no extra text before or after:") | |
parts.append(_FMT) | |
# Add conversation history | |
history_block = "\n".join( | |
f"{m['role'].upper()}: {m['content']}" for m in conversation[-10:] | |
) | |
prompt = ( | |
"\n".join(parts) + | |
"\n\nConversation so far:\n" + history_block + | |
"\n\nUSER: " + user_input.strip() | |
) | |
log(f"π Parameter prompt built for missing: {missing_params}") | |
return prompt | |
def _build_locale_aware_date_prompt(param, date_ctx: Dict, locale_data: Dict, locale_code: str) -> str: | |
"""Build date extraction prompt based on locale""" | |
# Locale'e ΓΆzel date expressions | |
date_expressions = locale_data.get("date_expressions", {}) | |
months = locale_data.get("months", {}) | |
prompt_parts = [ | |
f"β’ {param.name}: {param.extraction_prompt}", | |
" IMPORTANT DATE RULES:" | |
] | |
# Common date expressions | |
if locale_code.startswith("tr"): | |
# Turkish specific | |
prompt_parts.extend([ | |
f" - 'bugΓΌn' = {date_ctx['today']}", | |
f" - 'yarΔ±n' = {date_ctx['tomorrow']}", | |
f" - 'ΓΆbΓΌr gΓΌn' or 'ertesi gΓΌn' = {date_ctx['day_after_tomorrow']}", | |
f" - 'bu hafta sonu' = {date_ctx['this_weekend_saturday']} or {date_ctx['this_weekend_sunday']}", | |
f" - 'bu cumartesi' = {date_ctx['this_weekend_saturday']}", | |
f" - 'bu pazar' = {date_ctx['this_weekend_sunday']}", | |
f" - 'haftaya' or 'gelecek hafta' = add 7 days to current date", | |
f" - 'haftaya bugΓΌn' = {date_ctx['next_week_same_day']}", | |
f" - 'iki hafta sonra' = {date_ctx['two_weeks_later']}", | |
f" - 'X gΓΌn sonra' = add X days to today" | |
]) | |
elif locale_code.startswith("en"): | |
# English specific | |
prompt_parts.extend([ | |
f" - 'today' = {date_ctx['today']}", | |
f" - 'tomorrow' = {date_ctx['tomorrow']}", | |
f" - 'day after tomorrow' = {date_ctx['day_after_tomorrow']}", | |
f" - 'this weekend' = {date_ctx['this_weekend_saturday']} or {date_ctx['this_weekend_sunday']}", | |
f" - 'this Saturday' = {date_ctx['this_weekend_saturday']}", | |
f" - 'this Sunday' = {date_ctx['this_weekend_sunday']}", | |
f" - 'next week' = add 7 days to current date", | |
f" - 'in X days' = add X days to today" | |
]) | |
# DiΔer diller iΓ§in de eklenebilir | |
# Month names | |
if months: | |
month_list = ", ".join([ | |
f"{name}={num}" for num, name in sorted(months.items()) | |
]) | |
prompt_parts.append(f" - Month names: {month_list}") | |
# Date format hint | |
date_format = locale_data.get("date_format", "YYYY-MM-DD") | |
prompt_parts.append(f" - Expected date format: YYYY-MM-DD (convert from {date_format})") | |
return "\n".join(prompt_parts) | |
def build_smart_parameter_question_prompt( | |
collection_config, # ParameterCollectionConfig | |
intent_config, # IntentConfig | |
missing_params: List[str], | |
session, # Session object | |
project_language: str = "Turkish" | |
) -> str: | |
"""AkΔ±llΔ± parametre sorusu ΓΌretmek iΓ§in prompt oluΕtur""" | |
# Config'den template'i al | |
template = collection_config.collection_prompt | |
# Conversation history'yi formatla | |
conversation_history = _format_conversation_history(session.chat_history) | |
# Toplanan parametreleri formatla | |
collected_params = _format_collected_params(session.variables, intent_config) | |
# Eksik parametreleri formatla | |
missing_params_str = _format_missing_params(missing_params, intent_config) | |
# Cevaplanmayan parametreleri formatla | |
unanswered_params_str = _format_unanswered_params( | |
session.unanswered_parameters, | |
intent_config, | |
session.asked_parameters | |
) | |
# KaΓ§ parametre sorulacaΔΔ±nΔ± belirle | |
params_to_ask_count = min( | |
len(missing_params), | |
collection_config.max_params_per_question | |
) | |
# Template'i doldur | |
prompt = template.replace("{{conversation_history}}", conversation_history) | |
prompt = prompt.replace("{{intent_name}}", intent_config.name) | |
prompt = prompt.replace("{{intent_caption}}", intent_config.caption) | |
prompt = prompt.replace("{{collected_params}}", collected_params) | |
prompt = prompt.replace("{{missing_params}}", missing_params_str) | |
prompt = prompt.replace("{{unanswered_params}}", unanswered_params_str) | |
prompt = prompt.replace("{{max_params}}", str(params_to_ask_count)) | |
prompt = prompt.replace("{{project_language}}", project_language) | |
log(f"π Smart parameter question prompt built for {params_to_ask_count} params") | |
return prompt | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
# HELPER FUNCTIONS FOR SMART PARAMETER COLLECTION | |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
def _format_conversation_history(chat_history: List[Dict[str, str]]) -> str: | |
"""Format conversation history for prompt""" | |
# Son 5 mesajΔ± al | |
recent_history = chat_history[-5:] if len(chat_history) > 5 else chat_history | |
formatted = [] | |
for msg in recent_history: | |
role = "User" if msg["role"] == "user" else "Assistant" | |
formatted.append(f"{role}: {msg['content']}") | |
return "\n".join(formatted) if formatted else "No previous conversation" | |
def _format_collected_params(variables: Dict[str, str], intent_config) -> str: | |
"""Format collected parameters""" | |
collected = [] | |
for param in intent_config.parameters: | |
if param.variable_name in variables: | |
value = variables[param.variable_name] | |
collected.append(f"- {param.caption}: {value}") | |
return "\n".join(collected) if collected else "None collected yet" | |
def _format_missing_params(missing_params: List[str], intent_config) -> str: | |
"""Format missing parameters with their captions""" | |
missing = [] | |
for param_name in missing_params: | |
param = next((p for p in intent_config.parameters if p.name == param_name), None) | |
if param: | |
missing.append(f"- {param.caption} ({param.name})") | |
return "\n".join(missing) if missing else "None" | |
def _format_unanswered_params(unanswered_params: List[str], intent_config, asked_params: Dict[str, int]) -> str: | |
"""Format unanswered parameters with ask count""" | |
unanswered = [] | |
for param_name in unanswered_params: | |
param = next((p for p in intent_config.parameters if p.name == param_name), None) | |
if param: | |
ask_count = asked_params.get(param_name, 0) | |
unanswered.append(f"- {param.caption} (asked {ask_count} time{'s' if ask_count > 1 else ''})") | |
return "\n".join(unanswered) if unanswered else "None" | |
def extract_params_from_question(question: str, all_params: List[str], intent_config) -> List[str]: | |
""" | |
Sorulan sorudan hangi parametrelerin sorulduΔunu tahmin et | |
(LLM'in hangi parametreleri sorduΔunu anlamak iΓ§in basit bir yaklaΕΔ±m) | |
""" | |
found_params = [] | |
question_lower = question.lower() | |
for param_name in all_params: | |
param = next((p for p in intent_config.parameters if p.name == param_name), None) | |
if param: | |
# Parametre caption'Δ±nΔ±n veya keywords'ΓΌnΓΌn soruda geΓ§ip geΓ§mediΔini kontrol et | |
caption_words = param.caption.lower().split() | |
# Caption kelimelerinden herhangi biri soruda geΓ§iyorsa | |
if any(word in question_lower for word in caption_words if len(word) > 3): | |
found_params.append(param_name) | |
continue | |
# Parametre tipine gΓΆre ΓΆzel kontroller | |
if param.type == "date": | |
date_keywords = ["tarih", "zaman", "gΓΌn", "hafta", "ay", "yΔ±l", "ne zaman", "hangi gΓΌn"] | |
if any(keyword in question_lower for keyword in date_keywords): | |
found_params.append(param_name) | |
elif param.type == "city": | |
city_keywords = ["Εehir", "nereden", "nereye", "hangi il", "il", "yer"] | |
if any(keyword in question_lower for keyword in city_keywords): | |
found_params.append(param_name) | |
elif param.type == "number": | |
number_keywords = ["kaΓ§", "sayΔ±", "miktar", "adet", "kiΕi", "yolcu"] | |
if any(keyword in question_lower for keyword in number_keywords): | |
found_params.append(param_name) | |
# EΔer hiΓ§bir parametre bulunamadΔ±ysa, en az ΓΆncelikli olanΔ± dΓΆndΓΌr | |
if not found_params and all_params: | |
found_params = [all_params[0]] | |
log(f"π Extracted params from question: {found_params}") | |
return found_params |