mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
Preserve string-compatible prompt arguments (#4730)
This commit is contained in:
parent
34bdd480c9
commit
022547ad8c
2 changed files with 104 additions and 18 deletions
|
|
@ -1,5 +1,9 @@
|
|||
from pathlib import Path
|
||||
from typing import Annotated, Any
|
||||
|
||||
import pytest
|
||||
from mcp_types import EmbeddedResource, TextResourceContents
|
||||
from pydantic import Field
|
||||
|
||||
from fastmcp.prompts.base import (
|
||||
Message,
|
||||
|
|
@ -313,8 +317,75 @@ class TestPromptTypeConversion:
|
|||
|
||||
assert result.messages == [Message("Hello world (repeated 3 times)")]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("annotation", "value"),
|
||||
[
|
||||
(Annotated[str, Field(description="Text")], '"hello"'),
|
||||
(str | None, "null"),
|
||||
(Any, "123"),
|
||||
(object, "true"),
|
||||
(int | str, "42"),
|
||||
],
|
||||
)
|
||||
async def test_string_compatible_annotations_preserve_wire_strings(
|
||||
self, annotation: Any, value: str
|
||||
):
|
||||
def typed_prompt(value):
|
||||
return f"{type(value).__name__}:{value!r}"
|
||||
|
||||
typed_prompt.__annotations__ = {"value": annotation, "return": str}
|
||||
prompt = Prompt.from_function(typed_prompt)
|
||||
|
||||
result = await prompt.render(arguments={"value": value})
|
||||
|
||||
assert result.messages == [Message(f"str:{value!r}")]
|
||||
|
||||
async def test_optional_non_string_still_decodes_json_null(self):
|
||||
def optional_integer_prompt(value: int | None) -> str:
|
||||
return f"{type(value).__name__}:{value!r}"
|
||||
|
||||
prompt = Prompt.from_function(optional_integer_prompt)
|
||||
|
||||
result = await prompt.render(arguments={"value": "null"})
|
||||
|
||||
assert result.messages == [Message("NoneType:None")]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("annotation", "value", "expected"),
|
||||
[
|
||||
(bytes, '"hello"', b"hello"),
|
||||
(Path, '"folder/file.txt"', Path("folder/file.txt")),
|
||||
],
|
||||
)
|
||||
async def test_string_coercible_non_string_annotations_decode_json(
|
||||
self, annotation: Any, value: str, expected: Any
|
||||
):
|
||||
def typed_prompt(value):
|
||||
return f"{type(value).__name__}:{value!r}"
|
||||
|
||||
typed_prompt.__annotations__ = {"value": annotation, "return": str}
|
||||
prompt = Prompt.from_function(typed_prompt)
|
||||
|
||||
result = await prompt.render(arguments={"value": value})
|
||||
|
||||
assert result.messages == [Message(f"{type(expected).__name__}:{expected!r}")]
|
||||
|
||||
|
||||
class TestPromptArgumentDescriptions:
|
||||
def test_string_compatible_annotation_guidance_preserves_raw_strings(self):
|
||||
def documented_prompt(
|
||||
text: Annotated[str, Field(description="Text")],
|
||||
) -> str:
|
||||
return text
|
||||
|
||||
prompt = Prompt.from_function(documented_prompt)
|
||||
|
||||
assert prompt.arguments is not None
|
||||
text_arg = next(arg for arg in prompt.arguments if arg.name == "text")
|
||||
assert text_arg.description is not None
|
||||
assert "Provide as a JSON string" not in text_arg.description
|
||||
assert "Encode non-string values as JSON." in text_arg.description
|
||||
|
||||
def test_enhanced_descriptions_for_non_string_types(self):
|
||||
"""Test that non-string argument types get enhanced descriptions with JSON schema."""
|
||||
|
||||
|
|
@ -343,7 +414,7 @@ class TestPromptArgumentDescriptions:
|
|||
assert numbers_arg is not None
|
||||
assert numbers_arg.description is not None
|
||||
assert (
|
||||
"Provide as a JSON string matching the following schema:"
|
||||
"Provide a value matching the following JSON schema:"
|
||||
in numbers_arg.description
|
||||
)
|
||||
assert '{"items":{"type":"integer"},"type":"array"}' in numbers_arg.description
|
||||
|
|
@ -354,7 +425,7 @@ class TestPromptArgumentDescriptions:
|
|||
assert metadata_arg is not None
|
||||
assert metadata_arg.description is not None
|
||||
assert (
|
||||
"Provide as a JSON string matching the following schema:"
|
||||
"Provide a value matching the following JSON schema:"
|
||||
in metadata_arg.description
|
||||
)
|
||||
assert (
|
||||
|
|
@ -368,7 +439,7 @@ class TestPromptArgumentDescriptions:
|
|||
assert threshold_arg is not None
|
||||
assert threshold_arg.description is not None
|
||||
assert (
|
||||
"Provide as a JSON string matching the following schema:"
|
||||
"Provide a value matching the following JSON schema:"
|
||||
in threshold_arg.description
|
||||
)
|
||||
assert '{"type":"number"}' in threshold_arg.description
|
||||
|
|
@ -379,7 +450,7 @@ class TestPromptArgumentDescriptions:
|
|||
assert active_arg is not None
|
||||
assert active_arg.description is not None
|
||||
assert (
|
||||
"Provide as a JSON string matching the following schema:"
|
||||
"Provide a value matching the following JSON schema:"
|
||||
in active_arg.description
|
||||
)
|
||||
assert '{"type":"boolean"}' in active_arg.description
|
||||
|
|
@ -410,7 +481,7 @@ class TestPromptArgumentDescriptions:
|
|||
assert "A list of integers to process" in numbers_arg.description
|
||||
assert "\n\n" in numbers_arg.description # Should have newline separator
|
||||
assert (
|
||||
"Provide as a JSON string matching the following schema:"
|
||||
"Provide a value matching the following JSON schema:"
|
||||
in numbers_arg.description
|
||||
)
|
||||
|
||||
|
|
@ -427,7 +498,7 @@ class TestPromptArgumentDescriptions:
|
|||
# String parameters should not have schema enhancement
|
||||
if arg.description is not None:
|
||||
assert (
|
||||
"Provide as a JSON string matching the following schema:"
|
||||
"Provide a value matching the following JSON schema:"
|
||||
not in arg.description
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue