FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Fall back to unstructured output when output model selection fails (#3573) by Rainmemery · Pull Request #3587 · modelcontextprotocol/python-sdk · GitHub

Repository navigation

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (2) All 1 file type selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
11 changes: 10 additions & 1 deletion src/mcp/server/mcpserver/utilities/func_metadata.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
ConfigDict,
Field,
PrivateAttr,
PydanticSchemaGenerationError,
PydanticUserError,
TypeAdapter,
WithJsonSchema,
Expand Down Expand Up @@ -441,7 +442,15 @@ def func_metadata(
# structured_output=True still forces one.
return FuncMetadata(arg_model=arguments_model)

output_model, wrap_output = _create_output_model(original_annotation, return_type_expr, func.__name__)
try:
output_model, wrap_output = _create_output_model(original_annotation, return_type_expr, func.__name__)
except PydanticSchemaGenerationError as e:
# Picking the output model builds throwaway pydantic models for unsupported shapes (e.g.
# Iterator[str] wrapped in a {"result": ...} model), which can fail the same way the
# FuncMetadata construction below can. Degrade to the unstructured fallback instead of
# letting the error escape registration.
logger.info(f"Cannot create schema for type {return_type_expr} in {func.__name__}: {type(e).__name__}: {e}")
output_model, wrap_output = None, False

if output_model is not None:
try:
Expand Down
45 changes: 44 additions & 1 deletion tests/server/mcpserver/test_func_metadata.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
# pyright: reportMissingParameterType=false
# pyright: reportUnknownArgumentType=false
# pyright: reportUnknownLambdaType=false
from collections.abc import Callable
import typing
from collections.abc import AsyncIterator, Callable, Generator, Iterator
from dataclasses import dataclass
from typing import TYPE_CHECKING, Annotated, Any, Final, NamedTuple, TypedDict

Expand Down Expand Up @@ -1230,6 +1231,48 @@ def tree() -> Node:
assert result.structured_content == {"name": "root", "children": [{"name": "leaf", "children": []}]}


def test_iterator_return_annotations_register_as_unstructured():
"""Iterator/AsyncIterator return annotations take the unstructured fallback instead of crashing.

pydantic cannot build a schema for these annotations, which used to escape func_metadata as an
uncaught PydanticSchemaGenerationError at registration time (both for the default and for
structured_output=True, which must raise InvalidSignature instead).
"""

def gen_typing(n: int) -> typing.Iterator[str]:
yield from ["a"] * n

def gen_collections(n: int) -> Iterator[str]:
yield from ["a"] * n

def async_gen_typing(n: int) -> typing.AsyncIterator[str]:
yield "a"

def async_gen_collections(n: int) -> AsyncIterator[str]:
yield "a"

for fn in (gen_typing, gen_collections, async_gen_typing, async_gen_collections):
meta = func_metadata(fn)
assert meta.output_schema is None
assert meta.output_model is None

with pytest.raises(InvalidSignature) as exc_info:
func_metadata(fn, structured_output=True)
assert "is not serializable for structured output" in str(exc_info.value)
assert fn.__name__ in str(exc_info.value)


def test_generator_return_annotation_keeps_structured_output():
"""Generator return annotations are sequence-like for pydantic and must keep their structured schema."""

def gen(n: int) -> Generator[str, None, None]:
yield from ["a"] * n

meta = func_metadata(gen)
assert meta.output_schema is not None
assert meta.output_schema["properties"]["result"]["type"] == "array"


def test_structured_output_unserializable_type_error():
"""Test error when structured_output=True is used with unserializable types"""

Expand Down
Loading

Back | FazBrowse Home | New Git URL