mirascope 2.0.0a2__py3-none-any.whl → 2.0.0a4__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 (252) hide show
  1. mirascope/__init__.py +2 -2
  2. mirascope/api/__init__.py +6 -0
  3. mirascope/api/_generated/README.md +207 -0
  4. mirascope/api/_generated/__init__.py +141 -0
  5. mirascope/api/_generated/client.py +163 -0
  6. mirascope/api/_generated/core/__init__.py +52 -0
  7. mirascope/api/_generated/core/api_error.py +23 -0
  8. mirascope/api/_generated/core/client_wrapper.py +58 -0
  9. mirascope/api/_generated/core/datetime_utils.py +30 -0
  10. mirascope/api/_generated/core/file.py +70 -0
  11. mirascope/api/_generated/core/force_multipart.py +16 -0
  12. mirascope/api/_generated/core/http_client.py +619 -0
  13. mirascope/api/_generated/core/http_response.py +55 -0
  14. mirascope/api/_generated/core/jsonable_encoder.py +102 -0
  15. mirascope/api/_generated/core/pydantic_utilities.py +310 -0
  16. mirascope/api/_generated/core/query_encoder.py +60 -0
  17. mirascope/api/_generated/core/remove_none_from_dict.py +11 -0
  18. mirascope/api/_generated/core/request_options.py +35 -0
  19. mirascope/api/_generated/core/serialization.py +282 -0
  20. mirascope/api/_generated/docs/__init__.py +4 -0
  21. mirascope/api/_generated/docs/client.py +95 -0
  22. mirascope/api/_generated/docs/raw_client.py +132 -0
  23. mirascope/api/_generated/environment.py +9 -0
  24. mirascope/api/_generated/errors/__init__.py +17 -0
  25. mirascope/api/_generated/errors/bad_request_error.py +15 -0
  26. mirascope/api/_generated/errors/conflict_error.py +15 -0
  27. mirascope/api/_generated/errors/forbidden_error.py +15 -0
  28. mirascope/api/_generated/errors/internal_server_error.py +15 -0
  29. mirascope/api/_generated/errors/not_found_error.py +15 -0
  30. mirascope/api/_generated/health/__init__.py +7 -0
  31. mirascope/api/_generated/health/client.py +96 -0
  32. mirascope/api/_generated/health/raw_client.py +129 -0
  33. mirascope/api/_generated/health/types/__init__.py +8 -0
  34. mirascope/api/_generated/health/types/health_check_response.py +24 -0
  35. mirascope/api/_generated/health/types/health_check_response_status.py +5 -0
  36. mirascope/api/_generated/organizations/__init__.py +25 -0
  37. mirascope/api/_generated/organizations/client.py +380 -0
  38. mirascope/api/_generated/organizations/raw_client.py +876 -0
  39. mirascope/api/_generated/organizations/types/__init__.py +23 -0
  40. mirascope/api/_generated/organizations/types/organizations_create_response.py +24 -0
  41. mirascope/api/_generated/organizations/types/organizations_create_response_role.py +7 -0
  42. mirascope/api/_generated/organizations/types/organizations_get_response.py +24 -0
  43. mirascope/api/_generated/organizations/types/organizations_get_response_role.py +7 -0
  44. mirascope/api/_generated/organizations/types/organizations_list_response_item.py +24 -0
  45. mirascope/api/_generated/organizations/types/organizations_list_response_item_role.py +7 -0
  46. mirascope/api/_generated/organizations/types/organizations_update_response.py +24 -0
  47. mirascope/api/_generated/organizations/types/organizations_update_response_role.py +7 -0
  48. mirascope/api/_generated/projects/__init__.py +17 -0
  49. mirascope/api/_generated/projects/client.py +458 -0
  50. mirascope/api/_generated/projects/raw_client.py +1016 -0
  51. mirascope/api/_generated/projects/types/__init__.py +15 -0
  52. mirascope/api/_generated/projects/types/projects_create_response.py +30 -0
  53. mirascope/api/_generated/projects/types/projects_get_response.py +30 -0
  54. mirascope/api/_generated/projects/types/projects_list_response_item.py +30 -0
  55. mirascope/api/_generated/projects/types/projects_update_response.py +30 -0
  56. mirascope/api/_generated/reference.md +753 -0
  57. mirascope/api/_generated/traces/__init__.py +55 -0
  58. mirascope/api/_generated/traces/client.py +162 -0
  59. mirascope/api/_generated/traces/raw_client.py +168 -0
  60. mirascope/api/_generated/traces/types/__init__.py +95 -0
  61. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +36 -0
  62. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +31 -0
  63. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +25 -0
  64. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +54 -0
  65. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +23 -0
  66. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +28 -0
  67. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +24 -0
  68. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +35 -0
  69. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +35 -0
  70. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +27 -0
  71. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +54 -0
  72. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +23 -0
  73. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +28 -0
  74. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value_values_item.py +24 -0
  75. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +60 -0
  76. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +29 -0
  77. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +54 -0
  78. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +23 -0
  79. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +28 -0
  80. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value_values_item.py +24 -0
  81. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +24 -0
  82. mirascope/api/_generated/traces/types/traces_create_response.py +27 -0
  83. mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +28 -0
  84. mirascope/api/_generated/types/__init__.py +37 -0
  85. mirascope/api/_generated/types/already_exists_error.py +24 -0
  86. mirascope/api/_generated/types/already_exists_error_tag.py +5 -0
  87. mirascope/api/_generated/types/database_error.py +24 -0
  88. mirascope/api/_generated/types/database_error_tag.py +5 -0
  89. mirascope/api/_generated/types/http_api_decode_error.py +29 -0
  90. mirascope/api/_generated/types/http_api_decode_error_tag.py +5 -0
  91. mirascope/api/_generated/types/issue.py +40 -0
  92. mirascope/api/_generated/types/issue_tag.py +17 -0
  93. mirascope/api/_generated/types/not_found_error_body.py +24 -0
  94. mirascope/api/_generated/types/not_found_error_tag.py +5 -0
  95. mirascope/api/_generated/types/permission_denied_error.py +24 -0
  96. mirascope/api/_generated/types/permission_denied_error_tag.py +7 -0
  97. mirascope/api/_generated/types/property_key.py +7 -0
  98. mirascope/api/_generated/types/property_key_key.py +27 -0
  99. mirascope/api/_generated/types/property_key_key_tag.py +5 -0
  100. mirascope/api/client.py +255 -0
  101. mirascope/api/settings.py +81 -0
  102. mirascope/llm/__init__.py +45 -11
  103. mirascope/llm/calls/calls.py +81 -57
  104. mirascope/llm/calls/decorator.py +121 -115
  105. mirascope/llm/content/__init__.py +3 -2
  106. mirascope/llm/context/_utils.py +19 -6
  107. mirascope/llm/exceptions.py +30 -16
  108. mirascope/llm/formatting/_utils.py +9 -5
  109. mirascope/llm/formatting/format.py +2 -2
  110. mirascope/llm/formatting/from_call_args.py +2 -2
  111. mirascope/llm/messages/message.py +13 -5
  112. mirascope/llm/models/__init__.py +2 -2
  113. mirascope/llm/models/models.py +189 -81
  114. mirascope/llm/prompts/__init__.py +13 -12
  115. mirascope/llm/prompts/_utils.py +27 -24
  116. mirascope/llm/prompts/decorator.py +133 -204
  117. mirascope/llm/prompts/prompts.py +424 -0
  118. mirascope/llm/prompts/protocols.py +25 -59
  119. mirascope/llm/providers/__init__.py +44 -0
  120. mirascope/llm/{clients → providers}/_missing_import_stubs.py +8 -6
  121. mirascope/llm/providers/anthropic/__init__.py +29 -0
  122. mirascope/llm/providers/anthropic/_utils/__init__.py +23 -0
  123. mirascope/llm/providers/anthropic/_utils/beta_decode.py +271 -0
  124. mirascope/llm/providers/anthropic/_utils/beta_encode.py +216 -0
  125. mirascope/llm/{clients → providers}/anthropic/_utils/decode.py +44 -11
  126. mirascope/llm/providers/anthropic/_utils/encode.py +356 -0
  127. mirascope/llm/providers/anthropic/beta_provider.py +322 -0
  128. mirascope/llm/providers/anthropic/model_id.py +23 -0
  129. mirascope/llm/providers/anthropic/model_info.py +87 -0
  130. mirascope/llm/providers/anthropic/provider.py +416 -0
  131. mirascope/llm/{clients → providers}/base/__init__.py +3 -3
  132. mirascope/llm/{clients → providers}/base/_utils.py +25 -8
  133. mirascope/llm/{clients/base/client.py → providers/base/base_provider.py} +255 -126
  134. mirascope/llm/providers/google/__init__.py +21 -0
  135. mirascope/llm/{clients → providers}/google/_utils/decode.py +61 -7
  136. mirascope/llm/{clients → providers}/google/_utils/encode.py +44 -30
  137. mirascope/llm/providers/google/model_id.py +22 -0
  138. mirascope/llm/providers/google/model_info.py +62 -0
  139. mirascope/llm/providers/google/provider.py +442 -0
  140. mirascope/llm/providers/load_provider.py +54 -0
  141. mirascope/llm/providers/mlx/__init__.py +24 -0
  142. mirascope/llm/providers/mlx/_utils.py +129 -0
  143. mirascope/llm/providers/mlx/encoding/__init__.py +8 -0
  144. mirascope/llm/providers/mlx/encoding/base.py +69 -0
  145. mirascope/llm/providers/mlx/encoding/transformers.py +147 -0
  146. mirascope/llm/providers/mlx/mlx.py +237 -0
  147. mirascope/llm/providers/mlx/model_id.py +17 -0
  148. mirascope/llm/providers/mlx/provider.py +415 -0
  149. mirascope/llm/providers/model_id.py +16 -0
  150. mirascope/llm/providers/ollama/__init__.py +19 -0
  151. mirascope/llm/providers/ollama/provider.py +71 -0
  152. mirascope/llm/providers/openai/__init__.py +6 -0
  153. mirascope/llm/providers/openai/completions/__init__.py +25 -0
  154. mirascope/llm/{clients → providers}/openai/completions/_utils/__init__.py +2 -0
  155. mirascope/llm/{clients → providers}/openai/completions/_utils/decode.py +60 -6
  156. mirascope/llm/{clients → providers}/openai/completions/_utils/encode.py +37 -26
  157. mirascope/llm/providers/openai/completions/base_provider.py +513 -0
  158. mirascope/llm/providers/openai/completions/provider.py +22 -0
  159. mirascope/llm/providers/openai/model_id.py +31 -0
  160. mirascope/llm/providers/openai/model_info.py +303 -0
  161. mirascope/llm/providers/openai/provider.py +398 -0
  162. mirascope/llm/providers/openai/responses/__init__.py +21 -0
  163. mirascope/llm/{clients → providers}/openai/responses/_utils/decode.py +59 -6
  164. mirascope/llm/{clients → providers}/openai/responses/_utils/encode.py +34 -23
  165. mirascope/llm/providers/openai/responses/provider.py +469 -0
  166. mirascope/llm/providers/provider_id.py +23 -0
  167. mirascope/llm/providers/provider_registry.py +169 -0
  168. mirascope/llm/providers/together/__init__.py +19 -0
  169. mirascope/llm/providers/together/provider.py +40 -0
  170. mirascope/llm/responses/__init__.py +3 -0
  171. mirascope/llm/responses/base_response.py +14 -5
  172. mirascope/llm/responses/base_stream_response.py +35 -6
  173. mirascope/llm/responses/finish_reason.py +1 -0
  174. mirascope/llm/responses/response.py +33 -13
  175. mirascope/llm/responses/root_response.py +12 -13
  176. mirascope/llm/responses/stream_response.py +35 -23
  177. mirascope/llm/responses/usage.py +95 -0
  178. mirascope/llm/tools/__init__.py +9 -2
  179. mirascope/llm/tools/_utils.py +12 -3
  180. mirascope/llm/tools/protocols.py +4 -4
  181. mirascope/llm/tools/tool_schema.py +44 -9
  182. mirascope/llm/tools/tools.py +10 -9
  183. mirascope/ops/__init__.py +156 -0
  184. mirascope/ops/_internal/__init__.py +5 -0
  185. mirascope/ops/_internal/closure.py +1118 -0
  186. mirascope/ops/_internal/configuration.py +126 -0
  187. mirascope/ops/_internal/context.py +76 -0
  188. mirascope/ops/_internal/exporters/__init__.py +26 -0
  189. mirascope/ops/_internal/exporters/exporters.py +342 -0
  190. mirascope/ops/_internal/exporters/processors.py +104 -0
  191. mirascope/ops/_internal/exporters/types.py +165 -0
  192. mirascope/ops/_internal/exporters/utils.py +29 -0
  193. mirascope/ops/_internal/instrumentation/__init__.py +8 -0
  194. mirascope/ops/_internal/instrumentation/llm/__init__.py +8 -0
  195. mirascope/ops/_internal/instrumentation/llm/encode.py +238 -0
  196. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/__init__.py +38 -0
  197. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_input_messages.py +31 -0
  198. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_output_messages.py +38 -0
  199. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_system_instructions.py +18 -0
  200. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/shared.py +100 -0
  201. mirascope/ops/_internal/instrumentation/llm/llm.py +1288 -0
  202. mirascope/ops/_internal/propagation.py +198 -0
  203. mirascope/ops/_internal/protocols.py +51 -0
  204. mirascope/ops/_internal/session.py +139 -0
  205. mirascope/ops/_internal/spans.py +232 -0
  206. mirascope/ops/_internal/traced_calls.py +371 -0
  207. mirascope/ops/_internal/traced_functions.py +394 -0
  208. mirascope/ops/_internal/tracing.py +276 -0
  209. mirascope/ops/_internal/types.py +13 -0
  210. mirascope/ops/_internal/utils.py +75 -0
  211. mirascope/ops/_internal/versioned_calls.py +512 -0
  212. mirascope/ops/_internal/versioned_functions.py +346 -0
  213. mirascope/ops/_internal/versioning.py +303 -0
  214. mirascope/ops/exceptions.py +21 -0
  215. {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a4.dist-info}/METADATA +78 -3
  216. mirascope-2.0.0a4.dist-info/RECORD +247 -0
  217. {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a4.dist-info}/WHEEL +1 -1
  218. mirascope/graphs/__init__.py +0 -22
  219. mirascope/graphs/finite_state_machine.py +0 -625
  220. mirascope/llm/agents/__init__.py +0 -15
  221. mirascope/llm/agents/agent.py +0 -97
  222. mirascope/llm/agents/agent_template.py +0 -45
  223. mirascope/llm/agents/decorator.py +0 -176
  224. mirascope/llm/calls/base_call.py +0 -33
  225. mirascope/llm/clients/__init__.py +0 -34
  226. mirascope/llm/clients/anthropic/__init__.py +0 -25
  227. mirascope/llm/clients/anthropic/_utils/encode.py +0 -243
  228. mirascope/llm/clients/anthropic/clients.py +0 -819
  229. mirascope/llm/clients/anthropic/model_ids.py +0 -8
  230. mirascope/llm/clients/google/__init__.py +0 -20
  231. mirascope/llm/clients/google/clients.py +0 -853
  232. mirascope/llm/clients/google/model_ids.py +0 -15
  233. mirascope/llm/clients/openai/__init__.py +0 -25
  234. mirascope/llm/clients/openai/completions/__init__.py +0 -28
  235. mirascope/llm/clients/openai/completions/_utils/model_features.py +0 -81
  236. mirascope/llm/clients/openai/completions/clients.py +0 -833
  237. mirascope/llm/clients/openai/completions/model_ids.py +0 -8
  238. mirascope/llm/clients/openai/responses/__init__.py +0 -26
  239. mirascope/llm/clients/openai/responses/_utils/__init__.py +0 -13
  240. mirascope/llm/clients/openai/responses/_utils/model_features.py +0 -87
  241. mirascope/llm/clients/openai/responses/clients.py +0 -832
  242. mirascope/llm/clients/openai/responses/model_ids.py +0 -8
  243. mirascope/llm/clients/openai/shared/__init__.py +0 -7
  244. mirascope/llm/clients/openai/shared/_utils.py +0 -55
  245. mirascope/llm/clients/providers.py +0 -175
  246. mirascope-2.0.0a2.dist-info/RECORD +0 -102
  247. /mirascope/llm/{clients → providers}/base/kwargs.py +0 -0
  248. /mirascope/llm/{clients → providers}/base/params.py +0 -0
  249. /mirascope/llm/{clients/anthropic → providers/google}/_utils/__init__.py +0 -0
  250. /mirascope/llm/{clients → providers}/google/message.py +0 -0
  251. /mirascope/llm/{clients/google → providers/openai/responses}/_utils/__init__.py +0 -0
  252. {mirascope-2.0.0a2.dist-info → mirascope-2.0.0a4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,21 @@
1
+ """Google client implementation."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from .model_id import GoogleModelId
7
+ from .provider import GoogleProvider
8
+ else:
9
+ try:
10
+ from .model_id import GoogleModelId
11
+ from .provider import GoogleProvider
12
+ except ImportError: # pragma: no cover
13
+ from .._missing_import_stubs import (
14
+ create_import_error_stub,
15
+ create_provider_stub,
16
+ )
17
+
18
+ GoogleProvider = create_provider_stub("google", "GoogleProvider")
19
+ GoogleModelId = str
20
+
21
+ __all__ = ["GoogleModelId", "GoogleProvider"]
@@ -29,8 +29,10 @@ from ....responses import (
29
29
  FinishReasonChunk,
30
30
  RawMessageChunk,
31
31
  RawStreamEventChunk,
32
+ Usage,
33
+ UsageDeltaChunk,
32
34
  )
33
- from ..model_ids import GoogleModelId
35
+ from ..model_id import GoogleModelId, model_name
34
36
  from .encode import UNKNOWN_TOOL_ID
35
37
 
36
38
  GOOGLE_FINISH_REASON_MAP = {
@@ -43,6 +45,30 @@ GOOGLE_FINISH_REASON_MAP = {
43
45
  }
44
46
 
45
47
 
48
+ def _decode_usage(
49
+ usage: genai_types.GenerateContentResponseUsageMetadata | None,
50
+ ) -> Usage | None:
51
+ """Convert Google UsageMetadata to Mirascope Usage."""
52
+ if (
53
+ usage is None
54
+ or usage.prompt_token_count is None
55
+ or usage.candidates_token_count is None
56
+ ): # pragma: no cover
57
+ return None
58
+
59
+ reasoning_tokens = usage.thoughts_token_count or 0
60
+ output_tokens = usage.candidates_token_count + reasoning_tokens
61
+
62
+ return Usage(
63
+ input_tokens=usage.prompt_token_count,
64
+ output_tokens=output_tokens,
65
+ cache_read_tokens=usage.cached_content_token_count or 0,
66
+ cache_write_tokens=0,
67
+ reasoning_tokens=usage.thoughts_token_count or 0,
68
+ raw=usage,
69
+ )
70
+
71
+
46
72
  def _decode_content_part(part: genai_types.Part) -> AssistantContentPart | None:
47
73
  """Returns an `AssistantContentPart` (or `None`) decoded from a google `Part`"""
48
74
  if part.thought and part.text:
@@ -88,7 +114,7 @@ def _decode_candidate_content(
88
114
  candidate: genai_types.Candidate,
89
115
  ) -> Sequence[AssistantContentPart]:
90
116
  """Returns a sequence of `AssistantContentPart` decoded from a google `Candidate`"""
91
- content_parts = []
117
+ content_parts: list[AssistantContentPart] = []
92
118
  if candidate.content and candidate.content.parts:
93
119
  for part in candidate.content.parts:
94
120
  decoded_part = _decode_content_part(part)
@@ -98,9 +124,10 @@ def _decode_candidate_content(
98
124
 
99
125
 
100
126
  def decode_response(
101
- response: genai_types.GenerateContentResponse, model_id: GoogleModelId
102
- ) -> tuple[AssistantMessage, FinishReason | None]:
103
- """Returns an `AssistantMessage` and `FinishReason` extracted from a `GenerateContentResponse`"""
127
+ response: genai_types.GenerateContentResponse,
128
+ model_id: GoogleModelId,
129
+ ) -> tuple[AssistantMessage, FinishReason | None, Usage | None]:
130
+ """Returns an `AssistantMessage`, `FinishReason`, and `Usage` extracted from a `GenerateContentResponse`"""
104
131
  content: Sequence[AssistantContentPart] = []
105
132
  candidate_content: genai_types.Content | None = None
106
133
  finish_reason: FinishReason | None = None
@@ -115,12 +142,14 @@ def decode_response(
115
142
 
116
143
  assistant_message = AssistantMessage(
117
144
  content=content,
118
- provider="google",
145
+ provider_id="google",
119
146
  model_id=model_id,
147
+ provider_model_name=model_name(model_id),
120
148
  raw_message=candidate_content.model_dump(),
121
149
  )
122
150
 
123
- return assistant_message, finish_reason
151
+ usage = _decode_usage(response.usage_metadata)
152
+ return assistant_message, finish_reason, usage
124
153
 
125
154
 
126
155
  class _GoogleChunkProcessor:
@@ -130,6 +159,8 @@ class _GoogleChunkProcessor:
130
159
  self.current_content_type: Literal["text", "tool_call", "thought"] | None = None
131
160
  self.accumulated_parts: list[genai_types.Part] = []
132
161
  self.reconstructed_content = genai_types.Content(parts=[])
162
+ # Track previous cumulative usage to compute deltas
163
+ self.prev_usage = Usage()
133
164
 
134
165
  def process_chunk(
135
166
  self, chunk: genai_types.GenerateContentResponse
@@ -205,6 +236,29 @@ class _GoogleChunkProcessor:
205
236
  if finish_reason is not None:
206
237
  yield FinishReasonChunk(finish_reason=finish_reason)
207
238
 
239
+ # Emit usage delta if usage metadata is present
240
+ if chunk.usage_metadata:
241
+ usage_metadata = chunk.usage_metadata
242
+ current_input = usage_metadata.prompt_token_count or 0
243
+ current_output = usage_metadata.candidates_token_count or 0
244
+ current_cache_read = usage_metadata.cached_content_token_count or 0
245
+ current_reasoning = usage_metadata.thoughts_token_count or 0
246
+
247
+ yield UsageDeltaChunk(
248
+ input_tokens=current_input - self.prev_usage.input_tokens,
249
+ output_tokens=current_output - self.prev_usage.output_tokens,
250
+ cache_read_tokens=current_cache_read
251
+ - self.prev_usage.cache_read_tokens,
252
+ cache_write_tokens=0,
253
+ reasoning_tokens=current_reasoning - self.prev_usage.reasoning_tokens,
254
+ )
255
+
256
+ # Update previous usage
257
+ self.prev_usage.input_tokens = current_input
258
+ self.prev_usage.output_tokens = current_output
259
+ self.prev_usage.cache_read_tokens = current_cache_read
260
+ self.prev_usage.reasoning_tokens = current_reasoning
261
+
208
262
  def raw_message_chunk(self) -> RawMessageChunk:
209
263
  content = genai_types.Content(role="model", parts=self.accumulated_parts)
210
264
  return RawMessageChunk(raw_message=content.model_dump())
@@ -4,7 +4,8 @@ import base64
4
4
  import json
5
5
  from collections.abc import Sequence
6
6
  from functools import lru_cache
7
- from typing import Any, cast
7
+ from typing import Any, TypedDict, cast
8
+ from typing_extensions import Required
8
9
 
9
10
  from google.genai import types as genai_types
10
11
 
@@ -17,15 +18,20 @@ from ....formatting import (
17
18
  resolve_format,
18
19
  )
19
20
  from ....messages import AssistantMessage, Message, UserMessage
20
- from ....tools import FORMAT_TOOL_NAME, BaseToolkit, ToolSchema
21
- from ...base import BaseKwargs, Params, _utils as _base_utils
22
- from ..model_ids import GoogleModelId
21
+ from ....tools import FORMAT_TOOL_NAME, AnyToolSchema, BaseToolkit
22
+ from ...base import Params, _utils as _base_utils
23
+ from ..model_id import GoogleModelId, model_name
24
+ from ..model_info import MODELS_WITHOUT_STRUCTURED_OUTPUT_AND_TOOLS_SUPPORT
23
25
 
24
26
  UNKNOWN_TOOL_ID = "google_unknown_tool_id"
25
27
 
26
28
 
27
- class GoogleKwargs(BaseKwargs, genai_types.GenerateContentConfigDict):
28
- """Google's `GenerateContentConfigDict` typed dict, subclassing BaseKwargs for type safety."""
29
+ class GoogleKwargs(TypedDict, total=False):
30
+ """Kwargs for Google's generate_content method."""
31
+
32
+ model: Required[str]
33
+ contents: Required[genai_types.ContentListUnionDict]
34
+ config: genai_types.GenerateContentConfigDict
29
35
 
30
36
 
31
37
  def _resolve_refs(
@@ -52,7 +58,7 @@ def _encode_content(
52
58
  content: Sequence[ContentPart], encode_thoughts: bool
53
59
  ) -> list[genai_types.PartDict]:
54
60
  """Returns a list of google `PartDicts` converted from a sequence of Mirascope `ContentPart`s"""
55
- result = []
61
+ result: list[genai_types.PartDict] = []
56
62
 
57
63
  for part in content:
58
64
  if part.type == "text":
@@ -121,7 +127,7 @@ def _encode_message(
121
127
  """Returns a Google `ContentDict` converted from a Mirascope `Message`"""
122
128
  if (
123
129
  message.role == "assistant"
124
- and message.provider == "google"
130
+ and message.provider_id == "google"
125
131
  and message.model_id == model_id
126
132
  and message.raw_message
127
133
  and not encode_thoughts
@@ -144,7 +150,7 @@ def _encode_messages(
144
150
 
145
151
  @lru_cache(maxsize=128)
146
152
  def _convert_tool_to_function_declaration(
147
- tool: ToolSchema,
153
+ tool: AnyToolSchema,
148
154
  ) -> genai_types.FunctionDeclarationDict:
149
155
  """Convert a single Mirascope tool to Google FunctionDeclaration format with caching."""
150
156
  schema_dict = tool.parameters.model_dump(by_alias=True, exclude_none=True)
@@ -170,21 +176,22 @@ def encode_request(
170
176
  *,
171
177
  model_id: GoogleModelId,
172
178
  messages: Sequence[Message],
173
- tools: Sequence[ToolSchema] | BaseToolkit | None,
179
+ tools: Sequence[AnyToolSchema] | BaseToolkit[AnyToolSchema] | None,
174
180
  format: type[FormattableT] | Format[FormattableT] | None,
175
181
  params: Params,
176
- ) -> tuple[
177
- Sequence[Message],
178
- Format[FormattableT] | None,
179
- genai_types.ContentListUnionDict,
180
- GoogleKwargs,
181
- ]:
182
+ ) -> tuple[Sequence[Message], Format[FormattableT] | None, GoogleKwargs]:
182
183
  """Prepares a request for the genai `Client.models.generate_content` method."""
183
- google_config: GoogleKwargs = GoogleKwargs()
184
+ if not model_id.startswith("google/"): # pragma: no cover
185
+ raise ValueError(f"Model ID must start with 'google/' prefix, got: {model_id}")
186
+
187
+ google_config: genai_types.GenerateContentConfigDict = (
188
+ genai_types.GenerateContentConfigDict()
189
+ )
184
190
  encode_thoughts = False
191
+ google_model_name = model_name(model_id)
185
192
 
186
193
  with _base_utils.ensure_all_params_accessed(
187
- params=params, provider="google"
194
+ params=params, provider_id="google"
188
195
  ) as param_accessor:
189
196
  if param_accessor.temperature is not None:
190
197
  google_config["temperature"] = param_accessor.temperature
@@ -214,17 +221,23 @@ def encode_request(
214
221
  tools = tools.tools if isinstance(tools, BaseToolkit) else tools or []
215
222
  google_tools: list[genai_types.ToolDict] = []
216
223
 
217
- format = resolve_format(
218
- format,
219
- # Google does not support strict outputs when tools are present
220
- # (Gemini 2.5 will error, 2.0 and below will ignore tools)
221
- default_mode="strict" if not tools else "tool",
224
+ allows_strict_mode_with_tools = (
225
+ google_model_name not in MODELS_WITHOUT_STRUCTURED_OUTPUT_AND_TOOLS_SUPPORT
222
226
  )
227
+ # Older google models do not allow strict mode when using tools; if so, we use tool
228
+ # mode when tools are present by default for compatibility. Otherwise, prefer strict mode.
229
+ default_mode = "tool" if tools and not allows_strict_mode_with_tools else "strict"
230
+ format = resolve_format(format, default_mode=default_mode)
223
231
  if format is not None:
224
- if format.mode in ("strict", "json") and tools:
232
+ if (
233
+ format.mode in ("strict", "json")
234
+ and tools
235
+ and not allows_strict_mode_with_tools
236
+ ):
225
237
  raise FeatureNotSupportedError(
226
238
  feature=f"formatting_mode:{format.mode} with tools",
227
- provider="google",
239
+ provider_id="google",
240
+ model_id=model_id,
228
241
  )
229
242
 
230
243
  if format.mode == "strict":
@@ -271,9 +284,10 @@ def encode_request(
271
284
  if system_message_content:
272
285
  google_config["system_instruction"] = system_message_content
273
286
 
274
- return (
275
- messages,
276
- format,
277
- _encode_messages(remaining_messages, model_id, encode_thoughts),
278
- google_config,
287
+ kwargs = GoogleKwargs(
288
+ model=model_name(model_id),
289
+ contents=_encode_messages(remaining_messages, model_id, encode_thoughts),
290
+ config=google_config,
279
291
  )
292
+
293
+ return messages, format, kwargs
@@ -0,0 +1,22 @@
1
+ """Google registered LLM models."""
2
+
3
+ from typing import TypeAlias, get_args
4
+
5
+ from .model_info import GoogleKnownModels
6
+
7
+ GoogleModelId: TypeAlias = GoogleKnownModels | str
8
+ """The Google model ids registered with Mirascope."""
9
+
10
+ GOOGLE_KNOWN_MODELS: set[str] = set(get_args(GoogleKnownModels))
11
+
12
+
13
+ def model_name(model_id: GoogleModelId) -> str:
14
+ """Extract the google model name from a full model ID.
15
+
16
+ Args:
17
+ model_id: Full model ID (e.g. "google/gemini-2.5-flash")
18
+
19
+ Returns:
20
+ Provider-specific model ID (e.g. "gemini-2.5-flash")
21
+ """
22
+ return model_id.removeprefix("google/")
@@ -0,0 +1,62 @@
1
+ """Google model information.
2
+
3
+ This file is auto-generated by scripts/model_features/codegen_google.py
4
+ Do not edit manually - run the codegen script to update."""
5
+
6
+ from typing import Literal
7
+
8
+ GoogleKnownModels = Literal[
9
+ "google/gemini-2.0-flash",
10
+ "google/gemini-2.0-flash-001",
11
+ "google/gemini-2.0-flash-exp",
12
+ "google/gemini-2.0-flash-exp-image-generation",
13
+ "google/gemini-2.0-flash-lite",
14
+ "google/gemini-2.0-flash-lite-001",
15
+ "google/gemini-2.0-flash-lite-preview",
16
+ "google/gemini-2.0-flash-lite-preview-02-05",
17
+ "google/gemini-2.5-flash",
18
+ "google/gemini-2.5-flash-image",
19
+ "google/gemini-2.5-flash-image-preview",
20
+ "google/gemini-2.5-flash-lite",
21
+ "google/gemini-2.5-flash-lite-preview-09-2025",
22
+ "google/gemini-2.5-flash-preview-09-2025",
23
+ "google/gemini-2.5-pro",
24
+ "google/gemini-3-pro-image-preview",
25
+ "google/gemini-3-pro-preview",
26
+ "google/gemini-flash-latest",
27
+ "google/gemini-flash-lite-latest",
28
+ "google/gemini-pro-latest",
29
+ "google/gemini-robotics-er-1.5-preview",
30
+ "google/gemma-3-12b-it",
31
+ "google/gemma-3-1b-it",
32
+ "google/gemma-3-27b-it",
33
+ "google/gemma-3-4b-it",
34
+ "google/gemma-3n-e2b-it",
35
+ "google/gemma-3n-e4b-it",
36
+ "google/nano-banana-pro-preview",
37
+ ]
38
+ """Valid Google model IDs."""
39
+
40
+
41
+ MODELS_WITHOUT_STRUCTURED_OUTPUT_AND_TOOLS_SUPPORT: set[str] = {
42
+ "gemini-2.5-flash",
43
+ "gemini-2.5-flash-image",
44
+ "gemini-2.5-flash-image-preview",
45
+ "gemini-2.5-flash-lite",
46
+ "gemini-2.5-flash-lite-preview-09-2025",
47
+ "gemini-2.5-flash-preview-09-2025",
48
+ "gemini-2.5-pro",
49
+ "gemini-3-pro-image-preview",
50
+ "gemini-flash-latest",
51
+ "gemini-flash-lite-latest",
52
+ "gemini-pro-latest",
53
+ "gemini-robotics-er-1.5-preview",
54
+ "gemma-3-12b-it",
55
+ "gemma-3-1b-it",
56
+ "gemma-3-27b-it",
57
+ "gemma-3-4b-it",
58
+ "gemma-3n-e2b-it",
59
+ "gemma-3n-e4b-it",
60
+ "nano-banana-pro-preview",
61
+ }
62
+ """Models that do not support structured outputs when tools are present."""