SamiKoen commited on
Commit
dbd1c5c
·
1 Parent(s): 829ccbf

Restore gpt-5.5 as primary fuzzy matcher (V1 quality), local + nano as fallback

Browse files
Files changed (3) hide show
  1. config.py +5 -1
  2. product_matcher.py +73 -1
  3. tools.py +20 -7
config.py CHANGED
@@ -6,7 +6,11 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
6
  REALTIME_MODEL = "gpt-realtime-2"
7
  REALTIME_URL = f"wss://api.openai.com/v1/realtime?model={REALTIME_MODEL}"
8
 
9
- # Fallback model (sadece local matcher fail ederse devreye girer)
 
 
 
 
10
  NANO_MODEL = "gpt-5-nano"
11
  NANO_TIMEOUT = 6
12
 
 
6
  REALTIME_MODEL = "gpt-realtime-2"
7
  REALTIME_URL = f"wss://api.openai.com/v1/realtime?model={REALTIME_MODEL}"
8
 
9
+ # Tool icindeki fuzzy matching modeli (primary)
10
+ MATCHER_MODEL = "gpt-5.5"
11
+ MATCHER_TIMEOUT = 12
12
+
13
+ # Fallback model (matcher fail ederse)
14
  NANO_MODEL = "gpt-5-nano"
15
  NANO_TIMEOUT = 6
16
 
product_matcher.py CHANGED
@@ -15,7 +15,7 @@ import logging
15
 
16
  import requests
17
 
18
- from config import OPENAI_API_KEY, NANO_MODEL, NANO_TIMEOUT
19
  from product_index import get_index, normalize, public_view
20
 
21
  logger = logging.getLogger(__name__)
@@ -144,6 +144,78 @@ def extract_product_link_from_text(text: str) -> str | None:
144
  return m.group(0) if m else None
145
 
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  def nano_fallback_match(query: str, products_summary: list[dict]) -> str:
148
  """Local matcher hicbir sey bulamazsa, gpt-5-nano'ya kucuk bir liste gonder.
149
  products_summary: [{i: int, n: name, v: variant}, ...]
 
15
 
16
  import requests
17
 
18
+ from config import OPENAI_API_KEY, MATCHER_MODEL, MATCHER_TIMEOUT, NANO_MODEL, NANO_TIMEOUT
19
  from product_index import get_index, normalize, public_view
20
 
21
  logger = logging.getLogger(__name__)
 
144
  return m.group(0) if m else None
145
 
146
 
147
+ def gpt_match(query: str, products_summary: list[dict], asked_warehouse: str | None = None) -> str:
148
+ """V1'deki gpt-5.5 fuzzy matcher (primary). Yazim hatasi, sira farki, semantik
149
+ eslesme yapar. Donus: virgulle ayrilmis index string'i ('5,12') veya '-1'."""
150
+ import json as _json
151
+ if not OPENAI_API_KEY or not products_summary:
152
+ return "-1"
153
+
154
+ warehouse_filter = ""
155
+ if asked_warehouse:
156
+ warehouse_filter = (
157
+ f"\nIMPORTANT: User is asking specifically about {asked_warehouse} warehouse. "
158
+ f"Only return products available in that warehouse."
159
+ )
160
+
161
+ smart_prompt = f"""User is asking: "{query}"
162
+
163
+ FIRST CHECK: Is this actually a product search?
164
+ - If the message is a question about the system, service, or a general inquiry, return: -1
165
+ - If the message contains "musun", "misin", "neden", "nasıl", etc. it's likely NOT a product search
166
+ - Only proceed if this looks like a genuine product name or model
167
+
168
+ Find ALL products that match this query from the list below.
169
+ If user asks about specific size (S, M, L, XL, XXL, SMALL, MEDIUM, LARGE, X-LARGE), return only that size.
170
+ If user asks generally (without size), return ALL variants of the product.
171
+ {warehouse_filter}
172
+
173
+ CRITICAL TURKISH CHARACTER RULES:
174
+ - "MARLIN" and "MARLİN" are the SAME product (Turkish İ vs I)
175
+ - Treat these as equivalent: I/İ/ı, Ö/ö, Ü/ü, Ş/ş, Ğ/ğ, Ç/ç
176
+
177
+ IMPORTANT BRAND AND PRODUCT TYPE RULES:
178
+ - GOBIK: Spanish textile brand we import. When user asks about "gobik", return ALL products with "GOBIK" in the name.
179
+ - Product names contain type information: FORMA (jersey/cycling shirt), TAYT (tights), İÇLİK (base layer), YAĞMURLUK (raincoat), etc.
180
+ - Understand Turkish/English terms:
181
+ * "erkek forma" / "men's jersey" -> Find products with FORMA in name
182
+ * "tayt" / "tights" -> Find products with TAYT in name
183
+ * "içlik" / "base layer" -> Find products with İÇLİK in name
184
+ * "yağmurluk" / "raincoat" -> Find products with YAĞMURLUK in name
185
+ - Gender: UNISEX means for both men and women.
186
+
187
+ Products list (with warehouse availability):
188
+ {_json.dumps(products_summary, ensure_ascii=False, indent=2)}
189
+
190
+ Return ONLY index numbers of ALL matching products as comma-separated list (e.g., "5,8,12,15").
191
+ If no products found, return ONLY: -1
192
+ DO NOT return empty string or any explanation, ONLY numbers or -1
193
+ """
194
+
195
+ try:
196
+ r = requests.post(
197
+ "https://api.openai.com/v1/chat/completions",
198
+ headers={
199
+ "Content-Type": "application/json",
200
+ "Authorization": f"Bearer {OPENAI_API_KEY}",
201
+ },
202
+ json={
203
+ "model": MATCHER_MODEL,
204
+ "messages": [
205
+ {"role": "system", "content": "You are a product matcher. Find ALL matching products. Return only index numbers."},
206
+ {"role": "user", "content": smart_prompt},
207
+ ],
208
+ },
209
+ timeout=MATCHER_TIMEOUT,
210
+ )
211
+ if r.status_code == 200:
212
+ return r.json()["choices"][0]["message"]["content"].strip()
213
+ logger.warning(f"[matcher/gpt] HTTP {r.status_code}: {r.text[:200]}")
214
+ except Exception as e:
215
+ logger.warning(f"[matcher/gpt] fail: {e}")
216
+ return "-1"
217
+
218
+
219
  def nano_fallback_match(query: str, products_summary: list[dict]) -> str:
220
  """Local matcher hicbir sey bulamazsa, gpt-5-nano'ya kucuk bir liste gonder.
221
  products_summary: [{i: int, n: name, v: variant}, ...]
tools.py CHANGED
@@ -14,7 +14,7 @@ import time
14
 
15
  from config import CACHE_TTL_SEARCH
16
  from product_index import get_index, normalize
17
- from product_matcher import nano_fallback_match
18
  from stock_service import get_cached_warehouse_xml, search_cache_get, search_cache_set
19
 
20
  logger = logging.getLogger(__name__)
@@ -208,15 +208,28 @@ def get_warehouse_stock_smart(user_message: str) -> list[str] | None:
208
  "warehouses": whs_with_stock,
209
  })
210
 
211
- # 1. Local matcher
212
- indices_str = _local_match_indices(user_message, summary, asked_wh)
213
- logger.info(f"[stock/local] indices: {indices_str!r}")
 
 
 
 
214
 
215
- # 2. Nano fallback (sadece local hicbir sey bulamadiysa)
 
 
 
 
 
 
 
216
  if indices_str == "-1":
217
  mini = [{"i": s["index"], "n": s["name"], "v": s["variant"]} for s in summary]
218
- indices_str = nano_fallback_match(user_message, mini)
219
- logger.info(f"[stock/nano] indices: {indices_str!r}")
 
 
220
 
221
  if not indices_str or indices_str == "-1":
222
  search_cache_set(cache_key, None)
 
14
 
15
  from config import CACHE_TTL_SEARCH
16
  from product_index import get_index, normalize
17
+ from product_matcher import gpt_match, nano_fallback_match
18
  from stock_service import get_cached_warehouse_xml, search_cache_get, search_cache_set
19
 
20
  logger = logging.getLogger(__name__)
 
208
  "warehouses": whs_with_stock,
209
  })
210
 
211
+ # 1. PRIMARY: gpt-5.5 fuzzy matcher (yazim hatasi, sira, semantik esnek)
212
+ gpt_summary = [
213
+ {"index": s["index"], "name": s["name"], "variant": s["variant"], "warehouses": s["warehouses"]}
214
+ for s in summary
215
+ ]
216
+ indices_str = gpt_match(user_message, gpt_summary, asked_wh)
217
+ logger.info(f"[stock/gpt] indices: {indices_str!r}")
218
 
219
+ # 2. Local fallback (gpt cagrisi fail ederse)
220
+ if not indices_str or indices_str == "-1":
221
+ local_str = _local_match_indices(user_message, summary, asked_wh)
222
+ if local_str != "-1":
223
+ indices_str = local_str
224
+ logger.info(f"[stock/local-fallback] indices: {indices_str!r}")
225
+
226
+ # 3. Nano son fallback
227
  if indices_str == "-1":
228
  mini = [{"i": s["index"], "n": s["name"], "v": s["variant"]} for s in summary]
229
+ nano_str = nano_fallback_match(user_message, mini)
230
+ if nano_str != "-1":
231
+ indices_str = nano_str
232
+ logger.info(f"[stock/nano-fallback] indices: {indices_str!r}")
233
 
234
  if not indices_str or indices_str == "-1":
235
  search_cache_set(cache_key, None)