From 8a612beba7d7022a4f012433a31eca141776805c Mon Sep 17 00:00:00 2001 From: yilsem Date: Sun, 11 Oct 2026 01:55:34 +0300 Subject: [PATCH] feat(codex-bridge): tell the model the chat model can be changed by saying so; a nameless command asks which MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured in a live voice chat: asked whether it could change the model, Opus answered "I cannot change this chat's model here". Every request now carries one short developer note, and "modeli değiştir" without a name is answered by the bridge with how to say it. Co-Authored-By: Claude Opus 5.5 --- apps/desktop/assets/codex-bridge/bridge.py | 8 ++++++ .../assets/codex-bridge/model_gecis.py | 28 ++++++++++++++++++- .../assets/codex-bridge/test_denetim_ek.py | 2 +- .../assets/codex-bridge/test_model_gecis.py | 18 +++++++++++- 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/apps/desktop/assets/codex-bridge/bridge.py b/apps/desktop/assets/codex-bridge/bridge.py index d3be827..1eef550 100644 --- a/apps/desktop/assets/codex-bridge/bridge.py +++ b/apps/desktop/assets/codex-bridge/bridge.py @@ -1149,6 +1149,11 @@ class H(http.server.BaseHTTPRequestHandler): return None log(f"[{n} {gelen}] model sorusu kopruden cevaplandi: {etkin}") return f"Sohbetin modeli şu an {model_adi(etkin)}." + if komut["tur"] == "nasil": + if ortada: + return None + log(f"[{n} {gelen}] adsiz model komutu: nasil soylenecegi anlatildi") + 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 @@ -1230,6 +1235,9 @@ class H(http.server.BaseHTTPRequestHandler): GORULEN_ISTEMCI.add(kaynak) log(f"istemci goruldu: {kaynak}") try: + # Model, sohbetin modelinin soylenerek degistirilebildigini bilsin; yoksa "degistiremem" diyor. + if CFG.get("sesle_model", True) and CFG.get("model_bilgi", True) and sohbet and model_gecis.bilgi_ekle(req): + govde_degisti = True if is_hatti.masaustu_mu(kaynak) and is_hatti.ekle(req, is_hatti.talimat(CFG.get("aiturk_hat"))): govde_degisti = True except Exception as e: diff --git a/apps/desktop/assets/codex-bridge/model_gecis.py b/apps/desktop/assets/codex-bridge/model_gecis.py index 902977a..36f4e39 100644 --- a/apps/desktop/assets/codex-bridge/model_gecis.py +++ b/apps/desktop/assets/codex-bridge/model_gecis.py @@ -137,7 +137,8 @@ def komut_coz(metin): kelimeler = _sayilari_cevir(kelimeler) ad = [TAKMA.get(k, k) for k in kelimeler if k not in ATLA and k not in fiil] ad = [p for k in ad for p in re.findall(r"[a-z]+|\d+", k)] - return {"tur": "gec", "ad": ad} if ad else None + # 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 None @@ -257,6 +258,31 @@ def gecmisi_temizle(req, modeller, etkin=None): return n +BILGI_ISARETI = "[Model değiştirme]" +BILGI = (BILGI_ISARETI + " Bu sohbetin modeli söylenerek değiştirilebilir: kullanıcı \"modeli yap\" dediğinde " + "(örnek: \"modeli gemini flash low yap\", \"modeli opus beş beş yap\") değişikliği aradaki köprü yapar; senin bir " + "araç çağırman ya da ayar değiştirmen gerekmez. Kullanıcı modeli değiştirip değiştiremeyeceğini sorarsa " + "\"değiştiremem\" deme; bu cümleyi söylemesinin yeterli olduğunu kısaca anlat. Hangi modelde olduğunu öğrenmek " + "için \"seçili model hangisi\" demesi yeter.") +NASIL = ("Hangi modele geçeyim? Adını söylemen yeterli, örneğin: \"modeli gemini flash low yap\" ya da " + "\"modeli opus beş beş yap\". Şu anki modeli öğrenmek için \"seçili model hangisi\" de.") + + +def bilgi_ekle(req): + """Modele, sohbetin modelinin soylenerek degistirilebildigini bildirir (olcum 11 Ekim 2026: Opus "bu sohbetin + modelini buradan değiştiremem" dedi). Istegin basina tek developer mesaji; zaten varsa eklenmez. True: eklendi.""" + girdi = req.get("input") + if not isinstance(girdi, list) or not girdi: + return False + for oge in girdi[:4]: + icerik = oge.get("content") if isinstance(oge, dict) else None + yazi = icerik if isinstance(icerik, str) else " ".join(str(p.get("text") or "") for p in icerik or [] if isinstance(p, dict)) + if BILGI_ISARETI in yazi: + return False + girdi.insert(0, {"type": "message", "role": "developer", "content": [{"type": "input_text", "text": BILGI}]}) + return True + + def devir_notu(yeni_ad, eski_ad): return (f"[Köprü notu: kullanıcı bu sohbetin modelini değiştirdi. Önceki model {eski_ad} idi; şu andan itibaren " f"cevap veren model sensin ({yeni_ad}). Yarım kalan işi kaldığı yerden sürdür, baştan başlama. " diff --git a/apps/desktop/assets/codex-bridge/test_denetim_ek.py b/apps/desktop/assets/codex-bridge/test_denetim_ek.py index bf15f39..08f991a 100644 --- a/apps/desktop/assets/codex-bridge/test_denetim_ek.py +++ b/apps/desktop/assets/codex-bridge/test_denetim_ek.py @@ -258,7 +258,7 @@ class Zincir(unittest.TestCase): "time": types.SimpleNamespace(sleep=lambda s: None, time=time.time), "urllib": types.SimpleNamespace(request=types.SimpleNamespace(Request=urllib.request.Request, urlopen=Mock()), error=urllib.error), - "decompress": lambda body, enc: body, "MODEL_MAP": {}, "ROUTES": {}, "CFG": {}, "log": lambda m: None, + "decompress": lambda body, enc: body, "MODEL_MAP": {}, "ROUTES": {}, "CFG": {"model_bilgi": False}, "log": lambda m: None, "model_gecis": model_gecis, "YONLENDIRME": model_gecis.Yonlendirme(None), "bilinen_modeller": lambda: MODELS, "model_adi": lambda slug: dict(MODELS).get(slug, slug), "is_hatti": is_hatti, "GORULEN_ISTEMCI": set(), "yarim": yarim, "ozet_kopru": ozet_kopru, "OZET_FRENI": ozet_kopru.DonguFreni(), "pencere": pencere, diff --git a/apps/desktop/assets/codex-bridge/test_model_gecis.py b/apps/desktop/assets/codex-bridge/test_model_gecis.py index 2491014..65a9d6b 100644 --- a/apps/desktop/assets/codex-bridge/test_model_gecis.py +++ b/apps/desktop/assets/codex-bridge/test_model_gecis.py @@ -50,7 +50,7 @@ class Komut(unittest.TestCase): def test_ordinary_sentences_are_not_commands(self): for text in ("Masaüstünde kaç klasör var", "bu modeli kullanarak bir site yap", "yapay zeka modeli nedir", - "modeli değiştir", "hangi modeli önerirsin bu iş için bana söyle bakalım lütfen", + "hangi modeli önerirsin bu iş için bana söyle bakalım lütfen", "model " + "çok uzun bir cümle " * 5 + "yap"): self.assertFalse(pick(text) and "slug" in pick(text), text) self.assertEqual(g.komut_coz("seçili model hangisi"), {"tur": "sor"}) @@ -96,6 +96,22 @@ class Gecmis(unittest.TestCase): self.assertEqual(g.gecmisi_temizle(req, MODELS), 0) +class Bilgi(unittest.TestCase): + def test_model_is_told_once_that_the_chat_model_changes_by_saying_so(self): + req = {"input": [user("selam")]} + self.assertTrue(g.bilgi_ekle(req)) + self.assertEqual(req["input"][0]["role"], "developer") + self.assertIn("modeli yap", req["input"][0]["content"][0]["text"]) + self.assertFalse(g.bilgi_ekle(req)) + self.assertEqual(len(req["input"]), 2) + self.assertFalse(g.bilgi_ekle({"input": []})) + + def test_a_command_without_a_name_asks_which_model(self): + for text in ("modeli değiştir", "başka modele geç", "modeli değiştirir misin"): + self.assertEqual(g.komut_coz(text), {"tur": "nasil"}, text) + self.assertIsNone(g.komut_coz("modeli değiştirme")) + + class Yon(unittest.TestCase): def test_route_lasts_until_the_user_picks_another_model_in_the_list(self): with tempfile.TemporaryDirectory() as home: