BF-Realtime / tools.py
SamiKoen
ProductCatalog unification: check_warehouse_stock artik product_index ile (gpt-5-mini matcher kalkti)
c92f627
Raw
History Blame Contribute Delete
5.38 kB
"""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}"