## Issue
No linked issue — test-coverage contribution (follow-up to #5492 in the
same module).
## Change
`GoogleAiGeminiChatRequestParameters` (google-ai-gemini module) carries
the Gemini-specific request parameters and the
`overrideWith`/`defaultedBy` merge logic, but had no dedicated unit
tests. This PR adds 8 tests covering:
- builder round-trip for all Gemini-specific parameters and null
defaults
- `imageAspectRatio` acting as an alias for `aspectRatio`
- `overrideWith(...)`: Gemini-specific values overridden by another
Gemini parameters instance, and preserved when overriding with common
`ChatRequestParameters`
- `defaultedBy(...)`: defaults applied from Gemini parameters, and
preserved when defaulted by common parameters
- `equals`/`hashCode` contract
Test-only change; no production code touched.
## General checklist
- [x] There are no breaking changes
- [x] I have added unit tests for my change (test-only PR, no
integration tests needed)
- [x] I have manually run all the unit tests in the
`langchain4j-google-ai-gemini` module and they are all green (354 tests,
0 failures; integration tests require a live API key and were not run)
- [x] Code is formatted with `spotless:apply`
## 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
Closes#5568.
`cachedContentName` could previously only be configured globally on the
`GoogleAiGeminiChatModel`/`GoogleAiGeminiStreamingChatModel` builder,
attached to every request. Since a `ChatRequest` encapsulates a full
conversation and the Gemini `cachedContent` is the prefix for that
conversation, it should be configurable per request too.
This follows the exact pattern #4913 established for
`aspectRatio`/`imageSize`:
- `GoogleAiGeminiChatRequestParameters` gains a `cachedContentName`
field (getter, builder setter, `overrideWith` merge,
`equals`/`hashCode`/`toString`)
- `BaseGeminiChatModel` no longer keeps a separate `cachedContentName`
instance field; it's folded into `defaultRequestParameters` at
construction time and read off the resolved per-request `parameters` in
`createGenerateContentRequest`
- A `cachedContentName` set via `ChatRequestParameters` on an individual
request overrides the globally configured one (same override semantics
as `aspectRatio`/`imageSize`)
```java
ChatModel model = GoogleAiGeminiChatModel.builder()
.apiKey(System.getenv("GEMINI_AI_KEY"))
.cachedContentName("cachedContents/abc123") // default
.build();
ChatResponse response = model.chat(ChatRequest.builder()
.messages(UserMessage.from("..."))
.parameters(GoogleAiGeminiChatRequestParameters.builder()
.cachedContentName("cachedContents/xyz789") // overrides the default for this request
.build())
.build());
```
## Test plan
- [x] Updated existing `cachedContentNameInContentRequest` /
`defaultCachedContentNameInContentRequest` tests to exercise the full
`chat()` merge path (they previously bypassed it by calling
`createGenerateContentRequest` directly with an empty `ChatRequest`)
- [x] Added `shouldUseRequestLevelCachedContentNameWhenProvided`,
mirroring `shouldUseRequestLevelImageConfigWhenProvided`
- [x] `mvn -pl langchain4j-google-ai-gemini test` passes
- [x] `mvn -pl langchain4j-google-ai-gemini spotless:check` passes
---------
Co-authored-by: Benamira05 <145583236+Benamira05@users.noreply.github.com>
## Issue
Closes#4907
## Change
This PR adds request-level image config override support for
`GoogleAiGeminiChatModel`.
### What was added
- Added provider-specific request parameters class:
- `GoogleAiGeminiChatRequestParameters`
- supports:
- `aspectRatio(String)`
- `imageAspectRatio(String)` (alias)
- `imageSize(String)`
- Added merge behavior via `overrideWith/defaultedBy` for
Gemini-specific parameters.
- Updated `BaseGeminiChatModel` request building logic to resolve
effective image config with precedence:
- `request-level` > `builder-level` > `null`
- Updated default chat parameters construction to preserve
Gemini-specific request parameters through the common `ChatModel`
parameter merge flow.
- Added/updated unit tests in `GoogleAiGeminiChatModelTest`:
- request-level image config overrides builder-level defaults
- builder-level image config is used when request-level values are
absent
## 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](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
- [ ] 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
---------
Signed-off-by: ZhangDT-sky <485918776@qq.com>
Co-authored-by: Dmytro Liubarskyi <ljubarskij@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.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)
## Summary
- Add `IMAGE_RECITATION` to `GeminiFinishReason` enum in
`GeminiGenerateContentResponse`
- Map `IMAGE_RECITATION` to `CONTENT_FILTER` in `FinishReasonMapper`
## Problem
Gemini API returns `IMAGE_RECITATION` as a finish reason when generated
image content is too similar to copyrighted training data. Without this
enum value, Jackson deserialization fails with:
```
InvalidFormatException: Cannot deserialize value of type `GeminiFinishReason` from String "IMAGE_RECITATION": not one of the values accepted for Enum class
```
## Test plan
- [x] Verify `IMAGE_RECITATION` deserializes correctly from Gemini API
response
- [x] Verify it maps to `FinishReason.CONTENT_FILTER` as expected
- [x] Existing tests continue to pass
## Issue
N/A — this PR adds missing unit-test coverage; it does not change
behaviour and is not tied to a specific open issue. Happy to open one if
preferred.
## Change
Adds `FinishReasonMapperTest` for the Google AI Gemini module, covering
every `GeminiFinishReason` → `FinishReason` mapping in
`FinishReasonMapper`:
- `STOP` → `STOP`
- `MAX_TOKENS` → `LENGTH`
- `BLOCKLIST` / `PROHIBITED_CONTENT` / `RECITATION` / `SPII` / `SAFETY`
/ `LANGUAGE` → `CONTENT_FILTER`
- `MALFORMED_FUNCTION_CALL` / `FINISH_REASON_UNSPECIFIED` / `OTHER` →
`OTHER`
It also sweeps all `GeminiFinishReason` values to assert the mapper
never returns `null`, which guards against a new enum constant being
added without a corresponding mapping.
Sibling mappers in this module (`FunctionMapper`, `SchemaMapper`,
`PartsAndContentsMapper`) already have dedicated unit tests;
`FinishReasonMapper` was the remaining gap. No production code is
changed.
## 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 <!-- new test green
(5/5); provider ITs are gated by API-key env vars and were skipped -->
- [ ] I have manually run all the unit and integration tests in the core
and main modules, and they are all green <!-- test-only change in a leaf
module; core/main not exercised -->
- [ ] 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)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
`GoogleAiGeminiChatModel` and `GoogleAiGeminiStreamingChatModel` accept
`enableEnhancedCivicAnswers(Boolean)`
on the builder, but the value was never sent to the API.
`BaseGeminiChatModel#createGenerateContentRequest`
builds the `GeminiGenerationConfig` without it, so the option was
silently ignored.
`GeminiGenerationConfig` already maps the field
(`enableEnhancedCivicAnswers` with the matching
`@JsonProperty`); this just populates it from the existing field, next
to `responseLogprobs`. The fix is shared
by the sync and streaming models, since both go through
`createGenerateContentRequest`.
The builder option is documented as "Enables enhanced civic answers",
and the field is a real Gemini
`generationConfig` parameter (it also exists in the official
`google-genai` Java SDK's `GenerateContentConfig`),
so this is a missed wiring rather than an intentionally unsupported
option. The sibling `responseLogprobs` is
forwarded the same way.
Closes#5546
### Changes
- `BaseGeminiChatModel`: pass `enableEnhancedCivicAnswers` into the
`GeminiGenerationConfig` builder (shared by
the sync and streaming models).
- `GoogleAiGeminiEnhancedCivicAnswersTest` (new): asserts the flag is
forwarded when enabled and defaults to
`false` otherwise, using the existing mocked-`GeminiService`
request-capture pattern.
- `GoogleAiGeminiStreamingChatModelTest`: added enabled and default
cases on the streaming path, following that
test's existing `createGenerateContentRequest` style.
- `GoogleAiGeminiChatModelTest#shouldSendRequestWithAllParameters`:
expected config updated to include the
now-forwarded default, mirroring the existing `responseLogprobs(false)`
assertion.
### Testing
`mvn -pl langchain4j-google-ai-gemini test` -> 334 passing, 0 failures.
`spotless:check` passes.
### Checklist
- [x] No breaking changes (additive; default behavior unchanged, since
`false` was already the effective default).
- [x] Unit tests added (positive: enabled is forwarded; default: false;
both sync and streaming).
- [x] Ran the module build and tests locally (green) + spotless.
# Render Gemini executable code as a valid Markdown fenced block
## Issue
Closes#5516
## Change
`PartsAndContentsMapper.fromGPartsToAiMessage()` rendered Gemini
code-execution output as broken Markdown.
It appended the fence ` ```python ` and then
`programmingLanguage().toString()` (= `"python"`), producing `
```pythonpython `, with no newline before the code.
The `programmingLanguage() != null` ternary was dead code: the
`GeminiExecutableCode` compact constructor always defaults `null` to
`PYTHON`. A `// TODO: ... ```pythonpythonCODE``` ` comment already
flagged this.
The fix opens the fence once with a trailing newline and drops the
duplicate language append:
```java
fullText.append("Code executed:\n")
.append("```python\n")
.append(executableCode.code())
.append("\n```\n");
```
Output is now `` ```python\n{code}\n``` ``. This affects both blocking
and streaming paths, which share this method.
Note: this matches the sibling Output branch's opening fence (` ```\n
`). I also add a `\n` before the closing fence: the Output branch relies
on its content ending in a newline, but `code()` has no guaranteed
trailing newline, so the explicit `\n` is needed for a valid block.
Scope: one `if` block in one file, plus a unit test. No public API
change.
## 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](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 note: the unit test asserts the corrected output `Code
executed:\n```python\nprint(1)\n```\n`; it fails on the pre-fix code
(which emits `pythonpython` and no newlines), so it is fail-then-pass.
The positive case is the essence here because this path renders a normal
response. `*IT` tests require a GOOGLE_AI_GEMINI_API_KEY and were not
run (recorded only). The core/main checklist item is unchecked because
only the changed module's unit tests were run.
-->
## Issue
Closes#5288
Subset of #3774
## Change
Gemini's [context caching
API](https://ai.google.dev/gemini-api/docs/caching) allows a previously
created cache (`cachedContents/*`) to be attached to a `generateContent`
or `streamGenerateContent` call via the request's `cachedContent` field.
This reuses the cached context (system instructions, large documents,
etc.) server-side, reducing latency and token cost across repeated
calls.
Previously, `GeminiGenerateContentRequest` did not expose this field, so
the cache could not be used through the langchain4j Google AI Gemini
integration.
This PR is intentionally a **minimal, non-breaking addition** — it does
**not** implement cache lifecycle management (create / list / delete).
Callers are expected to create the cache out of band (via the REST API,
Python SDK, etc.) and pass the returned resource name here.
**Files changed:**
- `GeminiGenerateContentRequest` — added `cachedContent` record
component and corresponding builder method
- `BaseGeminiChatModel` — added `cachedContentName` field; threaded into
`createGenerateContentRequest()`; exposed `cachedContentName(String)` on
`GoogleAiGeminiChatModelBaseBuilder` so both `GoogleAiGeminiChatModel`
and `GoogleAiGeminiStreamingChatModel` inherit it
**Usage:**
```java
ChatModel model = GoogleAiGeminiChatModel.builder()
.apiKey(System.getenv("GEMINI_AI_KEY"))
.modelName("gemini-1.5-flash-001")
.cachedContentName("cachedContents/abc123")
.build();
```
When `cachedContentName` is null (default), the field is omitted from
the request body — existing requests are byte-identical.
**Tests:**
Added two unit tests in `GoogleAiGeminiStreamingChatModelTest`
(following the existing `seedParameterInContentRequest` /
`defaultSeedInContentRequest` pattern right above them):
- `cachedContentNameInContentRequest` — verifies the `cachedContent`
field is set on the request and serialized to JSON when
`cachedContentName(...)` is configured on the builder
- `defaultCachedContentNameInContentRequest` — verifies the field is
null and omitted from JSON when the builder method is not called (no
breaking change to existing requests)
## 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
- [ ] 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)
## 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>
## Issue
Closes#5307
## Change
This PR preserves Google AI Gemini tool call ids across the mapper
layer.
- Preserve Gemini `functionCall.id` when mapping tool calls to
`ToolExecutionRequest.id()`.
- Round-trip existing tool request/result ids back through Gemini
`functionCall` and `functionResponse`.
- Do not generate fallback ids, so responses that do not provide an id
keep the existing null-id behavior.
- Keep null ids omitted from serialized Gemini JSON through the existing
non-null serialization behavior.
## 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 the module unit tests and `verify`;
integration tests that require external credentials are skipped by the
existing environment guards
- [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] Documentation is not required for this internal mapper bug fix
- [X] Examples are not required for this bug fix
- [X] Spring Boot starter changes are not applicable
## Checklist for adding new maven module
N/A
## Checklist for adding new embedding store integration
N/A
## Checklist for changing existing embedding store integration
N/A
## Test plan
- [X] `./mvnw -pl langchain4j-google-ai-gemini
-Dtest=PartsAndContentsMapperTest,GeminiStreamingResponseBuilderTest,FunctionMapperTest
test`
- [X] `./mvnw -pl langchain4j-google-ai-gemini test`
- [X] `./mvnw -pl langchain4j-google-ai-gemini verify`
- [X] `./mvnw -pl langchain4j-core,langchain4j test`
- [X] `git diff --check`
- [X] Changed Google AI Gemini files pass `spotless:check`
## Issue
Closes#5110
## Change
Added `customHeaders(Map<String, String>)` and
`customHeaders(Supplier<Map<String, String>>)` builder methods to
`GoogleAiGeminiChatModel` and `GoogleAiGeminiStreamingChatModel` (via
`GoogleAiGeminiChatModelBaseBuilder` in `BaseGeminiChatModel`).
Header injection is handled in `GeminiService.buildHttpRequest()`, which
already builds each HTTP request — the supplier is called per request,
consistent with the Ollama and Mistral implementations.
The `GeminiService` constructor signature was extended with a nullable
`Supplier` parameter; all non-chat call sites (embedding, image, token
count, etc.) pass `null`.
## 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
## Change
Add support for the Gemini `VALIDATED` [function calling
mode](https://ai.google.dev/gemini-api/docs/function-calling?example=meeting#function_calling_modes)
(preview), which allows both function calls and natural language
responses while guaranteeing schema adherence.
Note: `VALIDATED` maps to null in `toToolChoice()` (same as `NONE`) to
prevent a round-trip override issue where the mode would be converted to
`ToolChoice.AUTO`, stored in default chat request parameters, and then
overwritten back to `GeminiMode.AUTO` by `toToolConfig()`.
## 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)
## 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
<!--
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#3219
## Change
Introduce a `GoogleAiGeminiTokenUsage` subclass of `TokenUsage`, in line
with the existing `OpenAiTokenUsage` and `AnthropicTokenUsage`, exposing
the `cachedContentTokenCount` and `thoughtsTokenCount` fields that
Gemini already returns in its `usageMetadata` but that were being
dropped on deserialization. The new subclass is now returned by the
chat, streaming chat and image models.
## 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)
## 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 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)
## Issue
Closes#4875
## Change
Add image generation configuration support to `GoogleAiGeminiChatModel`
so users can control generated image dimensions via request payload.
### What changed
- Added builder options in
`BaseGeminiChatModel.GoogleAiGeminiChatModelBaseBuilder`:
- `aspectRatio(String)`
- `imageAspectRatio(String)` (alias)
- `imageSize(String)`
- Added `imageConfig` construction in `BaseGeminiChatModel` when either
aspect ratio or image size is configured.
- Injected `imageConfig` into `generationConfig` for chat requests:
- `generationConfig.imageConfig.aspectRatio`
- `generationConfig.imageConfig.imageSize`
- Added unit test `shouldSendImageConfigWhenConfigured` in
`GoogleAiGeminiChatModelTest` to verify request payload mapping.
## 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](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
- [ ] 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
Signed-off-by: ZhangDT-sky <485918776@qq.com>
## Issue
Closes#4845
## Change
On Android, the DEX compiler does not preserve Java record constructor
parameter names at runtime. This causes Jackson to fail to
serialize/deserialize record components, resulting in empty request
bodies (`{}`) being sent to the Gemini API.
This PR adds explicit `@JsonProperty` annotations to all record
components in the `langchain4j-google-ai-gemini` module that were
previously missing them.
This follows the same pattern already established by:
- `GeminiGenerationConfig` (already fully annotated in this module)
- The entire `langchain4j-open-ai` module (211 `@JsonProperty`
annotations)
### Files changed (14)
- `GeminiContent.java` — `GeminiContent`, `GeminiPart`, `GeminiBlob`,
`GeminiFunctionCall`, `GeminiFunctionResponse`, `GeminiFileData`,
`GeminiExecutableCode`, `GeminiCodeExecutionResult`
- `GeminiGenerateContentRequest.java` — `GeminiGenerateContentRequest`,
`GeminiTool`, `GeminiGoogleMaps`, `GeminiToolConfig`
- `GeminiGenerateContentResponse.java` —
`GeminiGenerateContentResponse`, `GeminiCandidate`,
`GeminiUrlContextMetadata`, `GeminiUrlMetadata`, `GeminiUsageMetadata`
- `GeminiFunctionDeclaration.java`, `GeminiCountTokensRequest.java`,
`GeminiCountTokensResponse.java`, `GeminiModelsListResponse.java`,
`GeminiModelInfo.java`, `GeminiThinkingConfig.java`
- `GroundingMetadata.java` — `GroundingMetadata` and all nested records
- `UrlContextMetadata.java`, `GeminiEmbeddingRequestResponse.java`,
`BatchRequestResponse.java`, `GeminiFiles.java`
## General checklist
- [X] There are no breaking changes (API, behaviour)
## Issue
Closes https://github.com/langchain4j/langchain4j/issues/4652
## Change
- Add support for `@Tool`-annotated methods to return multimodal content
(images) to the LLM, not just text. Tools can now return `Image`,
`ImageContent`, `Content`, `List<Content>`, `Content[]`, etc.
- Refactor `ToolExecutionResultMessage` to store `List<Content>`
internally instead of a plain `String`, with backward-compatible
`text()` accessor and new `contents()` / `hasSingleText()`
methods.
- Refactor `ToolExecutionResult` to support
`resultContents(List<Content>)` as an alternative to
`resultText(String)` and `resultTextSupplier(Supplier)`.
- Implement multimodal tool result mapping for providers that support
it: Anthropic, Amazon Bedrock, and Google AI Gemini.
- Add `UnsupportedFeatureException` guards in providers that do not
support non-text tool results: Azure OpenAI, GitHub Models, Jlama,
Mistral AI, Ollama, OpenAI (Chat Completions
& Responses), Vertex AI (Anthropic & Gemini), Watsonx, and Workers AI.
- Update `ToolExecutedEvent` / `DefaultToolExecutedEvent` to carry
`resultContents` alongside the existing `resultText` accessor.
- Add comprehensive unit and integration tests
(`should_execute_tool_returning_Image`,
`should_execute_tool_returning_ImageContent`,
`should_execute_tool_returning_ContentList`,
`should_fail_when_tool_returns_image_and_provider_does_not_support_it`).
- Update tools documentation with new "Returning Images and Multimodal
Content" and "Multimodal Tool Results" sections.
## 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#4839
## Change
This PR replaces the reported `Stream.toList()` usages with
`Collectors.toList()` in the affected Android crash paths.
I manually verified the targeted tests for the affected Ollama and
Gemini paths and confirmed the fix locally.
Changes included:
- Replaced `Stream.toList()` in Ollama's `InternalOllamaHelper`
- Replaced `Stream.toList()` in Gemini's `PartsAndContentsMapper`
- Added a unit test for `InternalOllamaHelper.toToolExecutionRequests`
This keeps the behavior unchanged while improving compatibility with
Android runtimes that do not support `Stream.toList()`.
## 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](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
- [ ] 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
## Issue
Closes#4808
## Change
Added a null/empty guard for `candidates` in
`GeminiStreamingResponseBuilder.append()` before accessing `.get(0)`.
**Before:**
```java
GeminiCandidate firstCandidate = partialResponse.candidates().get(0);
```
**After:**
```java
List<GeminiCandidate> candidates = partialResponse.candidates();
if (candidates == null || candidates.isEmpty()) {
return new TextAndTools(Optional.empty(), Optional.empty(), List.of());
}
GeminiCandidate firstCandidate = candidates.get(0);
```
This is consistent with the Vertex AI Gemini module's
`StreamingChatResponseBuilder` which already has this guard (line
30-31).
## 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)
<!--
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
Fixes#4773
## Change
This PR fixes an issue where the `tools` field in a Gemini
`generateContent` request was being serialized as a single JSON object,
rather than a JSON array as documented in the official Gemini schema.
While the backend currently tolerates this, it is non-canonical and
could cause issues with strict validators or future API changes.
The fix involves:
* Changing the `tools` field in `GeminiGenerateContentRequest` to be a
`List<GeminiTool>`.
* Updating the internal `FunctionMapper` to wrap the created
`GeminiTool` in a `List`.
* Adjusting all internal call sites and unit tests to accommodate the
new list structure.
## 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 tests in the module I have
changed (`langchain4j-google-ai-gemini`), 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)
Co-authored-by: Dmytro Liubarskyi <ljubarskij@gmail.com>