Refaktor 1/4: data-driven registar alata umjesto elif lanaca
Uvodi Tool (idriz/tooling.py: shema + handler + mode inline/background). Assistant gradi self.tools/self.dispatch iz TOOLS, a handle_function_call generički pokreće alat po modu. Uklonjeni GAME_TOOLS set i dva elif lanca (run_game_tool, run_tool) -> _run_inline / _run_background. Ponašanje isto. Prvi korak ka plugin arhitekturi (mali app = jedan folder). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzKpYaEsuWDevuNnvSjcFW
This commit is contained in:
@@ -12,6 +12,7 @@ from openai import AsyncOpenAI
|
||||
from . import audio, coloring, game, printer, render, textgen, wiki
|
||||
from .audio import Mic, Speaker
|
||||
from .config import Config
|
||||
from .tooling import Tool
|
||||
|
||||
log = logging.getLogger("idriz.assistant")
|
||||
|
||||
@@ -123,9 +124,6 @@ TOOLS = [
|
||||
},
|
||||
]
|
||||
|
||||
GAME_TOOLS = {"superheroji_nova_partija", "superheroji_izbor", "superheroji_kraj"}
|
||||
|
||||
|
||||
def _slug(s: str) -> str:
|
||||
s = re.sub(r"[^a-zA-Z0-9]+", "-", s).strip("-").lower()
|
||||
return s[:40] or "posao"
|
||||
@@ -145,6 +143,17 @@ class Assistant:
|
||||
self.cancelled_responses: set[str] = set()
|
||||
self.match: game.Match | None = None
|
||||
|
||||
# Registar alata: shema + handler + način izvršavanja (umjesto elif lanaca).
|
||||
_schema = {t["name"]: t for t in TOOLS}
|
||||
self.tools: list[Tool] = [
|
||||
Tool(_schema["wikipedia_sazetak"], self.tool_wikipedia, "background"),
|
||||
Tool(_schema["bojanka"], self.tool_bojanka, "background"),
|
||||
Tool(_schema["superheroji_nova_partija"], self.game_nova, "inline"),
|
||||
Tool(_schema["superheroji_izbor"], self.game_izbor, "inline"),
|
||||
Tool(_schema["superheroji_kraj"], self.game_kraj, "inline"),
|
||||
]
|
||||
self.dispatch: dict[str, Tool] = {t.name: t for t in self.tools}
|
||||
|
||||
# ---------- alati ----------
|
||||
async def tool_wikipedia(self, args: dict) -> str:
|
||||
topic = args.get("tema") or args.get("tema_bosanski") or ""
|
||||
@@ -289,41 +298,35 @@ class Assistant:
|
||||
return f"Partija je prekinuta. Do sada vodi {st['pobjednici'][0]}. Poredak: {poredak}. Reci to djeci."
|
||||
return f"Partija je prekinuta. Poredak: {poredak}. Reci to djeci."
|
||||
|
||||
async def run_game_tool(self, name: str, call_id: str, args: dict) -> None:
|
||||
"""Igra: izračunaj rezultat pa ga odmah vrati modelu da ga izgovori."""
|
||||
PLACEHOLDER = ("Posao je pokrenut i trajat će oko pola minute do minutu. "
|
||||
"Reci djetetu da sačeka; javit ću kad bude gotovo.")
|
||||
|
||||
async def _send_output(self, call_id: str, output: str) -> None:
|
||||
await self.conn.send({
|
||||
"type": "conversation.item.create",
|
||||
"item": {"type": "function_call_output", "call_id": call_id, "output": output},
|
||||
})
|
||||
|
||||
async def _run_inline(self, tool: Tool, call_id: str, args: dict) -> None:
|
||||
"""Brzi alat: rezultat handlera ide odmah nazad modelu."""
|
||||
try:
|
||||
if name == "superheroji_nova_partija":
|
||||
result = await self.game_nova(args)
|
||||
elif name == "superheroji_izbor":
|
||||
result = await self.game_izbor(args)
|
||||
elif name == "superheroji_kraj":
|
||||
result = await self.game_kraj(args)
|
||||
else:
|
||||
result = f"Nepoznat alat {name}."
|
||||
result = await tool.handler(args)
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.exception("igra: alat %s nije uspio", name)
|
||||
result = f"Došlo je do greške u igri ({type(e).__name__}). Reci djeci da probamo ponovo."
|
||||
log.exception("alat %s nije uspio", tool.name)
|
||||
result = f"Došlo je do greške ({type(e).__name__}). Reci to djetetu kratko, na bosanskom."
|
||||
try:
|
||||
await self.conn.send({
|
||||
"type": "conversation.item.create",
|
||||
"item": {"type": "function_call_output", "call_id": call_id, "output": result},
|
||||
})
|
||||
await self._send_output(call_id, result)
|
||||
await self.conn.send({"type": "response.create"})
|
||||
except Exception: # noqa: BLE001
|
||||
log.exception("nisam uspio vratiti rezultat igre modelu")
|
||||
log.exception("nisam uspio vratiti rezultat alata %s modelu", tool.name)
|
||||
|
||||
async def run_tool(self, name: str, call_id: str, args: dict) -> None:
|
||||
"""Pozadinski posao; kad završi, obavijesti model porukom i zatraži novi odgovor."""
|
||||
async def _run_background(self, tool: Tool, args: dict) -> None:
|
||||
"""Dug posao; kad završi, obavijesti model porukom i zatraži novi odgovor."""
|
||||
try:
|
||||
if name == "wikipedia_sazetak":
|
||||
result = await self.tool_wikipedia(args)
|
||||
elif name == "bojanka":
|
||||
result = await self.tool_bojanka(args)
|
||||
else:
|
||||
result = f"Nepoznat alat {name}."
|
||||
result = await tool.handler(args)
|
||||
ok = True
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.exception("alat %s nije uspio", name)
|
||||
log.exception("alat %s nije uspio", tool.name)
|
||||
result = f"Posao nije uspio: {type(e).__name__}: {e}"
|
||||
ok = False
|
||||
await self.notify_model(("GOTOVO. " if ok else "GREŠKA. ") + result + " Reci to djetetu kratko, na bosanskom.")
|
||||
@@ -389,22 +392,20 @@ class Assistant:
|
||||
except json.JSONDecodeError:
|
||||
args = {}
|
||||
log.info("alat %s(%s)", name, args)
|
||||
if name in GAME_TOOLS:
|
||||
# igra: brzo, rezultat ide odmah nazad modelu (bez "radim na tome")
|
||||
task = asyncio.create_task(self.run_game_tool(name, call_id, args))
|
||||
self.jobs.add(task)
|
||||
task.add_done_callback(self.jobs.discard)
|
||||
tool = self.dispatch.get(name)
|
||||
if tool is None:
|
||||
await self._send_output(call_id, f"Nepoznat alat {name}.")
|
||||
await self.conn.send({"type": "response.create"})
|
||||
return
|
||||
task = asyncio.create_task(self.run_tool(name, call_id, args))
|
||||
if tool.mode == "background":
|
||||
# odmah vrati "radim na tome", pa posao ide u pozadini
|
||||
await self._send_output(call_id, self.PLACEHOLDER)
|
||||
await self.conn.send({"type": "response.create"})
|
||||
task = asyncio.create_task(self._run_background(tool, args))
|
||||
else:
|
||||
task = asyncio.create_task(self._run_inline(tool, call_id, args))
|
||||
self.jobs.add(task)
|
||||
task.add_done_callback(self.jobs.discard)
|
||||
# odmah vrati rezultat da model može reći "radim na tome"
|
||||
await self.conn.send({
|
||||
"type": "conversation.item.create",
|
||||
"item": {"type": "function_call_output", "call_id": call_id,
|
||||
"output": "Posao je pokrenut i trajat će oko pola minute do minutu. Reci djetetu da sačeka; javit ću kad bude gotovo."},
|
||||
})
|
||||
await self.conn.send({"type": "response.create"})
|
||||
|
||||
# ---------- audio petlje ----------
|
||||
async def mic_loop(self) -> None:
|
||||
|
||||
24
idriz/tooling.py
Normal file
24
idriz/tooling.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""Zajednički tipovi za registraciju alata (koraci ka plugin arhitekturi)."""
|
||||
from dataclasses import dataclass
|
||||
from typing import Awaitable, Callable
|
||||
|
||||
# Handler prima argumente alata i vraća tekst koji ide modelu.
|
||||
Handler = Callable[[dict], Awaitable[str]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Tool:
|
||||
"""Jedan alat: shema (OpenAI function schema), handler i način izvršavanja.
|
||||
|
||||
mode:
|
||||
- "inline" — brzo; rezultat handlera ide odmah nazad modelu (npr. igra).
|
||||
- "background" — dug posao; modelu se odmah kaže "radim na tome", a kad
|
||||
handler završi, rezultat stiže preko notify_model (npr. štampanje).
|
||||
"""
|
||||
schema: dict
|
||||
handler: Handler
|
||||
mode: str = "inline"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.schema["name"]
|
||||
Reference in New Issue
Block a user