Skip to content

feat(bedrock-batch): route /v1/embeddings JSONL to Titan v2 modelInput - #28865

Closed
OS-joaocastilho wants to merge 4 commits into
BerriAI:litellm_oss_branchfrom
OS-joaocastilho:feat/bedrock-batch-titan-embed-input-transform-v2
Closed

feat(bedrock-batch): route /v1/embeddings JSONL to Titan v2 modelInput#28865
OS-joaocastilho wants to merge 4 commits into
BerriAI:litellm_oss_branchfrom
OS-joaocastilho:feat/bedrock-batch-titan-embed-input-transform-v2

Conversation

@OS-joaocastilho

Copy link
Copy Markdown
Contributor

Relevant issues

Reopens the use case from #15506 (closed by stale-bot, no maintainer engagement). AWS Bedrock supports batch inference for amazon.titan-embed-text-v2:0 via CreateModelInvocationJob natively; the gap is only on the LiteLLM side, where BedrockFilesConfig._map_openai_to_bedrock_params had no embedding branch and an embedding JSONL would have silently been routed through the chat transformer.

Supersedes #28862, which was opened against main (wrong base) and accumulated force-pushes / close-reopen churn while we fixed up the base + addressed Greptile feedback. This PR is the clean, rebased version against litellm_oss_branch.

Changes vs litellm_oss_branch

  • litellm/llms/bedrock/files/transformation.py

    • BedrockFilesConfig._is_embedding_record static helper detects whether a JSONL line is an embedding request. Strict precedence: explicit url == \"/v1/embeddings\" -> embedding; any other non-empty url (e.g. /v1/chat/completions) -> NOT embedding (trust the caller's signal); only fall back to body-shape (input present AND messages absent) when url is missing.
    • BedrockFilesConfig._is_titan_v2_embed_model static helper accepts amazon.titan-embed-text-v2:0, bedrock/amazon.titan-embed-text-v2:0, us.amazon.titan-embed-text-v2:0 (cross-region inference profile), and ARN forms. Marker boundary check rejects lookalikes such as titan-embed-text-v20 or titan-embed-text-v2-experimental.
    • BedrockFilesConfig._coerce_embedding_input_to_string static helper normalizes the OpenAI input field into the single string Bedrock Titan v2 expects in inputText. Accepts string or single-element list; rejects None, multi-element lists, pre-tokenized inputs (List[int], List[List[int]]), and other types with actionable error messages.
    • New _map_openai_embedding_to_bedrock_params helper builds the Bedrock InvokeModel body via the existing AmazonTitanV2Config._transform_request, mapping OpenAI dimensions and encoding_format through AmazonTitanV2Config.map_openai_params so this stays in sync with the synchronous /v1/embeddings path.
    • _transform_openai_jsonl_content_to_bedrock_jsonl_content now dispatches per record to either the chat or the embedding transformer; the chat helper keeps its narrow contract.
    • Other embedding models (Titan G1, Titan Multimodal, Cohere Embed, Nova Multimodal Embeddings) raise NotImplementedError with a clear message until they get dedicated branches in follow-up PRs.
  • tests/test_litellm/llms/bedrock/files/

    • New input_batch_embeddings.jsonl and expected_bedrock_batch_embeddings.jsonl fixtures.
    • TestBedrockFilesEmbeddingTransformation class adds 17 mocked tests: happy-path round-trip; simple string input; dimensions + encoding_format mapping; body-shape fallback; single-element list unwrap; error paths (missing input, multi-element list, unsupported model, pre-tokenized List[int] and List[List[int]]); mixed chat+embedding batch in same JSONL; model-id boundary check; _is_embedding_record helper coverage; ambiguous "both input and messages" routes to chat; explicit chat URL short-circuits to chat; other non-embedding URLs route to chat; isolated coverage of the input-coercion helper.

Companion PR

The endpoint-propagation side (BedrockBatchesConfig.transform_*_batch_response reporting endpoint=\"/v1/embeddings\" correctly for embedding batches) lives in a separate PR to keep scope single-concern. That PR is also being filed under a v2 branch name.

Pre-Submission checklist

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement
  • My PR passes all unit tests on make test-unit (29/29 in tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py)
  • My PR's scope is as isolated as possible, it only solves 1 specific problem (input-side transform for Titan v2 embedding batches)
  • I will request @greptileai review after opening this PR

Type

New Feature

Changes vs main:
- BedrockFilesConfig now detects OpenAI batch JSONL lines whose `url`
  is /v1/embeddings (with body-shape fallback) and routes them through
  a new `_map_openai_embedding_to_bedrock_params` helper instead of the
  chat-completion transformer that silently produces an invalid body.
- The embedding helper currently supports Amazon Titan Text Embeddings V2
  only. Other embed models (Titan G1, Titan Multimodal, Cohere Embed,
  Nova Multimodal Embeddings) raise NotImplementedError with a clear
  message; each will get a dedicated branch + tests in follow-up PRs to
  keep schema-specific risks isolated.
- Validation refuses pre-tokenized inputs (List[int], List[List[int]])
  and multi-element string lists with explicit errors so callers emit
  one JSONL line per embedding instead of relying on us to fan out.
- Titan v2 model id match tolerates "bedrock/" prefix, cross-region
  inference profile prefix ("us.", "eu.", etc.), and ARN forms; the
  marker boundary check rejects lookalikes like "titan-embed-text-v20".
- Tests cover happy path (fixtures), dimensions/encoding_format mapping,
  body-shape fallback, single-element list unwrap, error paths
  (missing input, multi-element list, unsupported model, pre-tokenized),
  mixed chat+embedding batch, and the model-id boundary check.
Greptile-flagged gap in `_is_embedding_record`: when an OpenAI batch
JSONL line carries an explicit `url` pointing to a non-embedding
endpoint (e.g. `/v1/chat/completions`) AND its body happens to have
`input` without `messages`, the body-shape fallback would mis-route
that record to the embedding transformer and corrupt the modelInput.

Changes vs previous commit:
- `_is_embedding_record` now short-circuits to NOT-embedding whenever
  `url` is non-empty and not equal to `/v1/embeddings`. The body-shape
  fallback only runs when `url` is missing or empty. Docstring updated
  to spell out the precedence rules.
- Two new tests cover the case: direct helper assertion that an
  explicit chat url plus an input-bearing body returns False, plus an
  end-to-end check that the resulting modelInput contains no `inputText`
  key. A second test asserts the same short-circuit for arbitrary
  non-embeddings urls (`/v1/completions`, `/v1/responses`).

28/28 tests pass (was 26/26 before this commit + 2 new).
Splits the input-shape validation out of
`_map_openai_embedding_to_bedrock_params` into a new static helper
`_coerce_embedding_input_to_string`. Same semantics; the goal is to make
the validation testable in isolation and to give future
embedding-provider branches (Titan G1, Cohere) a reusable shaping
function instead of duplicating type checks.

- Helper accepts `str`, single-element `list[str]`, and raises
  `ValueError` / `NotImplementedError` with actionable messages for
  None, multi-element lists, pre-tokenized inputs (`list[int]` /
  `list[list[int]]`), and other unsupported types.
- New unit test exercises the helper directly across happy paths,
  None / missing input, multi-element string list, multi-element int
  list (caught as 'one input per JSONL record' since we can't
  disambiguate from 'multiple strings' without more context),
  pre-tokenized single-element list-of-list, single-element list of
  bare int, and dict input.

29/29 tests in the file still pass.
@OS-joaocastilho

Copy link
Copy Markdown
Contributor Author

@greptileai

@greptile-apps

greptile-apps Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds routing support for /v1/embeddings JSONL records in Bedrock batch inference, correctly dispatching amazon.titan-embed-text-v2:0 records to the Titan v2 InvokeModel schema via AmazonTitanV2Config, while all other embedding models raise NotImplementedError until dedicated branches are added.

  • Three static helpers (_is_embedding_record, _is_titan_v2_embed_model, _coerce_embedding_input_to_string) are introduced with strict URL-based routing precedence, a layered registry + marker boundary check, and tight input normalization; _transform_openai_jsonl_content_to_bedrock_jsonl_content now dispatches per-record based on these helpers.
  • 33 mocked unit tests cover happy-path round-trips, dimensions/encoding-format mapping, body-shape fallback, single-element list unwrapping, all error paths, mixed chat+embedding batches, registry-mode overrides, and boundary lookalike rejection — no real network calls are made.

Confidence Score: 5/5

Safe to merge — the change adds a new embedding routing branch in an isolated helper chain without touching the existing chat or passthrough paths.

The routing logic is well-contained: _is_embedding_record uses strict URL precedence so existing chat records cannot be accidentally redirected, _is_titan_v2_embed_model layers a registry mode check on top of the marker boundary so malformed lookalike ids are rejected, and _coerce_embedding_input_to_string fails loudly on every unsupported input shape rather than silently producing a corrupt body. All 33 tests are mocked, cover the error and boundary paths, and the fixture round-trip confirms the Titan v2 schema output is correct.

No files require special attention.

Important Files Changed

Filename Overview
litellm/llms/bedrock/files/transformation.py Adds embedding routing helpers and a new embedding-to-Bedrock param mapper; dispatch logic and boundary checks are correct, registry layering is sound.
tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py Adds 17 new mocked tests in TestBedrockFilesEmbeddingTransformation covering all routing branches, error paths, registry interactions, and boundary checks; no real network calls.
tests/test_litellm/llms/bedrock/files/input_batch_embeddings.jsonl Test fixture with three embedding records covering plain string, dimensions, and base64 encoding_format inputs.
tests/test_litellm/llms/bedrock/files/expected_bedrock_batch_embeddings.jsonl Expected Bedrock output fixture; encoding_format=base64 correctly maps to embeddingTypes=["binary"] per AmazonTitanV2Config.

Reviews (3): Last reviewed commit: "fix(bedrock-batch): layer registry mode ..." | Re-trigger Greptile

Comment thread litellm/llms/bedrock/files/transformation.py Outdated
Comment thread litellm/llms/bedrock/files/transformation.py
Comment thread litellm/llms/bedrock/files/transformation.py
@greptile-apps

greptile-apps Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR wires up /v1/embeddings JSONL records in Bedrock's batch-inference pipeline so that CreateModelInvocationJob receives a valid modelInput for amazon.titan-embed-text-v2:0, closing a gap where embedding records would previously have been silently routed through the chat transformer.

  • Adds _is_embedding_record, _is_titan_v2_embed_model, _coerce_embedding_input_to_string, and _map_openai_embedding_to_bedrock_params helpers to BedrockFilesConfig, and dispatches per-record in _transform_openai_jsonl_content_to_bedrock_jsonl_content.
  • Adds 17 mock-only unit tests plus two JSONL fixtures covering the happy path, parameter mapping (dimensions/encoding_format), error paths, mixed chat+embedding batches, and model-ID boundary checks.

Confidence Score: 3/5

The embedding transformation is logically correct and well-tested, but the model-detection logic is hardcoded directly in source rather than being driven by the model capability registry the rest of the codebase uses.

The routing and parameter-mapping logic work correctly for the supported model, and the 17 unit tests give good coverage of happy and error paths. However, _is_titan_v2_embed_model bakes "titan-embed-text-v2" directly into the library, meaning any future AWS batch-capable embedding model will silently fall through to NotImplementedError until users upgrade litellm — exactly the maintenance problem the repo's model-capability rules are designed to prevent.

litellm/llms/bedrock/files/transformation.py — the _is_titan_v2_embed_model function and _TITAN_V2_EMBED_MODEL_MARKER constant need to be replaced with a model_prices_and_context_window.json entry read via get_model_info.

Important Files Changed

Filename Overview
litellm/llms/bedrock/files/transformation.py Adds embedding routing via new static helpers; hardcodes Titan v2 model detection in violation of the repo rule requiring model capabilities to live in model_prices_and_context_window.json; unused provider parameter in _map_openai_embedding_to_bedrock_params.
tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py Adds 17 new mock-only tests covering happy paths, error paths, model boundary checks, and mixed chat+embedding batches. No real network calls.
tests/test_litellm/llms/bedrock/files/expected_bedrock_batch_embeddings.jsonl New fixture defining expected Bedrock modelInput for three embedding records; aligns with the encoding_format→embeddingTypes mapping in AmazonTitanV2Config.
tests/test_litellm/llms/bedrock/files/input_batch_embeddings.jsonl New OpenAI-format batch JSONL fixture used as round-trip input for the fixture comparison test.

Reviews (2): Last reviewed commit: "refactor(bedrock-batch): extract embeddi..." | Re-trigger Greptile

Comment thread litellm/llms/bedrock/files/transformation.py
Comment thread litellm/llms/bedrock/files/transformation.py
…param

Greptile-flagged issues on Titan v2 model detection:

1. Hardcoded substring without registry consultation conflicts with the
   project convention of treating `model_prices_and_context_window.json`
   as the source of truth for model capability flags.

   Fix: `_is_titan_v2_embed_model` now layers a registry mode check on
   top of the existing marker boundary. When `get_model_info` resolves
   the id, we additionally require `mode == "embedding"` so a malformed
   id whose path-component matches the marker but whose registered mode
   is "chat" doesn't slip through. Registry silence (cross-region
   inference profile prefixes like `us.amazon.titan-embed-text-v2:0`,
   ARN forms) keeps the substring-only behavior because the registry
   genuinely can't normalize those ids today. The substring is still
   needed because `mode == "embedding"` alone doesn't distinguish
   Titan v2's InvokeModel schema from Cohere Embed, Nova Multimodal,
   or Titan G1 - all also embedding mode, all with incompatible bodies.

2. `provider` parameter on `_map_openai_embedding_to_bedrock_params`
   was accepted but never used.

   Fix: dropped from the signature and the call site.

Also adds `_lookup_registry_mode` static helper (mirrors the one in the
sibling batches transformer) so the registry try/except shape lives in
one place instead of being inlined into the detector.

5 new tests pin the layered behavior:
- registry mode=chat overrides the marker match (rejected)
- registry mode=embedding + marker match (accepted)
- registry silent + marker match for cross-region and ARN ids (accepted)
- direct `_lookup_registry_mode` coverage across all return paths

33/33 tests in the file pass.
@OS-joaocastilho

Copy link
Copy Markdown
Contributor Author

Addressed both Greptile flags in 5342298:

Hardcoded model detection: _is_titan_v2_embed_model now layers a get_model_info registry check on top of the marker boundary. When the registry resolves the id, we additionally require mode == \"embedding\". Registry silence (cross-region inference profile prefixes, ARN forms) keeps the substring-only behavior because the registry doesn't normalize those today. The substring is still needed because mode == \"embedding\" alone doesn't distinguish Titan v2's InvokeModel schema from Cohere Embed / Nova Multimodal / Titan G1 (all mode == \"embedding\", all with incompatible bodies). Also added _lookup_registry_mode so the defensive try/except shape lives in one place.

Unused provider parameter: dropped from _map_openai_embedding_to_bedrock_params signature and the call site.

5 new tests pin the layered behavior: registry chat overrides marker (rejected), registry embedding + marker (accepted), registry silent + marker for cross-region and ARN ids (accepted), direct helper coverage across all return paths. 33/33 tests pass.

@greptileai

@OS-joaocastilho

Copy link
Copy Markdown
Contributor Author

Superseded by #28875 (fresh PR to reset Greptile's review state after it went silent on the follow-up fix commit). Code state is identical; Greptile's two flags were addressed in the latest commit on this branch and remain addressed on #28875.

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.

1 participant