From 2d8b884da855eebc12d7cda1f15533085f57d67f Mon Sep 17 00:00:00 2001 From: idriz Date: Fri, 4 Sep 2026 20:06:21 +0200 Subject: [PATCH] Igra: fallback samo Wikipedia (bez OpenAI web searcha) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Web search (Responses API) je znao trajati i do 100s. Pošto LLM ionako zna većinu likova, a Wikipedia je brza (~1s) i dobra za izmišljene likove, fallback kad LLM ne zna lika sada koristi SAMO englesku Wikipediju (wiki.py), uz 8s timeout. Uklonjen poziv responses.create/web_search. Izvor "web" -> "wiki" (kod, admin prikaz). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01LzKpYaEsuWDevuNnvSjcFW --- admin/templates/superheroji.html | 2 +- idriz/game.py | 53 ++++++++++++-------------------- 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/admin/templates/superheroji.html b/admin/templates/superheroji.html index 0e9d81a..9f1facb 100644 --- a/admin/templates/superheroji.html +++ b/admin/templates/superheroji.html @@ -47,7 +47,7 @@ {% for h in heroes %} {{ loop.index }} - {{ h.name }} {% if h.source == 'web' %}(web){% endif %} + {{ h.name }} {% if h.source == 'wiki' %}(wiki){% endif %} {{ h.rating }} {{ h.wins }} {{ h.losses }} diff --git a/idriz/game.py b/idriz/game.py index 8187b3d..0a40f84 100644 --- a/idriz/game.py +++ b/idriz/game.py @@ -1,4 +1,4 @@ -"""Igra 'Superheroji': prepoznavanje likova (najjači LLM + web search kad ne zna), +"""Igra 'Superheroji': prepoznavanje likova (najjači LLM + Wikipedia kad ne zna), presuđivanje borbe i pamćenje relativnih jačina kroz partije.""" import asyncio import json @@ -32,40 +32,27 @@ znatno slabijeg lika bez izuzetno jakog razloga. Poštuj ranije ishode da igra b Vrati JSON: {"pobjednik": "tačno ime pobjednika iz date liste", "razlog": "obrazloženje na bosanskom"}.""" -WEB_SEARCH_TIMEOUT = 20.0 # sekundi — da presuda ne visi predugo +WIKI_TIMEOUT = 8.0 # sekundi — Wikipedia je brza, ali ne visimo -async def _web_search(client: AsyncOpenAI, model: str, name: str) -> str: - """Kratak opis lika sa weba — koristi se SAMO kad LLM ne zna lika. Vremenski ograničeno.""" +async def _wiki_lookup(name: str) -> str: + """Kratak opis lika sa engleske Wikipedije — koristi se SAMO kad LLM ne zna lika.""" try: - resp = await asyncio.wait_for(client.responses.create( - model=model, - tools=[{"type": "web_search"}], - input=(f"Ko je izmišljeni lik '{name}'? Iz kojeg je djela (film, crtani, strip, igra, knjiga)? " - f"Ukratko opiši lika i njegove moći ili sposobnosti."), - ), timeout=WEB_SEARCH_TIMEOUT) - text = (resp.output_text or "").strip() - if text: - log.info("web search za %r: %d znakova", name, len(text)) - return text - except asyncio.TimeoutError: - log.warning("web search za %r prekinut nakon %.0fs", name, WEB_SEARCH_TIMEOUT) - except Exception: # noqa: BLE001 - log.exception("web search (responses API) nije uspio za %r", name) - # rezervni izvor: engleska Wikipedia (brzo) - try: - art = await asyncio.wait_for(asyncio.to_thread(wiki.find_article, name), timeout=8.0) + art = await asyncio.wait_for(asyncio.to_thread(wiki.find_article, name), timeout=WIKI_TIMEOUT) if art: + log.info("wikipedia za %r: %r (%d znakova)", name, art.title, len(art.text)) return f"{art.title}: {art.text[:800]}" - except (asyncio.TimeoutError, Exception): # noqa: BLE001 - log.warning("wiki fallback nije uspio/prekinut za %r", name) + except asyncio.TimeoutError: + log.warning("wikipedia za %r prekinuta nakon %.0fs", name, WIKI_TIMEOUT) + except Exception: # noqa: BLE001 + log.warning("wikipedia nije uspjela za %r", name) return "" async def resolve_hero(client: AsyncOpenAI, model: str, store: HeroStore, spoken: str) -> dict | None: """Prepoznaj lika. Vrati {key, name, desc, source, known} ili None ako se nikako ne može prepoznati. - Prvo gleda keš (ranije viđena imena). Zatim trenirano znanje LLM-a. Web search tek ako LLM ne zna. + Prvo gleda keš (ranije viđena imena). Zatim trenirano znanje LLM-a. Wikipedia tek ako LLM ne zna. """ spoken = (spoken or "").strip() if not spoken: @@ -80,7 +67,7 @@ async def resolve_hero(client: AsyncOpenAI, model: str, store: HeroStore, spoken async def _ask(extra: str = "") -> dict: content = f"Ime lika: {spoken}" if extra: - content += f"\n\nDodatne informacije sa weba (koristi ih):\n{extra}" + content += f"\n\nDodatne informacije sa Wikipedije (koristi ih):\n{extra}" resp = await client.chat.completions.create( model=model, messages=[{"role": "system", "content": RESOLVE_SYS}, {"role": "user", "content": content}], @@ -94,16 +81,16 @@ async def resolve_hero(client: AsyncOpenAI, model: str, store: HeroStore, spoken data = await _ask() source = "llm" if not data.get("poznat"): - # LLM ne zna lika -> web search, pa ponovo pitaj sa kontekstom - snippet = await _web_search(client, model, spoken) + # LLM ne zna lika -> Wikipedia, pa ponovo pitaj sa kontekstom + snippet = await _wiki_lookup(spoken) if snippet: data = await _ask(snippet) - source = "web" + source = "wiki" if not data.get("poznat") and snippet: # i dalje "nesiguran", ali imamo web opis — prihvati sa izgovorenim imenom data = {"poznat": True, "ime": spoken.title(), "original": "", "opis": snippet.split("\n")[0][:200]} - source = "web" + source = "wiki" if not data.get("poznat"): return None @@ -244,7 +231,7 @@ class Match: async def record_pick(self, player: str, hero: str) -> dict: """BRZO bilježi izbor (bez poziva LLM-u), da potvrda bude trenutna. - Pravo prepoznavanje lika (LLM + web search) radi se kasnije, pri presudi runde + Pravo prepoznavanje lika (LLM + Wikipedia) radi se kasnije, pri presudi runde (_resolve_pending), gdje ionako ima vremena i filler poruka. """ player = (player or "").strip() @@ -286,7 +273,7 @@ class Match: "poruka": f"{pick['name']} je zabilježen za {player}."} async def _resolve_pending(self) -> None: - """Prepoznaj sve još neprepoznate likove u rundi (LLM + web search) — paralelno.""" + """Prepoznaj sve još neprepoznate likove u rundi (LLM + Wikipedia) — paralelno.""" pending = [(pl, pk) for pl, pk in self.picks.items() if not pk.get("resolved")] if not pending: return @@ -297,14 +284,14 @@ class Match: else: # ni nakon pretrage nije prepoznat — koristi izgovoreno ime kao lik pk["key"] = normalize_key(pk["spoken"]) or "nepoznato" - self.store.upsert(pk["key"], pk["name"], "", "", "web") + self.store.upsert(pk["key"], pk["name"], "", "", "wiki") pk["resolved"] = True await asyncio.gather(*[_one(pl, pk) for pl, pk in pending]) async def resolve_round(self) -> dict: if not self.picks: return {"ok": False, "poruka": "Još niko nije izabrao heroja u ovoj rundi."} - # tek sada prepoznaj likove (LLM + web search); potvrde su već izgovorene + # tek sada prepoznaj likove (LLM + Wikipedia); potvrde su već izgovorene await self._resolve_pending() # heroji poredani po redoslijedu igranja order = self.current_order