mirascope 1.17.0__py3-none-any.whl → 1.18.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 (29) hide show
  1. mirascope/core/__init__.py +4 -0
  2. mirascope/core/base/stream_config.py +1 -1
  3. mirascope/core/gemini/__init__.py +10 -0
  4. mirascope/core/google/__init__.py +29 -0
  5. mirascope/core/google/_call.py +67 -0
  6. mirascope/core/google/_call_kwargs.py +13 -0
  7. mirascope/core/google/_utils/__init__.py +16 -0
  8. mirascope/core/google/_utils/_calculate_cost.py +88 -0
  9. mirascope/core/google/_utils/_convert_common_call_params.py +39 -0
  10. mirascope/core/google/_utils/_convert_finish_reason_to_common_finish_reasons.py +27 -0
  11. mirascope/core/google/_utils/_convert_message_params.py +177 -0
  12. mirascope/core/google/_utils/_get_json_output.py +37 -0
  13. mirascope/core/google/_utils/_handle_stream.py +35 -0
  14. mirascope/core/google/_utils/_message_param_converter.py +153 -0
  15. mirascope/core/google/_utils/_setup_call.py +180 -0
  16. mirascope/core/google/call_params.py +22 -0
  17. mirascope/core/google/call_response.py +202 -0
  18. mirascope/core/google/call_response_chunk.py +97 -0
  19. mirascope/core/google/dynamic_config.py +26 -0
  20. mirascope/core/google/stream.py +128 -0
  21. mirascope/core/google/tool.py +104 -0
  22. mirascope/core/vertex/__init__.py +15 -0
  23. mirascope/llm/_protocols.py +1 -0
  24. mirascope/llm/llm_call.py +4 -0
  25. mirascope/llm/llm_override.py +16 -3
  26. {mirascope-1.17.0.dist-info → mirascope-1.18.0.dist-info}/METADATA +4 -1
  27. {mirascope-1.17.0.dist-info → mirascope-1.18.0.dist-info}/RECORD +29 -11
  28. {mirascope-1.17.0.dist-info → mirascope-1.18.0.dist-info}/WHEEL +0 -0
  29. {mirascope-1.17.0.dist-info → mirascope-1.18.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,153 @@
1
+ from typing import cast
2
+
3
+ from google.genai import Client
4
+ from google.genai.types import (
5
+ Content,
6
+ ContentDict,
7
+ ContentOrDict,
8
+ )
9
+
10
+ from mirascope.core import BaseMessageParam
11
+ from mirascope.core.base import DocumentPart, ImagePart, TextPart
12
+ from mirascope.core.base._utils._base_message_param_converter import (
13
+ BaseMessageParamConverter,
14
+ )
15
+ from mirascope.core.base.message_param import (
16
+ AudioURLPart,
17
+ ImageURLPart,
18
+ ToolCallPart,
19
+ ToolResultPart,
20
+ )
21
+ from mirascope.core.gemini._utils._message_param_converter import _is_audio_mime
22
+ from mirascope.core.google._utils import convert_message_params
23
+
24
+
25
+ def _is_image_mime(mime_type: str) -> bool:
26
+ return mime_type in ["image/jpeg", "image/png", "image/gif", "image/webp"]
27
+
28
+
29
+ def _to_image_part(mime_type: str, data: bytes) -> ImagePart:
30
+ if not _is_image_mime(mime_type):
31
+ raise ValueError(
32
+ f"Unsupported image media type: {mime_type}. "
33
+ "Expected one of: image/jpeg, image/png, image/gif, image/webp."
34
+ )
35
+ return ImagePart(type="image", media_type=mime_type, image=data, detail=None)
36
+
37
+
38
+ def _to_document_part(mime_type: str, data: bytes) -> DocumentPart:
39
+ if mime_type != "application/pdf":
40
+ raise ValueError(
41
+ f"Unsupported document media type: {mime_type}. "
42
+ "Only application/pdf is supported."
43
+ )
44
+ return DocumentPart(type="document", media_type=mime_type, document=data)
45
+
46
+
47
+ class GoogleMessageParamConverter(BaseMessageParamConverter):
48
+ """Converts between Google `ContentDict` and Mirascope `BaseMessageParam`."""
49
+
50
+ @staticmethod
51
+ def to_provider(
52
+ message_params: list[BaseMessageParam], client: Client | None = None
53
+ ) -> list[ContentDict]:
54
+ """
55
+ Convert from Mirascope `BaseMessageParam` to Google `ContentDict`.
56
+ """
57
+ return convert_message_params(
58
+ cast(list[BaseMessageParam | ContentDict], message_params),
59
+ client or Client(),
60
+ )
61
+
62
+ @staticmethod
63
+ def from_provider(message_params: list[ContentOrDict]) -> list[BaseMessageParam]:
64
+ """
65
+ Convert from Google's `ContentDict` to Mirascope `BaseMessageParam`.
66
+ """
67
+ converted: list[BaseMessageParam] = []
68
+ for message_param in message_params:
69
+ if isinstance(message_param, dict):
70
+ message_param = Content.model_validate(message_param)
71
+ role: str = "assistant"
72
+ content_list = []
73
+ for part in message_param.parts or []:
74
+ if part.text:
75
+ content_list.append(TextPart(type="text", text=part.text))
76
+
77
+ elif part.inline_data:
78
+ blob = part.inline_data
79
+ mime = blob.mime_type or ""
80
+ data = blob.data or b""
81
+ if _is_image_mime(mime or ""):
82
+ content_list.append(_to_image_part(mime, data))
83
+ elif mime == "application/pdf":
84
+ content_list.append(_to_document_part(mime, data))
85
+ else:
86
+ raise ValueError(
87
+ f"Unsupported inline_data mime type: {mime}. Cannot convert to BaseMessageParam."
88
+ )
89
+
90
+ elif part.file_data:
91
+ if _is_image_mime(cast(str, part.file_data.mime_type)):
92
+ content_list.append(
93
+ ImageURLPart(
94
+ type="image_url",
95
+ url=cast(str, part.file_data.file_uri),
96
+ detail=None,
97
+ )
98
+ )
99
+ elif _is_audio_mime(cast(str, part.file_data.mime_type)):
100
+ content_list.append(
101
+ AudioURLPart(
102
+ type="audio_url",
103
+ url=cast(str, part.file_data.file_uri),
104
+ )
105
+ )
106
+ else:
107
+ raise ValueError(
108
+ f"Unsupported file_data mime type: {part.file_data.mime_type}. Cannot convert to BaseMessageParam."
109
+ )
110
+ elif part.function_call:
111
+ converted.append(
112
+ BaseMessageParam(
113
+ role=role,
114
+ content=[
115
+ ToolCallPart(
116
+ type="tool_call",
117
+ name=part.function_call.name, # pyright: ignore [reportArgumentType]
118
+ args=part.function_call.args,
119
+ )
120
+ ],
121
+ )
122
+ )
123
+ elif part.function_response:
124
+ converted.append(
125
+ BaseMessageParam(
126
+ role=role,
127
+ content=[
128
+ ToolResultPart(
129
+ type="tool_result",
130
+ name=part.function_response.name, # pyright: ignore [reportArgumentType]
131
+ content=part.function_response.response[ # pyright: ignore [reportArgumentType, reportOptionalSubscript]
132
+ "result"
133
+ ],
134
+ id=None,
135
+ is_error=False,
136
+ )
137
+ ],
138
+ )
139
+ )
140
+ else:
141
+ raise ValueError(
142
+ "Part does not contain any supported content (text, image, or document)."
143
+ )
144
+
145
+ if len(content_list) == 1 and isinstance(content_list[0], TextPart):
146
+ converted.append(
147
+ BaseMessageParam(role=role, content=content_list[0].text)
148
+ )
149
+ else:
150
+ if content_list:
151
+ converted.append(BaseMessageParam(role=role, content=content_list))
152
+
153
+ return converted
@@ -0,0 +1,180 @@
1
+ """This module contains the setup_call function, which is used to set up the"""
2
+
3
+ import contextlib
4
+ from collections.abc import Awaitable, Callable, Generator
5
+ from typing import Any, cast, overload
6
+
7
+ from google.genai import Client
8
+ from google.genai.types import (
9
+ ContentDict,
10
+ FunctionCallingConfig,
11
+ FunctionCallingConfigMode,
12
+ GenerateContentConfig,
13
+ GenerateContentConfigDict,
14
+ GenerateContentResponse,
15
+ Part,
16
+ PartDict,
17
+ ToolConfig,
18
+ ToolListUnion,
19
+ )
20
+ from pydantic import BaseModel
21
+
22
+ from ...base import BaseMessageParam, BaseTool, _utils
23
+ from ...base._utils import (
24
+ AsyncCreateFn,
25
+ CreateFn,
26
+ fn_is_async,
27
+ get_async_create_fn,
28
+ get_create_fn,
29
+ )
30
+ from ...base.call_params import CommonCallParams
31
+ from ...base.stream_config import StreamConfig
32
+ from .._call_kwargs import GoogleCallKwargs
33
+ from ..call_params import GoogleCallParams
34
+ from ..dynamic_config import GoogleDynamicConfig
35
+ from ..tool import GoogleTool
36
+ from ._convert_common_call_params import convert_common_call_params
37
+ from ._convert_message_params import convert_message_params
38
+
39
+
40
+ def _get_generate_content_config(
41
+ config: GenerateContentConfig | GenerateContentConfigDict,
42
+ ) -> GenerateContentConfig:
43
+ if isinstance(config, dict):
44
+ return GenerateContentConfig.model_validate(config)
45
+ return config
46
+
47
+
48
+ @contextlib.contextmanager
49
+ def _generate_content_config_context(
50
+ call_kwargs: GoogleCallKwargs,
51
+ ) -> Generator[GenerateContentConfig, None, None]:
52
+ config = call_kwargs.get("config", {})
53
+ config = _get_generate_content_config(config)
54
+ yield config
55
+ call_kwargs["config"] = config
56
+
57
+
58
+ @overload
59
+ def setup_call(
60
+ *,
61
+ model: str,
62
+ client: Client | None,
63
+ fn: Callable[..., Awaitable[GoogleDynamicConfig]],
64
+ fn_args: dict[str, Any],
65
+ dynamic_config: GoogleDynamicConfig,
66
+ tools: list[type[BaseTool] | Callable] | None,
67
+ json_mode: bool,
68
+ call_params: GoogleCallParams | CommonCallParams,
69
+ response_model: type[BaseModel] | None,
70
+ stream: bool | StreamConfig,
71
+ ) -> tuple[
72
+ AsyncCreateFn[GenerateContentResponse, GenerateContentResponse],
73
+ str | None,
74
+ list[ContentDict],
75
+ list[type[GoogleTool]] | None,
76
+ GoogleCallKwargs,
77
+ ]: ...
78
+
79
+
80
+ @overload
81
+ def setup_call(
82
+ *,
83
+ model: str,
84
+ client: Client | None,
85
+ fn: Callable[..., GoogleDynamicConfig],
86
+ fn_args: dict[str, Any],
87
+ dynamic_config: GoogleDynamicConfig,
88
+ tools: list[type[BaseTool] | Callable] | None,
89
+ json_mode: bool,
90
+ call_params: GoogleCallParams | CommonCallParams,
91
+ response_model: type[BaseModel] | None,
92
+ stream: bool | StreamConfig,
93
+ ) -> tuple[
94
+ CreateFn[GenerateContentResponse, GenerateContentResponse],
95
+ str | None,
96
+ list[ContentDict],
97
+ list[type[GoogleTool]] | None,
98
+ GoogleCallKwargs,
99
+ ]: ...
100
+
101
+
102
+ def setup_call(
103
+ *,
104
+ model: str,
105
+ client: Client | None,
106
+ fn: Callable[..., GoogleDynamicConfig | Awaitable[GoogleDynamicConfig]],
107
+ fn_args: dict[str, Any],
108
+ dynamic_config: GoogleDynamicConfig,
109
+ tools: list[type[BaseTool] | Callable] | None,
110
+ json_mode: bool,
111
+ call_params: GoogleCallParams | CommonCallParams,
112
+ response_model: type[BaseModel] | None,
113
+ stream: bool | StreamConfig,
114
+ ) -> tuple[
115
+ CreateFn[GenerateContentResponse, GenerateContentResponse]
116
+ | AsyncCreateFn[GenerateContentResponse, GenerateContentResponse],
117
+ str | None,
118
+ list[ContentDict],
119
+ list[type[GoogleTool]] | None,
120
+ GoogleCallKwargs,
121
+ ]:
122
+ prompt_template, messages, tool_types, base_call_kwargs = _utils.setup_call(
123
+ fn,
124
+ fn_args,
125
+ dynamic_config,
126
+ tools,
127
+ GoogleTool,
128
+ call_params,
129
+ convert_common_call_params,
130
+ )
131
+ call_kwargs = cast(GoogleCallKwargs, base_call_kwargs)
132
+ messages = cast(list[BaseMessageParam | ContentDict], messages)
133
+
134
+ if client is None:
135
+ client = Client()
136
+
137
+ messages = convert_message_params(messages, client)
138
+
139
+ if messages[0] and messages[0].get("role") == "system":
140
+ with _generate_content_config_context(call_kwargs) as config:
141
+ config.system_instruction = [
142
+ Part.model_validate(part)
143
+ for part in (messages.pop(0).get("parts") or [])
144
+ ]
145
+
146
+ if json_mode:
147
+ with _generate_content_config_context(call_kwargs) as config:
148
+ if not tools:
149
+ config.response_mime_type = "application/json"
150
+ messages[-1]["parts"].append( # pyright: ignore [reportTypedDictNotRequiredAccess, reportOptionalMemberAccess, reportArgumentType]
151
+ PartDict(text=_utils.json_mode_content(response_model))
152
+ ) # pyright: ignore [reportTypedDictNotRequiredAccess, reportOptionalMemberAccess, reportArgumentType]
153
+ elif response_model:
154
+ assert tool_types, "At least one tool must be provided for extraction."
155
+ with _generate_content_config_context(call_kwargs) as config:
156
+ config.tool_config = ToolConfig(
157
+ function_calling_config=FunctionCallingConfig(
158
+ mode=FunctionCallingConfigMode.ANY,
159
+ allowed_function_names=[tool_types[0]._name()],
160
+ )
161
+ )
162
+ config_tools = call_kwargs.pop("tools", None)
163
+ if config_tools:
164
+ with _generate_content_config_context(call_kwargs) as config:
165
+ config.tools = cast(ToolListUnion, config_tools)
166
+
167
+ call_kwargs |= {"model": model, "contents": messages}
168
+
169
+ create = (
170
+ get_async_create_fn(
171
+ client.aio.models.generate_content,
172
+ client.aio.models.generate_content_stream,
173
+ )
174
+ if fn_is_async(fn)
175
+ else get_create_fn(
176
+ client.models.generate_content, client.models.generate_content_stream
177
+ )
178
+ )
179
+
180
+ return create, prompt_template, messages, tool_types, call_kwargs
@@ -0,0 +1,22 @@
1
+ """usage docs: learn/calls.md#provider-specific-parameters"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from google.genai.types import (
6
+ GenerateContentConfigOrDict,
7
+ )
8
+ from typing_extensions import NotRequired
9
+
10
+ from ..base import BaseCallParams
11
+
12
+
13
+ class GoogleCallParams(BaseCallParams):
14
+ """The parameters to use when calling the Google API.
15
+
16
+ [Google API Reference](https://ai.google.dev/google-api/docs/text-generation?lang=python)
17
+
18
+ Attributes:
19
+ config: ...
20
+ """
21
+
22
+ config: NotRequired[GenerateContentConfigOrDict]
@@ -0,0 +1,202 @@
1
+ """This module contains the `GoogleCallResponse` class.
2
+
3
+ usage docs: learn/calls.md#handling-responses
4
+ """
5
+
6
+ from functools import cached_property
7
+
8
+ from google.genai.types import (
9
+ ContentDict,
10
+ ContentListUnion,
11
+ ContentListUnionDict,
12
+ FunctionResponseDict,
13
+ GenerateContentResponse,
14
+ PartDict,
15
+ # Import manually SchemaDict to avoid Pydantic error
16
+ SchemaDict, # noqa: F401
17
+ Tool,
18
+ )
19
+ from pydantic import computed_field
20
+
21
+ from .. import BaseMessageParam
22
+ from ..base import BaseCallResponse, transform_tool_outputs
23
+ from ..base.types import FinishReason
24
+ from ._utils import calculate_cost
25
+ from ._utils._convert_finish_reason_to_common_finish_reasons import (
26
+ _convert_finish_reasons_to_common_finish_reasons,
27
+ )
28
+ from ._utils._message_param_converter import GoogleMessageParamConverter
29
+ from .call_params import GoogleCallParams
30
+ from .dynamic_config import GoogleDynamicConfig
31
+ from .tool import GoogleTool
32
+
33
+
34
+ class GoogleCallResponse(
35
+ BaseCallResponse[
36
+ GenerateContentResponse,
37
+ GoogleTool,
38
+ Tool,
39
+ GoogleDynamicConfig,
40
+ ContentListUnion | ContentListUnionDict,
41
+ GoogleCallParams,
42
+ ContentDict,
43
+ ]
44
+ ):
45
+ """A convenience wrapper around the Google API response.
46
+
47
+ When calling the Google API using a function decorated with `google_call`, the
48
+ response will be a `GoogleCallResponse` instance with properties that allow for
49
+ more convenient access to commonly used attributes.
50
+
51
+
52
+ Example:
53
+
54
+ ```python
55
+ from mirascope.core import prompt_template
56
+ from mirascope.core.google import google_call
57
+
58
+
59
+ @google_call("google-1.5-flash")
60
+ def recommend_book(genre: str) -> str:
61
+ return f"Recommend a {genre} book"
62
+
63
+
64
+ response = recommend_book("fantasy") # response is an `GoogleCallResponse` instance
65
+ print(response.content)
66
+ ```
67
+ """
68
+
69
+ _provider = "google"
70
+
71
+ @property
72
+ def content(self) -> str:
73
+ """Returns the contained string content for the 0th choice."""
74
+ return self.response.candidates[0].content.parts[0].text # pyright: ignore [reportOptionalSubscript, reportReturnType, reportOptionalMemberAccess, reportOptionalIterable]
75
+
76
+ @property
77
+ def finish_reasons(self) -> list[str]:
78
+ """Returns the finish reasons of the response."""
79
+
80
+ return [
81
+ candidate.finish_reason.value
82
+ for candidate in (self.response.candidates or [])
83
+ if candidate and candidate.finish_reason is not None
84
+ ]
85
+
86
+ @property
87
+ def model(self) -> str:
88
+ """Returns the model name.
89
+
90
+ google.generativeai does not return model, so we return the model provided by
91
+ the user.
92
+ """
93
+ return self._model
94
+
95
+ @property
96
+ def id(self) -> str | None:
97
+ """Returns the id of the response.
98
+
99
+ google.generativeai does not return an id
100
+ """
101
+ return None
102
+
103
+ @property
104
+ def usage(self) -> None:
105
+ """Returns the usage of the chat completion.
106
+
107
+ google.generativeai does not have Usage, so we return None
108
+ """
109
+ return None
110
+
111
+ @property
112
+ def input_tokens(self) -> None:
113
+ """Returns the number of input tokens."""
114
+ return None
115
+
116
+ @property
117
+ def output_tokens(self) -> None:
118
+ """Returns the number of output tokens."""
119
+ return None
120
+
121
+ @property
122
+ def cost(self) -> float | None:
123
+ """Returns the cost of the call."""
124
+ return calculate_cost(self.input_tokens, self.output_tokens, self.model)
125
+
126
+ @computed_field
127
+ @cached_property
128
+ def message_param(self) -> ContentDict:
129
+ """Returns the models's response as a message parameter."""
130
+ return {"role": "model", "parts": self.response.candidates[0].content.parts} # pyright: ignore [reportReturnType, reportOptionalSubscript, reportOptionalMemberAccess]
131
+
132
+ @computed_field
133
+ @cached_property
134
+ def tools(self) -> list[GoogleTool] | None:
135
+ """Returns the list of tools for the 0th candidate's 0th content part."""
136
+ if self.tool_types is None:
137
+ return None
138
+
139
+ extracted_tools = []
140
+ for part in self.response.candidates[0].content.parts: # pyright: ignore [reportReturnType, reportOptionalSubscript, reportOptionalMemberAccess, reportOptionalIterable]
141
+ tool_call = part.function_call
142
+ for tool_type in self.tool_types:
143
+ if tool_call.name == tool_type._name(): # pyright: ignore [reportOptionalMemberAccess]
144
+ extracted_tools.append(tool_type.from_tool_call(tool_call)) # pyright: ignore [reportArgumentType]
145
+ break
146
+
147
+ return extracted_tools
148
+
149
+ @computed_field
150
+ @cached_property
151
+ def tool(self) -> GoogleTool | None:
152
+ """Returns the 0th tool for the 0th candidate's 0th content part.
153
+
154
+ Raises:
155
+ ValidationError: if the tool call doesn't match the tool's schema.
156
+ """
157
+ tools = self.tools
158
+ if tools:
159
+ return tools[0]
160
+ return None
161
+
162
+ @classmethod
163
+ @transform_tool_outputs
164
+ def tool_message_params(
165
+ cls, tools_and_outputs: list[tuple[GoogleTool, str]]
166
+ ) -> list[ContentDict]:
167
+ """Returns the tool message parameters for tool call results.
168
+
169
+ Args:
170
+ tools_and_outputs: The list of tools and their outputs from which the tool
171
+ message parameters should be constructed.
172
+
173
+ Returns:
174
+ The list of constructed `FunctionResponse` parameters.
175
+ """
176
+ return [
177
+ {
178
+ "role": "user",
179
+ "parts": [
180
+ PartDict(
181
+ function_response=FunctionResponseDict(
182
+ name=tool._name(), response={"result": output}
183
+ )
184
+ )
185
+ for tool, output in tools_and_outputs
186
+ ],
187
+ }
188
+ ]
189
+
190
+ @property
191
+ def common_finish_reasons(self) -> list[FinishReason] | None:
192
+ return _convert_finish_reasons_to_common_finish_reasons(self.finish_reasons)
193
+
194
+ @property
195
+ def common_message_param(self) -> BaseMessageParam:
196
+ return GoogleMessageParamConverter.from_provider([self.message_param])[0]
197
+
198
+ @property
199
+ def common_user_message_param(self) -> BaseMessageParam | None:
200
+ if not self.user_message_param:
201
+ return None
202
+ return GoogleMessageParamConverter.from_provider([self.user_message_param])[0]
@@ -0,0 +1,97 @@
1
+ """This module contains the `GoogleCallResponseChunk` class.
2
+
3
+ usage docs: learn/streams.md#handling-streamed-responses
4
+ """
5
+
6
+ from typing import cast
7
+
8
+ from google.genai.types import FinishReason as GoogleFinishReason
9
+ from google.genai.types import GenerateContentResponse
10
+
11
+ from mirascope.core.base.types import FinishReason
12
+
13
+ from ..base import BaseCallResponseChunk
14
+ from ._utils._convert_finish_reason_to_common_finish_reasons import (
15
+ _convert_finish_reasons_to_common_finish_reasons,
16
+ )
17
+
18
+
19
+ class GoogleCallResponseChunk(
20
+ BaseCallResponseChunk[GenerateContentResponse, GoogleFinishReason]
21
+ ):
22
+ """A convenience wrapper around the Google API streamed response chunks.
23
+
24
+ When calling the Google API using a function decorated with `google_call` and
25
+ `stream` set to `True`, the stream will contain `GoogleCallResponseChunk` instances
26
+
27
+ Example:
28
+
29
+ ```python
30
+ from mirascope.core import prompt_template
31
+ from mirascope.core.google import google_call
32
+
33
+
34
+ @google_call("google-1.5-flash", stream=True)
35
+ def recommend_book(genre: str) -> str:
36
+ return f"Recommend a {genre} book"
37
+
38
+
39
+ stream = recommend_book("fantasy") # response is an `GoogleStream`
40
+ for chunk, _ in stream:
41
+ print(chunk.content, end="", flush=True)
42
+ ```
43
+ """
44
+
45
+ @property
46
+ def content(self) -> str:
47
+ """Returns the chunk content for the 0th choice."""
48
+ return self.chunk.candidates[0].content.parts[0].text # pyright: ignore [reportOptionalMemberAccess, reportOptionalSubscript, reportReturnType]
49
+
50
+ @property
51
+ def finish_reasons(self) -> list[GoogleFinishReason]:
52
+ """Returns the finish reasons of the response."""
53
+ return [
54
+ candidate.finish_reason
55
+ for candidate in (self.chunk.candidates or [])
56
+ if candidate.finish_reason is not None
57
+ ]
58
+
59
+ @property
60
+ def model(self) -> None:
61
+ """Returns the model name.
62
+
63
+ google.generativeai does not return model, so we return None
64
+ """
65
+ return None
66
+
67
+ @property
68
+ def id(self) -> str | None:
69
+ """Returns the id of the response.
70
+
71
+ google.generativeai does not return an id
72
+ """
73
+ return None
74
+
75
+ @property
76
+ def usage(self) -> None:
77
+ """Returns the usage of the chat completion.
78
+
79
+ google.generativeai does not have Usage, so we return None
80
+ """
81
+ return None
82
+
83
+ @property
84
+ def input_tokens(self) -> None:
85
+ """Returns the number of input tokens."""
86
+ return None
87
+
88
+ @property
89
+ def output_tokens(self) -> None:
90
+ """Returns the number of output tokens."""
91
+ return None
92
+
93
+ @property
94
+ def common_finish_reasons(self) -> list[FinishReason] | None:
95
+ return _convert_finish_reasons_to_common_finish_reasons(
96
+ cast(list[str], self.finish_reasons)
97
+ )
@@ -0,0 +1,26 @@
1
+ """This module defines the function return type for functions as LLM calls."""
2
+
3
+ from google.genai import Client
4
+ from google.genai.types import ContentListUnion, ContentListUnionDict
5
+
6
+ from ..base import BaseDynamicConfig, BaseMessageParam
7
+ from .call_params import GoogleCallParams
8
+
9
+ GoogleDynamicConfig = BaseDynamicConfig[
10
+ ContentListUnion | ContentListUnionDict | BaseMessageParam, GoogleCallParams, Client
11
+ ]
12
+ """The function return type for functions wrapped with the `google_call` decorator.
13
+
14
+ Example:
15
+
16
+ ```python
17
+ from mirascope.core import prompt_template
18
+ from mirascope.core.google import GoogleDynamicConfig, google_call
19
+
20
+
21
+ @google_call("google-1.5-flash")
22
+ @prompt_template("Recommend a {capitalized_genre} book")
23
+ def recommend_book(genre: str) -> GoogleDynamicConfig:
24
+ return {"computed_fields": {"capitalized_genre": genre.capitalize()}}
25
+ ```
26
+ """