8
8
definitions and a client class for making requests to Gemini models.
9
9
"""
10
10
11
+ import logging
11
12
from enum import StrEnum
12
13
from typing import Any
13
14
15
+ import vertexai .generative_models as genai
16
+ from genkit .core .action import ActionRunContext
14
17
from genkit .core .typing import (
18
+ CustomPart ,
19
+ DataPart ,
15
20
GenerateRequest ,
16
21
GenerateResponse ,
22
+ GenerateResponseChunk ,
23
+ MediaPart ,
17
24
Message ,
18
25
ModelInfo ,
19
26
Role ,
20
27
Supports ,
21
28
TextPart ,
29
+ ToolRequestPart ,
30
+ ToolResponsePart ,
22
31
)
23
- from vertexai .generative_models import Content , GenerativeModel , Part
32
+
33
+ LOG = logging .getLogger (__name__ )
24
34
25
35
26
36
class GeminiVersion (StrEnum ):
@@ -83,7 +93,7 @@ class Gemini:
83
93
handling message formatting and response processing.
84
94
"""
85
95
86
- def __init__ (self , version : str ):
96
+ def __init__ (self , version : str | GeminiVersion ):
87
97
"""Initialize a Gemini client.
88
98
89
99
Args:
@@ -92,38 +102,93 @@ def __init__(self, version: str):
92
102
"""
93
103
self ._version = version
94
104
105
+ def is_multimode (self ):
106
+ return SUPPORTED_MODELS [self ._version ].supports .media
107
+
108
+ def build_messages (self , request : GenerateRequest ) -> list [genai .Content ]:
109
+ """Builds a list of VertexAI content from a request.
110
+
111
+ Args:
112
+ - request: a packed request for the model
113
+
114
+ Returns:
115
+ - a list of VertexAI GenAI Content for the request
116
+ """
117
+ messages : list [genai .Content ] = []
118
+ for message in request .messages :
119
+ parts : list [genai .Part ] = []
120
+ for part in message .content :
121
+ if isinstance (part .root , TextPart ):
122
+ parts .append (genai .Part .from_text (part .root .text ))
123
+ elif isinstance (part .root , MediaPart ):
124
+ if not self .is_multimode ():
125
+ LOG .error (
126
+ f'The model { self ._version } does not'
127
+ f' support multimode input'
128
+ )
129
+ continue
130
+ parts .append (
131
+ genai .Part .from_uri (
132
+ mime_type = part .root .media .content_type ,
133
+ uri = part .root .media .url ,
134
+ )
135
+ )
136
+ elif isinstance (part .root , ToolRequestPart | ToolResponsePart ):
137
+ LOG .warning ('Tools are not supported yet' )
138
+ elif isinstance (part .root , CustomPart ):
139
+ # TODO: handle CustomPart
140
+ LOG .warning ('The code part is not supported yet.' )
141
+ else :
142
+ LOG .error ('The type is not supported' )
143
+ messages .append (genai .Content (role = message .role .value , parts = parts ))
144
+
145
+ return messages
146
+
95
147
@property
96
- def gemini_model (self ) -> GenerativeModel :
148
+ def gemini_model (self ) -> genai . GenerativeModel :
97
149
"""Get the Vertex AI GenerativeModel instance.
98
150
99
151
Returns:
100
152
A configured GenerativeModel instance for the specified version.
101
153
"""
102
- return GenerativeModel (self ._version )
154
+ return genai . GenerativeModel (self ._version )
103
155
104
- def handle_request (self , request : GenerateRequest ) -> GenerateResponse :
156
+ def generate (
157
+ self , request : GenerateRequest , ctx : ActionRunContext
158
+ ) -> GenerateResponse | None :
105
159
"""Handle a generation request using the Gemini model.
106
160
107
161
Args:
108
162
request: The generation request containing messages and parameters.
163
+ ctx: additional context
109
164
110
165
Returns:
111
166
The model's response to the generation request.
112
167
"""
113
- messages : list [Content ] = []
114
- for m in request .messages :
115
- parts : list [Part ] = []
116
- for p in m .content :
117
- if p .root .text is not None :
118
- parts .append (Part .from_text (p .root .text ))
119
- else :
120
- raise Exception ('unsupported part type' )
121
- messages .append (Content (role = m .role .value , parts = parts ))
122
- response = self .gemini_model .generate_content (contents = messages )
168
+
169
+ messages = self .build_messages (request )
170
+ response = self .gemini_model .generate_content (
171
+ contents = messages , stream = ctx .is_streaming
172
+ )
173
+
174
+ text_response = ''
175
+ if ctx .is_streaming :
176
+ for chunk in response :
177
+ # TODO: Support other types of output
178
+ ctx .send_chunk (
179
+ GenerateResponseChunk (
180
+ role = Role .MODEL ,
181
+ content = [TextPart (text = chunk .text )],
182
+ )
183
+ )
184
+
185
+ else :
186
+ text_response = response .text
187
+
123
188
return GenerateResponse (
124
189
message = Message (
125
190
role = Role .MODEL ,
126
- content = [TextPart (text = response . text )],
191
+ content = [TextPart (text = text_response )],
127
192
)
128
193
)
129
194
0 commit comments