Igra: fallback samo Wikipedia (bez OpenAI web searcha)
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzKpYaEsuWDevuNnvSjcFW
This commit is contained in:
@@ -47,7 +47,7 @@
|
||||
{% for h in heroes %}
|
||||
<tr>
|
||||
<td class="rank">{{ loop.index }}</td>
|
||||
<td>{{ h.name }} {% if h.source == 'web' %}<span class="src">(web)</span>{% endif %}</td>
|
||||
<td>{{ h.name }} {% if h.source == 'wiki' %}<span class="src">(wiki)</span>{% endif %}</td>
|
||||
<td class="num">{{ h.rating }}</td>
|
||||
<td class="num">{{ h.wins }}</td>
|
||||
<td class="num">{{ h.losses }}</td>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user