diff --git a/admin/templates/superheroji.html b/admin/templates/superheroji.html
index 9f1facb..0e9d81a 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 == 'wiki' %}(wiki){% endif %} |
+ {{ h.name }} {% if h.source == 'web' %}(web){% endif %} |
{{ h.rating }} |
{{ h.wins }} |
{{ h.losses }} |
diff --git a/idriz/game.py b/idriz/game.py
index 0a40f84..eeb948d 100644
--- a/idriz/game.py
+++ b/idriz/game.py
@@ -1,4 +1,4 @@
-"""Igra 'Superheroji': prepoznavanje likova (najjači LLM + Wikipedia kad ne zna),
+"""Igra 'Superheroji': prepoznavanje likova (najjači LLM + web search kad ne zna),
presuđivanje borbe i pamćenje relativnih jačina kroz partije."""
import asyncio
import json
@@ -32,15 +32,32 @@ 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"}."""
-WIKI_TIMEOUT = 8.0 # sekundi — Wikipedia je brza, ali ne visimo
+WEB_SEARCH_TIMEOUT = 25.0 # sekundi — radi se u pozadini pri presudi (uz filler), ali ne visimo
+WIKI_TIMEOUT = 8.0
-async def _wiki_lookup(name: str) -> str:
- """Kratak opis lika sa engleske Wikipedije — koristi se SAMO kad LLM ne zna lika."""
+async def _web_search(client: AsyncOpenAI, model: str, name: str) -> str:
+ """Kratak opis lika sa weba — koristi se SAMO kad LLM ne zna lika. Wikipedia je rezerva."""
+ 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.warning("web search nije uspio za %r", name)
+ # rezervni izvor: engleska Wikipedia (brzo)
try:
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))
+ log.info("wikipedia (rezerva) za %r: %r", name, art.title)
return f"{art.title}: {art.text[:800]}"
except asyncio.TimeoutError:
log.warning("wikipedia za %r prekinuta nakon %.0fs", name, WIKI_TIMEOUT)
@@ -52,7 +69,7 @@ async def _wiki_lookup(name: str) -> str:
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. Wikipedia tek ako LLM ne zna.
+ Prvo gleda keš (ranije viđena imena). Zatim trenirano znanje LLM-a. Web search (uz Wikipedia rezervu) tek ako LLM ne zna.
"""
spoken = (spoken or "").strip()
if not spoken:
@@ -67,7 +84,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 Wikipedije (koristi ih):\n{extra}"
+ content += f"\n\nDodatne informacije sa weba (koristi ih):\n{extra}"
resp = await client.chat.completions.create(
model=model,
messages=[{"role": "system", "content": RESOLVE_SYS}, {"role": "user", "content": content}],
@@ -81,16 +98,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 -> Wikipedia, pa ponovo pitaj sa kontekstom
- snippet = await _wiki_lookup(spoken)
+ # LLM ne zna lika -> web search (Wikipedia rezerva), pa ponovo pitaj sa kontekstom
+ snippet = await _web_search(client, model, spoken)
if snippet:
data = await _ask(snippet)
- source = "wiki"
+ source = "web"
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 = "wiki"
+ source = "web"
if not data.get("poznat"):
return None
@@ -231,7 +248,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 + Wikipedia) radi se kasnije, pri presudi runde
+ Pravo prepoznavanje lika (LLM + web search) radi se kasnije, pri presudi runde
(_resolve_pending), gdje ionako ima vremena i filler poruka.
"""
player = (player or "").strip()
@@ -273,7 +290,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 + Wikipedia) — paralelno."""
+ """Prepoznaj sve još neprepoznate likove u rundi (LLM + web search) — paralelno."""
pending = [(pl, pk) for pl, pk in self.picks.items() if not pk.get("resolved")]
if not pending:
return
@@ -284,14 +301,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"], "", "", "wiki")
+ self.store.upsert(pk["key"], pk["name"], "", "", "web")
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 + Wikipedia); potvrde su već izgovorene
+ # tek sada prepoznaj likove (LLM + web search); potvrde su već izgovorene
await self._resolve_pending()
# heroji poredani po redoslijedu igranja
order = self.current_order