## Issue
Closes#1153 — distinguish APIs for embedding queries vs. documents/keys
(adds `EmbeddingInputType.QUERY`/`DOCUMENT` as a per-call parameter,
plus opt-in `embeddingInputType(...)` on
`EmbeddingStoreContentRetriever` / `EmbeddingStoreIngestor`).
Partially addresses #4019 — adds the multimodal image-embedding API at
the core level (`EmbeddingInput` of `Content` parts) and wires Cohere,
Voyage, Jina, Google (Gemini Embedding 2), and Bedrock Titan; does
not implement it for `OnnxEmbeddingModel`.
Relates to #5142 — provider-specific / per-call parameters for OpenAI
embeddings (`OpenAiEmbeddingRequestParameters`: `user`,
`encodingFormat`, `customParameters`; e.g. NVIDIA NIM `input_type` via
custom
parameters).
Relates to #4273 — observability for `EmbeddingModel` via listeners
(`EmbeddingModelListener` + request/response/error contexts, wired
across providers).
## Change
Introduces an `EmbeddingModel.embed(EmbeddingRequest) →
EmbeddingResponse` API, structured like `ChatModel`'s request/response
API, so embeddings can carry **per-call parameters** and **multimodal
inputs** and
participate in **observability**. Everything is additive and
`@Experimental`; the existing `embed(String)` / `embed(TextSegment)` /
`embedAll(List)` methods keep working unchanged.
### Core (`langchain4j-core`)
- New request/response types: `EmbeddingRequest`, `EmbeddingResponse`,
`EmbeddingResponseMetadata`, `EmbeddingRequestParameters` (+
`DefaultEmbeddingRequestParameters` and typed `EmbeddingParameter<T>`
tokens), `EmbeddingInput`, `EmbeddingInputType`.
- New default methods on `EmbeddingModel`: `embed(EmbeddingRequest)`,
`doEmbed(...)`, `defaultRequestParameters()`, `supportedParameters()`,
`supportedContentTypes()`, `provider()`, `listeners()`.
- **Strict opt-in / fail-fast:** per-call parameters and content types
are token/type-checked; a request that uses something the model doesn't
declare is rejected with `UnsupportedFeatureException` instead of
being silently ignored. `overrideWith` preserves the provider-specific
parameters subtype (as on the chat side).
- **Multimodal:** an `EmbeddingInput` is an ordered list of `Content`
parts (text/image); models fuse them into one embedding (or
one-per-item, per provider). Modality is auto-detected — no manual flag.
- **Observability:** `EmbeddingModelListener` + request/response/error
contexts (same shape as `ChatModelListener`), fired inline from
`embed(EmbeddingRequest)`. `addListener(...)` still works.
- **RAG opt-in:** `EmbeddingStoreContentRetriever` and
`EmbeddingStoreIngestor` gain an optional `embeddingInputType(...)`
(QUERY / DOCUMENT). Default behavior is unchanged (no input type sent).
- `ModelProvider`: added `COHERE`, `VOYAGE_AI`, `JINA`, with matching
OpenTelemetry `gen_ai.provider.name` mappings (`cohere` is a well-known
OTel value; `voyage_ai` / `jina` are custom, as permitted by the
spec).
### Providers
- **OpenAI** (dimensions, `user`/`encodingFormat`/custom params),
**Cohere** (Embed v4 multimodal + input types), **Voyage** (multimodal +
input types), **Jina** (CLIP multimodal), **Google AI Gemini** (input
types; **Gemini Embedding 2** multimodal), **Amazon Bedrock Titan**
(multimodal).
- **Google Gen AI** (`langchain4j-google-genai`): input type → SDK
`task_type`, per-call dimensions → `outputDimensionality`, `provider()`,
listeners.
- **Ollama**: text-only — `provider()` + listeners (per-call params
correctly fail fast).
- **In-process models** (ONNX / `AbstractInProcessEmbeddingModel`):
already work via the default `doEmbed→embedAll` bridge (text-only,
image/param requests fail fast); observability via `addListener(...)`.
No
code change (no builders to wire listeners into, no dedicated
`ModelProvider`).
- **Gemini Embedding 2** dropped the `task_type` parameter, so input
types are applied as prompt instructions (`task: search result | query:
…` / `title: none | text: …`) automatically; `gemini-embedding-001`
still uses `task_type`.
- `modelName` in the response metadata reflects the API-reported model
where the provider returns one (OpenAI/Voyage/Jina), falling back to the
configured name.
### Tests
- `AbstractEmbeddingModelIT` — a shared IT base (like
`AbstractChatModelIT`) covering the new API, convenience methods,
listeners, and fail-fast; each provider adds a small
`common/…EmbeddingModelIT` that
parameterizes it and declares its capabilities via `supports*()`
overrides.
- Mock-based unit tests per provider for wire format / routing /
fail-fast (run in CI without keys), plus core value-type and listener
tests.
### Docs
- Embedding-model section in the RAG tutorial (request/response,
multimodal, query-vs-document opt-in), the EmbeddingModel listener
section in the Observability tutorial, the embedding contribution
guidance in
`CONTRIBUTING.md`, and the six provider integration pages.
### Notes
- `EmbeddingResponseMetadata` intentionally has no `finishReason`
(embeddings have no finish reason). No real provider is affected: the
only provider that emits `STOP` (Cloudflare WorkersAI) overrides the
convenience methods directly, and every other provider always returned
`null` here.
- `revapi.json` suppressions were added where the new (non-breaking)
types are exposed in provider APIs.
## General checklist
- [x] There are no breaking changes (API, behaviour)
- [x] I have added unit and/or integration tests for my change
- [x] The tests cover both positive and negative cases
- [x] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green
- [x] I have added/updated the documentation
- [ ] I have manually run all the unit and integration tests in the core
and main modules, and they are all green
- [ ] I have added an example in the examples repo (only for "big"
features)
- [ ] I have added/updated Spring Boot starter(s) (if applicable)
---------
Co-authored-by: agent <agent@langchain4j.dev>
## Summary
Add configurable cache TTL support for Bedrock prompt caching.
Currently, all cache points use the default 5-minute TTL because
`CachePointBlock` is created without a TTL parameter. The AWS Bedrock
SDK (`bedrockruntime` 2.41+) supports `CacheTTL` with `VALUE_5_M` (5
minutes) and `VALUE_1_H` (1 hour), but there is no way to configure it
through LangChain4j.
## Problem
The `CACHE_POINT_BLOCK` in `AbstractBedrockChatModel` is hardcoded as a
static final field without TTL:
```java
private static final SystemContentBlock CACHE_POINT_BLOCK = SystemContentBlock.builder()
.cachePoint(CachePointBlock.builder()
.type(CachePointType.DEFAULT)
// no TTL set → defaults to 5 minutes
.build())
.build();
```
Users who need 1-hour TTL (e.g., for APIs called less frequently than
every 5 minutes) must bypass LangChain4j and call the Bedrock SDK
directly, losing access to LangChain4j's listener-based metrics, message
conversion, and other features.
## Solution
- **`BedrockChatRequestParameters`** — Added `cacheTtl` field and
`promptCaching(placement, ttl)` overload:
```java
BedrockChatRequestParameters.builder()
.promptCaching(BedrockCachePointPlacement.AFTER_SYSTEM,
CacheTTL.VALUE_1_H)
.build()
```
- **`AbstractBedrockChatModel`** — Replaced static `CACHE_POINT_BLOCK`
with dynamic `buildCachePoint(cacheTtl)` method that creates
`CachePointBlock` with the specified TTL. Applied to all 4 cache point
creation sites (system messages, user messages, tool configurations).
- **`BedrockChatModel` + `BedrockStreamingChatModel`** — Pass `cacheTtl`
through to all `extract*` methods.
## Backward Compatibility
**Fully backward-compatible.** When `cacheTtl` is `null` (default), the
behavior is identical to the existing implementation:
- The existing `promptCaching(placement)` method continues to work
unchanged
- `buildCachePoint(null)` returns a `CachePointBlock` without TTL (same
as before)
- No changes required in existing user code
## Usage
```java
// Existing code — unchanged, still works (5-minute default)
BedrockChatRequestParameters.builder()
.promptCaching(BedrockCachePointPlacement.AFTER_SYSTEM)
.build()
// New — 1-hour TTL
BedrockChatRequestParameters.builder()
.promptCaching(BedrockCachePointPlacement.AFTER_SYSTEM, CacheTTL.VALUE_5_M)
.build()
```
## Changed Files
| File | Change |
|------|--------|
| `BedrockChatRequestParameters.java` | Added `cacheTtl` field +
`promptCaching(placement, ttl)` overload |
| `AbstractBedrockChatModel.java` | Replaced static cache point with
dynamic `buildCachePoint(cacheTtl)` |
| `BedrockChatModel.java` | Pass `cacheTtl` to extract methods |
| `BedrockStreamingChatModel.java` | Pass `cacheTtl` to extract methods
|
## References
- [AWS Bedrock Prompt Caching
docs](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html)
- AWS SDK `CacheTTL` enum: `VALUE_5_M`, `VALUE_1_H`
- 1-hour TTL supported models: Claude Sonnet 4.5, Claude Opus 4.5,
Claude Haiku 4.5
---------
Co-authored-by: Dmytro Liubarskyi <ljubarskij@gmail.com>
## Issue
Closes https://github.com/langchain4j/langchain4j/issues/3804
Closes https://github.com/langchain4j/langchain4j/issues/3320
## Change
- Integrate https://revapi.org/ (`revapi-maven-plugin`) into the build
to automatically detect breaking API changes
- Revapi runs during the `verify` phase on every build, comparing the
current code against the latest released version on Maven Central
(`RELEASE`)
- Suppress `java.method.varargOverloadsOnlyDifferInVarargParameter`
globally — this is a design warning about varargs overloads, not an
actual breaking change
- Add per-module `revapi.json` justifications for pre-existing
`java.class.nonPublicPartOfAPI` warnings in `langchain4j-ollama`,
`langchain4j-bedrock`, `langchain4j-onnx-scoring`, and
`langchain4j-google-ai-gemini` — these are package-private classes
exposed through protected members on package-private base classes, so
they cannot actually leak to external
users
- Do not check depndencies - we do not expose them explicitly and are
not responsible for any changes there
- skip `integration-tests` modules - we do not release them
### How it works
- Revapi downloads the latest released JAR from Maven Central and
compares it against the locally built JAR at the bytecode level
- Any removal, signature change, or visibility reduction of public API
elements will fail the build
- Adding new public API is always fine
- To justify an intentional breaking change, add a `revapi.json` in the
module root — justifications auto-expire on the next release since the
baseline moves forward
## General checklist
- [X] There are no breaking changes (API, behaviour)
- [X] I have added unit and/or integration tests for my change
- [X] The tests cover both positive and negative cases
- [X] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green
- [X] I have manually run all the unit and integration tests in the
[core](https://github.com/langchain4j/langchain4j/tree/main/langchain4j-core)
and
[main](https://github.com/langchain4j/langchain4j/tree/main/langchain4j)
modules, and they are all green
- [ ] I have added/updated the
[documentation](https://github.com/langchain4j/langchain4j/tree/main/docs/docs)
- [ ] I have added an example in the [examples
repo](https://github.com/langchain4j/langchain4j-examples) (only for
"big" features)
- [ ] I have added/updated [Spring Boot
starter(s)](https://github.com/langchain4j/langchain4j-spring) (if
applicable)