diff --git a/docs/servers/sampling.mdx b/docs/servers/sampling.mdx index f4573f0ff..4b6dc2c68 100644 --- a/docs/servers/sampling.mdx +++ b/docs/servers/sampling.mdx @@ -463,7 +463,7 @@ FastMCP provides an OpenAI-compatible sampling handler that supports both basic import os from openai import OpenAI from fastmcp import FastMCP -from fastmcp.experimental.sampling.handlers.openai import OpenAISamplingHandler +from fastmcp.server.sampling.openai import OpenAISamplingHandler server = FastMCP( name="Sampling Server", @@ -487,7 +487,7 @@ import os from openai import OpenAI from fastmcp import FastMCP, Context -from fastmcp.experimental.sampling.handlers.openai import OpenAISamplingHandler +from fastmcp.server.sampling.openai import OpenAISamplingHandler async def async_main(): @@ -545,7 +545,7 @@ The fallback handler fully supports sampling with tools: ```python from fastmcp import FastMCP, Context -from fastmcp.experimental.sampling.handlers.openai import OpenAISamplingHandler +from fastmcp.server.sampling.openai import OpenAISamplingHandler from openai import OpenAI import os diff --git a/src/fastmcp/experimental/sampling/handlers/__init__.py b/src/fastmcp/experimental/sampling/handlers/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/fastmcp/experimental/sampling/handlers/base.py b/src/fastmcp/experimental/sampling/handlers/base.py deleted file mode 100644 index 0b12b9910..000000000 --- a/src/fastmcp/experimental/sampling/handlers/base.py +++ /dev/null @@ -1,21 +0,0 @@ -from abc import ABC, abstractmethod -from collections.abc import Awaitable - -from mcp import ClientSession, CreateMessageResult -from mcp.server.session import ServerSession -from mcp.shared.context import LifespanContextT, RequestContext -from mcp.types import CreateMessageRequestParams as SamplingParams -from mcp.types import ( - SamplingMessage, -) - - -class BaseLLMSamplingHandler(ABC): - @abstractmethod - def __call__( - self, - messages: list[SamplingMessage], - params: SamplingParams, - context: RequestContext[ServerSession, LifespanContextT] - | RequestContext[ClientSession, LifespanContextT], - ) -> str | CreateMessageResult | Awaitable[str | CreateMessageResult]: ... diff --git a/src/fastmcp/experimental/sampling/handlers/openai.py b/src/fastmcp/server/sampling/openai.py similarity index 95% rename from src/fastmcp/experimental/sampling/handlers/openai.py rename to src/fastmcp/server/sampling/openai.py index dc9cb029b..92d0c398b 100644 --- a/src/fastmcp/experimental/sampling/handlers/openai.py +++ b/src/fastmcp/server/sampling/openai.py @@ -1,5 +1,7 @@ +"""OpenAI sampling handler for FastMCP servers.""" + import json -from collections.abc import Iterator, Sequence +from collections.abc import Awaitable, Callable, Iterator, Sequence from typing import Any, get_args from mcp import ClientSession, ServerSession @@ -35,20 +37,30 @@ try: from openai.types.shared_params import FunctionDefinition except ImportError as e: raise ImportError( - "The `openai` package is not installed. Please install `fastmcp[openai]` or add `openai` to your dependencies manually." + "The `openai` package is not installed. " + "Please install `fastmcp[openai]` or add `openai` to your dependencies manually." ) from e -from typing_extensions import override -from fastmcp.experimental.sampling.handlers.base import BaseLLMSamplingHandler +SamplingHandlerResult = str | CreateMessageResult | CreateMessageResultWithTools + +ServerSamplingHandler = Callable[ + [ + list[SamplingMessage], + SamplingParams, + RequestContext[ServerSession, LifespanContextT], + ], + SamplingHandlerResult | Awaitable[SamplingHandlerResult], +] -class OpenAISamplingHandler(BaseLLMSamplingHandler): +class OpenAISamplingHandler: + """Sampling handler that uses the OpenAI API.""" + def __init__(self, default_model: ChatModel, client: OpenAI | None = None): self.client: OpenAI = client or OpenAI() self.default_model: ChatModel = default_model - @override async def __call__( self, messages: list[SamplingMessage], diff --git a/tests/experimental/sampling/test_openai_handler.py b/tests/server/sampling/test_openai_handler.py similarity index 97% rename from tests/experimental/sampling/test_openai_handler.py rename to tests/server/sampling/test_openai_handler.py index 99f7003c3..34f13b372 100644 --- a/tests/experimental/sampling/test_openai_handler.py +++ b/tests/server/sampling/test_openai_handler.py @@ -18,7 +18,7 @@ from openai.types.chat import ( ) from openai.types.chat.chat_completion import Choice -from fastmcp.experimental.sampling.handlers.openai import OpenAISamplingHandler +from fastmcp.server.sampling.openai import OpenAISamplingHandler def test_convert_sampling_messages_to_openai_messages(): diff --git a/uv.lock b/uv.lock index b3a36dbe6..210cf67f1 100644 --- a/uv.lock +++ b/uv.lock @@ -765,8 +765,7 @@ provides-extras = ["anthropic", "openai"] dev = [ { name = "dirty-equals", specifier = ">=0.9.0" }, { name = "fastapi", specifier = ">=0.115.12" }, - { name = "fastmcp", extras = ["anthropic"] }, - { name = "fastmcp", extras = ["openai"] }, + { name = "fastmcp", extras = ["anthropic", "openai"] }, { name = "inline-snapshot", extras = ["dirty-equals"], specifier = ">=0.27.2" }, { name = "ipython", specifier = ">=8.12.3" }, { name = "pdbpp", specifier = ">=0.11.7" },