Skip to content

Commit dbfc96b

Browse files
VykosMoltclaude
andcommitted
fix(tools): add the native function schema for generate_image
generate_image existed in the prompt sections, the executor and the image_gen MCP server, but not in FUNCTION_TOOL_SCHEMAS. API models are only sent schemas from that list, so the tool selector could pick it and the schema filter would drop it again — the model then improvised text-shaped calls it had no way to make work. The existing JSON fallback in function_call_to_tool_block already produces content the executor parses, so no converter branch is needed. A required-args entry is, though: without it a prompt-less call falls through to the line parser and the whole JSON blob becomes the image prompt. Fixes #5520 Co-Authored-By: Claude <noreply@anthropic.com>
1 parent affaee1 commit dbfc96b

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

src/tool_schemas.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
"write_file": ("path",),
2727
"edit_file": ("path",),
2828
"apply_patch": ("patch_text", "patchText", "patch"),
29+
# Without this, a prompt-less call falls through to the line parser in
30+
# tool_execution and the whole JSON blob becomes the image prompt.
31+
"generate_image": ("prompt",),
2932
}
3033

3134
# ---------------------------------------------------------------------------
@@ -1030,6 +1033,23 @@
10301033
}
10311034
}
10321035
},
1036+
{
1037+
"type": "function",
1038+
"function": {
1039+
"name": "generate_image",
1040+
"description": "Generate an image from a text prompt. Art, illustrations, photos.",
1041+
"parameters": {
1042+
"type": "object",
1043+
"properties": {
1044+
"prompt": {"type": "string", "description": "Image description prompt"},
1045+
"model": {"type": "string", "description": "Model name (auto-detects if omitted)"},
1046+
"size": {"type": "string", "description": "Image size (default 1024x1024)"},
1047+
"quality": {"type": "string", "description": "Quality: low, medium, high, auto (default medium)"},
1048+
},
1049+
"required": ["prompt"]
1050+
}
1051+
}
1052+
},
10331053
{
10341054
"type": "function",
10351055
"function": {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Issue #5520 — generate_image had no native function schema.
2+
3+
The tool exists in the fenced-block prompt, the executor, and the image_gen MCP
4+
server, but not in FUNCTION_TOOL_SCHEMAS. API models (native function calling)
5+
are only sent schemas from that list, so they could never call it and improvised
6+
malformed text calls instead.
7+
"""
8+
9+
import json
10+
11+
12+
def _schemas():
13+
import src.agent_tools # noqa: F401 (tool_schemas <-> agent_tools import cycle)
14+
from src.tool_schemas import FUNCTION_TOOL_SCHEMAS
15+
16+
return {s["function"]["name"]: s["function"] for s in FUNCTION_TOOL_SCHEMAS}
17+
18+
19+
def _convert(arguments):
20+
import src.agent_tools # noqa: F401
21+
from src.tool_schemas import function_call_to_tool_block
22+
23+
return function_call_to_tool_block("generate_image", json.dumps(arguments))
24+
25+
26+
def test_generate_image_is_offered_to_native_function_calling_models():
27+
schemas = _schemas()
28+
assert "generate_image" in schemas, (
29+
"generate_image has no native schema, so it is filtered out of the tools "
30+
"sent to API models even when the selector picks it"
31+
)
32+
assert schemas["generate_image"]["parameters"]["required"] == ["prompt"]
33+
34+
35+
def test_native_call_reaches_the_executor_with_its_arguments_intact():
36+
from src.tool_execution import _build_mcp_args
37+
38+
args = {"prompt": "a cat riding a bicycle", "model": "gpt-image-1",
39+
"size": "1024x1024", "quality": "high"}
40+
block = _convert(args)
41+
assert block is not None and block.tool_type == "generate_image"
42+
assert _build_mcp_args("generate_image", block.content) == args
43+
44+
45+
def test_call_without_a_prompt_is_rejected_instead_of_drawing_its_own_arguments():
46+
"""No prompt key means the line parser takes the whole JSON blob as the prompt,
47+
so the model gets an image of its own arguments."""
48+
assert _convert({"size": "512x512"}) is None
49+
assert _convert({}) is None
50+
assert _convert({"prompt": " "}) is None
51+
52+
53+
def test_advertised_parameters_are_the_ones_the_image_server_accepts():
54+
"""Anything the schema advertises but the executor drops is a silent no-op."""
55+
from mcp_servers.image_gen_server import list_tools
56+
import asyncio
57+
58+
server_schema = asyncio.run(list_tools())[0].inputSchema
59+
assert set(_schemas()["generate_image"]["parameters"]["properties"]) == set(
60+
server_schema["properties"]
61+
)

0 commit comments

Comments
 (0)