111 lines
3.8 KiB
Python
111 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from importlib import import_module
|
|
from typing import Optional, Type
|
|
|
|
from tools import Tool
|
|
|
|
|
|
TOOL_REGISTRY = {
|
|
"look": {
|
|
"module": "tools",
|
|
"class": "LookTool",
|
|
"kwargs": {},
|
|
"description": "Sends the 'schau' look command to refresh the room description.",
|
|
},
|
|
"simple": {
|
|
"module": "tools",
|
|
"class": "LookTool",
|
|
"kwargs": {},
|
|
"description": "Alias of 'look'. Sends the 'schau' look command to refresh the room description.",
|
|
},
|
|
"move": {
|
|
"module": "movement_tool",
|
|
"class": "MovementTool",
|
|
"kwargs": {},
|
|
"description": "Looks around and moves in one available direction, chosen randomly among unvisited exits.",
|
|
},
|
|
"movement": {
|
|
"module": "movement_tool",
|
|
"class": "MovementTool",
|
|
"kwargs": {},
|
|
"description": "Alias of 'move'. Looks around and moves in one available direction, chosen randomly among unvisited exits.",
|
|
},
|
|
"explore": {
|
|
"module": "tools",
|
|
"class": "ExploreTool",
|
|
"kwargs": {},
|
|
"description": "Sends 'schau' once, then 'untersuche <noun>' for each noun found in the room description.",
|
|
},
|
|
"communication": {
|
|
"module": "tools",
|
|
"class": "CommunicationTool",
|
|
"kwargs": {},
|
|
"description": "Responds to private tells with a friendly greeting via 'teile <player> mit ...'.",
|
|
},
|
|
"intelligent": {
|
|
"module": "intelligent_tool",
|
|
"class": "IntelligentCommunicationTool",
|
|
"kwargs": {
|
|
"model": os.environ.get("MISTLE_LLM_MODEL", "mistral/mistral-small")
|
|
},
|
|
"description": "Uses an LLM to craft a polite reply to private tells.",
|
|
},
|
|
"intelligentcommunication": {
|
|
"module": "intelligent_tool",
|
|
"class": "IntelligentCommunicationTool",
|
|
"kwargs": {
|
|
"model": os.environ.get("MISTLE_LLM_MODEL", "mistral/mistral-small")
|
|
},
|
|
"description": "Alias of 'intelligent'. Uses an LLM to craft a polite reply to private tells.",
|
|
},
|
|
}
|
|
|
|
TOOL_DESCRIPTIONS = {
|
|
name: meta["description"] for name, meta in TOOL_REGISTRY.items()
|
|
}
|
|
|
|
|
|
def build_tool(spec: str) -> Tool:
|
|
"""Instantiate a tool based on configuration."""
|
|
normalized = spec.strip() or "look"
|
|
key = normalized.lower()
|
|
|
|
if key in TOOL_REGISTRY:
|
|
meta = TOOL_REGISTRY[key]
|
|
module_name = meta["module"]
|
|
class_name = meta["class"]
|
|
kwargs = meta.get("kwargs", {})
|
|
try:
|
|
module = import_module(module_name)
|
|
tool_cls = getattr(module, class_name)
|
|
except AttributeError as exc: # pragma: no cover - optional dependency
|
|
raise RuntimeError(f"{class_name} is not available in tools module") from exc
|
|
tool = _instantiate_tool(tool_cls, normalized, kwargs)
|
|
model_name = kwargs.get("model") if kwargs else None
|
|
if model_name:
|
|
print(f"[Tool] Using LLM model: {model_name}")
|
|
return tool
|
|
|
|
if ":" in normalized:
|
|
module_name, class_name = normalized.split(":", 1)
|
|
if not module_name or not class_name:
|
|
raise RuntimeError("MISTLE_TOOL must be in 'module:ClassName' format")
|
|
module = import_module(module_name)
|
|
tool_cls = getattr(module, class_name)
|
|
return _instantiate_tool(tool_cls, normalized)
|
|
|
|
raise RuntimeError(f"Unknown tool spec '{spec}'.")
|
|
|
|
|
|
def _instantiate_tool(
|
|
tool_cls: Type[Tool], tool_spec: str, kwargs: Optional[dict] = None
|
|
) -> Tool:
|
|
if not issubclass(tool_cls, Tool):
|
|
raise RuntimeError(f"{tool_spec} is not a Tool subclass")
|
|
try:
|
|
kwargs = kwargs or {}
|
|
return tool_cls(**kwargs)
|
|
except TypeError as exc:
|
|
raise RuntimeError(f"Failed to instantiate {tool_spec}: {exc}") from exc
|