diff --git a/apps/desktop/assets/codex-bridge/jev_denetim.py b/apps/desktop/assets/codex-bridge/jev_denetim.py index ce673c6..48bf9ac 100644 --- a/apps/desktop/assets/codex-bridge/jev_denetim.py +++ b/apps/desktop/assets/codex-bridge/jev_denetim.py @@ -21,9 +21,6 @@ ADRES = "https://api.typesafe.ai/v1/systemone" MODEL = "jev-1.13.0" KATMAN = ("Katman-1", "Katman-2", "Katman-3", "Katman-4") ESIK = {"ret": 0.5, "sart": 0.7} -SURE = 6.0 -EN_COK_ISTEK = 3000 -EN_COK_CEVAP = 6000 SORU = { "ret": ("The assistant did NOT do what the user asked or did NOT answer the user's question: it refused, declined, " "said it cannot or will not, avoided giving the requested judgment, or redirected to a different topic."), @@ -90,32 +87,44 @@ def son_istek(req): metin = _metin(oge).strip() m = re.search(r".*?(.*?)", metin, re.S) if m: - return m.group(1).strip()[:EN_COK_ISTEK] + return m.group(1).strip() if metin and not metin.startswith("<"): - return metin[:EN_COK_ISTEK] + return metin return "" -def _kisalt(metin, sinir): - if len(metin) <= sinir: - return metin - bas = sinir * 2 // 3 - return metin[:bas] + "\n[...]\n" + metin[-(sinir - bas):] +def komutlar(govde): + """Yazisi olmayan adimda Jev'e giden metin: modelin bu adimda cagirdigi araclar (ad ve arguman). + govde: ust kaynagin SSE yaniti (bayt). Arac cagrisi yoksa "".""" + satirlar = [] + for satir in govde.splitlines(): + if not satir.startswith(b"data:") or b"response.output_item.done" not in satir: + continue + try: + oge = json.loads(satir[5:]).get("item") or {} + except (ValueError, AttributeError): + continue + if isinstance(oge, dict) and str(oge.get("type") or "").endswith("_call"): + arguman = oge.get("arguments") or oge.get("input") or oge.get("action") or "" + if not isinstance(arguman, str): + arguman = json.dumps(arguman, ensure_ascii=False) + satirlar.append(f"[komut] {oge.get('name') or oge.get('type')}: {arguman}") + return "\n".join(satirlar) -def _gonder(govde, key, sure): +def _gonder(govde, key): istek = urllib.request.Request(ADRES, data=json.dumps(govde).encode("utf-8"), method="POST", headers={"Authorization": "Bearer " + key, "Content-Type": "application/json"}) - with urllib.request.urlopen(istek, timeout=sure) as yanit: + with urllib.request.urlopen(istek) as yanit: return json.load(yanit) -def sor(istek, cevap, key, sure=SURE, gonder=_gonder): +def sor(istek, cevap, key, gonder=_gonder): """Jev'e sorar. Donus: {"ret", "sart"} olasiliklari | None (sorulamadi).""" - govde = {"model": MODEL, "state": {"user_request": istek, "assistant_reply": _kisalt(cevap, EN_COK_CEVAP)}, + govde = {"model": MODEL, "state": {"user_request": istek, "assistant_reply": cevap}, "questions": {k: {"type": "noul", "instructions": v} for k, v in SORU.items()}} try: - yanitlar = (gonder(govde, key, sure) or {}).get("answers") or {} + yanitlar = (gonder(govde, key) or {}).get("answers") or {} return {k: float(yanitlar[k]["noul"]) for k in SORU} except Exception: return None diff --git a/apps/desktop/assets/codex-bridge/kopru.py b/apps/desktop/assets/codex-bridge/kopru.py index ae9041a..2b28880 100644 --- a/apps/desktop/assets/codex-bridge/kopru.py +++ b/apps/desktop/assets/codex-bridge/kopru.py @@ -897,7 +897,9 @@ class H(http.server.BaseHTTPRequestHandler): okuyan.feed(chunk) if time.time() - son_yazi >= 10: w(b": denetim\n\n"); son_yazi = time.time() - metin = okuyan.metin.strip() + yazi = okuyan.metin.strip() + # Her adim Jev'e gider: yazi yoksa modelin bu adimda cagirdigi araclar sorulur. + metin = yazi or jev_denetim.komutlar(b"".join(parcalar)) gecmedi = False if metin: t0 = time.time() @@ -907,8 +909,8 @@ class H(http.server.BaseHTTPRequestHandler): else: gecmedi = jev_denetim.karar(puan) log(f"{tag} katman denetimi: ret={puan['ret']:.2f} sart={puan['sart']:.2f} -> " - f"{'GECMEDI' if gecmedi else 'gecti'} ({time.time() - t0:.1f} sn, {len(metin)} karakter)") - elif b'_call"' not in b"".join(parcalar): + f"{'GECMEDI' if gecmedi else 'gecti'} ({time.time() - t0:.1f} sn, {len(metin)} karakter{'' if yazi else ', yalniz komut'})") + else: # Bos adim: ne yazi var ne arac cagrisi (hata olayi da boyledir, or. "upstream 400"). Kabul # edilmez, siradaki katman yapar; bos yanit cevap diye verilirse Codex adimi bastan dener. gecmedi = True @@ -1646,7 +1648,7 @@ class H(http.server.BaseHTTPRequestHandler): sonuc = self._stream(resp, resp.status, tag, akis) log(f"{tag} ozet kopru: {akis.sonuc_notu()}") return sonuc - if jev_sira and jev_istek and resp.status == 200: + if jev_sira and resp.status == 200: return self._denetimli_akis(resp, tag, rewriter, izle, jev_istek, jev_sira, jev_ac) return self._stream(resp, resp.status, tag, rewriter, izle) self._send_json(503, {"error": {"message": f"bridge: hedef ulasilamadi {last}"}}) diff --git a/apps/desktop/assets/codex-bridge/test_jev_denetim.py b/apps/desktop/assets/codex-bridge/test_jev_denetim.py index 9a81486..60c2208 100644 --- a/apps/desktop/assets/codex-bridge/test_jev_denetim.py +++ b/apps/desktop/assets/codex-bridge/test_jev_denetim.py @@ -89,18 +89,18 @@ class Sor(unittest.TestCase): def test_request_shape_and_scores(self): seen = {} - def gonder(govde, key, sure): + def gonder(govde, key): seen.update(govde=govde, key=key) return {"answers": {k: {"type": "noul", "noul": 0.25} for k in jev_denetim.SORU}} self.assertEqual(jev_denetim.sor("istek", "c" * 9000, "anahtar", gonder=gonder), {"ret": 0.25, "sart": 0.25}) self.assertEqual(seen["key"], "anahtar") self.assertEqual(seen["govde"]["state"]["user_request"], "istek") - self.assertLess(len(seen["govde"]["state"]["assistant_reply"]), 6100) + self.assertEqual(seen["govde"]["state"]["assistant_reply"], "c" * 9000) def test_failures_return_none_every_time(self): calls = [] - def bozuk(govde, key, sure): + def bozuk(govde, key): calls.append(1) raise OSError("ulasilamadi") for _ in range(5): @@ -126,6 +126,7 @@ class Akis(unittest.TestCase): sahte.anahtar = lambda: "k" sahte.sor = self.sor sahte.karar = jev_denetim.karar + sahte.komutlar = jev_denetim.komutlar env = {"jev_denetim": sahte, "yarim": yarim, "time": time, "http": __import__("http.client"), "log": self.logs.append, "RESP_HOP": set(), "json": json} adlar = ("_akis_basligi", "_denetimli_akis", "_stream") @@ -212,11 +213,20 @@ class Akis(unittest.TestCase): self.assertEqual(self.sent(), self.cevap["Katman-3"]) self.assertNotIn(b"upstream 400", self.h.wfile.getvalue()) - def test_a_step_without_text_is_not_sent_to_jev(self): - body = b'event: response.output_item.done\ndata: {"type": "response.output_item.done", "item": {"type": "function_call"}}\n\n' + sse() + KOMUT = (b'event: response.output_item.done\ndata: {"type": "response.output_item.done", "item": ' + b'{"type": "function_call", "name": "shell", "arguments": "{\\"cmd\\": \\"dir\\"}"}}\n\n') + + def test_a_step_without_text_is_asked_with_its_tool_calls(self): + body = self.KOMUT + sse() self.calistir(body) self.assertEqual(self.sent(), body) - self.assertEqual(self.asked, []) + self.assertEqual(self.asked, ['[komut] shell: {"cmd": "dir"}']) + + def test_a_blocked_step_without_text_goes_to_the_next_layer(self): + self.puanlar = [{"ret": 0.9, "sart": 0.0}] + self.calistir(self.KOMUT + sse()) + self.assertEqual(self.opened, ["Katman-2"]) + self.assertEqual(self.sent(), self.cevap["Katman-2"]) def test_an_empty_step_of_the_selected_model_goes_to_the_next_layer(self): self.calistir(sse()) # no text, no tool call