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
@@ -27,6 +27,7 @@ from .....responses import (
27
27
  ChunkIterator,
28
28
  FinishReason,
29
29
  FinishReasonChunk,
30
+ ProviderToolUsage,
30
31
  RawMessageChunk,
31
32
  RawStreamEventChunk,
32
33
  Usage,
@@ -40,8 +41,21 @@ INCOMPLETE_DETAILS_TO_FINISH_REASON = {
40
41
  }
41
42
 
42
43
 
44
+ def _extract_tool_usage(
45
+ output: list[openai_types.ResponseOutputItem],
46
+ ) -> list[ProviderToolUsage] | None:
47
+ """Extract provider tool usage from OpenAI Responses output."""
48
+ web_search_count = sum(1 for item in output if item.type == "web_search_call")
49
+
50
+ if web_search_count == 0:
51
+ return None
52
+
53
+ return [ProviderToolUsage(name="web_search", call_count=web_search_count)]
54
+
55
+
43
56
  def _decode_usage(
44
57
  usage: openai_types.ResponseUsage | None,
58
+ output: list[openai_types.ResponseOutputItem] | None = None,
45
59
  ) -> Usage | None:
46
60
  """Convert OpenAI ResponseUsage to Mirascope Usage."""
47
61
  if usage is None: # pragma: no cover
@@ -63,6 +77,7 @@ def _decode_usage(
63
77
  else None
64
78
  )
65
79
  or 0,
80
+ provider_tool_usage=_extract_tool_usage(output) if output else None,
66
81
  raw=usage,
67
82
  )
68
83
 
@@ -112,7 +127,8 @@ def decode_response(
112
127
  for reasoning_content in output_item.content:
113
128
  if reasoning_content.type == "reasoning_text":
114
129
  parts.append(Thought(thought=reasoning_content.text))
115
-
130
+ elif output_item.type == "web_search_call":
131
+ pass # Skip, preserved in raw_message
116
132
  else:
117
133
  raise NotImplementedError(f"Unsupported output item: {output_item.type}")
118
134
 
@@ -134,7 +150,7 @@ def decode_response(
134
150
  ],
135
151
  )
136
152
 
137
- usage = _decode_usage(response.usage)
153
+ usage = _decode_usage(response.usage, response.output)
138
154
  return assistant_message, finish_reason, usage
139
155
 
140
156
 
@@ -145,6 +161,7 @@ class _OpenAIResponsesChunkProcessor:
145
161
  self.current_content_type: Literal["text", "tool_call", "thought"] | None = None
146
162
  self.refusal_encountered = False
147
163
  self.include_thoughts = include_thoughts
164
+ self.web_search_count = 0
148
165
 
149
166
  def process_chunk(self, event: ResponseStreamEvent) -> ChunkIterator:
150
167
  """Process a single OpenAI Responses stream event and yield the appropriate content chunks."""
@@ -178,6 +195,8 @@ class _OpenAIResponsesChunkProcessor:
178
195
  name=item.name,
179
196
  )
180
197
  self.current_content_type = "tool_call"
198
+ elif item.type == "web_search_call":
199
+ self.web_search_count += 1
181
200
  elif event.type == "response.function_call_arguments.delta":
182
201
  yield ToolCallChunk(id=self.current_tool_call_id, delta=event.delta)
183
202
  elif event.type == "response.function_call_arguments.done":
@@ -218,6 +237,15 @@ class _OpenAIResponsesChunkProcessor:
218
237
  # Emit usage delta if present
219
238
  if event.response.usage:
220
239
  usage = event.response.usage
240
+ provider_tool_usage = (
241
+ [
242
+ ProviderToolUsage(
243
+ name="web_search", call_count=self.web_search_count
244
+ )
245
+ ]
246
+ if self.web_search_count > 0
247
+ else None
248
+ )
221
249
  yield UsageDeltaChunk(
222
250
  input_tokens=usage.input_tokens,
223
251
  output_tokens=usage.output_tokens,
@@ -234,6 +262,7 @@ class _OpenAIResponsesChunkProcessor:
234
262
  else None
235
263
  )
236
264
  or 0,
265
+ provider_tool_usage=provider_tool_usage,
237
266
  )
238
267
 
239
268
 
@@ -17,6 +17,8 @@ from openai.types.responses import (
17
17
  ResponseTextConfigParam,
18
18
  ToolChoiceAllowedParam,
19
19
  ToolChoiceFunctionParam,
20
+ ToolParam,
21
+ WebSearchToolParam,
20
22
  response_create_params,
21
23
  )
22
24
  from openai.types.responses.easy_input_message_param import EasyInputMessageParam
@@ -34,12 +36,18 @@ from openai.types.shared_params.responses_model import ResponsesModel
34
36
  from .....exceptions import FeatureNotSupportedError
35
37
  from .....formatting import (
36
38
  Format,
39
+ FormatSpec,
37
40
  FormattableT,
38
- OutputParser,
39
41
  resolve_format,
40
42
  )
41
43
  from .....messages import AssistantMessage, Message, UserMessage
42
- from .....tools import FORMAT_TOOL_NAME, AnyToolSchema, BaseToolkit
44
+ from .....tools import (
45
+ FORMAT_TOOL_NAME,
46
+ AnyToolSchema,
47
+ BaseToolkit,
48
+ ProviderTool,
49
+ WebSearchTool,
50
+ )
43
51
  from ....base import _utils as _base_utils
44
52
  from ...model_id import OpenAIModelId, model_name
45
53
  from ...model_info import (
@@ -72,7 +80,7 @@ class ResponseCreateKwargs(TypedDict, total=False):
72
80
  temperature: float
73
81
  max_output_tokens: int
74
82
  top_p: float
75
- tools: list[FunctionToolParam] | Omit
83
+ tools: list[ToolParam] | Omit
76
84
  tool_choice: response_create_params.ToolChoice | Omit
77
85
  text: ResponseTextConfigParam
78
86
  reasoning: Reasoning | Omit
@@ -212,8 +220,16 @@ def _encode_message(
212
220
  return _encode_user_message(message)
213
221
 
214
222
 
215
- def _convert_tool_to_function_tool_param(tool: AnyToolSchema) -> FunctionToolParam:
216
- """Convert a Mirascope ToolSchema to OpenAI Responses FunctionToolParam."""
223
+ def _convert_tool_to_tool_param(
224
+ tool: AnyToolSchema | ProviderTool,
225
+ ) -> ToolParam:
226
+ """Convert a Mirascope ToolSchema to OpenAI Responses ToolParam."""
227
+ if isinstance(tool, WebSearchTool):
228
+ return WebSearchToolParam(type="web_search")
229
+ if isinstance(tool, ProviderTool):
230
+ raise FeatureNotSupportedError(
231
+ f"Provider tool {tool.name}", provider_id="openai:responses"
232
+ )
217
233
  schema_dict = tool.parameters.model_dump(by_alias=True, exclude_none=True)
218
234
  schema_dict["type"] = "object"
219
235
  _base_utils.ensure_additional_properties_false(schema_dict)
@@ -281,11 +297,8 @@ def encode_request(
281
297
  *,
282
298
  model_id: OpenAIModelId,
283
299
  messages: Sequence[Message],
284
- tools: Sequence[AnyToolSchema] | BaseToolkit[AnyToolSchema] | None,
285
- format: type[FormattableT]
286
- | Format[FormattableT]
287
- | OutputParser[FormattableT]
288
- | None,
300
+ tools: BaseToolkit[AnyToolSchema],
301
+ format: FormatSpec[FormattableT] | None,
289
302
  params: Params,
290
303
  ) -> tuple[Sequence[Message], Format[FormattableT] | None, ResponseCreateKwargs]:
291
304
  """Prepares a request for the `OpenAI.responses.create` method."""
@@ -334,8 +347,7 @@ def encode_request(
334
347
  if thinking_config.get("encode_thoughts_as_text"):
335
348
  encode_thoughts = True
336
349
 
337
- tools = tools.tools if isinstance(tools, BaseToolkit) else tools or []
338
- openai_tools = [_convert_tool_to_function_tool_param(tool) for tool in tools]
350
+ openai_tools = [_convert_tool_to_tool_param(tool) for tool in tools.tools]
339
351
 
340
352
  model_supports_strict = model_id not in MODELS_WITHOUT_JSON_SCHEMA_SUPPORT
341
353
  default_mode = "strict" if model_supports_strict else "tool"
@@ -346,16 +358,19 @@ def encode_request(
346
358
  kwargs["text"] = {"format": _create_strict_response_format(format)}
347
359
  elif format.mode == "tool":
348
360
  format_tool_schema = format.create_tool_schema()
349
- openai_tools.append(
350
- _convert_tool_to_function_tool_param(format_tool_schema)
351
- )
352
- if tools:
361
+ openai_tools.append(_convert_tool_to_tool_param(format_tool_schema))
362
+ if tools.tools:
363
+ # Only include function tools in tool_choice (filter out web search, etc.)
364
+ function_tools = cast(
365
+ list[FunctionToolParam],
366
+ [t for t in openai_tools if t.get("type") == "function"],
367
+ )
353
368
  kwargs["tool_choice"] = ToolChoiceAllowedParam(
354
369
  type="allowed_tools",
355
370
  mode="required",
356
371
  tools=[
357
372
  {"type": "function", "name": tool["name"]}
358
- for tool in openai_tools
373
+ for tool in function_tools
359
374
  ],
360
375
  )
361
376
  else:
@@ -10,7 +10,7 @@ from openai import AsyncOpenAI, BadRequestError as OpenAIBadRequestError, OpenAI
10
10
 
11
11
  from ....context import Context, DepsT
12
12
  from ....exceptions import BadRequestError, NotFoundError
13
- from ....formatting import Format, FormattableT, OutputParser
13
+ from ....formatting import FormatSpec, FormattableT
14
14
  from ....messages import Message
15
15
  from ....responses import (
16
16
  AsyncContextResponse,
@@ -22,16 +22,7 @@ from ....responses import (
22
22
  Response,
23
23
  StreamResponse,
24
24
  )
25
- from ....tools import (
26
- AsyncContextTool,
27
- AsyncContextToolkit,
28
- AsyncTool,
29
- AsyncToolkit,
30
- ContextTool,
31
- ContextToolkit,
32
- Tool,
33
- Toolkit,
34
- )
25
+ from ....tools import AsyncContextToolkit, AsyncToolkit, ContextToolkit, Toolkit
35
26
  from ...base import BaseProvider
36
27
  from .. import _utils as _shared_utils
37
28
  from ..model_id import OpenAIModelId, model_name
@@ -72,11 +63,8 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
72
63
  *,
73
64
  model_id: OpenAIModelId,
74
65
  messages: Sequence[Message],
75
- tools: Sequence[Tool] | Toolkit | None = None,
76
- format: type[FormattableT]
77
- | Format[FormattableT]
78
- | OutputParser[FormattableT]
79
- | None = None,
66
+ toolkit: Toolkit,
67
+ format: FormatSpec[FormattableT] | None = None,
80
68
  **params: Unpack[Params],
81
69
  ) -> Response | Response[FormattableT]:
82
70
  """Generate an `llm.Response` by synchronously calling the OpenAI Responses API.
@@ -94,7 +82,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
94
82
  messages, format, kwargs = _utils.encode_request(
95
83
  model_id=model_id,
96
84
  messages=messages,
97
- tools=tools,
85
+ tools=toolkit,
98
86
  format=format,
99
87
  params=params,
100
88
  )
@@ -112,7 +100,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
112
100
  model_id=model_id,
113
101
  provider_model_name=provider_model_name,
114
102
  params=params,
115
- tools=tools,
103
+ tools=toolkit,
116
104
  input_messages=messages,
117
105
  assistant_message=assistant_message,
118
106
  finish_reason=finish_reason,
@@ -125,11 +113,8 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
125
113
  *,
126
114
  model_id: OpenAIModelId,
127
115
  messages: Sequence[Message],
128
- tools: Sequence[AsyncTool] | AsyncToolkit | None = None,
129
- format: type[FormattableT]
130
- | Format[FormattableT]
131
- | OutputParser[FormattableT]
132
- | None = None,
116
+ toolkit: AsyncToolkit,
117
+ format: FormatSpec[FormattableT] | None = None,
133
118
  **params: Unpack[Params],
134
119
  ) -> AsyncResponse | AsyncResponse[FormattableT]:
135
120
  """Generate an `llm.AsyncResponse` by asynchronously calling the OpenAI Responses API.
@@ -147,7 +132,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
147
132
  messages, format, kwargs = _utils.encode_request(
148
133
  model_id=model_id,
149
134
  messages=messages,
150
- tools=tools,
135
+ tools=toolkit,
151
136
  format=format,
152
137
  params=params,
153
138
  )
@@ -165,7 +150,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
165
150
  model_id=model_id,
166
151
  provider_model_name=provider_model_name,
167
152
  params=params,
168
- tools=tools,
153
+ tools=toolkit,
169
154
  input_messages=messages,
170
155
  assistant_message=assistant_message,
171
156
  finish_reason=finish_reason,
@@ -178,11 +163,8 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
178
163
  *,
179
164
  model_id: OpenAIModelId,
180
165
  messages: Sequence[Message],
181
- tools: Sequence[Tool] | Toolkit | None = None,
182
- format: type[FormattableT]
183
- | Format[FormattableT]
184
- | OutputParser[FormattableT]
185
- | None = None,
166
+ toolkit: Toolkit,
167
+ format: FormatSpec[FormattableT] | None = None,
186
168
  **params: Unpack[Params],
187
169
  ) -> StreamResponse | StreamResponse[FormattableT]:
188
170
  """Generate a `llm.StreamResponse` by synchronously streaming from the OpenAI Responses API.
@@ -200,7 +182,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
200
182
  messages, format, kwargs = _utils.encode_request(
201
183
  model_id=model_id,
202
184
  messages=messages,
203
- tools=tools,
185
+ tools=toolkit,
204
186
  format=format,
205
187
  params=params,
206
188
  )
@@ -220,7 +202,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
220
202
  model_id=model_id,
221
203
  provider_model_name=provider_model_name,
222
204
  params=params,
223
- tools=tools,
205
+ tools=toolkit,
224
206
  input_messages=messages,
225
207
  chunk_iterator=chunk_iterator,
226
208
  format=format,
@@ -231,11 +213,8 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
231
213
  *,
232
214
  model_id: OpenAIModelId,
233
215
  messages: Sequence[Message],
234
- tools: Sequence[AsyncTool] | AsyncToolkit | None = None,
235
- format: type[FormattableT]
236
- | Format[FormattableT]
237
- | OutputParser[FormattableT]
238
- | None = None,
216
+ toolkit: AsyncToolkit,
217
+ format: FormatSpec[FormattableT] | None = None,
239
218
  **params: Unpack[Params],
240
219
  ) -> AsyncStreamResponse | AsyncStreamResponse[FormattableT]:
241
220
  """Generate a `llm.AsyncStreamResponse` by asynchronously streaming from the OpenAI Responses API.
@@ -253,7 +232,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
253
232
  messages, format, kwargs = _utils.encode_request(
254
233
  model_id=model_id,
255
234
  messages=messages,
256
- tools=tools,
235
+ tools=toolkit,
257
236
  format=format,
258
237
  params=params,
259
238
  )
@@ -273,7 +252,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
273
252
  model_id=model_id,
274
253
  provider_model_name=provider_model_name,
275
254
  params=params,
276
- tools=tools,
255
+ tools=toolkit,
277
256
  input_messages=messages,
278
257
  chunk_iterator=chunk_iterator,
279
258
  format=format,
@@ -285,13 +264,8 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
285
264
  ctx: Context[DepsT],
286
265
  model_id: OpenAIModelId,
287
266
  messages: Sequence[Message],
288
- tools: Sequence[Tool | ContextTool[DepsT]]
289
- | ContextToolkit[DepsT]
290
- | None = None,
291
- format: type[FormattableT]
292
- | Format[FormattableT]
293
- | OutputParser[FormattableT]
294
- | None = None,
267
+ toolkit: ContextToolkit[DepsT],
268
+ format: FormatSpec[FormattableT] | None = None,
295
269
  **params: Unpack[Params],
296
270
  ) -> ContextResponse[DepsT] | ContextResponse[DepsT, FormattableT]:
297
271
  """Generate a `llm.ContextResponse` by synchronously calling the OpenAI Responses API with context.
@@ -310,7 +284,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
310
284
  messages, format, kwargs = _utils.encode_request(
311
285
  model_id=model_id,
312
286
  messages=messages,
313
- tools=tools,
287
+ tools=toolkit,
314
288
  format=format,
315
289
  params=params,
316
290
  )
@@ -328,7 +302,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
328
302
  model_id=model_id,
329
303
  provider_model_name=provider_model_name,
330
304
  params=params,
331
- tools=tools,
305
+ tools=toolkit,
332
306
  input_messages=messages,
333
307
  assistant_message=assistant_message,
334
308
  finish_reason=finish_reason,
@@ -342,13 +316,8 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
342
316
  ctx: Context[DepsT],
343
317
  model_id: OpenAIModelId,
344
318
  messages: Sequence[Message],
345
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
346
- | AsyncContextToolkit[DepsT]
347
- | None = None,
348
- format: type[FormattableT]
349
- | Format[FormattableT]
350
- | OutputParser[FormattableT]
351
- | None = None,
319
+ toolkit: AsyncContextToolkit[DepsT],
320
+ format: FormatSpec[FormattableT] | None = None,
352
321
  **params: Unpack[Params],
353
322
  ) -> AsyncContextResponse[DepsT] | AsyncContextResponse[DepsT, FormattableT]:
354
323
  """Generate a `llm.AsyncContextResponse` by asynchronously calling the OpenAI Responses API with context.
@@ -367,7 +336,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
367
336
  messages, format, kwargs = _utils.encode_request(
368
337
  model_id=model_id,
369
338
  messages=messages,
370
- tools=tools,
339
+ tools=toolkit,
371
340
  format=format,
372
341
  params=params,
373
342
  )
@@ -385,7 +354,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
385
354
  model_id=model_id,
386
355
  provider_model_name=provider_model_name,
387
356
  params=params,
388
- tools=tools,
357
+ tools=toolkit,
389
358
  input_messages=messages,
390
359
  assistant_message=assistant_message,
391
360
  finish_reason=finish_reason,
@@ -399,13 +368,8 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
399
368
  ctx: Context[DepsT],
400
369
  model_id: OpenAIModelId,
401
370
  messages: Sequence[Message],
402
- tools: Sequence[Tool | ContextTool[DepsT]]
403
- | ContextToolkit[DepsT]
404
- | None = None,
405
- format: type[FormattableT]
406
- | Format[FormattableT]
407
- | OutputParser[FormattableT]
408
- | None = None,
371
+ toolkit: ContextToolkit[DepsT],
372
+ format: FormatSpec[FormattableT] | None = None,
409
373
  **params: Unpack[Params],
410
374
  ) -> ContextStreamResponse[DepsT] | ContextStreamResponse[DepsT, FormattableT]:
411
375
  """Generate a `llm.ContextStreamResponse` by synchronously streaming from the OpenAI Responses API with context.
@@ -424,7 +388,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
424
388
  messages, format, kwargs = _utils.encode_request(
425
389
  model_id=model_id,
426
390
  messages=messages,
427
- tools=tools,
391
+ tools=toolkit,
428
392
  format=format,
429
393
  params=params,
430
394
  )
@@ -445,7 +409,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
445
409
  model_id=model_id,
446
410
  provider_model_name=provider_model_name,
447
411
  params=params,
448
- tools=tools,
412
+ tools=toolkit,
449
413
  input_messages=messages,
450
414
  chunk_iterator=chunk_iterator,
451
415
  format=format,
@@ -457,13 +421,8 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
457
421
  ctx: Context[DepsT],
458
422
  model_id: OpenAIModelId,
459
423
  messages: Sequence[Message],
460
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
461
- | AsyncContextToolkit[DepsT]
462
- | None = None,
463
- format: type[FormattableT]
464
- | Format[FormattableT]
465
- | OutputParser[FormattableT]
466
- | None = None,
424
+ toolkit: AsyncContextToolkit[DepsT],
425
+ format: FormatSpec[FormattableT] | None = None,
467
426
  **params: Unpack[Params],
468
427
  ) -> (
469
428
  AsyncContextStreamResponse[DepsT]
@@ -485,7 +444,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
485
444
  messages, format, kwargs = _utils.encode_request(
486
445
  model_id=model_id,
487
446
  messages=messages,
488
- tools=tools,
447
+ tools=toolkit,
489
448
  format=format,
490
449
  params=params,
491
450
  )
@@ -506,7 +465,7 @@ class OpenAIResponsesProvider(BaseProvider[OpenAI]):
506
465
  model_id=model_id,
507
466
  provider_model_name=provider_model_name,
508
467
  params=params,
509
- tools=tools,
468
+ tools=toolkit,
510
469
  input_messages=messages,
511
470
  chunk_iterator=chunk_iterator,
512
471
  format=format,
@@ -29,7 +29,7 @@ from .streams import (
29
29
  ThoughtStream,
30
30
  ToolCallStream,
31
31
  )
32
- from .usage import Usage, UsageDeltaChunk
32
+ from .usage import ProviderToolUsage, Usage, UsageDeltaChunk
33
33
 
34
34
  __all__ = [
35
35
  "AnyResponse",
@@ -47,6 +47,7 @@ __all__ = [
47
47
  "ContextStreamResponse",
48
48
  "FinishReason",
49
49
  "FinishReasonChunk",
50
+ "ProviderToolUsage",
50
51
  "RawMessageChunk",
51
52
  "RawStreamEventChunk",
52
53
  "Response",
@@ -504,6 +504,8 @@ class BaseSyncStreamResponse(BaseStreamResponse[ChunkIterator, ToolkitT, Formatt
504
504
  self.usage.cache_read_tokens += chunk.cache_read_tokens
505
505
  self.usage.cache_write_tokens += chunk.cache_write_tokens
506
506
  self.usage.reasoning_tokens += chunk.reasoning_tokens
507
+ if chunk.provider_tool_usage:
508
+ self.usage.provider_tool_usage = chunk.provider_tool_usage
507
509
  else:
508
510
  yield self._handle_chunk(chunk)
509
511
 
@@ -725,6 +727,8 @@ class BaseAsyncStreamResponse(
725
727
  self.usage.cache_read_tokens += chunk.cache_read_tokens
726
728
  self.usage.cache_write_tokens += chunk.cache_write_tokens
727
729
  self.usage.reasoning_tokens += chunk.reasoning_tokens
730
+ if chunk.provider_tool_usage:
731
+ self.usage.provider_tool_usage = chunk.provider_tool_usage
728
732
  else:
729
733
  yield self._handle_chunk(chunk)
730
734
 
@@ -9,14 +9,14 @@ from ..context import Context, DepsT
9
9
  from ..formatting import Format, FormattableT
10
10
  from ..messages import AssistantMessage, Message, UserContent
11
11
  from ..tools import (
12
- AsyncContextTool,
13
12
  AsyncContextToolkit,
14
- AsyncTool,
13
+ AsyncContextTools,
15
14
  AsyncToolkit,
16
- ContextTool,
15
+ AsyncTools,
17
16
  ContextToolkit,
18
- Tool,
17
+ ContextTools,
19
18
  Toolkit,
19
+ Tools,
20
20
  )
21
21
  from ..types import Jsonable
22
22
  from .base_response import BaseResponse
@@ -39,7 +39,7 @@ class Response(BaseResponse[Toolkit, FormattableT]):
39
39
  model_id: "ModelId",
40
40
  provider_model_name: str,
41
41
  params: "Params",
42
- tools: Sequence[Tool] | Toolkit | None = None,
42
+ tools: Tools | None = None,
43
43
  format: Format[FormattableT] | None = None,
44
44
  input_messages: Sequence[Message],
45
45
  assistant_message: AssistantMessage,
@@ -112,7 +112,7 @@ class AsyncResponse(BaseResponse[AsyncToolkit, FormattableT]):
112
112
  model_id: "ModelId",
113
113
  provider_model_name: str,
114
114
  params: "Params",
115
- tools: Sequence[AsyncTool] | AsyncToolkit | None = None,
115
+ tools: AsyncTools | None = None,
116
116
  format: Format[FormattableT] | None = None,
117
117
  input_messages: Sequence[Message],
118
118
  assistant_message: AssistantMessage,
@@ -194,9 +194,7 @@ class ContextResponse(
194
194
  model_id: "ModelId",
195
195
  provider_model_name: str,
196
196
  params: "Params",
197
- tools: Sequence[Tool | ContextTool[DepsT]]
198
- | ContextToolkit[DepsT]
199
- | None = None,
197
+ tools: ContextTools[DepsT] | None = None,
200
198
  format: Format[FormattableT] | None = None,
201
199
  input_messages: Sequence[Message],
202
200
  assistant_message: AssistantMessage,
@@ -284,9 +282,7 @@ class AsyncContextResponse(
284
282
  model_id: "ModelId",
285
283
  provider_model_name: str,
286
284
  params: "Params",
287
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
288
- | AsyncContextToolkit[DepsT]
289
- | None = None,
285
+ tools: AsyncContextTools[DepsT] | None = None,
290
286
  format: Format[FormattableT] | None = None,
291
287
  input_messages: Sequence[Message],
292
288
  assistant_message: AssistantMessage,
@@ -9,14 +9,14 @@ from ..context import Context, DepsT
9
9
  from ..formatting import Format, FormattableT
10
10
  from ..messages import Message, UserContent
11
11
  from ..tools import (
12
- AsyncContextTool,
13
12
  AsyncContextToolkit,
14
- AsyncTool,
13
+ AsyncContextTools,
15
14
  AsyncToolkit,
16
- ContextTool,
15
+ AsyncTools,
17
16
  ContextToolkit,
18
- Tool,
17
+ ContextTools,
19
18
  Toolkit,
19
+ Tools,
20
20
  )
21
21
  from ..types import Jsonable
22
22
  from .base_stream_response import (
@@ -98,7 +98,7 @@ class StreamResponse(BaseSyncStreamResponse[Toolkit, FormattableT]):
98
98
  model_id: "ModelId",
99
99
  provider_model_name: str,
100
100
  params: "Params",
101
- tools: Sequence[Tool] | Toolkit | None = None,
101
+ tools: Tools | None = None,
102
102
  format: Format[FormattableT] | None = None,
103
103
  input_messages: Sequence[Message],
104
104
  chunk_iterator: ChunkIterator,
@@ -224,7 +224,7 @@ class AsyncStreamResponse(BaseAsyncStreamResponse[AsyncToolkit, FormattableT]):
224
224
  model_id: "ModelId",
225
225
  provider_model_name: str,
226
226
  params: "Params",
227
- tools: Sequence[AsyncTool] | AsyncToolkit | None = None,
227
+ tools: AsyncTools | None = None,
228
228
  format: Format[FormattableT] | None = None,
229
229
  input_messages: Sequence[Message],
230
230
  chunk_iterator: AsyncChunkIterator,
@@ -359,9 +359,7 @@ class ContextStreamResponse(
359
359
  model_id: "ModelId",
360
360
  provider_model_name: str,
361
361
  params: "Params",
362
- tools: Sequence[Tool | ContextTool[DepsT]]
363
- | ContextToolkit[DepsT]
364
- | None = None,
362
+ tools: ContextTools[DepsT] | None = None,
365
363
  format: Format[FormattableT] | None = None,
366
364
  input_messages: Sequence[Message],
367
365
  chunk_iterator: ChunkIterator,
@@ -502,9 +500,7 @@ class AsyncContextStreamResponse(
502
500
  model_id: "ModelId",
503
501
  provider_model_name: str,
504
502
  params: "Params",
505
- tools: Sequence[AsyncTool | AsyncContextTool[DepsT]]
506
- | AsyncContextToolkit[DepsT]
507
- | None = None,
503
+ tools: AsyncContextTools[DepsT] | None = None,
508
504
  format: Format[FormattableT] | None = None,
509
505
  input_messages: Sequence[Message],
510
506
  chunk_iterator: AsyncChunkIterator,