donkit-llm 0.1.4__py3-none-any.whl → 0.1.6__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.
@@ -43,6 +43,43 @@ class OpenAIModel(LLMModelAbstract):
43
43
  self._init_client(api_key, base_url, organization)
44
44
  self._capabilities = self._determine_capabilities()
45
45
 
46
+ def _clean_schema_for_openai(self, schema: dict, is_gpt5: bool = False) -> dict:
47
+ """Clean JSON Schema for OpenAI strict mode.
48
+
49
+ Args:
50
+ schema: JSON Schema to clean
51
+ is_gpt5: Currently unused - both GPT-4o and GPT-5 support title/description
52
+
53
+ OpenAI Structured Outputs supports:
54
+ - title and description (useful metadata for the model)
55
+ - Automatically adds additionalProperties: false for all objects
56
+ - Recursively processes nested objects and arrays
57
+
58
+ Note: The main requirement is additionalProperties: false for all objects,
59
+ which is automatically added by this method.
60
+ """
61
+ cleaned = {}
62
+
63
+ # Copy all fields, recursively processing nested structures
64
+ for key, value in schema.items():
65
+ if key == "properties" and isinstance(value, dict):
66
+ # Recursively clean properties
67
+ cleaned["properties"] = {
68
+ k: self._clean_schema_for_openai(v, is_gpt5) if isinstance(v, dict) else v
69
+ for k, v in value.items()
70
+ }
71
+ elif key == "items" and isinstance(value, dict):
72
+ # Recursively clean array items
73
+ cleaned["items"] = self._clean_schema_for_openai(value, is_gpt5)
74
+ else:
75
+ cleaned[key] = value
76
+
77
+ # Ensure additionalProperties is false for objects (required by OpenAI)
78
+ if cleaned.get("type") == "object" and "additionalProperties" not in cleaned:
79
+ cleaned["additionalProperties"] = False
80
+
81
+ return cleaned
82
+
46
83
  def _get_base_model_name(self) -> str:
47
84
  """Get base model name for capability/parameter detection.
48
85
 
@@ -263,7 +300,29 @@ class OpenAIModel(LLMModelAbstract):
263
300
  kwargs["tool_choice"] = request.tool_choice
264
301
 
265
302
  if request.response_format:
266
- kwargs["response_format"] = request.response_format
303
+ # OpenAI requires specific format for structured output
304
+ # If response_format is a JSON Schema dict with "type": "object", wrap it
305
+ if isinstance(request.response_format, dict):
306
+ if request.response_format.get("type") == "object":
307
+ # This is a JSON Schema - clean and wrap it in json_schema format
308
+ # GPT-5 and reasoning models need stricter schema cleaning
309
+ is_gpt5 = self._is_reasoning_model()
310
+ cleaned_schema = self._clean_schema_for_openai(
311
+ request.response_format, is_gpt5=is_gpt5
312
+ )
313
+ kwargs["response_format"] = {
314
+ "type": "json_schema",
315
+ "json_schema": {
316
+ "name": "response",
317
+ "strict": True,
318
+ "schema": cleaned_schema,
319
+ },
320
+ }
321
+ else:
322
+ # Already in correct format or simple type
323
+ kwargs["response_format"] = request.response_format
324
+ else:
325
+ kwargs["response_format"] = request.response_format
267
326
 
268
327
  return kwargs
269
328
 
@@ -330,6 +330,39 @@ class VertexAIModel(LLMModelAbstract):
330
330
 
331
331
  return convert(schema)
332
332
 
333
+ def _build_config_kwargs(
334
+ self, request: GenerateRequest, system_instruction: str | None = None
335
+ ) -> dict[str, Any]:
336
+ """Build configuration kwargs for Vertex AI generate/generate_stream."""
337
+ config_kwargs: dict[str, Any] = {
338
+ "temperature": request.temperature
339
+ if request.temperature is not None
340
+ else 0.2,
341
+ "top_p": request.top_p if request.top_p is not None else 0.95,
342
+ "max_output_tokens": request.max_tokens
343
+ if request.max_tokens is not None
344
+ else 8192,
345
+ }
346
+ if system_instruction:
347
+ config_kwargs["system_instruction"] = system_instruction
348
+ if request.stop:
349
+ config_kwargs["stop_sequences"] = request.stop
350
+ if request.response_format:
351
+ config_kwargs["response_mime_type"] = "application/json"
352
+ # If response_format is a JSON Schema dict with "type": "object", use it directly
353
+ if isinstance(request.response_format, dict):
354
+ if request.response_format.get("type") == "object":
355
+ # This is a JSON Schema - use it directly
356
+ config_kwargs["response_schema"] = self._clean_json_schema(
357
+ request.response_format
358
+ )
359
+ elif "schema" in request.response_format:
360
+ # Already wrapped in schema key
361
+ config_kwargs["response_schema"] = self._clean_json_schema(
362
+ request.response_format["schema"]
363
+ )
364
+ return config_kwargs
365
+
333
366
  async def generate(self, request: GenerateRequest) -> GenerateResponse:
334
367
  """Generate a response using Vertex AI."""
335
368
  await self.validate_request(request)
@@ -410,26 +443,7 @@ class VertexAIModel(LLMModelAbstract):
410
443
  contents.append(user_content)
411
444
  i += 1
412
445
 
413
- config_kwargs = {
414
- "temperature": request.temperature
415
- if request.temperature is not None
416
- else 0.2,
417
- "top_p": request.top_p if request.top_p is not None else 0.95,
418
- "max_output_tokens": request.max_tokens
419
- if request.max_tokens is not None
420
- else 8192,
421
- }
422
- if system_instruction:
423
- config_kwargs["system_instruction"] = system_instruction
424
- if request.stop:
425
- config_kwargs["stop_sequences"] = request.stop
426
- if request.response_format:
427
- config_kwargs["response_mime_type"] = "application/json"
428
- if "schema" in request.response_format:
429
- config_kwargs["response_schema"] = self._clean_json_schema(
430
- request.response_format["schema"]
431
- )
432
-
446
+ config_kwargs = self._build_config_kwargs(request, system_instruction)
433
447
  config = genai.types.GenerateContentConfig(**config_kwargs)
434
448
 
435
449
  if request.tools:
@@ -584,25 +598,7 @@ class VertexAIModel(LLMModelAbstract):
584
598
  contents.append(user_content)
585
599
  i += 1
586
600
 
587
- config_kwargs: dict[str, Any] = {
588
- "temperature": request.temperature
589
- if request.temperature is not None
590
- else 0.2,
591
- "top_p": request.top_p if request.top_p is not None else 0.95,
592
- "max_output_tokens": request.max_tokens
593
- if request.max_tokens is not None
594
- else 8192,
595
- }
596
- if system_instruction:
597
- config_kwargs["system_instruction"] = system_instruction
598
- if request.stop:
599
- config_kwargs["stop_sequences"] = request.stop
600
- if request.response_format:
601
- config_kwargs["response_mime_type"] = "application/json"
602
- if "schema" in request.response_format:
603
- config_kwargs["response_schema"] = self._clean_json_schema(
604
- request.response_format["schema"]
605
- )
601
+ config_kwargs = self._build_config_kwargs(request, system_instruction)
606
602
  config_kwargs["automatic_function_calling"] = (
607
603
  genai.types.AutomaticFunctionCallingConfig(maximum_remote_calls=100)
608
604
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: donkit-llm
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: Unified LLM model implementations for Donkit (OpenAI, Azure OpenAI, Claude, Vertex AI, Ollama)
5
5
  License: MIT
6
6
  Author: Donkit AI
@@ -5,8 +5,8 @@ donkit/llm/factory.py,sha256=KoZ9bD6FsZjU3ldKL7szznDSB8gI1slnI1jGGwKIuVY,9195
5
5
  donkit/llm/gemini_model.py,sha256=2uLoZr9HjUf1wxiZRGLQFcURCutsB2SV9f-1VaR6kGI,14413
6
6
  donkit/llm/model_abstract.py,sha256=aOgYh3I96PsxSxnkIJ1ETx5UFeRxozCD1c44wiKoBSs,8191
7
7
  donkit/llm/ollama_integration.py,sha256=WXeV2xNxP7gd1JyMsHMKaQOjvH7QYkLIPs7pmTPWFrg,13236
8
- donkit/llm/openai_model.py,sha256=66ioYAaoOS9Fo0C0w2LYdSnAKwXIt6qXvJsKXTuajm0,24609
9
- donkit/llm/vertex_model.py,sha256=XOo_uwJOa0wgArkD3pac7SulUYWkCc7lTRjyrBSpHPM,29284
10
- donkit_llm-0.1.4.dist-info/METADATA,sha256=tR6fRwBE36XEf_X4AjGlNg1e4_3xLLPLeVuk2denyho,742
11
- donkit_llm-0.1.4.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
12
- donkit_llm-0.1.4.dist-info/RECORD,,
8
+ donkit/llm/openai_model.py,sha256=zzo_wVYMqfId7IksfQyC4zr7lIMfiWmpeIqpKI3MgUo,27446
9
+ donkit/llm/vertex_model.py,sha256=LcdWBdx4JYzom2IsXxhNGEsrYf0N6JmwuRc3sqfKIos,29350
10
+ donkit_llm-0.1.6.dist-info/METADATA,sha256=RTN3wR7de8ToDFQeUs4WfD2nSnrvFc33GLmwrZF4bvc,742
11
+ donkit_llm-0.1.6.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
12
+ donkit_llm-0.1.6.dist-info/RECORD,,