@@ -23,10 +23,11 @@ defmodule Instructor.Adapters.Gemini do
23
23
"""
24
24
25
25
@ behaviour Instructor.Adapter
26
+ alias Instructor.SSEStreamParser
26
27
alias Instructor.Adapters
27
28
alias Instructor.JSONSchema
28
29
29
- @ supported_modes [ :tools , : json_schema]
30
+ @ supported_modes [ :json_schema ]
30
31
31
32
@ doc """
32
33
Run a completion against Google's Gemini API
@@ -102,7 +103,7 @@ defmodule Instructor.Adapters.Gemini do
102
103
generation_config =
103
104
generation_config
104
105
|> Map . put ( "response_mime_type" , "application/json" )
105
- |> Map . put ( "response_schema" , map_schema ( schema ) )
106
+ |> Map . put ( "response_schema" , normalize_json_schema ( schema ) )
106
107
107
108
params
108
109
|> Map . put ( :generationConfig , generation_config )
@@ -116,7 +117,7 @@ defmodule Instructor.Adapters.Gemini do
116
117
% {
117
118
name: tool [ "name" ] ,
118
119
description: tool [ "description" ] ,
119
- parameters: map_schema ( tool [ "parameters" ] )
120
+ parameters: normalize_json_schema ( tool [ "parameters" ] )
120
121
}
121
122
end )
122
123
}
@@ -155,23 +156,7 @@ defmodule Instructor.Adapters.Gemini do
155
156
json: params ,
156
157
rpc_function: :streamGenerateContent ,
157
158
into: fn { :data , data } , { req , resp } ->
158
- chunks =
159
- data
160
- |> String . split ( "\n " )
161
- |> Enum . filter ( fn line ->
162
- String . starts_with? ( line , "data: {" )
163
- end )
164
- |> Enum . map ( fn line ->
165
- line
166
- |> String . replace_prefix ( "data: " , "" )
167
- |> Jason . decode! ( )
168
- |> then ( & parse_stream_chunk_for_mode ( mode , & 1 ) )
169
- end )
170
-
171
- for chunk <- chunks do
172
- send ( pid , chunk )
173
- end
174
-
159
+ send ( pid , data )
175
160
{ :cont , { req , resp } }
176
161
end
177
162
)
@@ -196,6 +181,8 @@ defmodule Instructor.Adapters.Gemini do
196
181
end ,
197
182
fn task -> Task . await ( task ) end
198
183
)
184
+ |> SSEStreamParser . parse ( )
185
+ |> Stream . map ( fn chunk -> parse_stream_chunk_for_mode ( mode , chunk ) end )
199
186
end
200
187
201
188
defp do_chat_completion ( mode , params , config ) do
@@ -266,18 +253,70 @@ defmodule Instructor.Adapters.Gemini do
266
253
chunk
267
254
end
268
255
269
- defp map_schema ( schema ) do
270
- JSONSchema . traverse_and_update ( schema , fn
271
- % { "type" => _ } = x
272
- when is_map_key ( x , "format" ) or is_map_key ( x , "pattern" ) or
273
- is_map_key ( x , "title" ) or is_map_key ( x , "additionalProperties" ) ->
274
- Map . drop ( x , [ "format" , "pattern" , "title" , "additionalProperties" ] )
256
+ defp normalize_json_schema ( schema ) do
257
+ JSONSchema . traverse_and_update (
258
+ schema ,
259
+ fn
260
+ { % { "type" => _ } = x , path }
261
+ when is_map_key ( x , "format" ) or is_map_key ( x , "pattern" ) or
262
+ is_map_key ( x , "title" ) or is_map_key ( x , "additionalProperties" ) ->
263
+ x
264
+ |> Map . drop ( [ "format" , "pattern" , "title" , "additionalProperties" ] )
265
+ |> case do
266
+ % { "type" => "object" , "properties" => properties } when map_size ( properties ) == 0 ->
267
+ raise """
268
+ Invalid JSON Schema: object with no properties at path: #{ inspect ( path ) }
269
+
270
+ Gemini does not support empty objects. This is likely because have have a naked :map type
271
+ without any fields at #{ inspect ( path ) } . Try switching to an embedded schema instead.
272
+ """
273
+
274
+ x ->
275
+ x
276
+ end
275
277
276
- x ->
277
- x
278
- end )
278
+ { x , _path } ->
279
+ x
280
+ end ,
281
+ include_path: true
282
+ )
283
+ |> inline_defs ( )
284
+ end
285
+
286
+ defp inline_defs ( schema ) do
287
+ # First extract the definitions map for reference
288
+ { defs , schema } = Map . pop ( schema , "$defs" , % { } )
289
+
290
+ # Traverse and replace all $refs with their definitions
291
+ traverse_and_inline ( schema , defs )
292
+ end
293
+
294
+ defp traverse_and_inline ( schema , defs ) when is_map ( schema ) do
295
+ cond do
296
+ # If we find a $ref, replace it with the inlined definition
297
+ Map . has_key? ( schema , "$ref" ) ->
298
+ ref = schema [ "$ref" ]
299
+ def_key = String . replace_prefix ( ref , "#/$defs/" , "" )
300
+ definition = Map . get ( defs , def_key , % { } )
301
+ # Recursively inline any nested refs in the definition
302
+ traverse_and_inline ( definition , defs )
303
+
304
+ # Otherwise traverse all values in the map
305
+ true ->
306
+ schema
307
+ |> Enum . map ( fn { k , v } -> { k , traverse_and_inline ( v , defs ) } end )
308
+ |> Enum . into ( % { } )
309
+ end
279
310
end
280
311
312
+ # Handle arrays by traversing each element
313
+ defp traverse_and_inline ( schema , defs ) when is_list ( schema ) do
314
+ Enum . map ( schema , & traverse_and_inline ( & 1 , defs ) )
315
+ end
316
+
317
+ # Base case - return non-map/list values as is
318
+ defp traverse_and_inline ( schema , _defs ) , do: schema
319
+
281
320
defp snake_to_camel ( snake_case_string ) do
282
321
snake_case_string
283
322
|> String . split ( "_" )
0 commit comments