## Issue
Closes#5800
## Change
`Utils.readBytes(String url)` sends `Accept-Encoding: gzip, deflate` but
reads the
response body as a **raw** `InputStream`. `HttpURLConnection` only
decompresses gzip
transparently when it adds the `Accept-Encoding` header itself; because
the header is set
explicitly here, the JDK disables transparent decoding and returns the
body exactly as
sent. So when a server responds with `Content-Encoding: gzip` (nginx,
CDNs, S3, most image
hosts), `readBytes` returns the still-compressed bytes.
`readBytes(url)` is used to inline remote images/PDFs as base64 for
several model
integrations (Ollama, Bedrock, Vertex/Gemini image mappers,
`Image`/`PdfFile` helpers), so
a gzip-serving host silently produced a corrupted payload.
This PR decodes the response stream according to the `Content-Encoding`
header via a small
private helper:
- `gzip` / `x-gzip` → `GZIPInputStream`
- `deflate` → `InflaterInputStream` (zlib, per RFC 9110)
- anything else / no header → the raw stream, unchanged
No new dependencies. The uncompressed happy path is byte-for-byte
unchanged (same stream,
same copy loop). The wrapper is created inside the existing
try-with-resources, so closing
it closes the underlying HTTP stream.
Added `/gzip_endpoint` and `/deflate_endpoint` cases to
`UtilsTest.read_bytes` that serve a
compressed body with the matching `Content-Encoding` header and assert
the decoded bytes;
both fail against the previous behaviour.
## General checklist
- [X] There are no breaking changes (API, behaviour) — uncompressed
responses are unchanged
- [X] I have added unit and/or integration tests for my change
- [X] The tests cover both positive and negative cases — decoded
gzip/deflate plus the existing raw/uncompressed and HTTP-error paths
- [X] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green — `langchain4j-core`
`UtilsTest` (46/46) and `spotless:check` pass
- [ ] I have manually run all the unit and integration tests in the core
and main modules, and they are all green
- [ ] I have added/updated the documentation — n/a, internal utility, no
public API or behaviour visible in docs
- [ ] I have added an example in the examples repo (only for "big"
features) — n/a
- [ ] I have added/updated Spring Boot starter(s) (if applicable) — n/a
---------
Co-authored-by: Timur Rakhmatullin <174210871+TimurRakhmatullin86@users.noreply.github.com>
Co-authored-by: Dmytro Liubarskyi <ljubarskij@gmail.com>
## 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>
## Issue
Closes#5721
## Change
`RetryPolicy.jitterDelayMillis(int)` passed `(int) jitter` straight to
`Random.nextInt(bound)`, which requires a **strictly positive** bound.
The bound rounds down to `0` in two realistic configurations:
1. **Jitter disabled** — `jitterScale == 0` (the natural way to turn
jitter off; nothing validates against it).
2. **Small base delay** — when `rawDelayMs(retry) * jitterScale < 1`
(e.g. `delayMillis(1)` with the default `jitterScale = 0.2`).
In both cases `Random.nextInt(0)` throws `IllegalArgumentException:
bound must be positive`. Since `sleep(retry)` calls
`jitterDelayMillis(retry)` on every retry, such a policy throws on the
first retriable failure instead of retrying, masking the original error.
The default policy (`jitterScale = 0.2`, `delayMillis = 500`) is
unaffected, which is why the existing `jitter()` test didn't catch it.
**Fix:** when the jitter bound is `<= 0` there is nothing to add, so
return the base delay unchanged.
Added two unit tests (disabled jitter, and base delay too small for
jitter). Both fail on `main` with `IllegalArgumentException` and pass
with this change; the existing `jitter()` test for the default policy is
unchanged.
## 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
- [ ] I have manually run all the unit and integration tests in the core
and main modules, and they are all green
- [ ] I have added/updated the documentation
- [ ] I have added an example in the examples repo (only for "big"
features)
- [ ] I have added/updated Spring Boot starter(s) (if applicable)
<!-- Note: ran the affected `langchain4j-core` unit tests
(`RetryUtilsTest`, 12/12 green) and `spotless:apply`/`check` on the
changed files. Did not run the full core+main integration suites, which
require provider credentials; this change is a self-contained internal
utility fix with no documentation or API surface impact. -->
---------
Co-authored-by: Dmytro Liubarskyi <ljubarskij@gmail.com>
<!--
Thank you so much for your contribution!
Please fill in all the sections below.
Please open the PR as ready for review (not as a draft), with tests and
documentation already included.
Please note that PRs with breaking changes, or without tests and
documentation, will be rejected.
Please note that PRs will be reviewed based on the priority of the
issues they address.
We ask for your patience. We are doing our best to review your PR as
quickly as possible.
Please refrain from pinging and asking when it will be reviewed. Thank
you for understanding!
-->
## Issue
<!-- Please specify the ID of the issue this PR is addressing. For
example: "Closes #1234" or "Fixes #1234" -->
Closes#5696
## Change
<!-- Please describe the changes you made. -->
## General checklist
<!-- Please double-check the following points and mark them like this:
[X] -->
- [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
- [ ] 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)
## Checklist for adding new maven module
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have added my new module in the root `pom.xml` and
`langchain4j-bom/pom.xml`
## Checklist for adding new embedding store integration
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreIT` that extends
from either `EmbeddingStoreIT` or `EmbeddingStoreWithFilteringIT`
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreRemovalIT` that
extends from `EmbeddingStoreWithRemovalIT`
## Checklist for changing existing embedding store integration
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have manually verified that the
`{NameOfIntegration}EmbeddingStore` works correctly with the data
persisted using the latest released version of LangChain4j
## Issue
Closes#5716
## Change
`IsIn`/`IsNotIn` filters used a different numeric-conversion path than
`IsEqualTo`/`IsNotEqualTo`, causing the same metadata value to be
matched inconsistently between them:
- `IsEqualTo`/`IsNotEqualTo` → `NumberComparator.compareAsBigDecimals` →
`new BigDecimal(value.toString())`.
- `IsIn`/`IsNotIn` → `NumberComparator.containsAsBigDecimals` → a
private `toBigDecimal` helper using `BigDecimal.valueOf(floatValue)`,
which implicitly widens `float` to `double` first, introducing
floating-point representation error (e.g. `1.1f` becomes
`1.100000023841858` instead of `1.1`).
So `isEqualTo(1.1)` could match a `Float` metadata value that
`isIn(List.of(1.1))` did not match on the exact same data.
This PR makes `containsAsBigDecimals` use the same `new
BigDecimal(value.toString())` conversion as `compareAsBigDecimals`, for
both the actual value and every comparison value. This also removes the
narrow `Integer`/`Long`/`Float`/`Double` type whitelist in
`toBigDecimal` (which threw `IllegalArgumentException` for other
`Number` subtypes), aligning `IsIn`/`IsNotIn` with `IsEqualTo`'s more
permissive `Number`-based handling.
## General checklist
- [X] There are no breaking changes (API, behaviour) — this only fixes
an inconsistency; the corrected result matches what
`IsEqualTo`/`IsNotEqualTo` already produced
- [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)
## Test plan
- Added `IsInTest` and `IsNotInTest` (previously no dedicated test
classes existed for these filters), covering the existing documented
behavior plus a `shouldBeConsistentWithIsEqualToForFloatMetadata` /
`shouldBeConsistentWithIsNotEqualToForFloatMetadata` regression test
each.
- Verified both consistency tests fail without the fix (`Expecting value
to be true but was false` / `Expecting value to be false but was true`)
and pass with it applied; all other pre-existing filter tests were
unaffected.
- Ran the full `langchain4j-core` suite (1149 tests) — all green.
- Ran `./mvnw spotless:apply`/`spotless:check` — clean.
## Summary
Adds **Text-to-Speech (TTS)** support to LangChain4j: a new
provider-agnostic `TextToSpeechModel` abstraction in `langchain4j-core`,
an OpenAI implementation backed by the [OpenAI Speech
API](https://platform.openai.com/docs/api-reference/audio/createSpeech),
and the HTTP-client plumbing needed to return binary (audio) response
bodies intact.
## Changes
### `langchain4j-core` — new abstraction (`dev.langchain4j.model.audio`,
`@Experimental`)
- **`TextToSpeechModel`** — interface mirroring the existing
`AudioTranscriptionModel`:
- `synthesize(String text)` — convenience method using the model's
default voice
- `synthesize(TextToSpeechRequest request)`
- `provider()` defaulting to `OTHER`
- **`TextToSpeechRequest`** — carries `text` (validated non-blank) and
optional `voice`
- **`TextToSpeechResponse`** — wraps the core `Audio` type;
`from(Audio)` factory
### `langchain4j-open-ai` — OpenAI implementation
- **`OpenAiTextToSpeechModel`** — configurable `modelName`, `voice`
(default `alloy`), `timeout`, `maxRetries` (default `2`),
request/response logging, custom `baseUrl`, and `httpClientBuilder`.
Enforces
OpenAI's 4096-character input limit. `provider()` → `OPEN_AI`.
- **`OpenAiTextToSpeechModelName`** — `tts-1`, `tts-1-hd`,
`gpt-4o-mini-tts`, `gpt-4o-mini-tts-2025-12-15`
- **`OpenAiTextToSpeechRequest` / `OpenAiTextToSpeechResponse`** —
internal DTOs (`internal.audio.texttospeech`); the response is built
from the raw HTTP body (binary audio, not JSON)
- **`OpenAiTextToSpeechModelBuilderFactory`** — SPI factory for builder
customization
- **`OpenAiClient.textToSpeech(...)`** — `POST /audio/speech`, mapping
the raw bytes + `Content-Type` into the response
### HTTP client layer (shared) — binary response support
- **`SuccessfulHttpResponse`** — body is now stored as `byte[]`.
`body()` is preserved and returns a decoded `String` (using the charset
from the `Content-Type` header, UTF-8 fallback); adds `bodyBytes()` for
raw binary access and `contentType()`. The `body(String)` builder is
kept and a `body(byte[])` overload added — **additive, no public method
removed or changed**.
- **`JdkHttpClient`** — switched to `BodyHandlers.ofByteArray()`;
**`ApacheHttpClient` / `OkHttpClient`** read the body as raw bytes, so
binary payloads (audio) are no longer corrupted by text decoding.
- **`HttpResponseLogger`** — no longer decodes/dumps binary bodies as
text; logs `[binary body, N bytes, content-type: ...]` instead.
### Tests
- Core: `TextToSpeechModelTest`, `TextToSpeechRequestTest`
- HTTP: `SuccessfulHttpResponseTest` (charset decoding/edge cases),
`HttpClientIT` binary-response test asserting a valid MP3 is returned
across all client implementations
- OpenAI: `OpenAiTextToSpeechModelTest` (input-length and required-field
validation), `OpenAiTextToSpeechModelIT` (parameterized over every
supported model)
### Docs & API compatibility
- Added a **"Creating `OpenAiTextToSpeechModel`"** section to the OpenAI
docs page
- `revapi.json` updated to acknowledge the core
`TextToSpeechRequest`/`TextToSpeechResponse` types intentionally exposed
by `OpenAiTextToSpeechModel`
## Design notes
- Named `TextToSpeechModel` / `synthesize(...)` for discoverability (TTS
is the universally recognized term) and to read as a clear inverse of
the existing `AudioTranscriptionModel` / `transcribe(...)`.
- The shared HTTP-client change is **behavior-preserving**: for every
client (JDK / Apache / OkHttp), responses without an explicit charset
were already decoded as UTF-8, and explicit charsets are still
honored — verified empirically against `httpclient5 5.6.1` / `httpcore5
5.4`. The `String → byte[]` move is additive at the public API level.
## General checklist
- [x] There are no breaking changes (API, behaviour) — the shared
`SuccessfulHttpResponse` change is additive and verified
behavior-preserving; please review the HTTP-client layer regardless, as
it affects all
providers
- [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
- [x] 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)
---------
Co-authored-by: Dmytro Liubarskyi <ljubarskij@gmail.com>
## Issue
Closes#5116
## Change
Adds a new **`onUnmappedRawEvent`** streaming callback that gives
advanced users access to provider streaming events that LangChain4j does
**not** map to one of its typed callbacks — e.g. OpenAI server-tool
lifecycle events (`response.web_search_call.in_progress` / `searching` /
`completed`). This is an escape hatch so power users don't have to fall
back to a provider's native SDK for events we don't model yet.
**API** (both `@Experimental`, `@since 1.17.0`, `default` so nothing
breaks):
- `StreamingChatResponseHandler.onUnmappedRawEvent(Object rawEvent)` —
low-level API
- `TokenStream.onUnmappedRawEvent(Consumer<Object> rawEventHandler)` —
AI Services API
**Semantics — no duplication.** The callback fires **only** for events
that were *not* already delivered via a typed callback
(`onPartialResponse`, `onPartialThinking`, `onPartialToolCall`,
`onCompleteToolCall`, `onCompleteResponse`). So you can consume the
typed callbacks and the raw stream together without seeing the same
event twice. This is enforced by a small internal
`MappingTrackingStreamingChatResponseHandler` that records whether an
event was mapped to a typed callback (`wasMapped()`), and providers emit
the raw event only when it wasn't.
> Naming note: it's called `onUnmappedRawEvent` (not `onRawEvent`) to
leave room for a planned follow-up that also exposes the raw event
behind *mapped* callbacks (e.g. `PartialToolCall.rawEvent()`), giving a
clean mapped/unmapped split.
**The concrete `rawEvent` type is provider-specific:**
| Provider | Raw event type |
|---|---|
| OpenAI, Anthropic, Google AI Gemini, Mistral, Ollama |
`dev.langchain4j.http.client.sse.ServerSentEvent` |
| OpenAI (official) – Responses API |
`com.openai.models.responses.ResponseStreamEvent` |
| OpenAI (official) – Chat Completions API |
`com.openai.models.chat.completions.ChatCompletionChunk` |
| Amazon Bedrock |
`software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput`
|
| Google GenAI | `com.google.genai.types.GenerateContentResponse` |
**Providers wired:** OpenAI (Chat Completions + Responses),
OpenAI-official (Chat Completions + Responses), Anthropic, Google AI
Gemini, Google GenAI, Amazon Bedrock, Mistral, Ollama.
**Docs:** added a "Unmapped Raw Events" section to
`response-streaming.md` and the callback to the `TokenStream` example in
`ai-services.md`.
## General checklist
- [x] There are no breaking changes (API, behaviour) — all additions are
`default` and `@Experimental`
- [x] I have added unit and/or integration tests for my change
- [x] The tests cover both positive and negative cases — i.e. unmapped
events are forwarded *and* typed (text/tool) events are **not** repeated
as raw events
- [x] I have manually run all the unit and integration tests in the
module(s) 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
- [x] I have added/updated the
[documentation](https://github.com/langchain4j/langchain4j/tree/main/docs/docs)
<!--
Thank you so much for your contribution!
Please fill in all the sections below.
Please open the PR as a draft initially. Once it is reviewed and
approved, we will ask you to add documentation and examples.
Please note that PRs with breaking changes or without tests will be
rejected.
Please note that PRs will be reviewed based on the priority of the
issues they address.
We ask for your patience. We are doing our best to review your PR as
quickly as possible.
Please refrain from pinging and asking when it will be reviewed. Thank
you for understanding!
-->
## Issue
<!-- Please specify the ID of the issue this PR is addressing. For
example: "Closes #1234" or "Fixes #1234" -->
Closes #
## Change
Add a new `@ReverseTool` annotation allowing to declare the method
reversing the action performed by a tool. When an `AiService` is
configured as `transactional` (the name is very questionable let me know
if you have any better idea about it) and a tool invocation fails, the
reverse tool are invoked to roll back all the actions taken during the
invocation of that `AiService`.
@dliubarskyi My plan is to use this feature also in the agentic module,
but I will eventually do this with a follow up pull request, once we
will find an agreement on this.
@jmartisk I don't know if this has some impact on MCP tools and if this
feature could be extended also for them. Please let me know if you have
any advice on this regard.
## General checklist
<!-- Please double-check the following points and mark them like this:
[X] -->
- [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
<!-- Before adding documentation and example(s) (below), please wait
until the PR is reviewed and approved. -->
- [x] 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)
## Checklist for adding new maven module
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have added my new module in the root `pom.xml` and
`langchain4j-bom/pom.xml`
## Checklist for adding new embedding store integration
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreIT` that extends
from either `EmbeddingStoreIT` or `EmbeddingStoreWithFilteringIT`
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreRemovalIT` that
extends from `EmbeddingStoreWithRemovalIT`
## Checklist for changing existing embedding store integration
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have manually verified that the
`{NameOfIntegration}EmbeddingStore` works correctly with the data
persisted using the latest released version of LangChain4j
---------
Co-authored-by: Dmytro Liubarskyi <ljubarskij@gmail.com>
## Issue
Closes#5510
## Change
`CustomMimeTypesFileTypeDetector`'s default video mappings had a typo in
the WMV entry key: `defaultMappings.put("mmv", "video/wmv")`. All
sibling video entries key on the file extension (`mp4`, `mov`, `avi`,
`webm`, …), and the value `video/wmv` makes the intended key `"wmv"`
obvious. With the typo, real `.wmv` files never matched the default
mapping and fell through to the JDK (`Files.probeContentType` →
`video/x-ms-wmv`), while the `.mmv` key was dead (no such extension).
This affects multimodal video MIME detection for the Gemini/Vertex AI
providers.
This PR changes the key `"mmv"` → `"wmv"`, keeping the value
`"video/wmv"` unchanged. One production line; no behaviour change for
any other extension, no API change, no new dependency. Spotless
(palantir) collapses the manual column alignment only on the changed
line, as expected for `ratchetFrom origin/main`.
## Tests
Added a deterministic unit test mirroring the existing default-mapping
String assertions:
`assertThat(new
CustomMimeTypesFileTypeDetector().probeContentType("video.wmv")).isEqualTo("video/wmv");`
This is a positive fail-then-pass assertion: it fails on current `main`
(returns the JDK fallback `video/x-ms-wmv`) and passes after the fix. A
negative case is not meaningful for a mapping-key correction, so none
was added.
## General checklist
- [X] There are no breaking changes (API, behaviour)
- [X] I have added unit and/or integration tests for my change
- [ ] The tests cover both positive and negative cases — positive-only;
negative case not meaningful for a mapping-key fix
- [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
and main modules, and they are all green
- [ ] I have added/updated the documentation — not applicable (internal
mapping fix)
- [ ] I have added an example in the examples repo — not applicable
- [ ] I have added/updated Spring Boot starter(s) — not applicable
<!-- "Checklist for adding new maven module" omitted: no new module. -->
<!-- "Checklist for adding/changing embedding store integration"
omitted: not an embedding store change. -->
<!--
Thank you so much for your contribution!
Please fill in all the sections below.
Please open the PR as a draft initially. Once it is reviewed and
approved, we will ask you to add documentation and examples.
Please note that PRs with breaking changes or without tests will be
rejected.
Please note that PRs will be reviewed based on the priority of the
issues they address.
We ask for your patience. We are doing our best to review your PR as
quickly as possible.
Please refrain from pinging and asking when it will be reviewed. Thank
you for understanding!
-->
## Issue
<!-- Please specify the ID of the issue this PR is addressing. For
example: "Closes #1234" or "Fixes #1234" -->
Closes #
## Change
<!-- Please describe the changes you made. -->
## General checklist
<!-- Please double-check the following points and mark them like this:
[X] -->
- [x] There are no breaking changes (API, behaviour)
- [x] I have added unit and/or integration tests for my change
- [ ] The tests cover both positive and negative cases
- [ ] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green
- [ ] 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
<!-- Before adding documentation and example(s) (below), please wait
until the PR is reviewed and approved. -->
- [ ] 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)
## Checklist for adding new maven module
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have added my new module in the root `pom.xml` and
`langchain4j-bom/pom.xml`
## Checklist for adding new embedding store integration
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreIT` that extends
from either `EmbeddingStoreIT` or `EmbeddingStoreWithFilteringIT`
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreRemovalIT` that
extends from `EmbeddingStoreWithRemovalIT`
## Checklist for changing existing embedding store integration
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have manually verified that the
`{NameOfIntegration}EmbeddingStore` works correctly with the data
persisted using the latest released version of LangChain4j
Signed-off-by: Anjali K <anjali.kakkar@ltts.com>
Co-authored-by: Anjali K <anjali.kakkar@ltts.com>
This is essentially an evolution of what
`AiServices#systemMessageProvider` provides
with additional contextual information.
Originally mentioned in
https://github.com/quarkiverse/quarkus-langchain4j/issues/2466
---------
Co-authored-by: Dmytro Liubarskyi <ljubarskij@gmail.com>
## Issue
Progresses #3916
### Summary
This PR refactors the batch processing response model to use a single
unified `BatchResponse<T>` class instead of the previous sealed
interface hierarchy (`BatchSuccess`, `BatchIncomplete`, `BatchError`).
This simplifies the API and provides a more consistent experience across
different batch model implementations. Will update docs and examples in
separate PRs.
### Breaking Changes
**Batch Models affected:**
- `BatchChatModel` (moved to `dev.langchain4j.model.chat`)
- `BatchEmbeddingModel` (moved to `dev.langchain4j.model.embedding`)
- `BatchImageModel` (moved to `dev.langchain4j.model.image`)
**Google AI Gemini (@Experimental) implementations:**
The existing Gemini batch implementations are updated to reflect these
new interfaces and the unified `BatchResponse` model.
#### Migration Guide
**Before:**
The API used a sealed interface hierarchy (`BatchSuccess`,
`BatchIncomplete`, etc.) and a shared `retrieveBatchResults` method.
```java
BatchResponse<ChatResponse> response = batchModel.retrieveBatchResults(batchName);
if (response instanceof BatchSuccess<ChatResponse> success) {
List<ChatResponse> results = success.responses();
} else if (response instanceof BatchIncomplete) { ... }
```
**After:**
The API uses a unified `BatchResponse<T>` with helper methods and
specialized `submit`/`retrieve` methods. Batch identifiers are now
wrapped in `BatchId`.
```java
// Using the new BatchRequest wrapper and submit method
String id = batchModel.submit(new BatchRequest<>(chatRequests)).batchId();
// Polling with the new retrieve method
BatchResponse<ChatResponse> response = batchModel.retrieve(id);
if (response.state() == BatchState.SUCCEEDED) {
List<ChatResponse> results = response.responses(); // Results list
} else if (!response.state().isTerminal()) {
// Still running
} else if (response.state() == BatchState.FAILED) {
List<BatchError> errors = response.errors(); // Detailed error info
}
```
### Changes
- **Decoupled Interfaces**: Created `BatchChatModel`,
`BatchEmbeddingModel`, and `BatchImageModel` in their respective
packages.
- **Unified `BatchResponse<T>`**: Replaced the sealed hierarchy with a
single class.
- **New `BatchRequest<T>`**: Introduced a wrapper for requests to allow
for future job-level parameters (e.g., Gemini's display name) without
breaking method signatures.
- **New Value Objects**:
- `BatchId`: Type-safe identifier for batch jobs.
- `BatchPage<T>`: Standardized record for paginated results in `list()`
operations.
- `BatchError`: Detailed error tracking including codes and
provider-specific metadata.
- **`BatchState` Enum**: Explicit lifecycle tracking (`PENDING`,
`RUNNING`, `SUCCEEDED`, `FAILED`, `CANCELLED`, `EXPIRED`).
## General checklist
<!-- Please double-check the following points and mark them like this:
[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
<!-- Before adding documentation and example(s) (below), please wait
until the PR is reviewed and approved. -->
- [ ] I have added/updated the
[documentation](https://github.com/langchain4j/langchain4j/tree/main/docs/docs)
---------
Signed-off-by: Ricardo Zanini <ricardozanini@gmail.com>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: ZhangDT-sky <485918776@qq.com>
Co-authored-by: Mario Fusco <mario.fusco@gmail.com>
Co-authored-by: Julien Dubois <julien.dubois@gmail.com>
Co-authored-by: Bruno Baptista <brunobat@gmail.com>
Co-authored-by: Dmytro Liubarskyi <ljubarskij@gmail.com>
Co-authored-by: DragonFSKY <38503900+DragonFSKY@users.noreply.github.com>
Co-authored-by: David Pilato <david@pilato.fr>
Co-authored-by: Jean Bisutti <jean.bisutti@gmail.com>
Co-authored-by: Jan Martiska <wraychus@gmail.com>
Co-authored-by: Qice Sun <qicesun0401@gmail.com>
Co-authored-by: Marco Belladelli <marcobladel@gmail.com>
Co-authored-by: wangji0923 <wjgpt0923@gmail.com>
Co-authored-by: 复试资料 <study@example.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Katia Aresti <karesti@redhat.com>
Co-authored-by: Katia Aresti <karestig@ibm.com>
Co-authored-by: LXT <307322522@qq.com>
Co-authored-by: Chan <59447235+chancehee@users.noreply.github.com>
Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com>
Co-authored-by: Farzad Sedaghatbin <farzad@kixy.com>
Co-authored-by: Vasilije Jukic <88736998+VasilijeJukic01@users.noreply.github.com>
Co-authored-by: Faisal Dilawar <dilawar.faisal@gmail.com>
Co-authored-by: Gaurav Katheriya <gauravstu10@gmail.com>
Co-authored-by: Max Lepikhin <46848373+maxlepikhin@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pedro Vieira <pedrovcristao@hotmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Hanabi <78060373+KurobaKaitou@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: odysseaspenta <odysseas@sysnetint.com>
Co-authored-by: Xin Wang <xinwang@apache.org>
Co-authored-by: Daksh R Jain <dakshjain737@gmail.com>
Co-authored-by: 小雨 <1537372358@qq.com>
Co-authored-by: Kimgyuilli <87853959+Kimgyuilli@users.noreply.github.com>
Co-authored-by: jiajingda <dadamonkey1@gmail.com>
Co-authored-by: ZhangDT <485918776@qq.com>
Co-authored-by: Andrea Di Maio <85737936+andreadimaio@users.noreply.github.com>
Co-authored-by: Antoine Rey <antoine.rey@free.fr>
Co-authored-by: Sanel Z. <sanelz@gmail.com>
Co-authored-by: Harikrishna <harikrishna553@gmail.com>
Co-authored-by: Harikrishna <harikrishna.gurram@walmart.com>
Co-authored-by: Srinadh Bhattiprolu <ss.bhattiprolu@gmail.com>
Co-authored-by: eye-gu <734164350@qq.com>
Co-authored-by: leochame <leocham.cn@gmail.com>
Co-authored-by: Hervé Boutemy <hboutemy@apache.org>
Co-authored-by: jrsperry <43385427+jrsperry@users.noreply.github.com>
Co-authored-by: Joshua Sperry <jrsperry@halosight.com>
Co-authored-by: mcopes73 <mcopes73@gmail.com>
Co-authored-by: Dmytro Skarzhynets <d.skarzh@protonmail.com>
Co-authored-by: 정다해(Dahae Jung) <dahae@29cm.co.kr>
Co-authored-by: Diego Berríos <130251753+diegoberriosr@users.noreply.github.com>
Co-authored-by: suryateja-g13 <89782129+suryateja-g13@users.noreply.github.com>
Co-authored-by: Gorre Surya <sgorre92@gmail.com>
Co-authored-by: Sahal Hussain <146409442+sahalhes@users.noreply.github.com>
Co-authored-by: unsignedint <rc@braveface.nz>
Co-authored-by: giveup <giveup@users.noreply.github.com>
Co-authored-by: zxuhan7 <zxuhan7@gmail.com>
Co-authored-by: Stéphane Philippart <stephane.philippart@ovhcloud.com>
Co-authored-by: Eric Lin <31666172+elin-coursera2@users.noreply.github.com>
Co-authored-by: Eric Lin <elin@coursera.org>
<!--
Thank you so much for your contribution!
Please fill in all the sections below.
Please open the PR as a draft initially. Once it is reviewed and
approved, we will ask you to add documentation and examples.
Please note that PRs with breaking changes or without tests will be
rejected.
Please note that PRs will be reviewed based on the priority of the
issues they address.
We ask for your patience. We are doing our best to review your PR as
quickly as possible.
Please refrain from pinging and asking when it will be reviewed. Thank
you for understanding!
-->
## Issue
<!-- Please specify the ID of the issue this PR is addressing. For
example: "Closes #1234" or "Fixes #1234" -->
Closes#5343
## Change
<!-- Please describe the changes you made. -->
## General checklist
<!-- Please double-check the following points and mark them like this:
[X] -->
- [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
- [ ] 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
<!-- Before adding documentation and example(s) (below), please wait
until the PR is reviewed and approved. -->
- [ ] 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)
## Checklist for adding new maven module
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have added my new module in the root `pom.xml` and
`langchain4j-bom/pom.xml`
## Checklist for adding new embedding store integration
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreIT` that extends
from either `EmbeddingStoreIT` or `EmbeddingStoreWithFilteringIT`
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreRemovalIT` that
extends from `EmbeddingStoreWithRemovalIT`
## Checklist for changing existing embedding store integration
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have manually verified that the
`{NameOfIntegration}EmbeddingStore` works correctly with the data
persisted using the latest released version of LangChain4j
## Issue
Closes #
## Change
* Introduce @EmbeddingVector as replacement to avoid naming collisions
* Add utility methods to created and apply embeddings on entities
* Test more databases and add SAP HANA as well as CockroachDB support
## 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
- [x] I have added/updated the
[documentation](https://github.com/langchain4j/langchain4j/tree/main/docs/docs)
- [x] I have added an example in the [examples
repo](https://github.com/langchain4j/langchain4j-examples) (only for
"big" features)
- [x] I have added/updated [Spring Boot
starter(s)](https://github.com/langchain4j/langchain4j-spring) (if
applicable)
## Checklist for adding new embedding store integration
- [x] I have added a `{NameOfIntegration}EmbeddingStoreIT` that extends
from either `EmbeddingStoreIT` or `EmbeddingStoreWithFilteringIT`
- [x] I have added a `{NameOfIntegration}EmbeddingStoreRemovalIT` that
extends from `EmbeddingStoreWithRemovalIT`
## Checklist for changing existing embedding store integration
- [x] I have manually verified that the
`{NameOfIntegration}EmbeddingStore` works correctly with the data
persisted using the latest released version of LangChain4j
Twelve public exception classes in the exception hierarchy had no
class-level Javadoc, making it hard for integrators to understand when
each type is thrown, whether a retry is safe, and how the exceptions
relate to one another.
This commit adds class-level Javadoc to:
- LangChain4jException - root exception for all library errors
- RetriableException - transient errors safe to retry
- NonRetriableException - permanent errors where retry will not help
- RateLimitException - HTTP 429 / quota exceeded
- HttpException - raw HTTP error carrying the status code
- AuthenticationException - HTTP 401 / 403 credential failures
- InvalidRequestException - malformed or policy-violating requests
- ModelNotFoundException - HTTP 404 / unknown model identifier
- InternalServerException - HTTP 5xx server-side errors
- TimeoutException - HTTP 408 / client-side timeouts
- UnresolvedModelServerException - DNS / unresolved host
- UnsupportedFeatureException - feature not supported by provider
ContentFilteredException, ToolArgumentsException, and
ToolExecutionException already had Javadoc and are unchanged.
No behaviour changes; documentation only.
## Issue
Added class-level Javadoc to the 12 exception classes in the exception
hierarchy
that were missing it. No behaviour changes; documentation only.
Closes #
## Change
<!-- Please describe the changes you made. -->
## General checklist
<!-- Please double-check the following points and mark them like this:
[X] -->
- [X] There are no breaking changes (API, behaviour)
- [ ] I have added unit and/or integration tests for my change
- [ ] 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
<!-- Before adding documentation and example(s) (below), please wait
until the PR is reviewed and approved. -->
- [ ] 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)
## Checklist for adding new maven module
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have added my new module in the root `pom.xml` and
`langchain4j-bom/pom.xml`
## Checklist for adding new embedding store integration
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreIT` that extends
from either `EmbeddingStoreIT` or `EmbeddingStoreWithFilteringIT`
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreRemovalIT` that
extends from `EmbeddingStoreWithRemovalIT`
## Checklist for changing existing embedding store integration
<!-- Please double-check the following points and mark them like this:
[X] -->
- [ ] I have manually verified that the
`{NameOfIntegration}EmbeddingStore` works correctly with the data
persisted using the latest released version of LangChain4j
## Summary
Removed 18 unused import statements from 17 source files across multiple
modules.
### Modules affected
- `langchain4j-core` (6 files)
- `langchain4j` (3 files)
- `langchain4j-azure-ai-search`
- `langchain4j-cassandra`
- `langchain4j-couchbase`
- `langchain4j-agentic`
- `langchain4j-agentic-mcp`
- `langchain4j-local-ai`
- `langchain4j-workers-ai`
- `document-loaders/langchain4j-document-loader-azure-storage-blob`
### Verification
Each import was verified to be unused — the imported symbol does not
appear in any non-import line of its file (including javadoc `@link` and
`@see` tags).
## Issue
Closes#3956
## Change
When an output guardrail fails, the offending `AiMessage` was previously
left in chat memory, potentially polluting future conversations.
Adds `failureWithMessageRemoval()` and `fatalWithMessageRemoval()` to
`OutputGuardrail` and `OutputGuardrailResult`. When the flag is set, the
executor removes the last `AiMessage` from `ChatMemory` before throwing
the `OutputGuardrailException`. The full result is also attached to the
exception via `OutputGuardrailException.result()` so callers can inspect
it.
Behaviour without the flag is unchanged (backwards-compatible).
## 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
- [ ] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green
- [ ] 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
Co-authored-by: Benamira05 <145583236+Benamira05@users.noreply.github.com>
## Issue
Closes#4938
## Change
Adds `guardrailName()` to `GuardrailExecutedEvent` to improve
observability, especially when guardrails are wrapped using decorators.
### Details
- Added `guardrailName()` to `GuardrailExecutedEvent` with a default
implementation returning `guardrailClass().getSimpleName()`
- Propagated guardrail name from execution layer to observability events
- Added a default `name()` method to `Guardrail` interface to represent
logical guardrail identity
- Updated event builder and default implementation to carry
`guardrailName`
- Added tests covering wrapped guardrails for both success and failure
scenarios
This allows listeners to distinguish logical guardrails (e.g.
`PromptInjectionGuardrail`) instead of only seeing adapter classes (e.g.
`InputGuardrailAdapter`).
Related discussion:
https://github.com/langchain4j/langchain4j/discussions/4914
## 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
and main modules, and they are all green
- [x] I have added/updated the documentation (Javadoc on
Guardrail.name() andGuardrailExecutedEvent.guardrailName())
- [ ] I have added an example in the examples repo (only for "big"
features)
- [ ] I have added/updated Spring Boot starter(s) (if applicable)
## Checklist for adding new maven module
- [ ] I have added my new module in the root `pom.xml` and
`langchain4j-bom/pom.xml`
## Checklist for adding new embedding store integration
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreIT` that extends
from either `EmbeddingStoreIT` or `EmbeddingStoreWithFilteringIT`
- [ ] I have added a `{NameOfIntegration}EmbeddingStoreRemovalIT` that
extends from `EmbeddingStoreWithRemovalIT`
## Checklist for changing existing embedding store integration
- [ ] I have manually verified that the
`{NameOfIntegration}EmbeddingStore` works correctly with the data
persisted using the latest released version of LangChain4j
---------
Co-authored-by: nhthinh-axonivy <nhthinh@axonactive.com>
## Issue
Triggered by
https://github.com/langchain4j/langchain4j/issues/5280#issuecomment-4572579268
## Change
Previously, calling
`ChatRequest.builder().parameters(p).responseFormat(rf).build()` (or any
other individual setter combined with `parameters(...)`) threw
`IllegalArgumentException: Cannot set both 'parameters' and
'responseFormat' on ChatRequest`. This made
`chatRequest.toBuilder().responseFormat(rf).build()` unusable — a
natural
pattern when modifying a single field of an existing `ChatRequest` (e.g.
inside a `chatRequestTransformer` on `AiServices`):
```java
AiServices.builder(MyService.class)
.chatModel(chatModel)
.chatRequestTransformer(req ->
req.toBuilder().responseFormat(responseFormat).build())
.build();
```
Individual setters now override the corresponding fields of parameters
via ChatRequestParameters.overrideWith(...). All other fields of
parameters — including provider-specific
fields on subclasses (e.g. OpenAiChatRequestParameters) — are preserved.
Notes:
- When only .parameters(p) is set without any individual setters, p is
passed through unchanged (identity preserved). This ensures mocked
ChatRequestParameters keep working, and
subclasses that don't override overrideWith keep their concrete type.
- When individual setters ARE used together with .parameters(p), merging
is performed via overrideWith — provider-specific subclasses that
properly override overrideWith keep
their type and their extra fields.
- Null / empty-collection overrides are skipped (existing values kept) —
same semantics as overrideWith. Documented in the Builder javadoc.
- The per-field validation throws are gone; the constructor branches
once on whether any individual setter was used.
## 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
- [X] 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)
## Summary
Fixes#5277
**Bug fix — empty list crash in `shouldReturnImmediately()`**
`ToolService.shouldReturnImmediately()` crashes with
`IndexOutOfBoundsException` when the `returnBehaviors` list is empty,
because `list.get(list.size() - 1)` becomes `list.get(-1)`. This happens
when programmatically created tools (via `ToolSpecification` +
`ToolExecutor`) are used with dynamic tool providers that return no
tools for a given request.
Added an `isEmpty()` guard that returns `false` (semantically equivalent
to all tools having `TO_LLM` behavior).
**Enhancement — `ToolSpecification` now supports `ReturnBehavior`**
`ToolSpecification` had no `returnBehavior` field, so users registering
tools via `AiServices.builder().tools(Map<ToolSpecification,
ToolExecutor>)` had no way to set `ReturnBehavior`. The only
programmatic path was through
`ToolProviderResult.Builder.add(ToolSpecification, ToolExecutor,
ReturnBehavior)`.
Added a `returnBehavior` field to `ToolSpecification` (nullable,
defaults to `null` → `TO_LLM`). When set,
`ToolService.tools(Map<ToolSpecification, ToolExecutor>)` now extracts
it automatically.
### Changes
- **`ToolSpecification.java`**: Added `returnBehavior` field, getter
(`@Experimental`), builder method, updated
`equals`/`hashCode`/`toString`/`toBuilder`
- **`ToolSpecificationJsonUtils.java`**: Serialize/deserialize
`returnBehavior` in JSON
- **`ToolService.java`**: Empty list guard in
`shouldReturnImmediately()`; extract `returnBehavior` from
`ToolSpecification` in `tools(Map)` method
- **`ReturnBehaviorCombinationsTest.java`**: Added test for empty list
case
- **`ProgrammaticCreatedImmediateReturnToolTest.java`**: Added tests for
`ToolSpecification.returnBehavior` via the `Map` API (immediate return +
default TO_LLM)
## Test plan
- [x] `ReturnBehaviorCombinationsTest` — all existing combinations still
pass + new empty list test
- [x] `ProgrammaticCreatedImmediateReturnToolTest` — new tests for
`returnBehavior` on `ToolSpecification` via Map API
- [x] `StreamingProgrammaticCreatedImmediateReturnToolTest` — existing
streaming tests still pass
- [x] `ToolSpecificationsTest` — existing tests still pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Issue
Closes#4383
## Change
Added a new integration module `langchain4j-google-genai`.
This module integrates the Google Gen AI SDK
(`com.google.genai:google-genai`), enabling support for Google's Gemini
models through the official Java client.
## 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
and main modules, and they are all green
- [x] I have added/updated the documentation
- [ ] I have added an example in the examples repo (only for "big"
features)
- [ ] I have added/updated Spring Boot starter(s) (if applicable)
## Checklist for adding new maven module
- [x] I have added my new module in the root `pom.xml` and
`langchain4j-bom/pom.xml`
---------
Co-authored-by: Guillaume Laforge <glaforge@gmail.com>
Co-authored-by: Dmytro Liubarskyi <ljubarskij@gmail.com>
## Issue
Closes#5166
## Change
- **`getActualType` no longer raw-casts the inner type argument.** It
has been replaced with a `rawClassOf(Type)` helper that peels the type
to its raw `Class`:
- `Class<?>`-> returned as-is.
- `ParameterizedType` -> its raw type.
- `WildcardType` -> `rawClassOf(upperBounds[0])`.
- `TypeVariable` -> `rawClassOf(bounds[0])`.
- `GenericArrayType` -> `Object[].class`.
- Anything else (or `null`) -> `Object.class`, so the walker degrades to
an empty object schema instead of NPE-ing in downstream branches.
- **The `Collection` branch propagates the inner `Type` into the
recursive call** (via a new `collectionElementType` helper), so nested
generics like `List<List<X>>` resolve all the way down. Previously the
call passed `null`, so anything below the first level lost its type
information and crashed once the outer cast started succeeding.
- Behaviour for previously-working cases (`List<String>`,
`List<MyClass>`, ...) is unchanged.
- The element type of `List<Foo<Bar>>` resolves to `Foo`; the inner
generic argument is intentionally not modelled in the schema (the LLM
contract for these never carried `Bar` either).
## General checklist
<!-- Please double-check the following points and mark them like this:
[X] -->
- [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
<!-- Before adding documentation and example(s) (below), please wait
until the PR is reviewed and approved. -->
- [ ] 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)
---------
Co-authored-by: Dmytro Liubarskyi <ljubarskij@gmail.com>
Closes#5128
## Summary
- Adds a `Boolean strict` field to `ToolSpecification` enabling per-tool
strict schema enforcement
- When `null` (default), defers to the model-level `strictTools` setting
— fully backward compatible
- When `true`/`false`, overrides the model-level default for that
specific tool
- Updates `AnthropicMapper` and `OpenAiUtils` to resolve per-tool strict
before falling back to the model default
- Includes JSON serialization/deserialization support and tests for all
three modules
## Motivation
Anthropic limits strict tools to **20 per request**. Agentic
architectures that send 20+ tools in a single request cannot use
`strictTools(true)` on the model without hitting this limit. Per-tool
strict allows selectively enforcing strict on high-value tools while
leaving others non-strict.
## Usage
```java
ToolSpecification.builder()
.name("runSql")
.description("Execute a SQL query")
.parameters(schema)
.strict(true) // enforce strict for this tool
.build();
```
## Test plan
- [x] `ToolSpecificationJsonTest` — round-trip for `strict` true, false,
null; rejection of non-boolean
- [x] `AnthropicMapperTest` — per-tool true overrides model null,
per-tool false overrides model true, per-tool null falls back to model
- [x] `OpenAiUtilsTest` — same three override/fallback scenarios
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Issue
Closes#5085
## Change
- New `@P(defaultValue = "...")` attribute lets tool authors declare a
runtime fallback that LangChain4j substitutes when the LLM omits an
argument.
- Works for primitives, boxed types, `String`, `enum`, `UUID`,
`BigDecimal`/`BigInteger`, `List<T>`/`Set<T>`/arrays, `Map<K,V>`, and
POJOs (including nested and polymorphic
types).
- Eager validation at AI Service registration time: misconfigured
defaults fail with `IllegalConfigurationException` at
`AiServices.builder(...).tools(...).build()` rather than
on the first LLM call.
## Usage
```java
enum SortBy { RELEVANCE, DATE, RATING }
@Tool
List<Article> searchArticles(
String query,
@P(defaultValue = "10") int limit,
@P(defaultValue = "[\"en\"]") List<String> languages,
@P(defaultValue = "RELEVANCE") SortBy sortBy
) {
// limit -> 10, languages -> ["en"], sortBy -> SortBy.RELEVANCE when
omitted by the LLM
}
```
## Semantics
- **Setting `defaultValue` makes the parameter optional in the JSON
schema** — the parameter is *not* added to the schema's `required`
array, regardless of `@P(required)`. The
LLM is told it may omit the argument; if it does, LangChain4j fills in
the default before invoking the method.
- **Defaults are re-parsed on every invocation**, so a tool that mutates
a defaulted `List`/`Map`/POJO does not contaminate later calls.
- **Defaults only apply to absence, not to wrong-typed values**: if the
LLM sends `"banana"` for an `int`, the coercion error propagates
normally — the default is not used as a
fallback.
- **Empty-string default `@P(defaultValue = "") String filter` is
distinct from "no default set"** — implemented via a `NO_DEFAULT`
sentinel constant on `P`.
## Supported types
| Type | Format | Example |
|------------------------------|--------------------------|------------------------------------------|
| `String` | used verbatim | `defaultValue = "USD"` |
| Primitive / boxed primitive | type-specific conversion | `"10"`,
`"3.14"`, `"true"` |
| `enum` | enum constant name | `defaultValue = "EUR"` |
| `UUID` | `UUID.fromString` | `"550e8400-e29b-41d4-a716-446655440000"`
|
| `BigDecimal`, `BigInteger` | numeric literal | `"1.5"`, `"100"` |
| `List<T>` / `Set<T>` / array | JSON array | `"[\"a\",\"b\"]"`,
`"[1,2,3]"` |
| `Map<K,V>` | JSON object | `"{\"a\":1,\"b\":2}"` |
| POJOs (including nested) | JSON object |
`"{\"name\":\"Klaus\",\"age\":42}"` |
## Registration-time validation
The default value string is parsed at AI Service registration time. The
following all throw `IllegalConfigurationException` from
`AiServices.builder(...).tools(...).build()`,
naming the offending `ClassName.methodName.parameterName`:
- Unparseable defaults — typos (`@P(defaultValue = "ten") int x`),
numeric overflow (`"999999999999999"` on an `int`), invalid enum
constants, invalid `UUID`, invalid booleans.
- `defaultValue` combined with `Optional<T>` — `Optional` already
encodes absence; pick one mechanism.
- `defaultValue` on framework-injected parameters (`@ToolMemoryId`,
`InvocationContext`, etc.) — they never come from the LLM.
Existing rule **relaxed**: `@P(required = false)` on a primitive without
`defaultValue` still throws (a primitive can't represent absence), but
is now legal *with* a
`defaultValue`:
```java
@Tool
void process(@P(required = false, defaultValue = "0") int startLine) {
... }
```
The validation error message has been updated to point users to this
option.
## 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
<!-- Before adding documentation and example(s) (below), please wait
until the PR is reviewed and approved. -->
- [x] 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)
## Issue
Fixes#5085
## Change
- When the LLM omitted a required primitive parameter (e.g. `int`,
`boolean`), Java reflection threw a generic NPE during unboxing (`Cannot
invoke
"java.lang.Number.intValue()" because the return value of
"sun.invoke.util.ValueConversions.primitiveConversion(...)" is null`).
The error leaked JVM-internal `MethodHandle`
plumbing and gave the LLM nothing actionable to retry with.
- Now `DefaultToolExecutor.prepareArguments` detects the missing
primitive up front and throws `IllegalArgumentException("Required
parameter \"argN\" of tool \"toolName\" is
missing")`. The exception flows through the existing
`wrapToolArgumentsExceptions` path, so AI Service users see a
`ToolArgumentsException` (with the original
`IllegalArgumentException` as cause) and can route it through
`toolArgumentsErrorHandler` as usual.
- Adds eager validation at AI Service build time: `@P(required = false)`
on a primitive parameter is contradictory (primitives cannot represent
absence) and now fails with
`IllegalConfigurationException` from `ToolService.findTools`, alongside
the other tool-misconfig checks. This catches the developer error before
any LLM call.
## Breaking changes
1. **Default behavior: AI Service now throws when the LLM omits a
required primitive parameter, instead of letting the LLM receive an
obscure error message.**
Before this PR, when the LLM called a tool without a required primitive,
Java reflection NPE'd during unboxing. That NPE bubbled up through the
*tool execution* error path (`InvocationTargetException` →
`ToolExecutionException`) and the default `toolExecutionErrorHandler`
returned the (ugly, JVM-internal) message to the LLM as a tool result
with `isError=true`. The agent loop continued and the LLM had a chance
to retry with corrected arguments.
After this PR, the missing primitive is detected before the method is
invoked and surfaced as an *arguments* error (`IllegalArgumentException`
→ wrapped as `ToolArgumentsException`). It is now routed through
`toolArgumentsErrorHandler`, whose default is to **throw** — so
`assistant.chat(...)` propagates the exception out and the AI Service
flow stops.
**Net effect:** what used to be a (clumsy) recoverable agent step is now
a fatal error by default. Users who relied on the LLM-driven recovery
for missing primitives must configure a `toolArgumentsErrorHandler` that
returns the error to the LLM:
```java
AiServices.builder(...)
...
.toolArgumentsErrorHandler((error, ctx) -> ToolErrorHandlerResult.text(error.getMessage()))
.build();
```
The exception text itself is also cleaner:
`IllegalArgumentException("Required parameter \"argN\" of tool \"...\"
is missing") ` instead of the previous `NullPointerException("Cannot
invoke \"java.lang.Number.intValue()\" ...")`. Anyone catching the NPE
by type or matching its text will need to update.
2. `@P(required = false)` on a primitive parameter now fails at AI
Service build time.
Previously this misconfiguration silently passed validation and produced
the runtime NPE described above whenever the LLM happened to omit the
parameter.
Now `AiServices.builder(...).tools(yourToolObject).build()` throws
`IllegalConfigurationException` immediately, naming the offending
ClassName.methodName.
Migration: if you really want parameter to be optional, change the
parameter to a boxed type (`Integer`, `Long`, `Boolean`, …) or
`Optional<T>`.
## General checklist
- [ ] 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)
## Issue
Closes https://github.com/langchain4j/langchain4j/issues/2557
## Change
- Polymorphic types (sealed interfaces/classes, or types annotated with
Jackson
`@JsonSubTypes`/`@JsonTypeInfo`) can now be used as AI Service return
types and as
tool parameters. The schema sent to the LLM contains an `anyOf` over
concrete
subtypes with a discriminator property, and the LLM's response (or tool
call) is
dispatched to the correct subtype automatically.
- Works for the polymorphic type itself, for `List<T>`/`Set<T>` of
polymorphic, and
for polymorphic types nested inside other POJOs. Fixes the
previously-empty schema
generated for interface/abstract return types.
- Sealed types work with **zero annotations**; Jackson-annotated types
are also
supported and respect the user-configured `property`, `defaultImpl`,
`visible`,
`@JsonSubTypes.Type(name=...)`, and `@JsonTypeName`.
- Unsupported Jackson configurations (`Id.CLASS`, `Id.MINIMAL_CLASS`,
`Id.CUSTOM`,
`Id.DEDUCTION`, `As.WRAPPER_OBJECT`, `As.WRAPPER_ARRAY`,
`As.EXTERNAL_PROPERTY`,
`As.EXISTING_PROPERTY`) fail fast with a clear
`UnsupportedFeatureException` at
schema-generation time, instead of silently producing a payload Jackson
can't parse.
- Discriminator field collisions on subtypes are detected and reported
with three
remediation options (rename, change `property=...`, or set
`visible=true`).
## 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
- [X] 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)
## Issue
Closes https://github.com/langchain4j/langchain4j/issues/4974
## Change
- Adds a new `ReturnBehavior.IMMEDIATE_IF_LAST` that halts the AI
service execution
loop when a tool with this behavior is the **last** tool call in the LLM
response,
saving one full LLM round trip compared to letting the LLM issue the
halt tool
alone in the next turn.
- Refactors the tool-handling internals (`AiServiceTool`,
`ToolProviderResult`,
`ToolServiceContext`, `ToolService`, `Skills`) so adding future
`ReturnBehavior`
values is a one-liner instead of threading parallel sets through every
layer.
## 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
- [x] 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)