pydantic-ai-slim 0.4.11__tar.gz → 0.5.0__tar.gz

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.

Potentially problematic release.


This version of pydantic-ai-slim might be problematic. Click here for more details.

Files changed (101) hide show
  1. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/PKG-INFO +3 -3
  2. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_function_schema.py +7 -4
  3. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/messages.py +37 -10
  4. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/openai.py +22 -12
  5. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/tools.py +13 -5
  6. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/.gitignore +0 -0
  7. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/LICENSE +0 -0
  8. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/README.md +0 -0
  9. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/__init__.py +0 -0
  10. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/__main__.py +0 -0
  11. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_a2a.py +0 -0
  12. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_agent_graph.py +0 -0
  13. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_cli.py +0 -0
  14. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_griffe.py +0 -0
  15. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_mcp.py +0 -0
  16. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_output.py +0 -0
  17. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_parts_manager.py +0 -0
  18. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_run_context.py +0 -0
  19. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_system_prompt.py +0 -0
  20. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_thinking_part.py +0 -0
  21. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_tool_manager.py +0 -0
  22. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/_utils.py +0 -0
  23. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/ag_ui.py +0 -0
  24. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/agent.py +0 -0
  25. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/common_tools/__init__.py +0 -0
  26. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/common_tools/duckduckgo.py +0 -0
  27. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/common_tools/tavily.py +0 -0
  28. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/direct.py +0 -0
  29. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/exceptions.py +0 -0
  30. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/ext/__init__.py +0 -0
  31. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/ext/aci.py +0 -0
  32. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/ext/langchain.py +0 -0
  33. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/format_as_xml.py +0 -0
  34. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/format_prompt.py +0 -0
  35. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/mcp.py +0 -0
  36. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/__init__.py +0 -0
  37. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/anthropic.py +0 -0
  38. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/bedrock.py +0 -0
  39. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/cohere.py +0 -0
  40. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/fallback.py +0 -0
  41. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/function.py +0 -0
  42. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/gemini.py +0 -0
  43. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/google.py +0 -0
  44. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/groq.py +0 -0
  45. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/huggingface.py +0 -0
  46. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/instrumented.py +0 -0
  47. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/mcp_sampling.py +0 -0
  48. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/mistral.py +0 -0
  49. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/openai.py +0 -0
  50. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/test.py +0 -0
  51. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/models/wrapper.py +0 -0
  52. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/output.py +0 -0
  53. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/__init__.py +0 -0
  54. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/_json_schema.py +0 -0
  55. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/amazon.py +0 -0
  56. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/anthropic.py +0 -0
  57. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/cohere.py +0 -0
  58. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/deepseek.py +0 -0
  59. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/google.py +0 -0
  60. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/grok.py +0 -0
  61. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/meta.py +0 -0
  62. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/mistral.py +0 -0
  63. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/moonshotai.py +0 -0
  64. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/profiles/qwen.py +0 -0
  65. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/__init__.py +0 -0
  66. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/anthropic.py +0 -0
  67. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/azure.py +0 -0
  68. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/bedrock.py +0 -0
  69. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/cohere.py +0 -0
  70. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/deepseek.py +0 -0
  71. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/fireworks.py +0 -0
  72. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/github.py +0 -0
  73. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/google.py +0 -0
  74. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/google_gla.py +0 -0
  75. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/google_vertex.py +0 -0
  76. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/grok.py +0 -0
  77. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/groq.py +0 -0
  78. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/heroku.py +0 -0
  79. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/huggingface.py +0 -0
  80. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/mistral.py +0 -0
  81. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/moonshotai.py +0 -0
  82. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/openai.py +0 -0
  83. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/openrouter.py +0 -0
  84. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/together.py +0 -0
  85. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/providers/vercel.py +0 -0
  86. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/py.typed +0 -0
  87. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/result.py +0 -0
  88. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/retries.py +0 -0
  89. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/settings.py +0 -0
  90. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/toolsets/__init__.py +0 -0
  91. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/toolsets/abstract.py +0 -0
  92. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/toolsets/combined.py +0 -0
  93. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/toolsets/deferred.py +0 -0
  94. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/toolsets/filtered.py +0 -0
  95. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/toolsets/function.py +0 -0
  96. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/toolsets/prefixed.py +0 -0
  97. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/toolsets/prepared.py +0 -0
  98. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/toolsets/renamed.py +0 -0
  99. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/toolsets/wrapper.py +0 -0
  100. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pydantic_ai/usage.py +0 -0
  101. {pydantic_ai_slim-0.4.11 → pydantic_ai_slim-0.5.0}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-ai-slim
3
- Version: 0.4.11
3
+ Version: 0.5.0
4
4
  Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
5
5
  Author-email: Samuel Colvin <samuel@pydantic.dev>, Marcelo Trylesinski <marcelotryle@gmail.com>, David Montague <david@pydantic.dev>, Alex Hall <alex@pydantic.dev>, Douwe Maan <douwe@pydantic.dev>
6
6
  License-Expression: MIT
@@ -30,7 +30,7 @@ Requires-Dist: exceptiongroup; python_version < '3.11'
30
30
  Requires-Dist: griffe>=1.3.2
31
31
  Requires-Dist: httpx>=0.27
32
32
  Requires-Dist: opentelemetry-api>=1.28.0
33
- Requires-Dist: pydantic-graph==0.4.11
33
+ Requires-Dist: pydantic-graph==0.5.0
34
34
  Requires-Dist: pydantic>=2.10
35
35
  Requires-Dist: typing-inspection>=0.4.0
36
36
  Provides-Extra: a2a
@@ -51,7 +51,7 @@ Requires-Dist: cohere>=5.16.0; (platform_system != 'Emscripten') and extra == 'c
51
51
  Provides-Extra: duckduckgo
52
52
  Requires-Dist: ddgs>=9.0.0; extra == 'duckduckgo'
53
53
  Provides-Extra: evals
54
- Requires-Dist: pydantic-evals==0.4.11; extra == 'evals'
54
+ Requires-Dist: pydantic-evals==0.5.0; extra == 'evals'
55
55
  Provides-Extra: google
56
56
  Requires-Dist: google-genai>=1.24.0; extra == 'google'
57
57
  Provides-Extra: groq
@@ -154,9 +154,13 @@ def function_schema( # noqa: C901
154
154
  if p.kind == Parameter.VAR_POSITIONAL:
155
155
  annotation = list[annotation]
156
156
 
157
- # FieldInfo.from_annotation expects a type, `annotation` is Any
157
+ required = p.default is Parameter.empty
158
+ # FieldInfo.from_annotated_attribute expects a type, `annotation` is Any
158
159
  annotation = cast(type[Any], annotation)
159
- field_info = FieldInfo.from_annotation(annotation)
160
+ if required:
161
+ field_info = FieldInfo.from_annotation(annotation)
162
+ else:
163
+ field_info = FieldInfo.from_annotated_attribute(annotation, p.default)
160
164
  if field_info.description is None:
161
165
  field_info.description = field_descriptions.get(field_name)
162
166
 
@@ -164,7 +168,7 @@ def function_schema( # noqa: C901
164
168
  field_name,
165
169
  field_info,
166
170
  decorators,
167
- required=p.default is Parameter.empty,
171
+ required=required,
168
172
  )
169
173
  # noinspection PyTypeChecker
170
174
  td_schema.setdefault('metadata', {})['is_model_like'] = is_model_like(annotation)
@@ -281,7 +285,6 @@ def _build_schema(
281
285
  td_schema = core_schema.typed_dict_schema(
282
286
  fields,
283
287
  config=core_config,
284
- total=var_kwargs_schema is None,
285
288
  extras_schema=gen_schema.generate_schema(var_kwargs_schema) if var_kwargs_schema else None,
286
289
  )
287
290
  return td_schema, None
@@ -106,7 +106,7 @@ class FileUrl(ABC):
106
106
  - `GoogleModel`: `VideoUrl.vendor_metadata` is used as `video_metadata`: https://ai.google.dev/gemini-api/docs/video-understanding#customize-video-processing
107
107
  """
108
108
 
109
- _media_type: str | None = field(init=False, repr=False)
109
+ _media_type: str | None = field(init=False, repr=False, compare=False)
110
110
 
111
111
  def __init__(
112
112
  self,
@@ -120,19 +120,21 @@ class FileUrl(ABC):
120
120
  self.force_download = force_download
121
121
  self._media_type = media_type
122
122
 
123
- @abstractmethod
124
- def _infer_media_type(self) -> str:
125
- """Return the media type of the file, based on the url."""
126
-
127
123
  @property
128
124
  def media_type(self) -> str:
129
- """Return the media type of the file, based on the url or the provided `_media_type`."""
125
+ """Return the media type of the file, based on the URL or the provided `media_type`."""
130
126
  return self._media_type or self._infer_media_type()
131
127
 
128
+ @abstractmethod
129
+ def _infer_media_type(self) -> str:
130
+ """Infer the media type of the file based on the URL."""
131
+ raise NotImplementedError
132
+
132
133
  @property
133
134
  @abstractmethod
134
135
  def format(self) -> str:
135
136
  """The file format."""
137
+ raise NotImplementedError
136
138
 
137
139
  __repr__ = _utils.dataclasses_no_defaults_repr
138
140
 
@@ -182,7 +184,9 @@ class VideoUrl(FileUrl):
182
184
  elif self.is_youtube:
183
185
  return 'video/mp4'
184
186
  else:
185
- raise ValueError(f'Unknown video file extension: {self.url}')
187
+ raise ValueError(
188
+ f'Could not infer media type from video URL: {self.url}. Explicitly provide a `media_type` instead.'
189
+ )
186
190
 
187
191
  @property
188
192
  def is_youtube(self) -> bool:
@@ -238,7 +242,9 @@ class AudioUrl(FileUrl):
238
242
  if self.url.endswith('.aac'):
239
243
  return 'audio/aac'
240
244
 
241
- raise ValueError(f'Unknown audio file extension: {self.url}')
245
+ raise ValueError(
246
+ f'Could not infer media type from audio URL: {self.url}. Explicitly provide a `media_type` instead.'
247
+ )
242
248
 
243
249
  @property
244
250
  def format(self) -> AudioFormat:
@@ -278,7 +284,9 @@ class ImageUrl(FileUrl):
278
284
  elif self.url.endswith('.webp'):
279
285
  return 'image/webp'
280
286
  else:
281
- raise ValueError(f'Unknown image file extension: {self.url}')
287
+ raise ValueError(
288
+ f'Could not infer media type from image URL: {self.url}. Explicitly provide a `media_type` instead.'
289
+ )
282
290
 
283
291
  @property
284
292
  def format(self) -> ImageFormat:
@@ -312,9 +320,28 @@ class DocumentUrl(FileUrl):
312
320
 
313
321
  def _infer_media_type(self) -> str:
314
322
  """Return the media type of the document, based on the url."""
323
+ # Common document types are hardcoded here as mime-type support for these
324
+ # extensions varies across operating systems.
325
+ if self.url.endswith(('.md', '.mdx', '.markdown')):
326
+ return 'text/markdown'
327
+ elif self.url.endswith('.asciidoc'):
328
+ return 'text/x-asciidoc'
329
+ elif self.url.endswith('.txt'):
330
+ return 'text/plain'
331
+ elif self.url.endswith('.pdf'):
332
+ return 'application/pdf'
333
+ elif self.url.endswith('.rtf'):
334
+ return 'application/rtf'
335
+ elif self.url.endswith('.docx'):
336
+ return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
337
+ elif self.url.endswith('.xlsx'):
338
+ return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
339
+
315
340
  type_, _ = guess_type(self.url)
316
341
  if type_ is None:
317
- raise ValueError(f'Unknown document file extension: {self.url}')
342
+ raise ValueError(
343
+ f'Could not infer media type from document URL: {self.url}. Explicitly provide a `media_type` instead.'
344
+ )
318
345
  return type_
319
346
 
320
347
  @property
@@ -47,11 +47,6 @@ def openai_model_profile(model_name: str) -> ModelProfile:
47
47
  _STRICT_INCOMPATIBLE_KEYS = [
48
48
  'minLength',
49
49
  'maxLength',
50
- 'pattern',
51
- 'format',
52
- 'minimum',
53
- 'maximum',
54
- 'multipleOf',
55
50
  'patternProperties',
56
51
  'unevaluatedProperties',
57
52
  'propertyNames',
@@ -61,11 +56,21 @@ _STRICT_INCOMPATIBLE_KEYS = [
61
56
  'contains',
62
57
  'minContains',
63
58
  'maxContains',
64
- 'minItems',
65
- 'maxItems',
66
59
  'uniqueItems',
67
60
  ]
68
61
 
62
+ _STRICT_COMPATIBLE_STRING_FORMATS = [
63
+ 'date-time',
64
+ 'time',
65
+ 'date',
66
+ 'duration',
67
+ 'email',
68
+ 'hostname',
69
+ 'ipv4',
70
+ 'ipv6',
71
+ 'uuid',
72
+ ]
73
+
69
74
  _sentinel = object()
70
75
 
71
76
 
@@ -127,6 +132,9 @@ class OpenAIJsonSchemaTransformer(JsonSchemaTransformer):
127
132
  value = schema.get(key, _sentinel)
128
133
  if value is not _sentinel:
129
134
  incompatible_values[key] = value
135
+ if format := schema.get('format'):
136
+ if format not in _STRICT_COMPATIBLE_STRING_FORMATS:
137
+ incompatible_values['format'] = format
130
138
  description = schema.get('description')
131
139
  if incompatible_values:
132
140
  if self.strict is True:
@@ -158,11 +166,13 @@ class OpenAIJsonSchemaTransformer(JsonSchemaTransformer):
158
166
  schema['required'] = list(schema['properties'].keys())
159
167
 
160
168
  elif self.strict is None:
161
- if (
162
- schema.get('additionalProperties') is not False
163
- or 'properties' not in schema
164
- or 'required' not in schema
165
- ):
169
+ if schema.get('additionalProperties', None) not in (None, False):
170
+ self.is_strict_compatible = False
171
+ else:
172
+ # additional properties are disallowed by default
173
+ schema['additionalProperties'] = False
174
+
175
+ if 'properties' not in schema or 'required' not in schema:
166
176
  self.is_strict_compatible = False
167
177
  else:
168
178
  required = schema['required']
@@ -133,11 +133,19 @@ A = TypeVar('A')
133
133
 
134
134
  class GenerateToolJsonSchema(GenerateJsonSchema):
135
135
  def typed_dict_schema(self, schema: core_schema.TypedDictSchema) -> JsonSchemaValue:
136
- s = super().typed_dict_schema(schema)
137
- total = schema.get('total')
138
- if 'additionalProperties' not in s and (total is True or total is None):
139
- s['additionalProperties'] = False
140
- return s
136
+ json_schema = super().typed_dict_schema(schema)
137
+ # Workaround for https://github.com/pydantic/pydantic/issues/12123
138
+ if 'additionalProperties' not in json_schema: # pragma: no branch
139
+ extra = schema.get('extra_behavior') or schema.get('config', {}).get('extra_fields_behavior')
140
+ if extra == 'allow':
141
+ extras_schema = schema.get('extras_schema', None)
142
+ if extras_schema is not None:
143
+ json_schema['additionalProperties'] = self.generate_inner(extras_schema) or True
144
+ else:
145
+ json_schema['additionalProperties'] = True # pragma: no cover
146
+ elif extra == 'forbid':
147
+ json_schema['additionalProperties'] = False
148
+ return json_schema
141
149
 
142
150
  def _named_required_fields_schema(self, named_required_fields: Sequence[tuple[str, bool, Any]]) -> JsonSchemaValue:
143
151
  # Remove largely-useless property titles