fastmcp 2.13.3__py3-none-any.whl → 2.14.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastmcp/__init__.py +0 -21
- fastmcp/cli/__init__.py +0 -3
- fastmcp/cli/__main__.py +5 -0
- fastmcp/cli/cli.py +8 -22
- fastmcp/cli/install/shared.py +0 -15
- fastmcp/cli/tasks.py +110 -0
- fastmcp/client/auth/oauth.py +9 -9
- fastmcp/client/client.py +739 -136
- fastmcp/client/elicitation.py +11 -5
- fastmcp/client/messages.py +7 -5
- fastmcp/client/roots.py +2 -1
- fastmcp/client/sampling/__init__.py +69 -0
- fastmcp/client/sampling/handlers/__init__.py +0 -0
- fastmcp/client/sampling/handlers/anthropic.py +387 -0
- fastmcp/client/sampling/handlers/openai.py +399 -0
- fastmcp/client/tasks.py +551 -0
- fastmcp/client/transports.py +72 -21
- fastmcp/contrib/component_manager/component_service.py +4 -20
- fastmcp/dependencies.py +25 -0
- fastmcp/experimental/sampling/handlers/__init__.py +5 -0
- fastmcp/experimental/sampling/handlers/openai.py +4 -169
- fastmcp/experimental/server/openapi/__init__.py +15 -13
- fastmcp/experimental/utilities/openapi/__init__.py +12 -38
- fastmcp/prompts/prompt.py +38 -38
- fastmcp/resources/resource.py +33 -16
- fastmcp/resources/template.py +69 -59
- fastmcp/server/auth/__init__.py +0 -9
- fastmcp/server/auth/auth.py +127 -3
- fastmcp/server/auth/oauth_proxy.py +47 -97
- fastmcp/server/auth/oidc_proxy.py +7 -0
- fastmcp/server/auth/providers/in_memory.py +2 -2
- fastmcp/server/auth/providers/oci.py +2 -2
- fastmcp/server/context.py +509 -180
- fastmcp/server/dependencies.py +464 -6
- fastmcp/server/elicitation.py +285 -47
- fastmcp/server/event_store.py +177 -0
- fastmcp/server/http.py +15 -3
- fastmcp/server/low_level.py +56 -12
- fastmcp/server/middleware/middleware.py +2 -2
- fastmcp/server/openapi/__init__.py +35 -0
- fastmcp/{experimental/server → server}/openapi/components.py +4 -3
- fastmcp/{experimental/server → server}/openapi/routing.py +1 -1
- fastmcp/{experimental/server → server}/openapi/server.py +6 -5
- fastmcp/server/proxy.py +53 -40
- fastmcp/server/sampling/__init__.py +10 -0
- fastmcp/server/sampling/run.py +301 -0
- fastmcp/server/sampling/sampling_tool.py +108 -0
- fastmcp/server/server.py +793 -552
- fastmcp/server/tasks/__init__.py +21 -0
- fastmcp/server/tasks/capabilities.py +22 -0
- fastmcp/server/tasks/config.py +89 -0
- fastmcp/server/tasks/converters.py +206 -0
- fastmcp/server/tasks/handlers.py +356 -0
- fastmcp/server/tasks/keys.py +93 -0
- fastmcp/server/tasks/protocol.py +355 -0
- fastmcp/server/tasks/subscriptions.py +205 -0
- fastmcp/settings.py +101 -103
- fastmcp/tools/tool.py +83 -49
- fastmcp/tools/tool_transform.py +1 -12
- fastmcp/utilities/components.py +3 -3
- fastmcp/utilities/json_schema_type.py +4 -4
- fastmcp/utilities/mcp_config.py +1 -2
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +1 -1
- fastmcp/{experimental/utilities → utilities}/openapi/README.md +7 -35
- fastmcp/utilities/openapi/__init__.py +63 -0
- fastmcp/{experimental/utilities → utilities}/openapi/formatters.py +5 -5
- fastmcp/{experimental/utilities → utilities}/openapi/json_schema_converter.py +1 -1
- fastmcp/utilities/tests.py +11 -5
- fastmcp/utilities/types.py +8 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/METADATA +7 -4
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/RECORD +79 -63
- fastmcp/client/sampling.py +0 -56
- fastmcp/experimental/sampling/handlers/base.py +0 -21
- fastmcp/server/auth/providers/bearer.py +0 -25
- fastmcp/server/openapi.py +0 -1087
- fastmcp/server/sampling/handler.py +0 -19
- fastmcp/utilities/openapi.py +0 -1568
- /fastmcp/{experimental/server → server}/openapi/README.md +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/director.py +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/models.py +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/parser.py +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/schemas.py +0 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/WHEEL +0 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
"""Sampling types and helper functions for FastMCP servers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import TYPE_CHECKING, Generic
|
|
8
|
+
|
|
9
|
+
from mcp.types import (
|
|
10
|
+
ClientCapabilities,
|
|
11
|
+
CreateMessageResult,
|
|
12
|
+
CreateMessageResultWithTools,
|
|
13
|
+
ModelHint,
|
|
14
|
+
ModelPreferences,
|
|
15
|
+
SamplingCapability,
|
|
16
|
+
SamplingMessage,
|
|
17
|
+
SamplingToolsCapability,
|
|
18
|
+
TextContent,
|
|
19
|
+
ToolChoice,
|
|
20
|
+
ToolResultContent,
|
|
21
|
+
ToolUseContent,
|
|
22
|
+
)
|
|
23
|
+
from mcp.types import CreateMessageRequestParams as SamplingParams
|
|
24
|
+
from mcp.types import Tool as SDKTool
|
|
25
|
+
from typing_extensions import TypeVar
|
|
26
|
+
|
|
27
|
+
from fastmcp.exceptions import ToolError
|
|
28
|
+
from fastmcp.server.sampling.sampling_tool import SamplingTool
|
|
29
|
+
from fastmcp.utilities.logging import get_logger
|
|
30
|
+
|
|
31
|
+
logger = get_logger(__name__)
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from fastmcp.server.context import Context
|
|
35
|
+
|
|
36
|
+
ResultT = TypeVar("ResultT", default=str)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class SamplingResult(Generic[ResultT]):
|
|
41
|
+
"""Result of a sampling operation.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
text: The text representation of the result (raw text or JSON for structured).
|
|
45
|
+
result: The typed result (str for text, parsed object for structured output).
|
|
46
|
+
history: All messages exchanged during sampling.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
text: str | None
|
|
50
|
+
result: ResultT
|
|
51
|
+
history: list[SamplingMessage]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class SampleStep:
|
|
56
|
+
"""Result of a single sampling call.
|
|
57
|
+
|
|
58
|
+
Represents what the LLM returned in this step plus the message history.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
response: CreateMessageResult | CreateMessageResultWithTools
|
|
62
|
+
history: list[SamplingMessage]
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def is_tool_use(self) -> bool:
|
|
66
|
+
"""True if the LLM is requesting tool execution."""
|
|
67
|
+
if isinstance(self.response, CreateMessageResultWithTools):
|
|
68
|
+
return self.response.stopReason == "toolUse"
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def text(self) -> str | None:
|
|
73
|
+
"""Extract text from the response, if available."""
|
|
74
|
+
content = self.response.content
|
|
75
|
+
if isinstance(content, list):
|
|
76
|
+
for block in content:
|
|
77
|
+
if isinstance(block, TextContent):
|
|
78
|
+
return block.text
|
|
79
|
+
return None
|
|
80
|
+
elif isinstance(content, TextContent):
|
|
81
|
+
return content.text
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def tool_calls(self) -> list[ToolUseContent]:
|
|
86
|
+
"""Get the list of tool calls from the response."""
|
|
87
|
+
content = self.response.content
|
|
88
|
+
if isinstance(content, list):
|
|
89
|
+
return [c for c in content if isinstance(c, ToolUseContent)]
|
|
90
|
+
elif isinstance(content, ToolUseContent):
|
|
91
|
+
return [content]
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _parse_model_preferences(
|
|
96
|
+
model_preferences: ModelPreferences | str | list[str] | None,
|
|
97
|
+
) -> ModelPreferences | None:
|
|
98
|
+
"""Convert model preferences to ModelPreferences object."""
|
|
99
|
+
if model_preferences is None:
|
|
100
|
+
return None
|
|
101
|
+
elif isinstance(model_preferences, ModelPreferences):
|
|
102
|
+
return model_preferences
|
|
103
|
+
elif isinstance(model_preferences, str):
|
|
104
|
+
return ModelPreferences(hints=[ModelHint(name=model_preferences)])
|
|
105
|
+
elif isinstance(model_preferences, list):
|
|
106
|
+
if not all(isinstance(h, str) for h in model_preferences):
|
|
107
|
+
raise ValueError("All elements of model_preferences list must be strings.")
|
|
108
|
+
return ModelPreferences(hints=[ModelHint(name=h) for h in model_preferences])
|
|
109
|
+
else:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
"model_preferences must be one of: ModelPreferences, str, list[str], or None."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# --- Standalone functions for sample_step() ---
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def determine_handler_mode(context: Context, needs_tools: bool) -> bool:
|
|
119
|
+
"""Determine whether to use fallback handler or client for sampling.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
context: The MCP context.
|
|
123
|
+
needs_tools: Whether the sampling request requires tool support.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
True if fallback handler should be used, False to use client.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
ValueError: If client lacks required capability and no fallback configured.
|
|
130
|
+
"""
|
|
131
|
+
fastmcp = context.fastmcp
|
|
132
|
+
session = context.session
|
|
133
|
+
|
|
134
|
+
# Check what capabilities the client has
|
|
135
|
+
has_sampling = session.check_client_capability(
|
|
136
|
+
capability=ClientCapabilities(sampling=SamplingCapability())
|
|
137
|
+
)
|
|
138
|
+
has_tools_capability = session.check_client_capability(
|
|
139
|
+
capability=ClientCapabilities(
|
|
140
|
+
sampling=SamplingCapability(tools=SamplingToolsCapability())
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if fastmcp.sampling_handler_behavior == "always":
|
|
145
|
+
if fastmcp.sampling_handler is None:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
"sampling_handler_behavior is 'always' but no handler configured"
|
|
148
|
+
)
|
|
149
|
+
return True
|
|
150
|
+
elif fastmcp.sampling_handler_behavior == "fallback":
|
|
151
|
+
client_sufficient = has_sampling and (not needs_tools or has_tools_capability)
|
|
152
|
+
if not client_sufficient:
|
|
153
|
+
if fastmcp.sampling_handler is None:
|
|
154
|
+
if needs_tools and has_sampling and not has_tools_capability:
|
|
155
|
+
raise ValueError(
|
|
156
|
+
"Client does not support sampling with tools. "
|
|
157
|
+
"The client must advertise the sampling.tools capability."
|
|
158
|
+
)
|
|
159
|
+
raise ValueError("Client does not support sampling")
|
|
160
|
+
return True
|
|
161
|
+
elif fastmcp.sampling_handler_behavior is not None:
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"Invalid sampling_handler_behavior: {fastmcp.sampling_handler_behavior!r}. "
|
|
164
|
+
"Must be 'always', 'fallback', or None."
|
|
165
|
+
)
|
|
166
|
+
elif not has_sampling:
|
|
167
|
+
raise ValueError("Client does not support sampling")
|
|
168
|
+
elif needs_tools and not has_tools_capability:
|
|
169
|
+
raise ValueError(
|
|
170
|
+
"Client does not support sampling with tools. "
|
|
171
|
+
"The client must advertise the sampling.tools capability."
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def call_sampling_handler(
|
|
178
|
+
context: Context,
|
|
179
|
+
messages: list[SamplingMessage],
|
|
180
|
+
*,
|
|
181
|
+
system_prompt: str | None,
|
|
182
|
+
temperature: float | None,
|
|
183
|
+
max_tokens: int,
|
|
184
|
+
model_preferences: ModelPreferences | str | list[str] | None,
|
|
185
|
+
sdk_tools: list[SDKTool] | None,
|
|
186
|
+
tool_choice: ToolChoice | None,
|
|
187
|
+
) -> CreateMessageResult | CreateMessageResultWithTools:
|
|
188
|
+
"""Make LLM call using the fallback handler.
|
|
189
|
+
|
|
190
|
+
Note: This function expects the caller (sample_step) to have validated that
|
|
191
|
+
sampling_handler is set via determine_handler_mode(). The checks below are
|
|
192
|
+
safeguards against internal misuse.
|
|
193
|
+
"""
|
|
194
|
+
if context.fastmcp.sampling_handler is None:
|
|
195
|
+
raise RuntimeError("sampling_handler is None")
|
|
196
|
+
if context.request_context is None:
|
|
197
|
+
raise RuntimeError("request_context is None")
|
|
198
|
+
|
|
199
|
+
result = context.fastmcp.sampling_handler(
|
|
200
|
+
messages,
|
|
201
|
+
SamplingParams(
|
|
202
|
+
systemPrompt=system_prompt,
|
|
203
|
+
messages=messages,
|
|
204
|
+
temperature=temperature,
|
|
205
|
+
maxTokens=max_tokens,
|
|
206
|
+
modelPreferences=_parse_model_preferences(model_preferences),
|
|
207
|
+
tools=sdk_tools,
|
|
208
|
+
toolChoice=tool_choice,
|
|
209
|
+
),
|
|
210
|
+
context.request_context,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if inspect.isawaitable(result):
|
|
214
|
+
result = await result
|
|
215
|
+
|
|
216
|
+
# Convert string to CreateMessageResult
|
|
217
|
+
if isinstance(result, str):
|
|
218
|
+
return CreateMessageResult(
|
|
219
|
+
role="assistant",
|
|
220
|
+
content=TextContent(type="text", text=result),
|
|
221
|
+
model="unknown",
|
|
222
|
+
stopReason="endTurn",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
async def execute_tools(
|
|
229
|
+
tool_calls: list[ToolUseContent],
|
|
230
|
+
tool_map: dict[str, SamplingTool],
|
|
231
|
+
mask_error_details: bool = False,
|
|
232
|
+
) -> list[ToolResultContent]:
|
|
233
|
+
"""Execute tool calls and return results.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
tool_calls: List of tool use requests from the LLM.
|
|
237
|
+
tool_map: Mapping from tool name to SamplingTool.
|
|
238
|
+
mask_error_details: If True, mask detailed error messages from tool execution.
|
|
239
|
+
When masked, only generic error messages are returned to the LLM.
|
|
240
|
+
Tools can explicitly raise ToolError to bypass masking when they want
|
|
241
|
+
to provide specific error messages to the LLM.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
List of tool result content blocks.
|
|
245
|
+
"""
|
|
246
|
+
tool_results: list[ToolResultContent] = []
|
|
247
|
+
|
|
248
|
+
for tool_use in tool_calls:
|
|
249
|
+
tool = tool_map.get(tool_use.name)
|
|
250
|
+
if tool is None:
|
|
251
|
+
tool_results.append(
|
|
252
|
+
ToolResultContent(
|
|
253
|
+
type="tool_result",
|
|
254
|
+
toolUseId=tool_use.id,
|
|
255
|
+
content=[
|
|
256
|
+
TextContent(
|
|
257
|
+
type="text",
|
|
258
|
+
text=f"Error: Unknown tool '{tool_use.name}'",
|
|
259
|
+
)
|
|
260
|
+
],
|
|
261
|
+
isError=True,
|
|
262
|
+
)
|
|
263
|
+
)
|
|
264
|
+
else:
|
|
265
|
+
try:
|
|
266
|
+
result_value = await tool.run(tool_use.input)
|
|
267
|
+
tool_results.append(
|
|
268
|
+
ToolResultContent(
|
|
269
|
+
type="tool_result",
|
|
270
|
+
toolUseId=tool_use.id,
|
|
271
|
+
content=[TextContent(type="text", text=str(result_value))],
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
except ToolError as e:
|
|
275
|
+
# ToolError is the escape hatch - always pass message through
|
|
276
|
+
logger.exception(f"Error calling sampling tool '{tool_use.name}'")
|
|
277
|
+
tool_results.append(
|
|
278
|
+
ToolResultContent(
|
|
279
|
+
type="tool_result",
|
|
280
|
+
toolUseId=tool_use.id,
|
|
281
|
+
content=[TextContent(type="text", text=str(e))],
|
|
282
|
+
isError=True,
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
except Exception as e:
|
|
286
|
+
# Generic exceptions - mask based on setting
|
|
287
|
+
logger.exception(f"Error calling sampling tool '{tool_use.name}'")
|
|
288
|
+
if mask_error_details:
|
|
289
|
+
error_text = f"Error executing tool '{tool_use.name}'"
|
|
290
|
+
else:
|
|
291
|
+
error_text = f"Error executing tool '{tool_use.name}': {e}"
|
|
292
|
+
tool_results.append(
|
|
293
|
+
ToolResultContent(
|
|
294
|
+
type="tool_result",
|
|
295
|
+
toolUseId=tool_use.id,
|
|
296
|
+
content=[TextContent(type="text", text=error_text)],
|
|
297
|
+
isError=True,
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
return tool_results
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""SamplingTool for use during LLM sampling requests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from mcp.types import Tool as SDKTool
|
|
10
|
+
from pydantic import BaseModel, ConfigDict
|
|
11
|
+
|
|
12
|
+
from fastmcp.tools.tool import ParsedFunction
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SamplingTool(BaseModel):
|
|
16
|
+
"""A tool that can be used during LLM sampling.
|
|
17
|
+
|
|
18
|
+
SamplingTools bundle a tool's schema (name, description, parameters) with
|
|
19
|
+
an executor function, enabling servers to execute agentic workflows where
|
|
20
|
+
the LLM can request tool calls during sampling.
|
|
21
|
+
|
|
22
|
+
In most cases, pass functions directly to ctx.sample():
|
|
23
|
+
|
|
24
|
+
def search(query: str) -> str:
|
|
25
|
+
'''Search the web.'''
|
|
26
|
+
return web_search(query)
|
|
27
|
+
|
|
28
|
+
result = await context.sample(
|
|
29
|
+
messages="Find info about Python",
|
|
30
|
+
tools=[search], # Plain functions work directly
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
Create a SamplingTool explicitly when you need custom name/description:
|
|
34
|
+
|
|
35
|
+
tool = SamplingTool.from_function(search, name="web_search")
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
name: str
|
|
39
|
+
description: str | None = None
|
|
40
|
+
parameters: dict[str, Any]
|
|
41
|
+
fn: Callable[..., Any]
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
44
|
+
|
|
45
|
+
async def run(self, arguments: dict[str, Any] | None = None) -> Any:
|
|
46
|
+
"""Execute the tool with the given arguments.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
arguments: Dictionary of arguments to pass to the tool function.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The result of executing the tool function.
|
|
53
|
+
"""
|
|
54
|
+
if arguments is None:
|
|
55
|
+
arguments = {}
|
|
56
|
+
|
|
57
|
+
result = self.fn(**arguments)
|
|
58
|
+
if inspect.isawaitable(result):
|
|
59
|
+
result = await result
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
def _to_sdk_tool(self) -> SDKTool:
|
|
63
|
+
"""Convert to an mcp.types.Tool for SDK compatibility.
|
|
64
|
+
|
|
65
|
+
This is used internally when passing tools to the MCP SDK's
|
|
66
|
+
create_message() method.
|
|
67
|
+
"""
|
|
68
|
+
return SDKTool(
|
|
69
|
+
name=self.name,
|
|
70
|
+
description=self.description,
|
|
71
|
+
inputSchema=self.parameters,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_function(
|
|
76
|
+
cls,
|
|
77
|
+
fn: Callable[..., Any],
|
|
78
|
+
*,
|
|
79
|
+
name: str | None = None,
|
|
80
|
+
description: str | None = None,
|
|
81
|
+
) -> SamplingTool:
|
|
82
|
+
"""Create a SamplingTool from a function.
|
|
83
|
+
|
|
84
|
+
The function's signature is analyzed to generate a JSON schema for
|
|
85
|
+
the tool's parameters. Type hints are used to determine parameter types.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
fn: The function to create a tool from.
|
|
89
|
+
name: Optional name override. Defaults to the function's name.
|
|
90
|
+
description: Optional description override. Defaults to the function's docstring.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
A SamplingTool wrapping the function.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ValueError: If the function is a lambda without a name override.
|
|
97
|
+
"""
|
|
98
|
+
parsed = ParsedFunction.from_function(fn, validate=True)
|
|
99
|
+
|
|
100
|
+
if name is None and parsed.name == "<lambda>":
|
|
101
|
+
raise ValueError("You must provide a name for lambda functions")
|
|
102
|
+
|
|
103
|
+
return cls(
|
|
104
|
+
name=name or parsed.name,
|
|
105
|
+
description=description or parsed.description,
|
|
106
|
+
parameters=parsed.input_schema,
|
|
107
|
+
fn=parsed.fn,
|
|
108
|
+
)
|