Use prompt-declared Responses tool names
Some checks are pending
Build And Publish Docker Images / plan (push) Waiting to run
Build And Publish Docker Images / build (push) Blocked by required conditions

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:
Alessandro 2026-06-15 15:06:48 +02:00
parent 9bcef39028
commit f90bb63a9f
3 changed files with 135 additions and 4 deletions

View file

@ -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

View file

@ -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.

View 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"