Spaces:
Running
Running
SamiKoen
ProductCatalog unification: check_warehouse_stock artik product_index ile (gpt-5-mini matcher kalkti)
c92f627 | """Realtime asistan icin tool implementasyonlari. | |
| WhatsApp BF-WAB'da kanitlanmis smart_warehouse_with_price.py'yi kullanir. | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import re | |
| from product_matcher import ( | |
| find_main_local_substring, | |
| find_main_product_in_text, | |
| find_main_via_nano, | |
| ) | |
| from warehouse_lookup import lookup_stock_for_query | |
| logger = logging.getLogger(__name__) | |
| # ---------- Telaffuz duzeltmeleri ---------- | |
| def apply_pronunciation_fixes(text: str) -> str: | |
| """Sesli asistan Ingilizce fonetik kullaniyor — TR 'C' sesi Ingilizce 'J' ile uyusur. | |
| Caddebostan/CADDEBOSTAN -> Jaddebostan (J = TR C)""" | |
| if not isinstance(text, str): | |
| return text | |
| for src in ("Caddebostan", "CADDEBOSTAN", "Cadde Bostan", "CADDE BOSTAN"): | |
| text = text.replace(src, "Jaddebostan") | |
| return text | |
| # ---------- URL'leri tool sonucundan strip ---------- | |
| def strip_urls(text: str) -> str: | |
| """Modelin URL okumamasi icin tool result'undan link satirlari kaldirilir.""" | |
| if not text: | |
| return text | |
| text = re.sub(r"(?im)^\s*(?:Link|URL|Url|🔗.*?):.*$", "", text) | |
| text = re.sub(r"https?://\S+", "", text) | |
| text = re.sub(r"\n{3,}", "\n\n", text).strip() | |
| return text | |
| # ---------- OpenAI realtime tool definition ---------- | |
| TOOLS = [ | |
| { | |
| "type": "function", | |
| "name": "show_product", | |
| "description": ( | |
| "Bir urun bahsedildiginde sag monitorde urun sayfasini OTOMATIK acar. " | |
| "Hizli ve hafif (XML/stok cekmez). Musteri sadece urun adi soylediginde " | |
| "veya hakkinda bilgi istediginde KULLAN — fiyat/gorsel/aciklama sayfada gorunur. " | |
| "Stok/depo sorulari icin kullanma." | |
| ), | |
| "parameters": { | |
| "type": "object", | |
| "properties": { | |
| "product_name": { | |
| "type": "string", | |
| "description": ( | |
| "Tam urun adi: marka + model + kategori " | |
| "(orn. 'Marlin 5', 'Madone SLR 9', 'Bontrager Comp sele')" | |
| ), | |
| } | |
| }, | |
| "required": ["product_name"], | |
| }, | |
| }, | |
| { | |
| "type": "function", | |
| "name": "check_warehouse_stock", | |
| "description": ( | |
| "Müşteri SADECE stok/depo durumunu sordugunda kullan: " | |
| "'var mi?', 'stokta mi?', 'Caddebostan'da var mi?', 'hangi magazada?'. " | |
| "Magaza-bazli stok bilgisi için BizimHesap XML'ini ceker (yavas/maliyetli). " | |
| "Sadece urun gostermek/aciklamak icin show_product kullan." | |
| ), | |
| "parameters": { | |
| "type": "object", | |
| "properties": { | |
| "user_message": { | |
| "type": "string", | |
| "description": ( | |
| "Musterinin orijinal stok/depo sorusu " | |
| "(orn. 'Madone SLR 9 Caddebostan'da var mi', 'Marlin 5 stokta mi')" | |
| ), | |
| } | |
| }, | |
| "required": ["user_message"], | |
| }, | |
| }, | |
| ] | |
| def show_product_local(product_name: str) -> str: | |
| """Hybrid matcher: | |
| 1) Local substring match (en kesin, bedava, anlik) — 'marlin 6' -> 'MARLIN 6 GEN 3' | |
| 2) Nano-first (gpt-5-nano) — fuzzy/eksik kelimeli sorgular icin | |
| 3) Local distinctive-token matcher — son catalog | |
| """ | |
| q = product_name or "" | |
| # 1) Substring (cogu net soru bunu eslesir, anlik) | |
| p = find_main_local_substring(q) | |
| if p: | |
| logger.info(f"[show] substring hit: {p.get('name')}") | |
| else: | |
| # 2) Nano (fuzzy/eksik/marka tutarsizligi durumlari) | |
| p = find_main_via_nano(q) | |
| if p: | |
| logger.info(f"[show] nano hit: {p.get('name')}") | |
| else: | |
| # 3) Token-based (network/quota fail icin) | |
| p = find_main_product_in_text(q) | |
| if not p: | |
| return f"Urun bulunamadi: {product_name}" | |
| name = p.get("name") or "" | |
| link = p.get("link") or "" | |
| if not link: | |
| return name | |
| return f"{name}\nLink: {link}" | |
| def _normalize_tool_input(arguments: dict) -> dict: | |
| """Tool arg'larinda 'Jaddebostan' (sesli telaffuz) -> 'Caddebostan' (gercek depo adi). | |
| BizimHesap'ta JADDEBOSTAN diye depo yok; arama bozulmasin.""" | |
| out = dict(arguments) | |
| for k, v in out.items(): | |
| if isinstance(v, str): | |
| out[k] = v.replace("Jaddebostan", "Caddebostan").replace("JADDEBOSTAN", "CADDEBOSTAN") | |
| return out | |
| def handle_tool_call_sync(name: str, arguments: dict) -> str: | |
| """Tool dispatcher. Sync — realtime relay'de to_thread ile sarilacak.""" | |
| arguments = _normalize_tool_input(arguments) | |
| try: | |
| if name == "show_product": | |
| q = arguments.get("product_name", "") | |
| logger.info(f"[tool] show_product: {q!r}") | |
| return apply_pronunciation_fixes(show_product_local(q)) | |
| if name == "check_warehouse_stock": | |
| msg = arguments.get("user_message", "") | |
| logger.info(f"[tool] check_warehouse_stock: {msg!r}") | |
| result = lookup_stock_for_query(msg) | |
| if result is None: | |
| return "Stok bilgisi bulunamadi." | |
| return apply_pronunciation_fixes(str(result)) | |
| return f"Bilinmeyen fonksiyon: {name}" | |
| except Exception as e: | |
| logger.exception(f"Tool call hatasi ({name})") | |
| return f"Hata: {e}" | |