Skip to content

fix(consolidation): stop emitting unsupported maxItems that breaks all Bedrock consolidation (#2500) - #2502

Merged
nicoloboschi merged 3 commits into
vectorize-io:mainfrom
r266-tech:fix/consolidation-bedrock-maxitems-2500
Jul 27, 2026
Merged

fix(consolidation): stop emitting unsupported maxItems that breaks all Bedrock consolidation (#2500)#2502
nicoloboschi merged 3 commits into
vectorize-io:mainfrom
r266-tech:fix/consolidation-bedrock-maxitems-2500

Conversation

@r266-tech

Copy link
Copy Markdown
Contributor

Fixes #2500.

Problem

Setting max_observations_per_scope > 0 on a Bedrock-backed bank (any Claude model via Bedrock Converse) causes 100% of consolidation batches to fail — every batch fails all 3 retries and is silently skipped, so memory quality decays permanently:

litellm.BadRequestError: BedrockException - {"message":"The model returned the following errors:
output_config.format.schema: For 'array' type, property 'maxItems' is not supported"}

Root cause

_build_response_model() dynamically subclasses the consolidation response model when an observation cap is active and puts a Pydantic max_length on creates. Pydantic v2 serializes list max_length to the JSON-schema maxItems keyword, and Bedrock Converse rejects maxItems on array types outright — so a single maxItems in the structured-output schema breaks the whole call.

Fix

Drop the schema-level constraint: _build_response_model() now always returns the base _ConsolidationBatchResponse, whose creates field carries no length constraint, so the emitted schema is provider-portable.

This is safe because the observation cap does not depend on the schema hint — it is enforced by two other layers that are unchanged:

  1. Prompt capacity note — whenever the cap binds (remaining_observation_slots <= len(memories), or == 0), the consolidation prompt already tells the model "This scope has {k} observation slot(s) remaining … Prefer UPDATE over CREATE" / "OBSERVATION LIMIT REACHED … Do NOT create new ones". Every provider (Bedrock or not) still receives the cap.
  2. Authoritative truncation — the batch-call path unconditionally does creates = creates[:remaining_observation_slots], with your own comment "some LLM providers may not enforce JSON schema max_length". This was already the guaranteed enforcement path.

Known tradeoff (documented deliberately)

Providers that did honor maxItems lose that redundant schema-level ceiling and now rely on the prompt note + keep-first-N truncation. But keep-first-N was already your designated fallback for non-honoring providers, and consolidation creates are not priority-ordered, so this matches existing semantics. If you'd prefer to preserve the hint for compatible providers, a provider-gated variant (emit maxItems only when the provider supports it) is a straightforward follow-up — I kept this PR to the minimal, provably-safe change that unblocks Bedrock.

Tests

TestBuildResponseModel is rewritten to the new contract:

End-to-end cap enforcement remains covered by the existing test_max_observations_per_scope_* integration tests (which exercise the truncation path via a mock LLM, independent of schema validation).

@nicoloboschi nicoloboschi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@r266-tech can you make this change only true if a new config about the llm "supports max items" is set to false (default = true). the maxItems is actually very important here and removing from any llm is quite concerning to me

@r266-tech
r266-tech force-pushed the fix/consolidation-bedrock-maxitems-2500 branch 2 times, most recently from 36c717c to d56ae5e Compare July 20, 2026 17:22
@r266-tech

Copy link
Copy Markdown
Contributor Author

Reworked this around an explicit capability flag as requested.

HINDSIGHT_API_LLM_SUPPORTS_MAX_ITEMS defaults to true, so existing schema-level maxItems enforcement remains unchanged. Operators on Bedrock Converse (or another backend that rejects the keyword) can set it to false; that choice is read from the operation's config, and the existing runtime truncation remains authoritative.

I also added zero-slot and positive-slot tests that make the LLM overproduce with schema enforcement disabled and assert the returned creates are still capped, plus environment/default and schema-shape coverage. The focused tests pass (10 tests), changed-source Ruff checks pass, and the reworked diff passed Codex adversarial review.

@nicoloboschi nicoloboschi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@nicoloboschi

Copy link
Copy Markdown
Collaborator

@r266-tech please rebase

r266-tech and others added 3 commits July 22, 2026 00:35
…l Bedrock consolidation (vectorize-io#2500)

_build_response_model attached a Pydantic max_length to creates, which serializes to JSON-schema maxItems; Bedrock Converse rejects maxItems on array types, failing 100% of consolidation for capped Bedrock banks. The cap is already enforced by the prompt capacity note + unconditional truncation to remaining_observation_slots, so the schema constraint is dropped.
…-io#2500 regression)

Rewrite TestBuildResponseModel to the new contract: factory always returns the base model, schema omits maxItems (Bedrock-compatible), over-cap creates are accepted (truncated downstream) rather than rejected. End-to-end cap enforcement remains covered by the existing max_observations_per_scope integration tests.
@r266-tech
r266-tech force-pushed the fix/consolidation-bedrock-maxitems-2500 branch from d56ae5e to ea9a6d7 Compare July 21, 2026 16:57
@r266-tech

Copy link
Copy Markdown
Contributor Author

Rebased onto a23187a4 as requested.

During the post-rebase adversarial review I also tightened the new capability flag: it now defaults to true for legacy direct HindsightConfig construction, and ambiguous environment values fail fast instead of silently disabling maxItems. The focused schema/config/runtime-cap tests pass (15 tests), along with changed-source Ruff and ty checks. CI is running on ea9a6d77.

@nicoloboschi
nicoloboschi merged commit c908fad into vectorize-io:main Jul 27, 2026
88 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

max_observations_per_scope > 0 breaks ALL consolidation on Bedrock: emits unsupported "maxItems" JSON Schema keyword

2 participants