@@ -41,6 +41,14 @@ import io.cequence.openaiscala.service.{
41
41
}
42
42
43
43
import scala .concurrent .{ExecutionContext , Future }
44
+ import io .cequence .openaiscala .domain .settings .ChatCompletionResponseFormatType
45
+ import io .cequence .openaiscala .domain .JsonSchema
46
+ import io .cequence .openaiscala .gemini .domain .Schema
47
+ import com .typesafe .scalalogging .Logger
48
+ import io .cequence .openaiscala .gemini .domain .SchemaType
49
+ import org .slf4j .LoggerFactory
50
+
51
+ import scala .collection .immutable .Traversable
44
52
45
53
private [service] class OpenAIGeminiChatCompletionService (
46
54
underlying : GeminiService
@@ -49,6 +57,8 @@ private[service] class OpenAIGeminiChatCompletionService(
49
57
) extends OpenAIChatCompletionService
50
58
with OpenAIChatCompletionStreamedServiceExtra {
51
59
60
+ protected val logger : Logger = Logger (LoggerFactory .getLogger(this .getClass))
61
+
52
62
override def createChatCompletion (
53
63
messages : Seq [BaseMessage ],
54
64
settings : CreateChatCompletionSettings
@@ -185,7 +195,31 @@ private[service] class OpenAIGeminiChatCompletionService(
185
195
private def toGeminiSettings (
186
196
settings : CreateChatCompletionSettings ,
187
197
systemMessage : Option [BaseMessage ]
188
- ): GenerateContentSettings =
198
+ ): GenerateContentSettings = {
199
+
200
+ // handle json schema
201
+ val responseFormat =
202
+ settings.response_format_type.getOrElse(ChatCompletionResponseFormatType .text)
203
+
204
+ val jsonSchema =
205
+ if (
206
+ responseFormat == ChatCompletionResponseFormatType .json_schema && settings.jsonSchema.isDefined
207
+ ) {
208
+ settings.jsonSchema.get.structure match {
209
+ case Left (schema) =>
210
+ Some (toGeminiJSONSchema(schema))
211
+ case Right (_) =>
212
+ logger.warn(
213
+ " Map-like legacy JSON schema is not supported for conversion to Gemini schema."
214
+ )
215
+ None
216
+ }
217
+ } else
218
+ None
219
+
220
+ // check for unsupported fields
221
+ checkNotSupported(settings)
222
+
189
223
GenerateContentSettings (
190
224
model = settings.model,
191
225
tools = None , // TODO
@@ -196,7 +230,7 @@ private[service] class OpenAIGeminiChatCompletionService(
196
230
GenerationConfig (
197
231
stopSequences = (if (settings.stop.nonEmpty) Some (settings.stop) else None ),
198
232
responseMimeType = None ,
199
- responseSchema = None , // TODO: support JSON!
233
+ responseSchema = jsonSchema,
200
234
responseModalities = None ,
201
235
candidateCount = settings.n,
202
236
maxOutputTokens = settings.max_tokens,
@@ -214,6 +248,84 @@ private[service] class OpenAIGeminiChatCompletionService(
214
248
),
215
249
cachedContent = None
216
250
)
251
+ }
252
+
253
+ private def checkNotSupported (
254
+ settings : CreateChatCompletionSettings
255
+ ) = {
256
+ def notSupported (
257
+ field : CreateChatCompletionSettings => Option [_],
258
+ fieldName : String
259
+ ): Unit =
260
+ field(settings).foreach { _ =>
261
+ logger.warn(s " Field $fieldName is not yet supported for Gemini. Skipping... " )
262
+ }
263
+
264
+ def notSupportedCollection (
265
+ field : CreateChatCompletionSettings => Traversable [_],
266
+ fieldName : String
267
+ ): Unit =
268
+ if (field(settings).nonEmpty) {
269
+ logger.warn(s " Field $fieldName is not supported for Gemini. Skipping... " )
270
+ }
271
+
272
+ notSupported(_.reasoning_effort, " reasoning_effort" )
273
+ notSupported(_.service_tier, " service_tier" )
274
+ notSupported(_.parallel_tool_calls, " parallel_tool_calls" )
275
+ notSupportedCollection(_.metadata, " metadata" )
276
+ notSupportedCollection(_.logit_bias, " logit_bias" )
277
+ notSupported(_.user, " user" )
278
+ notSupported(_.store, " store" )
279
+ }
280
+
281
+ private def toGeminiJSONSchema (
282
+ jsonSchema : JsonSchema
283
+ ): Schema = jsonSchema match {
284
+ case JsonSchema .String (description, enumVals) =>
285
+ Schema (
286
+ `type` = SchemaType .STRING ,
287
+ description = description,
288
+ `enum` = Some (enumVals)
289
+ )
290
+
291
+ case JsonSchema .Number (description) =>
292
+ Schema (
293
+ `type` = SchemaType .NUMBER ,
294
+ description = description
295
+ )
296
+
297
+ case JsonSchema .Integer (description) =>
298
+ Schema (
299
+ `type` = SchemaType .INTEGER ,
300
+ description = description
301
+ )
302
+
303
+ case JsonSchema .Boolean (description) =>
304
+ Schema (
305
+ `type` = SchemaType .BOOLEAN ,
306
+ description = description
307
+ )
308
+
309
+ case JsonSchema .Object (properties, required) =>
310
+ Schema (
311
+ `type` = SchemaType .OBJECT ,
312
+ properties = Some (
313
+ properties.map { case (key, jsonSchema) =>
314
+ key -> toGeminiJSONSchema(jsonSchema)
315
+ }.toMap
316
+ ),
317
+ required = Some (required)
318
+ )
319
+
320
+ case JsonSchema .Array (items) =>
321
+ Schema (
322
+ `type` = SchemaType .ARRAY ,
323
+ items = Some (toGeminiJSONSchema(items))
324
+ )
325
+
326
+ case _ =>
327
+ throw new OpenAIScalaClientException (s " Unsupported JSON schema type for Gemini. " )
328
+ }
217
329
218
330
private def toOpenAIResponse (
219
331
response : GenerateContentResponse
0 commit comments