mirascope 2.0.2__py3-none-any.whl → 2.1.0__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.
Files changed (64) hide show
  1. mirascope/_stubs.py +39 -18
  2. mirascope/api/_generated/__init__.py +4 -0
  3. mirascope/api/_generated/project_memberships/__init__.py +4 -0
  4. mirascope/api/_generated/project_memberships/client.py +91 -0
  5. mirascope/api/_generated/project_memberships/raw_client.py +239 -0
  6. mirascope/api/_generated/project_memberships/types/__init__.py +4 -0
  7. mirascope/api/_generated/project_memberships/types/project_memberships_get_response.py +33 -0
  8. mirascope/api/_generated/project_memberships/types/project_memberships_get_response_role.py +7 -0
  9. mirascope/api/_generated/reference.md +72 -0
  10. mirascope/llm/__init__.py +19 -0
  11. mirascope/llm/calls/decorator.py +17 -24
  12. mirascope/llm/formatting/__init__.py +2 -2
  13. mirascope/llm/formatting/format.py +2 -4
  14. mirascope/llm/formatting/types.py +19 -2
  15. mirascope/llm/models/models.py +66 -146
  16. mirascope/llm/prompts/decorator.py +5 -16
  17. mirascope/llm/prompts/prompts.py +5 -13
  18. mirascope/llm/providers/anthropic/_utils/beta_decode.py +22 -7
  19. mirascope/llm/providers/anthropic/_utils/beta_encode.py +22 -16
  20. mirascope/llm/providers/anthropic/_utils/decode.py +45 -7
  21. mirascope/llm/providers/anthropic/_utils/encode.py +28 -15
  22. mirascope/llm/providers/anthropic/beta_provider.py +33 -69
  23. mirascope/llm/providers/anthropic/provider.py +52 -91
  24. mirascope/llm/providers/base/_utils.py +4 -9
  25. mirascope/llm/providers/base/base_provider.py +89 -205
  26. mirascope/llm/providers/google/_utils/decode.py +51 -1
  27. mirascope/llm/providers/google/_utils/encode.py +38 -21
  28. mirascope/llm/providers/google/provider.py +33 -69
  29. mirascope/llm/providers/mirascope/provider.py +25 -61
  30. mirascope/llm/providers/mlx/encoding/base.py +3 -6
  31. mirascope/llm/providers/mlx/encoding/transformers.py +4 -8
  32. mirascope/llm/providers/mlx/mlx.py +9 -21
  33. mirascope/llm/providers/mlx/provider.py +33 -69
  34. mirascope/llm/providers/openai/completions/_utils/encode.py +39 -20
  35. mirascope/llm/providers/openai/completions/base_provider.py +34 -75
  36. mirascope/llm/providers/openai/provider.py +25 -61
  37. mirascope/llm/providers/openai/responses/_utils/decode.py +31 -2
  38. mirascope/llm/providers/openai/responses/_utils/encode.py +32 -17
  39. mirascope/llm/providers/openai/responses/provider.py +34 -75
  40. mirascope/llm/responses/__init__.py +2 -1
  41. mirascope/llm/responses/base_stream_response.py +4 -0
  42. mirascope/llm/responses/response.py +8 -12
  43. mirascope/llm/responses/stream_response.py +8 -12
  44. mirascope/llm/responses/usage.py +44 -0
  45. mirascope/llm/tools/__init__.py +24 -0
  46. mirascope/llm/tools/provider_tools.py +18 -0
  47. mirascope/llm/tools/tool_schema.py +4 -2
  48. mirascope/llm/tools/toolkit.py +24 -6
  49. mirascope/llm/tools/types.py +112 -0
  50. mirascope/llm/tools/web_search_tool.py +32 -0
  51. mirascope/ops/__init__.py +19 -1
  52. mirascope/ops/_internal/instrumentation/__init__.py +20 -0
  53. mirascope/ops/_internal/instrumentation/llm/common.py +19 -49
  54. mirascope/ops/_internal/instrumentation/llm/model.py +61 -82
  55. mirascope/ops/_internal/instrumentation/llm/serialize.py +36 -12
  56. mirascope/ops/_internal/instrumentation/providers/__init__.py +29 -0
  57. mirascope/ops/_internal/instrumentation/providers/anthropic.py +78 -0
  58. mirascope/ops/_internal/instrumentation/providers/base.py +179 -0
  59. mirascope/ops/_internal/instrumentation/providers/google_genai.py +85 -0
  60. mirascope/ops/_internal/instrumentation/providers/openai.py +82 -0
  61. {mirascope-2.0.2.dist-info → mirascope-2.1.0.dist-info}/METADATA +96 -68
  62. {mirascope-2.0.2.dist-info → mirascope-2.1.0.dist-info}/RECORD +64 -54
  63. {mirascope-2.0.2.dist-info → mirascope-2.1.0.dist-info}/WHEEL +0 -0
  64. {mirascope-2.0.2.dist-info → mirascope-2.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,6 +6,38 @@ from dataclasses import dataclass
6
6
  from typing import Any, Literal
7
7
 
8
8
 
9
+ @dataclass(kw_only=True)
10
+ class ProviderToolUsage:
11
+ """Usage data for a provider's server-side tool.
12
+
13
+ Tracks usage of tools executed by the provider that have separate pricing
14
+ beyond standard token costs (e.g., web search, code execution).
15
+ """
16
+
17
+ name: str
18
+ """Tool name matching our ProviderTool.name (e.g., "web_search").
19
+
20
+ This is the consistent cross-provider identifier that matches
21
+ the corresponding Mirascope ProviderTool class (e.g., WebSearchTool.name).
22
+ """
23
+
24
+ call_count: int = 0
25
+ """Number of invocations/calls of this tool."""
26
+
27
+ duration_seconds: float | None = None
28
+ """Duration in seconds for time-based tools (e.g., code execution).
29
+
30
+ None if not applicable to this tool type.
31
+ """
32
+
33
+ metadata: dict[str, Any] | None = None
34
+ """Provider-specific metadata for debugging/analytics.
35
+
36
+ Examples:
37
+ - Google grounding: {"queries": ["query1", "query2"]}
38
+ """
39
+
40
+
9
41
  @dataclass(kw_only=True)
10
42
  class UsageDeltaChunk:
11
43
  """A chunk containing incremental token usage information from a streaming response.
@@ -31,6 +63,9 @@ class UsageDeltaChunk:
31
63
  reasoning_tokens: int = 0
32
64
  """Delta in reasoning/thinking tokens."""
33
65
 
66
+ provider_tool_usage: list[ProviderToolUsage] | None = None
67
+ """Provider tool usage (emitted in final chunk only)."""
68
+
34
69
 
35
70
  @dataclass(kw_only=True)
36
71
  class Usage:
@@ -86,6 +121,15 @@ class Usage:
86
121
  Will be 0 if not reported by the provider or if the model does not support reasoning.
87
122
  """
88
123
 
124
+ provider_tool_usage: list[ProviderToolUsage] | None = None
125
+ """Provider tool usage data for tools with separate pricing.
126
+
127
+ Tracks usage of server-side tools executed by the provider (e.g., web search,
128
+ code execution) that have pricing beyond standard token costs.
129
+
130
+ Will be None if no provider tools were used.
131
+ """
132
+
89
133
  raw: Any = None
90
134
  """The raw usage object from the provider."""
91
135
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  from .decorator import ToolDecorator, tool
4
4
  from .protocols import AsyncContextToolFn, AsyncToolFn, ContextToolFn, ToolFn
5
+ from .provider_tools import ProviderTool
5
6
  from .tool_schema import (
6
7
  FORMAT_TOOL_NAME,
7
8
  AnyToolFn,
@@ -19,21 +20,38 @@ from .toolkit import (
19
20
  ToolkitT,
20
21
  )
21
22
  from .tools import AsyncContextTool, AsyncTool, ContextTool, Tool, ToolT
23
+ from .types import (
24
+ AnyTools,
25
+ AsyncContextTools,
26
+ AsyncTools,
27
+ ContextTools,
28
+ Tools,
29
+ normalize_async_context_tools,
30
+ normalize_async_tools,
31
+ normalize_context_tools,
32
+ normalize_tools,
33
+ )
34
+ from .web_search_tool import WebSearchTool
22
35
 
23
36
  __all__ = [
24
37
  "FORMAT_TOOL_NAME",
25
38
  "AnyToolFn",
26
39
  "AnyToolSchema",
40
+ "AnyTools",
27
41
  "AsyncContextTool",
28
42
  "AsyncContextToolFn",
29
43
  "AsyncContextToolkit",
44
+ "AsyncContextTools",
30
45
  "AsyncTool",
31
46
  "AsyncToolFn",
32
47
  "AsyncToolkit",
48
+ "AsyncTools",
33
49
  "BaseToolkit",
34
50
  "ContextTool",
35
51
  "ContextToolFn",
36
52
  "ContextToolkit",
53
+ "ContextTools",
54
+ "ProviderTool",
37
55
  "Tool",
38
56
  "ToolDecorator",
39
57
  "ToolFn",
@@ -43,5 +61,11 @@ __all__ = [
43
61
  "ToolT",
44
62
  "Toolkit",
45
63
  "ToolkitT",
64
+ "Tools",
65
+ "WebSearchTool",
66
+ "normalize_async_context_tools",
67
+ "normalize_async_tools",
68
+ "normalize_context_tools",
69
+ "normalize_tools",
46
70
  "tool",
47
71
  ]
@@ -0,0 +1,18 @@
1
+ """Base class for provider-native tools."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class ProviderTool:
8
+ """Base class for tools executed natively by providers.
9
+
10
+ Unlike regular tools which define functions that you execute locally,
11
+ provider tools are capabilities built into the provider's API.
12
+ The provider handles execution entirely server-side.
13
+
14
+ Provider tools have no sync/async distinction since they are not
15
+ executed by your code - they are configuration passed to the provider.
16
+ """
17
+
18
+ name: str
@@ -40,8 +40,7 @@ ToolFnT = TypeVar(
40
40
  covariant=True,
41
41
  )
42
42
 
43
- AnyToolSchema: TypeAlias = "ToolSchema[AnyToolFn]"
44
- ToolSchemaT = TypeVar("ToolSchemaT", bound=AnyToolSchema, covariant=True)
43
+ ToolSchemaT = TypeVar("ToolSchemaT", bound="ToolSchema[AnyToolFn]", covariant=True)
45
44
 
46
45
 
47
46
  ModelJsonSchema = TypedDict(
@@ -317,3 +316,6 @@ class ToolSchema(Generic[ToolFnT]):
317
316
  is performed.
318
317
  """
319
318
  return tool_call.name == self.name
319
+
320
+
321
+ AnyToolSchema: TypeAlias = ToolSchema[AnyToolFn]
@@ -6,6 +6,7 @@ from ..content import ToolCall, ToolOutput
6
6
  from ..context import Context, DepsT
7
7
  from ..exceptions import ToolNotFoundError
8
8
  from ..types import Jsonable
9
+ from .provider_tools import ProviderTool
9
10
  from .tool_schema import ToolSchemaT
10
11
  from .tools import AsyncContextTool, AsyncTool, ContextTool, Tool
11
12
 
@@ -24,13 +25,24 @@ class BaseToolkit(Generic[ToolSchemaT]):
24
25
  including name validation and tool lookup.
25
26
  """
26
27
 
27
- tools: Sequence[ToolSchemaT]
28
+ tools: Sequence[ToolSchemaT | ProviderTool]
28
29
  """The tools included in the toolkit."""
29
30
 
30
31
  tools_dict: dict[str, ToolSchemaT]
31
- """A mapping from tool names to tools in the toolkit."""
32
+ """A mapping from tool names to tools in the toolkit.
32
33
 
33
- def __init__(self, tools: Sequence[ToolSchemaT] | None) -> None:
34
+ This dict does not include any `ProviderTool`s, since they do not correspond
35
+ to tool calls that your code executes.
36
+ """
37
+
38
+ provider_tools_dict: dict[str, ProviderTool]
39
+ """A mapping from provider tool names to provider tools in the toolkit.
40
+
41
+ Provider tools are capabilities built into the provider's API (like web search)
42
+ that are executed server-side, not by your code.
43
+ """
44
+
45
+ def __init__(self, tools: Sequence[ToolSchemaT | ProviderTool] | None) -> None:
34
46
  """Initialize the toolkit with a collection of tools.
35
47
 
36
48
  Args:
@@ -41,10 +53,16 @@ class BaseToolkit(Generic[ToolSchemaT]):
41
53
  """
42
54
  self.tools = tools or []
43
55
  self.tools_dict = {}
56
+ self.provider_tools_dict = {}
44
57
  for tool in self.tools:
45
- if tool.name in self.tools_dict:
46
- raise ValueError(f"Multiple tools with name: {tool.name}")
47
- self.tools_dict[tool.name] = tool
58
+ if isinstance(tool, ProviderTool):
59
+ if tool.name in self.provider_tools_dict:
60
+ raise ValueError(f"Multiple provider tools with name: {tool.name}")
61
+ self.provider_tools_dict[tool.name] = tool
62
+ else:
63
+ if tool.name in self.tools_dict:
64
+ raise ValueError(f"Multiple tools with name: {tool.name}")
65
+ self.tools_dict[tool.name] = tool
48
66
 
49
67
  def get(self, tool_call: ToolCall) -> ToolSchemaT:
50
68
  """Get a tool that can execute a specific tool call.
@@ -0,0 +1,112 @@
1
+ """Type aliases for tool parameter types used in provider signatures."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import TypeAlias
7
+ from typing_extensions import TypeAliasType
8
+
9
+ from ..context import DepsT
10
+ from .provider_tools import ProviderTool
11
+ from .tool_schema import AnyToolSchema
12
+ from .toolkit import (
13
+ AsyncContextToolkit,
14
+ AsyncToolkit,
15
+ BaseToolkit,
16
+ ContextToolkit,
17
+ Toolkit,
18
+ )
19
+ from .tools import AsyncContextTool, AsyncTool, ContextTool, Tool
20
+
21
+ AnyTools: TypeAlias = (
22
+ Sequence[AnyToolSchema | ProviderTool] | BaseToolkit[AnyToolSchema]
23
+ )
24
+
25
+ Tools: TypeAlias = Sequence[Tool | ProviderTool] | Toolkit
26
+ """Type alias for sync tool parameters: a sequence of Tools or a Toolkit."""
27
+
28
+ AsyncTools: TypeAlias = Sequence[AsyncTool | ProviderTool] | AsyncToolkit
29
+ """Type alias for async tool parameters: a sequence of AsyncTools or an AsyncToolkit."""
30
+
31
+ ContextTools = TypeAliasType(
32
+ "ContextTools",
33
+ Sequence[Tool | ContextTool[DepsT] | ProviderTool] | ContextToolkit[DepsT],
34
+ type_params=(DepsT,),
35
+ )
36
+ """Type alias for sync context tool parameters: a sequence of Tools/ContextTools or a ContextToolkit."""
37
+
38
+ AsyncContextTools = TypeAliasType(
39
+ "AsyncContextTools",
40
+ Sequence[AsyncTool | AsyncContextTool[DepsT] | ProviderTool]
41
+ | AsyncContextToolkit[DepsT],
42
+ type_params=(DepsT,),
43
+ )
44
+ """Type alias for async context tool parameters: a sequence of AsyncTools/AsyncContextTools or an AsyncContextToolkit."""
45
+
46
+
47
+ def normalize_tools(tools: Tools | None) -> Toolkit:
48
+ """Normalize tools input to a Toolkit.
49
+
50
+ Args:
51
+ tools: A sequence of Tools, a Toolkit, or None.
52
+
53
+ Returns:
54
+ A Toolkit containing the tools (or an empty Toolkit if None).
55
+ """
56
+ if tools is None:
57
+ return Toolkit(None)
58
+ if isinstance(tools, Toolkit):
59
+ return tools
60
+ return Toolkit(tools)
61
+
62
+
63
+ def normalize_async_tools(tools: AsyncTools | None) -> AsyncToolkit:
64
+ """Normalize async tools input to an AsyncToolkit.
65
+
66
+ Args:
67
+ tools: A sequence of AsyncTools, an AsyncToolkit, or None.
68
+
69
+ Returns:
70
+ An AsyncToolkit containing the tools (or an empty AsyncToolkit if None).
71
+ """
72
+ if tools is None:
73
+ return AsyncToolkit(None)
74
+ if isinstance(tools, AsyncToolkit):
75
+ return tools
76
+ return AsyncToolkit(tools)
77
+
78
+
79
+ def normalize_context_tools(
80
+ tools: ContextTools[DepsT] | None,
81
+ ) -> ContextToolkit[DepsT]:
82
+ """Normalize context tools input to a ContextToolkit.
83
+
84
+ Args:
85
+ tools: A sequence of Tools/ContextTools, a ContextToolkit, or None.
86
+
87
+ Returns:
88
+ A ContextToolkit containing the tools (or an empty ContextToolkit if None).
89
+ """
90
+ if tools is None:
91
+ return ContextToolkit(None)
92
+ if isinstance(tools, ContextToolkit):
93
+ return tools
94
+ return ContextToolkit(tools)
95
+
96
+
97
+ def normalize_async_context_tools(
98
+ tools: AsyncContextTools[DepsT] | None,
99
+ ) -> AsyncContextToolkit[DepsT]:
100
+ """Normalize async context tools input to an AsyncContextToolkit.
101
+
102
+ Args:
103
+ tools: A sequence of AsyncTools/AsyncContextTools, an AsyncContextToolkit, or None.
104
+
105
+ Returns:
106
+ An AsyncContextToolkit containing the tools (or an empty AsyncContextToolkit if None).
107
+ """
108
+ if tools is None:
109
+ return AsyncContextToolkit(None)
110
+ if isinstance(tools, AsyncContextToolkit):
111
+ return tools
112
+ return AsyncContextToolkit(tools)
@@ -0,0 +1,32 @@
1
+ """Web search tool for provider-native web search capabilities."""
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+ from .provider_tools import ProviderTool
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class WebSearchTool(ProviderTool):
10
+ """Web search tool that allows the model to search the internet.
11
+
12
+ This is a provider tool - the search is executed server-side by the provider,
13
+ not by your code. The model decides when to search based on the prompt,
14
+ and the provider returns search results with citations.
15
+
16
+ Supported providers include Anthropic, Google, and OpenAI (when using the Responses API).
17
+
18
+ Example:
19
+ ```python
20
+ from mirascope import llm
21
+
22
+ @llm.call("anthropic/claude-sonnet-4-5", tools=[llm.WebSearchTool()])
23
+ def search_web(query: str) -> str:
24
+ return f"Search the web for: {query}"
25
+
26
+ response = search_web("Who won the 2024 Super Bowl?")
27
+ print(response.text()) # Response includes citations from web search
28
+ ```
29
+ """
30
+
31
+ name: str = field(default="web_search", init=False)
32
+ """The tool name. Always "web_search" for this tool type."""
mirascope/ops/__init__.py CHANGED
@@ -12,9 +12,18 @@ stub_module_if_missing("mirascope.ops", "ops")
12
12
  # ruff: noqa: E402
13
13
  from ._internal.configuration import configure, tracer_context
14
14
  from ._internal.context import propagated_context
15
- from ._internal.instrumentation.llm import (
15
+ from ._internal.instrumentation import (
16
+ instrument_anthropic,
17
+ instrument_google_genai,
16
18
  instrument_llm,
19
+ instrument_openai,
20
+ is_anthropic_instrumented,
21
+ is_google_genai_instrumented,
22
+ is_openai_instrumented,
23
+ uninstrument_anthropic,
24
+ uninstrument_google_genai,
17
25
  uninstrument_llm,
26
+ uninstrument_openai,
18
27
  )
19
28
  from ._internal.propagation import (
20
29
  ContextPropagator,
@@ -99,13 +108,22 @@ __all__ = [
99
108
  "extract_session_id",
100
109
  "get_propagator",
101
110
  "inject_context",
111
+ "instrument_anthropic",
112
+ "instrument_google_genai",
102
113
  "instrument_llm",
114
+ "instrument_openai",
115
+ "is_anthropic_instrumented",
116
+ "is_google_genai_instrumented",
117
+ "is_openai_instrumented",
103
118
  "propagated_context",
104
119
  "reset_propagator",
105
120
  "session",
106
121
  "span",
107
122
  "trace",
108
123
  "tracer_context",
124
+ "uninstrument_anthropic",
125
+ "uninstrument_google_genai",
109
126
  "uninstrument_llm",
127
+ "uninstrument_openai",
110
128
  "version",
111
129
  ]
@@ -1,8 +1,28 @@
1
1
  """Instrumentation modules for various frameworks and libraries."""
2
2
 
3
3
  from .llm import instrument_llm, uninstrument_llm
4
+ from .providers import (
5
+ instrument_anthropic,
6
+ instrument_google_genai,
7
+ instrument_openai,
8
+ is_anthropic_instrumented,
9
+ is_google_genai_instrumented,
10
+ is_openai_instrumented,
11
+ uninstrument_anthropic,
12
+ uninstrument_google_genai,
13
+ uninstrument_openai,
14
+ )
4
15
 
5
16
  __all__ = [
17
+ "instrument_anthropic",
18
+ "instrument_google_genai",
6
19
  "instrument_llm",
20
+ "instrument_openai",
21
+ "is_anthropic_instrumented",
22
+ "is_google_genai_instrumented",
23
+ "is_openai_instrumented",
24
+ "uninstrument_anthropic",
25
+ "uninstrument_google_genai",
7
26
  "uninstrument_llm",
27
+ "uninstrument_openai",
8
28
  ]
@@ -17,10 +17,11 @@ from opentelemetry.semconv.attributes import error_attributes as ErrorAttributes
17
17
  from opentelemetry.trace import SpanKind, Status, StatusCode
18
18
 
19
19
  from .....llm import (
20
- AnyToolFn,
20
+ AnyTools,
21
21
  AnyToolSchema,
22
22
  BaseToolkit,
23
23
  Format,
24
+ FormatSpec,
24
25
  FormattableT,
25
26
  Jsonable,
26
27
  Message,
@@ -28,9 +29,9 @@ from .....llm import (
28
29
  ModelId,
29
30
  Params,
30
31
  ProviderId,
32
+ ProviderTool,
31
33
  RootResponse,
32
34
  ToolkitT,
33
- ToolSchema,
34
35
  )
35
36
  from ...configuration import get_tracer
36
37
  from ...utils import json_dumps
@@ -45,6 +46,7 @@ from .serialize import (
45
46
  serialize_mirascope_cost,
46
47
  serialize_mirascope_messages,
47
48
  serialize_mirascope_usage,
49
+ serialize_tools,
48
50
  )
49
51
 
50
52
  logger = logging.getLogger(__name__)
@@ -64,10 +66,7 @@ else:
64
66
  Tracer = None
65
67
 
66
68
 
67
- ToolsParam: TypeAlias = (
68
- Sequence[ToolSchema[AnyToolFn]] | BaseToolkit[AnyToolSchema] | None
69
- )
70
- FormatParam: TypeAlias = Format[FormattableT] | None
69
+ FormatParam: TypeAlias = FormatSpec[FormattableT] | None
71
70
  ParamsDict: TypeAlias = Mapping[str, str | int | float | bool | Sequence[str] | None]
72
71
  SpanAttributes: TypeAlias = Mapping[str, AttributeValue]
73
72
  AttributeSetter: TypeAlias = Callable[[str, AttributeValue], None]
@@ -180,53 +179,13 @@ def _assign_request_message_attributes(
180
179
  )
181
180
 
182
181
 
183
- def _collect_tool_schemas(
184
- tools: Sequence[ToolSchema[AnyToolFn]] | BaseToolkit[AnyToolSchema],
185
- ) -> list[ToolSchema[AnyToolFn]]:
186
- """Collect ToolSchema instances from a tools parameter."""
187
- iterable = list(tools.tools) if isinstance(tools, BaseToolkit) else list(tools)
188
- schemas: list[ToolSchema[AnyToolFn]] = []
189
- for tool in iterable:
190
- if isinstance(tool, ToolSchema):
191
- schemas.append(tool)
192
- return schemas
193
-
194
-
195
- def _serialize_tool_definitions(
196
- tools: ToolsParam,
197
- format: FormatParam = None,
198
- ) -> str | None:
199
- """Serialize tool definitions to JSON for span attributes."""
200
- if tools is None:
201
- tool_schemas: list[ToolSchema[AnyToolFn]] = []
202
- else:
203
- tool_schemas = _collect_tool_schemas(tools)
204
-
205
- if isinstance(format, Format) and format.mode == "tool":
206
- tool_schemas.append(format.create_tool_schema())
207
-
208
- if not tool_schemas:
209
- return None
210
- definitions: list[dict[str, str | int | bool | dict[str, str | int | bool]]] = []
211
- for tool in tool_schemas:
212
- tool_def: dict[str, str | int | bool | dict[str, str | int | bool]] = {
213
- "name": tool.name,
214
- "description": tool.description,
215
- "parameters": tool.parameters.model_dump(by_alias=True, mode="json"),
216
- }
217
- if tool.strict is not None:
218
- tool_def["strict"] = tool.strict
219
- definitions.append(tool_def)
220
- return json_dumps(definitions)
221
-
222
-
223
182
  def _build_request_attributes(
224
183
  *,
225
184
  operation: str,
226
185
  provider: ProviderId,
227
186
  model_id: ModelId,
228
187
  messages: Sequence[Message],
229
- tools: ToolsParam,
188
+ tools: AnyTools | None,
230
189
  format: FormatParam,
231
190
  params: ParamsDict,
232
191
  ) -> dict[str, AttributeValue]:
@@ -244,7 +203,18 @@ def _build_request_attributes(
244
203
  messages=messages,
245
204
  )
246
205
 
247
- tool_payload = _serialize_tool_definitions(tools, format=format)
206
+ tool_schemas: list[AnyToolSchema | ProviderTool] = []
207
+ if tools is None:
208
+ tool_schemas = []
209
+ elif isinstance(tools, BaseToolkit):
210
+ tool_schemas = list(tools.tools)
211
+ else:
212
+ tool_schemas = list(tools)
213
+
214
+ if isinstance(format, Format) and format.mode == "tool":
215
+ tool_schemas.append(format.create_tool_schema())
216
+
217
+ tool_payload = serialize_tools(tool_schemas)
248
218
  if tool_payload:
249
219
  # The incubating semconv module does not yet expose a constant for this key.
250
220
  attrs["gen_ai.tool.definitions"] = tool_payload
@@ -477,7 +447,7 @@ def start_model_span(
477
447
  model: Model,
478
448
  *,
479
449
  messages: Sequence[Message],
480
- tools: ToolsParam,
450
+ tools: AnyTools | None,
481
451
  format: FormatParam,
482
452
  activate: bool = True,
483
453
  ) -> Iterator[SpanContext]: