fix: tools run now from intelligent agent
This commit is contained in:
parent
278cb7a8ec
commit
f95b824a94
5 changed files with 124 additions and 55 deletions
|
|
@ -64,7 +64,7 @@ Python-based Telnet helper for connecting to MUD servers, handling login flows,
|
||||||
#agent intelligent
|
#agent intelligent
|
||||||
```
|
```
|
||||||
|
|
||||||
Add guidance text after the type and optional modifiers separated by `|`, e.g. `#agent intelligent explore carefully|model=mistral/mistral-large-2407|delay=2`.
|
Add guidance text after the type and optional modifiers separated by `|`, e.g. `#agent intelligent explore carefully|model=mistral/mistral-large-2407|delay=2`. The agent only calls built-in tools (`simple`, `move`, `movement`, `explore`, `communication`, `intelligentcommunication`) and refuses unknown names.
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from agents import Agent
|
||||||
from tools import Tool
|
from tools import Tool
|
||||||
|
|
||||||
ToolBuilder = Callable[[str], Tool]
|
ToolBuilder = Callable[[str], Tool]
|
||||||
ToolRunner = Callable[[Tool], None]
|
ToolRunner = Callable[[Tool], bool]
|
||||||
CommandRunner = Callable[[str], None]
|
CommandRunner = Callable[[str], None]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -33,8 +33,7 @@ def run_agent(
|
||||||
print(f"[Agent] Failed to load tool '{name}': {exc}", file=sys.stderr)
|
print(f"[Agent] Failed to load tool '{name}': {exc}", file=sys.stderr)
|
||||||
return False
|
return False
|
||||||
print(f"[Agent] Running tool '{name}'")
|
print(f"[Agent] Running tool '{name}'")
|
||||||
run_tool(tool)
|
return run_tool(tool)
|
||||||
return True
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
agent.run(
|
agent.run(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import sys
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from threading import Event
|
from threading import Event
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional, Sequence
|
||||||
|
|
||||||
|
|
||||||
ToolInvoker = Callable[[str], bool]
|
ToolInvoker = Callable[[str], bool]
|
||||||
|
|
@ -83,7 +83,7 @@ class LoopAgent(Agent):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def build_agent(spec: str) -> Agent:
|
def build_agent(spec: str, *, allowed_tools: Optional[Sequence[str]] = None) -> Agent:
|
||||||
normalized = spec.strip()
|
normalized = spec.strip()
|
||||||
if not normalized:
|
if not normalized:
|
||||||
raise RuntimeError("Agent specification must not be empty")
|
raise RuntimeError("Agent specification must not be empty")
|
||||||
|
|
@ -123,7 +123,8 @@ def build_agent(spec: str) -> Agent:
|
||||||
else:
|
else:
|
||||||
instruction = segment
|
instruction = segment
|
||||||
|
|
||||||
agent = IntelligentAgent(instruction=instruction)
|
whitelist = tuple(sorted({tool.lower() for tool in (allowed_tools or [])}))
|
||||||
|
agent = IntelligentAgent(instruction=instruction, allowed_tools=whitelist)
|
||||||
if model:
|
if model:
|
||||||
agent.model = model
|
agent.model = model
|
||||||
agent.turn_delay = turn_delay if turn_delay > 0 else 1.0
|
agent.turn_delay = turn_delay if turn_delay > 0 else 1.0
|
||||||
|
|
|
||||||
72
app.py
72
app.py
|
|
@ -10,6 +10,33 @@ from typing import Callable, Optional, Type
|
||||||
from tools import Tool, SimpleTool
|
from tools import Tool, SimpleTool
|
||||||
from agents import Agent, build_agent
|
from agents import Agent, build_agent
|
||||||
from agent_runtime import run_agent
|
from agent_runtime import run_agent
|
||||||
|
|
||||||
|
BUILTIN_TOOLS = {
|
||||||
|
"simple": ("tools", "SimpleTool", {}),
|
||||||
|
"explore": ("tools", "ExploreTool", {}),
|
||||||
|
"communication": ("tools", "CommunicationTool", {}),
|
||||||
|
"movement": ("movement_tool", "MovementTool", {}),
|
||||||
|
"move": ("movement_tool", "MovementTool", {}),
|
||||||
|
"intelligent": (
|
||||||
|
"intelligent_tool",
|
||||||
|
"IntelligentCommunicationTool",
|
||||||
|
{"model": os.environ.get("MISTLE_LLM_MODEL", "mistral/mistral-small-2407")},
|
||||||
|
),
|
||||||
|
"intelligentcommunication": (
|
||||||
|
"intelligent_tool",
|
||||||
|
"IntelligentCommunicationTool",
|
||||||
|
{"model": os.environ.get("MISTLE_LLM_MODEL", "mistral/mistral-small-2407")},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
AVAILABLE_TOOL_NAMES = [
|
||||||
|
"move",
|
||||||
|
"movement",
|
||||||
|
"explore",
|
||||||
|
"communication",
|
||||||
|
"intelligentcommunication",
|
||||||
|
"simple",
|
||||||
|
]
|
||||||
from telnetclient import TelnetClient
|
from telnetclient import TelnetClient
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -163,25 +190,8 @@ def build_tool(spec: str) -> Tool:
|
||||||
if key == "simple":
|
if key == "simple":
|
||||||
return SimpleTool()
|
return SimpleTool()
|
||||||
|
|
||||||
builtin_tools = {
|
if key in BUILTIN_TOOLS:
|
||||||
"explore": ("tools", "ExploreTool", {}),
|
module_name, class_name, kwargs = BUILTIN_TOOLS[key]
|
||||||
"communication": ("tools", "CommunicationTool", {}),
|
|
||||||
"movement": ("movement_tool", "MovementTool", {}),
|
|
||||||
"move": ("movement_tool", "MovementTool", {}),
|
|
||||||
"intelligent": (
|
|
||||||
"intelligent_tool",
|
|
||||||
"IntelligentCommunicationTool",
|
|
||||||
{"model": os.environ.get("MISTLE_LLM_MODEL", "mistral/mistral-small-2407")},
|
|
||||||
),
|
|
||||||
"intelligentcommunication": (
|
|
||||||
"intelligent_tool",
|
|
||||||
"IntelligentCommunicationTool",
|
|
||||||
{"model": os.environ.get("MISTLE_LLM_MODEL", "mistral/mistral-small-2407")},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
if key in builtin_tools:
|
|
||||||
module_name, class_name, kwargs = builtin_tools[key]
|
|
||||||
try:
|
try:
|
||||||
module = import_module(module_name)
|
module = import_module(module_name)
|
||||||
tool_cls = getattr(module, class_name)
|
tool_cls = getattr(module, class_name)
|
||||||
|
|
@ -397,7 +407,7 @@ def main() -> int:
|
||||||
print("[Agent] Usage: #agent <agent_spec>")
|
print("[Agent] Usage: #agent <agent_spec>")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
temp_agent = build_agent(spec)
|
temp_agent = build_agent(spec, allowed_tools=AVAILABLE_TOOL_NAMES)
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
print(f"[Agent] Failed to configure '{spec}': {exc}", file=sys.stderr)
|
print(f"[Agent] Failed to configure '{spec}': {exc}", file=sys.stderr)
|
||||||
return
|
return
|
||||||
|
|
@ -411,7 +421,7 @@ def main() -> int:
|
||||||
except Exception as exc: # pragma: no cover - defensive
|
except Exception as exc: # pragma: no cover - defensive
|
||||||
print(f"[Agent] observe failed: {exc}", file=sys.stderr)
|
print(f"[Agent] observe failed: {exc}", file=sys.stderr)
|
||||||
|
|
||||||
def run_tool_instance(tool: Tool) -> None:
|
def run_tool_instance(tool: Tool) -> bool:
|
||||||
run_tool_loop(
|
run_tool_loop(
|
||||||
client,
|
client,
|
||||||
state,
|
state,
|
||||||
|
|
@ -429,6 +439,7 @@ def main() -> int:
|
||||||
observe(output_after)
|
observe(output_after)
|
||||||
except Exception as exc: # pragma: no cover - defensive
|
except Exception as exc: # pragma: no cover - defensive
|
||||||
print(f"[Agent] observe failed: {exc}", file=sys.stderr)
|
print(f"[Agent] observe failed: {exc}", file=sys.stderr)
|
||||||
|
return True
|
||||||
|
|
||||||
thread = Thread(
|
thread = Thread(
|
||||||
target=run_agent,
|
target=run_agent,
|
||||||
|
|
@ -475,3 +486,22 @@ def main() -> int:
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
raise SystemExit(main())
|
raise SystemExit(main())
|
||||||
|
BUILTIN_TOOLS = {
|
||||||
|
"simple": ("tools", "SimpleTool", {}),
|
||||||
|
"explore": ("tools", "ExploreTool", {}),
|
||||||
|
"communication": ("tools", "CommunicationTool", {}),
|
||||||
|
"movement": ("movement_tool", "MovementTool", {}),
|
||||||
|
"move": ("movement_tool", "MovementTool", {}),
|
||||||
|
"intelligent": (
|
||||||
|
"intelligent_tool",
|
||||||
|
"IntelligentCommunicationTool",
|
||||||
|
{"model": os.environ.get("MISTLE_LLM_MODEL", "mistral/mistral-small-2407")},
|
||||||
|
),
|
||||||
|
"intelligentcommunication": (
|
||||||
|
"intelligent_tool",
|
||||||
|
"IntelligentCommunicationTool",
|
||||||
|
{"model": os.environ.get("MISTLE_LLM_MODEL", "mistral/mistral-small-2407")},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
AVAILABLE_TOOL_NAMES = sorted(set(BUILTIN_TOOLS.keys()))
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ class IntelligentAgent(Agent):
|
||||||
max_output_tokens: int = 200
|
max_output_tokens: int = 200
|
||||||
instruction: str = ""
|
instruction: str = ""
|
||||||
turn_delay: float = 0.0
|
turn_delay: float = 0.0
|
||||||
|
allowed_tools: tuple[str, ...] = ()
|
||||||
history: Deque[dict[str, str]] = field(default_factory=deque, init=False)
|
history: Deque[dict[str, str]] = field(default_factory=deque, init=False)
|
||||||
|
|
||||||
def observe(self, message: str) -> None:
|
def observe(self, message: str) -> None:
|
||||||
|
|
@ -58,6 +59,17 @@ class IntelligentAgent(Agent):
|
||||||
if self.instruction:
|
if self.instruction:
|
||||||
messages.append({"role": "system", "content": self.instruction})
|
messages.append({"role": "system", "content": self.instruction})
|
||||||
messages.extend(self.history)
|
messages.extend(self.history)
|
||||||
|
if self.allowed_tools:
|
||||||
|
tool_list = ", ".join(self.allowed_tools)
|
||||||
|
messages.append(
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": (
|
||||||
|
"Available tools: "
|
||||||
|
f"{tool_list}. Use only these tool names when type=tool; do not invent new tools."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
messages.append(
|
messages.append(
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
|
|
@ -100,11 +112,8 @@ class IntelligentAgent(Agent):
|
||||||
try:
|
try:
|
||||||
content = response["choices"][0]["message"]["content"].strip()
|
content = response["choices"][0]["message"]["content"].strip()
|
||||||
print(f"[Agent] LLM raw output: {content}")
|
print(f"[Agent] LLM raw output: {content}")
|
||||||
if content.startswith("```"):
|
json_payload = self._extract_json(content)
|
||||||
content = content.strip("` ")
|
payload = json.loads(json_payload)
|
||||||
if "\n" in content:
|
|
||||||
content = content.split("\n", 1)[1]
|
|
||||||
payload = json.loads(content)
|
|
||||||
except (KeyError, IndexError, TypeError, json.JSONDecodeError) as exc:
|
except (KeyError, IndexError, TypeError, json.JSONDecodeError) as exc:
|
||||||
print(f"[Agent] Invalid LLM response: {exc}", file=sys.stderr)
|
print(f"[Agent] Invalid LLM response: {exc}", file=sys.stderr)
|
||||||
print(f"[Agent] Raw content: {content}", file=sys.stderr)
|
print(f"[Agent] Raw content: {content}", file=sys.stderr)
|
||||||
|
|
@ -120,10 +129,17 @@ class IntelligentAgent(Agent):
|
||||||
print("[Agent] LLM returned empty action", file=sys.stderr)
|
print("[Agent] LLM returned empty action", file=sys.stderr)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
allowed_map = {tool.lower(): tool for tool in self.allowed_tools}
|
||||||
|
|
||||||
if action_type == "tool":
|
if action_type == "tool":
|
||||||
success = invoke_tool(value)
|
lower = value.lower()
|
||||||
print(f"[Agent] Executed tool: {value} (success={success})")
|
if allowed_map and lower not in allowed_map:
|
||||||
self.history.append({"role": "assistant", "content": f"TOOL {value}"})
|
print(f"[Agent] Tool '{value}' not in allowed list {self.allowed_tools}", file=sys.stderr)
|
||||||
|
return
|
||||||
|
canonical = allowed_map.get(lower, value)
|
||||||
|
success = invoke_tool(canonical)
|
||||||
|
print(f"[Agent] Executed tool: {canonical} (success={success})")
|
||||||
|
self.history.append({"role": "assistant", "content": f"TOOL {canonical}"})
|
||||||
self._trim_history()
|
self._trim_history()
|
||||||
if not success:
|
if not success:
|
||||||
return
|
return
|
||||||
|
|
@ -136,40 +152,63 @@ class IntelligentAgent(Agent):
|
||||||
print(f"[Agent] Unknown action type '{action_type}'", file=sys.stderr)
|
print(f"[Agent] Unknown action type '{action_type}'", file=sys.stderr)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.turn_delay > 0 and stop_event.wait(self.turn_delay):
|
if self.turn_delay > 0 and stop_event.wait(self.turn_delay):
|
||||||
break
|
return
|
||||||
|
|
||||||
messages = [{"role": "system", "content": self.system_prompt}]
|
messages = [{"role": "system", "content": self.system_prompt}]
|
||||||
if self.instruction:
|
if self.instruction:
|
||||||
messages.append({"role": "system", "content": self.instruction})
|
messages.append({"role": "system", "content": self.instruction})
|
||||||
messages.extend(self.history)
|
messages.extend(self.history)
|
||||||
|
if self.allowed_tools:
|
||||||
|
tool_list = ", ".join(self.allowed_tools)
|
||||||
messages.append(
|
messages.append(
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": (
|
"content": (
|
||||||
"Respond with JSON only. Schema: {\n"
|
"Available tools: "
|
||||||
" \"type\": \"tool\" or \"command\",\n"
|
f"{tool_list}. Use only these tool names when type=tool; do not invent new tools."
|
||||||
" \"value\": string,\n"
|
|
||||||
" \"notes\": optional string\n}"""
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if not self.history or self.history[-1]["role"] != "assistant":
|
messages.append(
|
||||||
messages.append(
|
{
|
||||||
{
|
"role": "system",
|
||||||
"role": "assistant",
|
"content": (
|
||||||
"content": "I will decide the next action now.",
|
"Respond with JSON only. Schema: {\n"
|
||||||
}
|
" \"type\": \"tool\" or \"command\",\n"
|
||||||
)
|
" \"value\": string,\n"
|
||||||
|
" \"notes\": optional string\n}"""
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if not self.history or self.history[-1]["role"] != "assistant":
|
||||||
messages.append(
|
messages.append(
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "assistant",
|
||||||
"content": "What is the next action you will take?",
|
"content": "I will decide the next action now.",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
messages.append(
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "What is the next action you will take?",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
print("[Agent] Intelligent agent finished.")
|
print("[Agent] Intelligent agent finished.")
|
||||||
|
|
||||||
def _trim_history(self) -> None:
|
def _trim_history(self) -> None:
|
||||||
while len(self.history) > 50:
|
while len(self.history) > 50:
|
||||||
self.history.popleft()
|
self.history.popleft()
|
||||||
|
|
||||||
|
def _extract_json(self, content: str) -> str:
|
||||||
|
text = content.strip()
|
||||||
|
if text.startswith("```"):
|
||||||
|
text = text.strip("` ")
|
||||||
|
if "\n" in text:
|
||||||
|
text = text.split("\n", 1)[1]
|
||||||
|
start = text.find("{")
|
||||||
|
end = text.rfind("}")
|
||||||
|
if start == -1 or end == -1 or end < start:
|
||||||
|
raise json.JSONDecodeError("No JSON object found", text, 0)
|
||||||
|
return text[start : end + 1]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue