mirror of
https://github.com/agent0ai/agent-zero.git
synced 2026-07-09 17:08:29 +00:00
Use prompt-declared Responses tool names
Prefer explicit tool_name examples and first prompt headings when deriving native Responses function tool names, falling back to the prompt filename only when no callable name is declared. Add regression coverage for code_execution_tool, memory_load, call_subordinate, behaviour_adjustment, and filename-only fallback, and document the contract in responses_tools DOX.
This commit is contained in:
parent
9bcef39028
commit
f90bb63a9f
3 changed files with 135 additions and 4 deletions
|
|
@ -10,6 +10,10 @@ from helpers import files, subagents
|
|||
|
||||
|
||||
FUNCTION_NAME_PATTERN = re.compile(r"^[A-Za-z0-9_-]{1,64}$")
|
||||
TOOL_NAME_EXAMPLE_PATTERN = re.compile(
|
||||
r"""["']tool_name["']\s*:\s*["']([A-Za-z0-9_-]{1,64})["']"""
|
||||
)
|
||||
TOOL_HEADING_PATTERN = re.compile(r"^\s{0,3}#{1,6}\s+(.+?)\s*$", re.MULTILINE)
|
||||
TOOL_PROMPT_PREFIX = "agent.system.tool."
|
||||
TOOL_PROMPT_SUFFIX = ".md"
|
||||
MAX_TOOL_DESCRIPTION_CHARS = 1024
|
||||
|
|
@ -62,10 +66,8 @@ def _local_tool_prompts(agent: Any) -> list[tuple[str, str]]:
|
|||
result: list[tuple[str, str]] = []
|
||||
for tool_file in tool_files:
|
||||
basename = os.path.basename(tool_file)
|
||||
tool_name = _tool_name_from_prompt_basename(basename)
|
||||
if not tool_name:
|
||||
continue
|
||||
if not _include_local_tool_prompt(agent, tool_name):
|
||||
fallback_name = _tool_name_from_prompt_basename(basename)
|
||||
if not fallback_name:
|
||||
continue
|
||||
try:
|
||||
prompt = agent.read_prompt(basename)
|
||||
|
|
@ -74,6 +76,9 @@ def _local_tool_prompts(agent: Any) -> list[tuple[str, str]]:
|
|||
prompt = files.read_file(tool_file)
|
||||
except Exception:
|
||||
prompt = ""
|
||||
tool_name = _tool_name_from_prompt(prompt, fallback=fallback_name)
|
||||
if not _include_local_tool_prompt(agent, tool_name):
|
||||
continue
|
||||
result.append((tool_name, prompt))
|
||||
return result
|
||||
|
||||
|
|
@ -116,6 +121,28 @@ def _tool_name_from_prompt_basename(basename: str) -> str:
|
|||
return name
|
||||
|
||||
|
||||
def _tool_name_from_prompt(prompt: str, *, fallback: str) -> str:
|
||||
for match in TOOL_NAME_EXAMPLE_PATTERN.finditer(prompt or ""):
|
||||
name = match.group(1).strip()
|
||||
if FUNCTION_NAME_PATTERN.fullmatch(name):
|
||||
return name
|
||||
|
||||
for match in TOOL_HEADING_PATTERN.finditer(prompt or ""):
|
||||
name = _tool_name_from_heading(match.group(1))
|
||||
if name:
|
||||
return name
|
||||
|
||||
return fallback
|
||||
|
||||
|
||||
def _tool_name_from_heading(heading: str) -> str:
|
||||
token = (heading or "").strip().split(None, 1)[0] if heading else ""
|
||||
name = token.strip("`'\" :")
|
||||
if FUNCTION_NAME_PATTERN.fullmatch(name):
|
||||
return name
|
||||
return ""
|
||||
|
||||
|
||||
def _native_tool_name(tool_name: str) -> str:
|
||||
if FUNCTION_NAME_PATTERN.fullmatch(tool_name):
|
||||
return tool_name
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
## Local Contracts
|
||||
|
||||
- Build local function tools from enabled `agent.system.tool.*.md` prompt files.
|
||||
- Local prompt-derived function names prefer explicit `"tool_name"` examples, then the first prompt heading, and only fall back to the prompt filename when the prompt declares no callable name.
|
||||
- Preserve original Agent Zero tool names through the native Responses name map.
|
||||
- Keep MCP tool schemas merged after local prompt-derived tools.
|
||||
- Connector remote tools are advertised only when `_a0_connector` runtime metadata says the matching connected CLI capability is currently available.
|
||||
|
|
|
|||
103
tests/test_responses_tools.py
Normal file
103
tests/test_responses_tools.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from helpers import responses_tools
|
||||
|
||||
|
||||
class FakeAgent:
|
||||
def __init__(self, prompt_root: Path):
|
||||
self.prompt_root = prompt_root
|
||||
|
||||
def read_prompt(self, file: str, **kwargs) -> str:
|
||||
return (self.prompt_root / file).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _write_prompt(prompt_root: Path, basename: str, content: str) -> None:
|
||||
(prompt_root / basename).write_text(content.strip() + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def test_responses_function_tools_use_prompt_declared_names(monkeypatch, tmp_path):
|
||||
prompt_root = tmp_path / "prompts"
|
||||
prompt_root.mkdir()
|
||||
_write_prompt(
|
||||
prompt_root,
|
||||
"agent.system.tool.code_exe.md",
|
||||
"""
|
||||
### code_execution_tool
|
||||
run terminal commands
|
||||
```json
|
||||
{"tool_name": "code_execution_tool", "tool_args": {"runtime": "terminal"}}
|
||||
```
|
||||
""",
|
||||
)
|
||||
_write_prompt(
|
||||
prompt_root,
|
||||
"agent.system.tool.memory.md",
|
||||
"""
|
||||
## memory tools
|
||||
durable memory operations
|
||||
```json
|
||||
{"tool_name": "memory_load", "tool_args": {"query": "responses naming"}}
|
||||
```
|
||||
""",
|
||||
)
|
||||
_write_prompt(
|
||||
prompt_root,
|
||||
"agent.system.tool.call_sub.md",
|
||||
"""
|
||||
### call_subordinate
|
||||
delegate a subtask
|
||||
```json
|
||||
{"tool_name": "call_subordinate", "tool_args": {"message": "inspect"}}
|
||||
```
|
||||
""",
|
||||
)
|
||||
_write_prompt(
|
||||
prompt_root,
|
||||
"agent.system.tool.behaviour.md",
|
||||
"""
|
||||
### behaviour_adjustment
|
||||
update persistent behavioral rules
|
||||
""",
|
||||
)
|
||||
_write_prompt(
|
||||
prompt_root,
|
||||
"agent.system.tool.filename_only.md",
|
||||
"plain prompt with no declared callable name",
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
responses_tools.subagents,
|
||||
"get_paths",
|
||||
lambda *args, **kwargs: [str(prompt_root)],
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
responses_tools,
|
||||
"_include_local_tool_prompt",
|
||||
lambda agent, tool_name: True,
|
||||
)
|
||||
monkeypatch.setattr(responses_tools, "_mcp_tools", lambda agent: [])
|
||||
|
||||
tools, name_map = responses_tools.build_responses_function_tools(
|
||||
FakeAgent(prompt_root)
|
||||
)
|
||||
|
||||
names = {tool["name"] for tool in tools}
|
||||
assert {
|
||||
"code_execution_tool",
|
||||
"memory_load",
|
||||
"call_subordinate",
|
||||
"behaviour_adjustment",
|
||||
"filename_only",
|
||||
} <= names
|
||||
assert not {"code_exe", "memory", "call_sub", "behaviour"} & names
|
||||
assert name_map["code_execution_tool"] == "code_execution_tool"
|
||||
assert name_map["memory_load"] == "memory_load"
|
||||
assert name_map["call_subordinate"] == "call_subordinate"
|
||||
assert name_map["behaviour_adjustment"] == "behaviour_adjustment"
|
||||
assert name_map["filename_only"] == "filename_only"
|
||||
Loading…
Add table
Add a link
Reference in a new issue