mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-21 21:14:17 +02:00
Add output schema to tool decorator
This commit is contained in:
parent
4b77c3a405
commit
cc69e56758
3 changed files with 120 additions and 0 deletions
|
|
@ -768,6 +768,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
|
|
@ -781,6 +782,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
|
|
@ -793,6 +795,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
tags: set[str] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
||||
exclude_args: list[str] | None = None,
|
||||
enabled: bool | None = None,
|
||||
|
|
@ -815,6 +818,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name: Optional name for the tool (keyword-only, alternative to name_or_fn)
|
||||
description: Optional description of what the tool does
|
||||
tags: Optional set of tags for categorizing the tool
|
||||
output_schema: Optional JSON schema for the tool's output
|
||||
annotations: Optional annotations about the tool's behavior (e.g. {"is_async": True})
|
||||
exclude_args: Optional list of argument names to exclude from the tool schema
|
||||
enabled: Optional boolean to enable or disable the tool
|
||||
|
|
@ -867,6 +871,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name=tool_name,
|
||||
description=description,
|
||||
tags=tags,
|
||||
output_schema=output_schema,
|
||||
annotations=annotations,
|
||||
exclude_args=exclude_args,
|
||||
serializer=self._tool_serializer,
|
||||
|
|
@ -897,6 +902,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
name=tool_name,
|
||||
description=description,
|
||||
tags=tags,
|
||||
output_schema=output_schema,
|
||||
annotations=annotations,
|
||||
exclude_args=exclude_args,
|
||||
enabled=enabled,
|
||||
|
|
|
|||
|
|
@ -390,6 +390,15 @@ class TestToolDecorator:
|
|||
def my_function(x: int) -> str:
|
||||
return f"Result: {x}"
|
||||
|
||||
async def test_tool_decorator_with_output_schema(self):
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool(output_schema={"type": "integer"})
|
||||
def my_function(x: int) -> str:
|
||||
return f"Result: {x}"
|
||||
|
||||
assert my_function.output_schema == {"type": "integer"}
|
||||
|
||||
|
||||
class TestResourceDecorator:
|
||||
async def test_no_resources_before_decorator(self):
|
||||
|
|
|
|||
|
|
@ -369,6 +369,111 @@ class TestToolFromFunctionOutputSchema:
|
|||
tool = Tool.from_function(func)
|
||||
assert tool.output_schema is None
|
||||
|
||||
async def test_provided_output_schema_takes_precedence_over_json_compatible_annotation(
|
||||
self,
|
||||
):
|
||||
"""Test that provided output_schema takes precedence over inferred schema from JSON-compatible annotation."""
|
||||
|
||||
def func() -> dict[str, int]:
|
||||
return {"a": 1, "b": 2}
|
||||
|
||||
# Provide a custom output schema that differs from the inferred one
|
||||
custom_schema = {"type": "string", "description": "Custom schema"}
|
||||
|
||||
tool = Tool.from_function(func, output_schema=custom_schema)
|
||||
assert tool.output_schema == custom_schema
|
||||
|
||||
async def test_provided_output_schema_takes_precedence_over_complex_annotation(
|
||||
self,
|
||||
):
|
||||
"""Test that provided output_schema takes precedence over inferred schema from complex annotation."""
|
||||
|
||||
def func() -> list[dict[str, int | float]]:
|
||||
return [{"a": 1, "b": 2.5}]
|
||||
|
||||
# Provide a custom output schema that differs from the inferred one
|
||||
custom_schema = {"type": "object", "properties": {"custom": {"type": "string"}}}
|
||||
|
||||
tool = Tool.from_function(func, output_schema=custom_schema)
|
||||
assert tool.output_schema == custom_schema
|
||||
|
||||
async def test_provided_output_schema_takes_precedence_over_unserializable_annotation(
|
||||
self,
|
||||
):
|
||||
"""Test that provided output_schema takes precedence over None schema from unserializable annotation."""
|
||||
|
||||
class Unserializable:
|
||||
def __init__(self, data: Any):
|
||||
self.data = data
|
||||
|
||||
def func() -> Unserializable:
|
||||
return Unserializable(data="test")
|
||||
|
||||
# Provide a custom output schema even though the annotation is unserializable
|
||||
custom_schema = {"type": "array", "items": {"type": "string"}}
|
||||
|
||||
tool = Tool.from_function(func, output_schema=custom_schema)
|
||||
assert tool.output_schema == custom_schema
|
||||
|
||||
async def test_provided_output_schema_takes_precedence_over_no_annotation(self):
|
||||
"""Test that provided output_schema takes precedence over None schema from no annotation."""
|
||||
|
||||
def func():
|
||||
return "hello"
|
||||
|
||||
# Provide a custom output schema even though there's no return annotation
|
||||
custom_schema = {"type": "number", "minimum": 0}
|
||||
|
||||
tool = Tool.from_function(func, output_schema=custom_schema)
|
||||
assert tool.output_schema == custom_schema
|
||||
|
||||
async def test_provided_output_schema_takes_precedence_over_converted_annotation(
|
||||
self,
|
||||
):
|
||||
"""Test that provided output_schema takes precedence over converted schema from Image/Audio/File annotations."""
|
||||
|
||||
def func() -> Image:
|
||||
return Image(data=b"test")
|
||||
|
||||
# Provide a custom output schema that differs from the converted ImageContent schema
|
||||
custom_schema = {
|
||||
"type": "object",
|
||||
"properties": {"custom_image": {"type": "string"}},
|
||||
}
|
||||
|
||||
tool = Tool.from_function(func, output_schema=custom_schema)
|
||||
assert tool.output_schema == custom_schema
|
||||
|
||||
async def test_provided_output_schema_takes_precedence_over_union_annotation(self):
|
||||
"""Test that provided output_schema takes precedence over inferred schema from union annotation."""
|
||||
|
||||
def func() -> str | int | None:
|
||||
return "hello"
|
||||
|
||||
# Provide a custom output schema that differs from the inferred union schema
|
||||
custom_schema = {"type": "boolean"}
|
||||
|
||||
tool = Tool.from_function(func, output_schema=custom_schema)
|
||||
assert tool.output_schema == custom_schema
|
||||
|
||||
async def test_provided_output_schema_takes_precedence_over_pydantic_annotation(
|
||||
self,
|
||||
):
|
||||
"""Test that provided output_schema takes precedence over inferred schema from Pydantic model annotation."""
|
||||
|
||||
class Person(BaseModel):
|
||||
name: str
|
||||
age: int
|
||||
|
||||
def func() -> Person:
|
||||
return Person(name="John", age=30)
|
||||
|
||||
# Provide a custom output schema that differs from the inferred Person schema
|
||||
custom_schema = {"type": "array", "items": {"type": "number"}}
|
||||
|
||||
tool = Tool.from_function(func, output_schema=custom_schema)
|
||||
assert tool.output_schema == custom_schema
|
||||
|
||||
|
||||
class TestLegacyToolJsonParsing:
|
||||
"""Tests for Tool's JSON pre-parsing functionality."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue