Skip to content

Commit 5f6f1e1

Browse files
feat(llm): image content blocks (proposal 0015) (#44)
* feat(llm): add ProviderUnsupportedContentBlock error category Adds the provider_unsupported_content_block canonical category from llm-provider §7 (introduced by proposal 0015). Raised when the bound model does not support a content block type used in the request (e.g., a text-only model received an image block, or the model supports images but not the requested media_type or source variant). The exception carries block_type and reason attributes so callers can route on the specific unsupported case; mirrors the precedent StructuredOutputInvalid set in PR-1 (carry the structured payload the caller needs for diagnostics + recovery). Non-transient by default — NOT added to TRANSIENT_CATEGORIES. The bound model's capability set doesn't change between calls, so retrying without changing the request, the bound model, or the provider won't succeed. Users who want fallback semantics MAY route on the category in a userland middleware (e.g., switch to a multimodal-capable provider). Distinct from ProviderInvalidRequest: ProviderInvalidRequest covers spec-shape violations (the request is malformed); this category covers capability mismatches (the request is well-formed but the bound model can't fulfill it). * feat(llm): content-block types + UserMessage extension Adds the content-block surface from llm-provider §3.1 (proposal 0015): - TextBlock(type, text) with a non-empty-text validator - ImageSourceURL(type, url) and ImageSourceInline(type, base64_data), joined by an ImageSource discriminated union over the source's ``type`` field - ImageBlock(type, source, media_type, detail) with a validator that rejects inline sources missing a media_type. detail defaults to None so the wire omits the field unless explicitly set (providers apply their own conceptual default of "auto"); the docstring spells out the subtle case of an explicit detail="auto" - ContentBlock discriminated union over TextBlock | ImageBlock UserMessage.content becomes ``str | list[ContentBlock]``. The existing _check_content validator extends to enforce the non-empty rule on both shapes. Other roles (system, assistant, tool) stay text-string only — content blocks are user-only in v1 per the spec. media_type is typed as ``str | None`` (not a Literal of the three guaranteed types) so callers can pass additional image/* types providers document support for. * feat(llm/openai): content-array wire mapping + content-rejection mapping Two extensions in OpenAIProvider for proposal 0015: - _message_to_wire's user case now branches on content shape: string maps directly (the v0.4.0 form); a content-block sequence maps to OpenAI's content-array form per §8.1.1 via the new _block_to_wire helper. TextBlock → {type: "text", text}. ImageBlock(URL) → {type: "image_url", image_url: {url, detail?}}. ImageBlock(inline) constructs an RFC 2397 data: URI from media_type + base64_data and routes through the same image_url entry shape. The detail hint goes on the wire only when the spec block has it set (None on the spec block omits it from the wire; providers apply their own default of "auto" per §3.1.2). - classify_http_error's 400 branch now routes content-rejection bodies to ProviderUnsupportedContentBlock rather than the generic ProviderInvalidRequest. Detection is a heuristic on error.code (known set: image_content_not_supported, unsupported_image_media_type, audio_content_not_supported, video_content_not_supported, unsupported_content_block; plus an image+not_supported substring fallback), error.type (image_parse_error, image_content_not_supported), and error.message ("does not support" + image/audio/video). The spec is implementation-defined on the detection rule (§8.3); the heuristic lives inline so it's evolvable as OpenAI's error-code surface shifts. _extract_rejected_block_type pulls a best-effort "image" / "audio" / "video" identifier out of the error code or message for surfacing on ProviderUnsupportedContentBlock.block_type. * test(conformance): drive 0015 fixtures 009-020 Removes the 12 deferred-skip rows for content-block fixtures from both _DEFERRED_FIXTURES dicts (test_llm_provider.py runtime + the test_fixture_parsing.py typed parser). _build_message in test_llm_provider.py extends the user case to pass raw["content"] through (str or list) unchanged; Pydantic's discriminated union on the content-block ``type`` field parses each dict in the list to the right TextBlock / ImageBlock variant automatically. LlmCallSpec.messages in harness/directives.py is already typed as list[dict[str, Any]] (permissive), so the typed parser accepts the content-block list-of-dicts shape without model extensions. The parsing tests slip past for the 009-020 fixtures via the same path PR-1's 021-028 used. All 28 llm-provider conformance fixtures now pass (the prior 16 plus the 12 new content-block ones). Full suite: 515 pass, 72 skipped (down from 84 — only the 16 deferred fixtures for proposals 0011 / 0014 / 0017 remain). * test+docs: content-block unit tests + docs + CHANGELOG entry Adds tests/unit/test_content_blocks.py (24 tests) covering bits the conformance fixtures don't exercise directly: - TextBlock / ImageBlock construction validation (non-empty text, inline-needs-media_type, detail enum, URL source can skip media_type) - UserMessage construction from dict-form content blocks (the path the conformance test fixture loader uses) - _block_to_wire mapping for text, URL with/without detail, inline base64 (RFC 2397 data URI construction) - classify_http_error 400 routing to ProviderUnsupportedContentBlock via the heuristic; negative cases (unrelated 400 stays ProviderInvalidRequest) - _extract_rejected_block_type picks up "image" / "audio" from error.code or error.message Docs: - docs/concepts/llms.md: new "Content blocks (multimodal user messages)" section between Structured output and Routing, covering the two content shapes, URL vs inline sources, the detail hint, and the new ProviderUnsupportedContentBlock category. - docs/model-providers/index.md: errors table extended to 9 categories with the new row + a Behaviour-guarantees note that OpenAIProvider does post-receive detection only; pre-send is a userland-middleware pattern. - docs/model-providers/authoring.md: "Beyond the skeleton" gains a content-blocks entry pointing custom-provider authors at the multimodal wire mapping + the unsupported-content category. CHANGELOG [Unreleased] gains 3 entries: the user-message content extension, the OpenAI wire mapping, and the new error category. All in the same release as PR-1's 0016 entries per the consolidated- release strategy. * fix: CoPilot review pass on PR #44 - audio/video symmetry in the substring fallback of _looks_like_content_rejection - explicit isinstance(block, ImageBlock) guard in _block_to_wire to surface added union variants as a TypeError instead of an AttributeError on .source - clarify ImageBlock.media_type docstring: permitted but redundant on URL sources (the URL payload carries content-type), provider implementations MAY consume it as a hint - reword CHANGELOG qualifier '(proposal X, spec vY.Z)' → '(proposal X, introduced in spec vY.Z)' on the 0015 and 0016 entries so it doesn't read like a per-entry submodule pin change
1 parent 05806b2 commit 5f6f1e1

11 files changed

Lines changed: 730 additions & 37 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The
88

99
### Added
1010

11-
- **Structured output (proposal 0016, spec v0.14.0).** `Provider.complete()` now accepts an optional `response_schema` parameter — either a JSON Schema dict or a Pydantic `BaseModel` subclass. When supplied, the provider constrains the model's output to the schema and populates `Response.parsed` with the validated value (`dict` for dict-schema input, a `BaseModel` instance for class input). New `StructuredOutputInvalid` error category (non-transient by default) raises on JSON parse failure or schema validation failure; carries the requested schema, the raw response content, and a failure description.
11+
- **Image content blocks for user messages (proposal 0015, introduced in spec v0.13.0).** `UserMessage.content` now accepts `str | list[ContentBlock]`. The block surface introduces `TextBlock`, `ImageBlock`, `ImageSourceURL`, `ImageSourceInline`, and the `ContentBlock` / `ImageSource` discriminated unions over the block / source `type` field. `ImageBlock` carries a `media_type` (required for inline sources; ignored for URL sources; typed as `str | None` so callers MAY pass any `image/*` type the bound model supports) and an optional `detail` hint (`"auto"` / `"low"` / `"high"`; `None` default omits the field from the wire so providers apply their own default). System, assistant, and tool messages stay text-string-only; image inputs are user-only in v1.
12+
- **`OpenAIProvider` content-array wire mapping.** When `UserMessage.content` is a content-block sequence, the wire body uses OpenAI's `content` array per §8.1.1. `TextBlock → {type: "text", text}`. `ImageBlock` with a URL source maps to `{type: "image_url", image_url: {url, detail?}}`. `ImageBlock` with an inline source constructs an RFC 2397 `data:<media_type>;base64,<base64_data>` URI and goes through the same `image_url` entry shape. Inline bytes pass through unchanged — no inspection, transcoding, or re-encoding.
13+
- **New error category `ProviderUnsupportedContentBlock` (non-transient).** Raised when the bound model rejects a content block type / media variant. Distinct from `ProviderInvalidRequest` (which covers spec-shape malformation): this category surfaces a *capability* mismatch, letting callers route differently (e.g., fall back to a multimodal-capable provider) without overloading the malformed-request category. Carries `block_type` ("image" / "audio" / "video") and `reason` (provider's human-readable message) when those are recoverable from the rejection. `OpenAIProvider` detects content rejection via HTTP 400 bodies — heuristic on `error.code` (known set: `image_content_not_supported`, `unsupported_image_media_type`, `audio_content_not_supported`, etc.), `error.type` (`image_parse_error`), and `error.message` ("does not support" + image/audio/video).
14+
- **Structured output (proposal 0016, introduced in spec v0.14.0).** `Provider.complete()` now accepts an optional `response_schema` parameter — either a JSON Schema dict or a Pydantic `BaseModel` subclass. When supplied, the provider constrains the model's output to the schema and populates `Response.parsed` with the validated value (`dict` for dict-schema input, a `BaseModel` instance for class input). New `StructuredOutputInvalid` error category (non-transient by default) raises on JSON parse failure or schema validation failure; carries the requested schema, the raw response content, and a failure description.
1215
- **`OpenAIProvider` native response_format wire path.** When `response_schema` is supplied, the chat-completions request body carries `response_format: { type: "json_schema", json_schema: { name, schema, strict } }`. The `strict` flag is determined by a deep recursive walk over the schema (object-property required-coverage rule across `anyOf` / `oneOf` / `allOf` and `$ref` targets, with cycle protection); unresolvable refs fall through to `strict: false`. The `name` field uses `schema.title` when present, otherwise a deterministic sha256-prefix hash.
1316
- **`OpenAIProvider` prompt-augmentation fallback.** Constructor flag `force_prompt_augmentation_fallback: bool` (default `False`) and read-only inspect property `uses_prompt_augmentation_fallback: bool`. When the flag is on, structured-output calls build a fresh message list with a system directive containing the serialized schema, omit `response_format` from the wire, and validate the response post-receive. The caller's original `messages` list is never mutated. Use for OpenAI-compatible servers (older vLLM, some LM Studio releases, llama.cpp variants) that reject or silently ignore `response_format`.
1417
- **Provider-agnostic schema helpers.** `openarmature.llm.validate_response_schema(schema)` (raises `ProviderInvalidRequest` when the schema is not a dict with a top-level `type: "object"`) and `openarmature.llm.strict_mode_supported(schema)` (the deep-tree strict-mode constraint check) are exported for reuse by future Anthropic/Gemini providers.

docs/concepts/llms.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,117 @@ on every object. Pydantic-derived schemas may need `model_config =
221221
ConfigDict(extra="forbid")` on the class to get the
222222
`additionalProperties: false` in the generated JSON Schema.
223223

224+
## Content blocks (multimodal user messages)
225+
226+
User messages carry content in one of two shapes: a plain text string,
227+
or an ordered sequence of typed content blocks. The string form is the
228+
common case. Blocks are how you mix non-text modalities into a single
229+
turn. v1 defines two block types: text and image. Audio and video are
230+
deferred to future proposals.
231+
232+
System, assistant, and tool messages stay text-string only. Image
233+
inputs are user-only in v1; image outputs (assistant-message-borne
234+
images, e.g. DALL-E-style generation) are out of scope.
235+
236+
### Text and image blocks
237+
238+
A text block is the array-form equivalent of a text-string message:
239+
`TextBlock(text="describe this")`. A user message holding a single
240+
text block is normatively equivalent to one with `content="describe
241+
this"`.
242+
243+
An image block carries one source — URL or inline base64 — plus an
244+
optional `detail` hint:
245+
246+
```python
247+
from openarmature.llm import (
248+
ImageBlock,
249+
ImageSourceInline,
250+
ImageSourceURL,
251+
OpenAIProvider,
252+
TextBlock,
253+
UserMessage,
254+
)
255+
256+
257+
async def describe_image(provider: OpenAIProvider) -> str:
258+
response = await provider.complete(
259+
[
260+
UserMessage(
261+
content=[
262+
ImageBlock(
263+
source=ImageSourceURL(url="https://example.com/diagram.png"),
264+
detail="high", # optional; omitted from wire when None
265+
),
266+
TextBlock(text="What does this diagram show?"),
267+
]
268+
)
269+
]
270+
)
271+
return response.message.content
272+
```
273+
274+
Block order is preserved on the wire. Providers vary in whether they
275+
treat order as semantically meaningful (an image followed by its
276+
describing text is a different signal from text followed by the
277+
image); construct the sequence in the order you want the model to
278+
perceive it.
279+
280+
### URL vs inline sources
281+
282+
- **URL source** (`ImageSourceURL`): the provider fetches the URL. Any
283+
scheme the provider documents support for is valid (`http(s)://`,
284+
`data:`, etc.). The framework passes it through unchanged.
285+
- **Inline source** (`ImageSourceInline`): the image is sent as
286+
base64-encoded bytes in the request body. The `media_type` field on
287+
the surrounding `ImageBlock` is **required** for inline sources (and
288+
ignored for URL sources). The framework constructs an RFC 2397
289+
`data:<media_type>;base64,<bytes>` URI for the wire; it does not
290+
inspect, transcode, or re-encode the bytes.
291+
292+
OpenAI, Anthropic, and Google all accept `image/png`, `image/jpeg`,
293+
and `image/webp` as guaranteed media types. `media_type` is typed as
294+
`str | None`, so callers MAY pass additional `image/*` types when
295+
they know the bound model supports them; portable code sticks to the
296+
three.
297+
298+
### The `detail` hint
299+
300+
`detail` is a per-image hint to the provider about processing
301+
fidelity: `"auto"`, `"low"`, or `"high"`. The class default is `None`,
302+
which **omits the field from the wire** and lets the provider apply
303+
its own default (conceptually `"auto"`). Setting `detail="auto"`
304+
explicitly on the spec block forces the wire to carry an explicit
305+
`"auto"` — usually unnecessary, since the provider's default is the
306+
same value.
307+
308+
### When the model can't handle the block
309+
310+
`provider_unsupported_content_block` raises when the bound model
311+
rejects a content block type or media variant. Concrete cases:
312+
313+
- A text-only model (e.g., `gpt-3.5-turbo`) received an image block.
314+
- The model supports images but not the requested `media_type`.
315+
- The model supports the type but rejected the specific source variant
316+
(a URL the provider can't fetch, for example).
317+
318+
The error category is **non-transient**: retrying without changing
319+
the request, the bound model, or the provider won't succeed. Userland
320+
fallback patterns (e.g., a middleware that routes to a multimodal
321+
provider on this category) compose cleanly against it.
322+
323+
`ProviderUnsupportedContentBlock` carries `block_type` ("image",
324+
"audio", "video") and `reason` (the provider's human-readable
325+
message) when those are recoverable from the rejection.
326+
327+
`OpenAIProvider` detects content rejection via the response body —
328+
HTTP 400 with an error code like `image_content_not_supported` or a
329+
message like "does not support image inputs." Pre-send capability
330+
checks (failing fast before the wire trip when you know the model
331+
doesn't support images) live above the provider as userland
332+
middleware — the provider doesn't ship a static model-capability
333+
catalog.
334+
224335
## Routing on parsed fields
225336

226337
A conditional edge is a function `state -> str` that names the next

docs/model-providers/authoring.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,16 @@ of:
198198
- **Tool calls.** Wire-mapping the `tool_calls` array on
199199
`AssistantMessage` to the Provider's expected shape, parsing tool
200200
results back from `ToolMessage`s.
201+
- **Content blocks (multimodal user input).** Wire-mapping the
202+
`list[ContentBlock]` form of `UserMessage.content` to the provider's
203+
multimodal shape (OpenAI's `image_url` content-array entries,
204+
Anthropic's image blocks, Google's `inlineData` parts, etc.). The
205+
spec types (`TextBlock`, `ImageBlock`, `ImageSourceURL`,
206+
`ImageSourceInline`) are stable across providers; only the wire
207+
shape differs. Provider authors targeting non-multimodal models
208+
MUST surface `ProviderUnsupportedContentBlock` when the request
209+
carries blocks the bound model can't serve — pre-send or
210+
post-receive per §7.
201211
- **Structured output.** Threading `response_schema` through the
202212
request body (native `response_format` if the underlying wire
203213
supports it; prompt-augmentation fallback otherwise) and validating

docs/model-providers/index.md

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,35 @@ class Provider(Protocol):
6464

6565
## Errors
6666

67-
Eight canonical error categories cover every failure mode:
68-
69-
| Error | Trigger |
70-
| --------------------------- | ---------------------------------------------------------------------- |
71-
| `ProviderAuthentication` | 401 / 403 (bad key, expired token) |
72-
| `ProviderUnavailable` | 5xx, network failure, timeout |
73-
| `ProviderInvalidModel` | Bound model doesn't exist on the provider |
74-
| `ProviderModelNotLoaded` | Model known but not currently serving |
75-
| `ProviderRateLimit` | 429 (with `Retry-After` exposed) |
76-
| `ProviderInvalidResponse` | 200 OK that fails to parse |
77-
| `ProviderInvalidRequest` | Malformed request (per-message or list-level) |
78-
| `StructuredOutputInvalid` | Response failed to parse as JSON or failed to validate against schema |
67+
Nine canonical error categories cover every failure mode:
68+
69+
| Error | Trigger |
70+
| ---------------------------------- | ---------------------------------------------------------------------- |
71+
| `ProviderAuthentication` | 401 / 403 (bad key, expired token) |
72+
| `ProviderUnavailable` | 5xx, network failure, timeout |
73+
| `ProviderInvalidModel` | Bound model doesn't exist on the provider |
74+
| `ProviderModelNotLoaded` | Model known but not currently serving |
75+
| `ProviderRateLimit` | 429 (with `Retry-After` exposed) |
76+
| `ProviderInvalidResponse` | 200 OK that fails to parse |
77+
| `ProviderInvalidRequest` | Malformed request (per-message or list-level) |
78+
| `ProviderUnsupportedContentBlock` | Bound model rejected a content block (image / audio / media-type) |
79+
| `StructuredOutputInvalid` | Response failed to parse as JSON or failed to validate against schema |
7980

8081
Three of these (`Unavailable`, `RateLimit`, `ModelNotLoaded`) are
8182
exported in `TRANSIENT_CATEGORIES`, the canonical "safe to retry"
8283
set used by the default retry-middleware classifier.
83-
`StructuredOutputInvalid` is non-transient by default; see
84-
[Structured output](#structured-output) below.
84+
`StructuredOutputInvalid` and `ProviderUnsupportedContentBlock` are
85+
non-transient by default. See [Content blocks](../concepts/llms.md#content-blocks-multimodal-user-messages)
86+
in the LLMs concept page for the multimodal contract; see
87+
[Structured output](#structured-output) below for the
88+
`response_schema` path.
89+
90+
`OpenAIProvider` detects unsupported-content-block rejections via
91+
the response body (HTTP 400 with an error code or message indicating
92+
content rejection) — a post-receive mapping rather than a static
93+
pre-send capability check. Pre-send protection is a userland
94+
middleware pattern when callers know the bound model's capabilities
95+
up front.
8596

8697
## Structured output
8798

src/openarmature/llm/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
PROVIDER_MODEL_NOT_LOADED,
3131
PROVIDER_RATE_LIMIT,
3232
PROVIDER_UNAVAILABLE,
33+
PROVIDER_UNSUPPORTED_CONTENT_BLOCK,
3334
STRUCTURED_OUTPUT_INVALID,
3435
TRANSIENT_CATEGORIES,
3536
LlmProviderError,
@@ -40,12 +41,19 @@
4041
ProviderModelNotLoaded,
4142
ProviderRateLimit,
4243
ProviderUnavailable,
44+
ProviderUnsupportedContentBlock,
4345
StructuredOutputInvalid,
4446
)
4547
from .messages import (
4648
AssistantMessage,
49+
ContentBlock,
50+
ImageBlock,
51+
ImageSource,
52+
ImageSourceInline,
53+
ImageSourceURL,
4754
Message,
4855
SystemMessage,
56+
TextBlock,
4957
Tool,
5058
ToolCall,
5159
ToolMessage,
@@ -69,10 +77,16 @@
6977
"PROVIDER_MODEL_NOT_LOADED",
7078
"PROVIDER_RATE_LIMIT",
7179
"PROVIDER_UNAVAILABLE",
80+
"PROVIDER_UNSUPPORTED_CONTENT_BLOCK",
7281
"STRUCTURED_OUTPUT_INVALID",
7382
"TRANSIENT_CATEGORIES",
7483
"AssistantMessage",
84+
"ContentBlock",
7585
"FinishReason",
86+
"ImageBlock",
87+
"ImageSource",
88+
"ImageSourceInline",
89+
"ImageSourceURL",
7690
"LlmProviderError",
7791
"Message",
7892
"OpenAIProvider",
@@ -85,10 +99,12 @@
8599
"ProviderModelNotLoaded",
86100
"ProviderRateLimit",
87101
"ProviderUnavailable",
102+
"ProviderUnsupportedContentBlock",
88103
"Response",
89104
"RuntimeConfig",
90105
"StructuredOutputInvalid",
91106
"SystemMessage",
107+
"TextBlock",
92108
"Tool",
93109
"ToolCall",
94110
"ToolMessage",

src/openarmature/llm/errors.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
PROVIDER_RATE_LIMIT = "provider_rate_limit"
3030
PROVIDER_INVALID_RESPONSE = "provider_invalid_response"
3131
PROVIDER_INVALID_REQUEST = "provider_invalid_request"
32+
PROVIDER_UNSUPPORTED_CONTENT_BLOCK = "provider_unsupported_content_block"
3233
STRUCTURED_OUTPUT_INVALID = "structured_output_invalid"
3334

3435

@@ -137,6 +138,48 @@ class ProviderInvalidRequest(LlmProviderError):
137138
category = PROVIDER_INVALID_REQUEST
138139

139140

141+
# Non-transient by default — the bound model's capability set does
142+
# not change between calls, so retrying without changing the request
143+
# (the message list, the bound model, or the provider) will not
144+
# succeed.
145+
#
146+
# Distinct from ProviderInvalidRequest. ProviderInvalidRequest covers
147+
# spec-shape violations (the request is malformed at the wire layer);
148+
# ProviderUnsupportedContentBlock covers capability mismatches (the
149+
# request is well-formed but the bound model can't fulfill it).
150+
# Splitting them lets callers route the unsupported-content case
151+
# differently (e.g., fall back to a multimodal-capable provider)
152+
# without overloading the malformed-request category.
153+
class ProviderUnsupportedContentBlock(LlmProviderError):
154+
"""Raised when the bound model does not support a content block
155+
type used in the request.
156+
157+
Examples: a text-only model received an image block, or the model
158+
supports images but not the requested ``media_type`` or ``source``
159+
variant.
160+
161+
Attributes:
162+
block_type: The block type that was rejected (e.g., ``"image"``),
163+
when the provider's response makes this identifiable.
164+
reason: The provider's human-readable description of the
165+
rejection, when available.
166+
"""
167+
168+
category = PROVIDER_UNSUPPORTED_CONTENT_BLOCK
169+
block_type: str | None
170+
reason: str | None
171+
172+
def __init__(
173+
self,
174+
*args: Any,
175+
block_type: str | None = None,
176+
reason: str | None = None,
177+
) -> None:
178+
super().__init__(*args)
179+
self.block_type = block_type
180+
self.reason = reason
181+
182+
140183
# Non-transient by default — a model that fails schema compliance on a
141184
# given prompt usually fails the same way on retry. The default
142185
# RetryMiddleware classifier does NOT retry this category. Users wanting
@@ -184,6 +227,7 @@ def __init__(
184227
"PROVIDER_MODEL_NOT_LOADED",
185228
"PROVIDER_RATE_LIMIT",
186229
"PROVIDER_UNAVAILABLE",
230+
"PROVIDER_UNSUPPORTED_CONTENT_BLOCK",
187231
"STRUCTURED_OUTPUT_INVALID",
188232
"TRANSIENT_CATEGORIES",
189233
"LlmProviderError",
@@ -194,5 +238,6 @@ def __init__(
194238
"ProviderModelNotLoaded",
195239
"ProviderRateLimit",
196240
"ProviderUnavailable",
241+
"ProviderUnsupportedContentBlock",
197242
"StructuredOutputInvalid",
198243
]

0 commit comments

Comments
 (0)