feat(codex-bridge): natural model command, spelled-out names, any gateway model, clear miss
Live voice chat 01a12804: the user said "Seed iki nokta bir turbo yap" (no "modeli", name spelled out) and the model was in the gateway but not in Codex's list; the main model then promised the bridge would do it. A sentence ending in a command verb whose name fits exactly one model is now a command, single letters are joined, a name missing from Codex's list is looked up in the gateway's full model list, and an explicit command whose name fits nothing is answered by the bridge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a612beba7
commit
a46ff51019
3 files changed
+92
-5
No files matched your search
@@ -773,6 +773,31 @@ def bilinen_modeller():
|
||||
if m.get("slug") and m["slug"] not in gorulen]
|
||||
return liste
|
||||
|
||||
_GECIT = {"zaman": 0.0, "liste": []}
|
||||
|
||||
def gecit_modelleri():
|
||||
"""AITURK gecidindeki BUTUN modeller (Codex listesinde gorunmeyenler dahil), "ide/<kimlik>" olarak. Soylenen ad
|
||||
Codex listesinde yoksa buraya bakilir (olcum 11 Ekim 2026: "Seed 2.1 Turbo" gecitte vardi, listede yoktu).
|
||||
10 dakika bellekte tutulur; alinamazsa bos liste."""
|
||||
simdi = time.time()
|
||||
if simdi - _GECIT["zaman"] < 600:
|
||||
return _GECIT["liste"]
|
||||
_GECIT["zaman"] = simdi
|
||||
rota = ROUTES.get("aiturk-ide")
|
||||
if not rota:
|
||||
_GECIT["liste"] = []
|
||||
return []
|
||||
try:
|
||||
adres = rota["url"].rsplit("/", 1)[0] + "/models"
|
||||
istek = urllib.request.Request(adres, headers={"Authorization": f"Bearer {rota['key']}", "User-Agent": UA})
|
||||
with urllib.request.urlopen(istek, timeout=6) as yanit:
|
||||
veri = json.loads(yanit.read())
|
||||
_GECIT["liste"] = [("ide/" + m["id"], m["id"]) for m in veri.get("data", []) if isinstance(m, dict) and m.get("id")]
|
||||
except Exception as e:
|
||||
log(f"gecit model listesi alinamadi: {type(e).__name__}: {e}")
|
||||
_GECIT["liste"] = []
|
||||
return _GECIT["liste"]
|
||||
|
||||
def model_adi(slug):
|
||||
return next((ad for kimlik, ad in bilinen_modeller() if kimlik == slug), None) or slug
|
||||
|
||||
@@ -1156,7 +1181,16 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
return f"Sohbetin modeli şu an {model_adi(etkin)}. " + model_gecis.NASIL
|
||||
sonuc = model_gecis.eslestir(komut["ad"], bilinen_modeller())
|
||||
if not sonuc:
|
||||
return None # ad hicbir modele benzemiyor: komut degil, modele gider
|
||||
sonuc = model_gecis.eslestir(komut["ad"], gecit_modelleri()) # Codex listesinde olmayan gecit modeli
|
||||
if not komut.get("kesin", True) and not (sonuc and "slug" in sonuc):
|
||||
return None # "modeli" denmemis ve ad tek modele oturmuyor: komut degil, modele gider
|
||||
if not sonuc:
|
||||
if ortada:
|
||||
return None
|
||||
# Acik komut ("modeli ... yap") ama ad bulunamadi: model "kopru yapacak" diye yanlis soz vermesin.
|
||||
log(f"[{n} {gelen}] model komutu: ad bulunamadi {komut['ad']}")
|
||||
return (f"\"{' '.join(komut['ad'])}\" adına uyan bir model bulamadım, model değişmedi; sohbetin modeli hâlâ "
|
||||
f"{model_adi(etkin)}. Adını biraz farklı söyler misin?")
|
||||
if "adaylar" in sonuc:
|
||||
soru = ("Hangi modeli istediğini tek modele bağlayamadım, model değişmedi. En yakınlar: "
|
||||
+ ", ".join(sonuc["adaylar"]) + ". Adını biraz daha tam söyler misin?")
|
||||
|
||||
@@ -120,11 +120,42 @@ def _sayilari_cevir(kelimeler):
|
||||
return cikti
|
||||
|
||||
|
||||
DOLGU = {"evet", "tamam", "hadi", "peki", "tabii", "hayir", "yani", "sey", "ee", "eee", "hmm", "olur", "simdi"}
|
||||
|
||||
|
||||
def _harfleri_birlestir(kelimeler):
|
||||
"""Harf harf soylenen ad tek sozcuk olur: "s e e d iki nokta bir turbo" -> "seed iki nokta bir turbo"."""
|
||||
cikti, harfler = [], []
|
||||
for k in kelimeler + [""]:
|
||||
if len(k) == 1 and k.isalpha():
|
||||
harfler.append(k)
|
||||
continue
|
||||
if len(harfler) >= 3:
|
||||
cikti.append("".join(harfler))
|
||||
else:
|
||||
cikti.extend(harfler)
|
||||
harfler = []
|
||||
if k:
|
||||
cikti.append(k)
|
||||
# Ad hem heceleyerek hem duz soylendiyse ("seed s e e d ...") tekrar atilir.
|
||||
return [k for i, k in enumerate(cikti) if i == 0 or k != cikti[i - 1]]
|
||||
|
||||
|
||||
def komut_coz(metin):
|
||||
"""None | {"tur": "gec", "ad": [parcalar]} | {"tur": "sor"}."""
|
||||
kelimeler = re.findall(r"[a-z0-9]+", duz(metin))
|
||||
if not kelimeler or len(kelimeler) > EN_COK_KELIME or not any(k.startswith("model") for k in kelimeler):
|
||||
kelimeler = _harfleri_birlestir(re.findall(r"[a-z0-9]+", duz(metin)))
|
||||
if not kelimeler or len(kelimeler) > EN_COK_KELIME:
|
||||
return None
|
||||
if not any(k.startswith("model") for k in kelimeler):
|
||||
# "modeli" denmeden soylenen komut ("Seed iki nokta bir turbo yap", "gemini flash low'a gec"): yalniz cumle
|
||||
# bir komut fiiliyle bitiyorsa ve ad bir modele TAM uyuyorsa komut sayilir (kesin=False: cagiran, ad
|
||||
# bulunamazsa mesaji komut saymaz, modele birakir).
|
||||
if any(k.startswith(OLUMSUZ) for k in kelimeler) or kelimeler[-1] not in FIILLER or len(kelimeler) > 9:
|
||||
return None
|
||||
kelimeler = _sayilari_cevir(kelimeler)
|
||||
ad = [TAKMA.get(k, k) for k in kelimeler[:-1] if k not in ATLA and k not in DOLGU]
|
||||
ad = [p for k in ad for p in re.findall(r"[a-z]+|\d+", k)]
|
||||
return {"tur": "gec", "ad": ad, "kesin": False} if len(ad) >= 2 else None
|
||||
if len(kelimeler) <= 8 and any(k in SORU for k in kelimeler):
|
||||
return {"tur": "sor"} if any(k in SORU_ISARETI for k in kelimeler) else None
|
||||
if any(k.startswith(OLUMSUZ) for k in kelimeler):
|
||||
@@ -135,10 +166,10 @@ def komut_coz(metin):
|
||||
if all(k in DEVAM_EKI for k in kelimeler[son_fiil + 1:]):
|
||||
kelimeler = kelimeler[:son_fiil + 1]
|
||||
kelimeler = _sayilari_cevir(kelimeler)
|
||||
ad = [TAKMA.get(k, k) for k in kelimeler if k not in ATLA and k not in fiil]
|
||||
ad = [TAKMA.get(k, k) for k in kelimeler if k not in ATLA and k not in fiil and k not in DOLGU]
|
||||
ad = [p for k in ad for p in re.findall(r"[a-z]+|\d+", k)]
|
||||
# Ad soylenmemis ("modeli değiştir", "başka modele geç"): degisiklik yok, nasil soylenecegi anlatilir.
|
||||
return {"tur": "gec", "ad": ad} if ad else {"tur": "nasil"}
|
||||
return {"tur": "gec", "ad": ad, "kesin": True} if ad else {"tur": "nasil"}
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -112,6 +112,28 @@ class Bilgi(unittest.TestCase):
|
||||
self.assertIsNone(g.komut_coz("modeli değiştirme"))
|
||||
|
||||
|
||||
class Dogal(unittest.TestCase):
|
||||
GECIT = [("ide/9router/ModelARK/dola-seed-2-1-turbo-260628", "9router/ModelARK/dola-seed-2-1-turbo-260628"),
|
||||
("ide/9router/ModelARK/seed-2-0-pro-260328", "9router/ModelARK/seed-2-0-pro-260328")]
|
||||
|
||||
def test_command_without_the_word_model_and_a_spelled_out_name(self):
|
||||
for text in ("Seed iki nokta bir turbo yap", "Evet. Seed, S E E D iki nokta bir turbo yap"):
|
||||
command = g.komut_coz(text)
|
||||
self.assertEqual((command["ad"], command["kesin"]), (["seed", "2", "1", "turbo"], False), text)
|
||||
self.assertEqual(g.eslestir(command["ad"], self.GECIT)["slug"], "ide/9router/ModelARK/dola-seed-2-1-turbo-260628")
|
||||
self.assertTrue(g.komut_coz("Modeli Seed iki nokta bir turbo yap")["kesin"])
|
||||
self.assertEqual(g.eslestir(g.komut_coz("gemini flash low'a geç")["ad"], MODELS)["slug"],
|
||||
"ide/9router-ag-gemini-3.8-flash-low")
|
||||
|
||||
def test_ordinary_requests_ending_in_a_verb_are_not_commands(self):
|
||||
for text in ("test yap", "kahve yap", "yapma"):
|
||||
self.assertIsNone(g.komut_coz(text), text)
|
||||
for text in ("şu dosyayı düzelt ve commit yap", "rapor hazırla gemini flash low ile karşılaştırma yap"):
|
||||
command = g.komut_coz(text)
|
||||
self.assertFalse(command["kesin"])
|
||||
self.assertFalse((g.eslestir(command["ad"], MODELS) or {}).get("slug"), text)
|
||||
|
||||
|
||||
class Yon(unittest.TestCase):
|
||||
def test_route_lasts_until_the_user_picks_another_model_in_the_list(self):
|
||||
with tempfile.TemporaryDirectory() as home:
|
||||
|
||||
Reference in new issue
Block a user