fix: Guard empty input and validate id/embedding sizes in VespaEmbeddingStore.addAll (#5632)

## Issue
<!-- 사람이 실제 이슈 번호로 갱신: 위 issues/ 초안을 등록한 뒤 그 번호를 적는다 -->
Closes #5629

## Change
`VespaEmbeddingStore.addAll(ids, embeddings, embedded)` now returns
early when `ids` or `embeddings` is null or empty, so an empty batch no
longer opens a feed client or contacts the Vespa server. This matches
the merged PgVector precedent (PR #4422) and the behaviour of other
embedding stores (Milvus, Qdrant, etc.).

The method also validates that `ids` and `embeddings` have the same
size, alongside the existing `embedded` size check. This is a
sibling-parity addition: today a mismatch surfaces only as
`IndexOutOfBoundsException` when the 3-arg method is called directly,
since the public `addAll(embeddings)` path builds matching ids
internally. The new check fails fast with a clear
`IllegalArgumentException` instead.

The existing feed logic, exception messages, and formatting are
unchanged. `isNullOrEmpty` was already imported, so no new dependencies
or imports were added.

## 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 <!-- Unit tests +
spotless green; module ITs (VespaEmbeddingStoreIT/CloudIT) need a live
Vespa server and were not run locally -->
- [ ] 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 <!-- N/A — change is isolated to
langchain4j-vespa -->
- [ ] I have added/updated the
[documentation](https://github.com/langchain4j/langchain4j/tree/main/docs/docs)
<!-- N/A — no public API/behaviour doc change -->
- [ ] I have added an example in the [examples
repo](https://github.com/langchain4j/langchain4j-examples) (only for
"big" features) <!-- N/A — small bug fix -->
- [ ] I have added/updated [Spring Boot
starter(s)](https://github.com/langchain4j/langchain4j-spring) (if
applicable) <!-- N/A -->

<!-- Checklist for adding new maven module: 생략 — 새 모듈 아님 -->
<!-- Checklist for adding/changing embedding store integration: 생략 — 신규
통합 아님, 동작 변경은 데이터 영속 포맷과 무관(빈 입력 early-return + size 검증) -->
This commit is contained in:
Eunbin Son 2026-06-29 18:04:25 +09:00 committed by GitHub
parent d225579243
commit c289860d60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 34 additions and 0 deletions

View File

@ -170,6 +170,12 @@ public class VespaEmbeddingStore implements EmbeddingStore<TextSegment> {
@Override
public void addAll(List<String> ids, List<Embedding> embeddings, List<TextSegment> embedded) {
if (isNullOrEmpty(ids) || isNullOrEmpty(embeddings)) {
return;
}
if (ids.size() != embeddings.size()) {
throw new IllegalArgumentException("The list of ids and embeddings must have the same size");
}
if (embedded != null && embeddings.size() != embedded.size()) {
throw new IllegalArgumentException("The list of embeddings and embedded must have the same size");
}

View File

@ -1,10 +1,14 @@
package dev.langchain4j.store.embedding.vespa;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import dev.langchain4j.data.embedding.Embedding;
import java.lang.reflect.Field;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.List;
import org.junit.jupiter.api.Test;
class VespaEmbeddingStoreTest {
@ -82,6 +86,30 @@ class VespaEmbeddingStoreTest {
assertThat(getFieldValue(store, "logResponses")).isEqualTo(false);
}
@Test
void should_throw_when_ids_and_embeddings_have_different_sizes() {
// Given
VespaEmbeddingStore store =
VespaEmbeddingStore.builder().url("https://test.vespa.ai").build();
Embedding embedding1 = Embedding.from(List.of(1f, 2f, 3f));
Embedding embedding2 = Embedding.from(List.of(4f, 5f, 6f));
// When + Then
assertThatThrownBy(() -> store.addAll(List.of("id1"), List.of(embedding1, embedding2), null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("same size");
}
@Test
void should_not_throw_or_call_server_when_input_is_empty() {
// Given
VespaEmbeddingStore store =
VespaEmbeddingStore.builder().url("https://test.vespa.ai").build();
// When + Then - empty input returns before any feeder/server interaction
assertThatCode(() -> store.addAll(List.of(), List.of(), null)).doesNotThrowAnyException();
}
private Object getFieldValue(Object object, String fieldName) throws Exception {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);