diff --git a/README.md b/README.md index b63b0fc..3102b2f 100644 --- a/README.md +++ b/README.md @@ -376,6 +376,131 @@ System.out.println(match.getDistance()); // Output: 0.273891836405 > Learn more about [semantic routing](https://redis.github.io/redis-vl-java/redisvl/current/semantic-router.html). +## πŸ§ͺ Experimental: VCR Test System + +RedisVL includes an experimental VCR (Video Cassette Recorder) test system for recording and replaying LLM/embedding API calls. This enables: + +- **Deterministic tests** - Replay recorded responses for consistent results +- **Cost reduction** - Avoid repeated API calls during test runs +- **Speed improvement** - Local Redis playback is faster than API calls +- **Offline testing** - Run tests without network access or API keys + +### Quick Start with JUnit 5 + +The simplest way to use VCR is with the declarative annotations: + +```java +import com.redis.vl.test.vcr.VCRMode; +import com.redis.vl.test.vcr.VCRModel; +import com.redis.vl.test.vcr.VCRTest; + +@VCRTest(mode = VCRMode.PLAYBACK_OR_RECORD) +class MyLLMTest { + + // Models are automatically wrapped by VCR + @VCRModel(modelName = "text-embedding-3-small") + private EmbeddingModel embeddingModel = createEmbeddingModel(); + + @VCRModel + private ChatLanguageModel chatModel = createChatModel(); + + @Test + void testEmbedding() { + // First run: Records API response to Redis + // Subsequent runs: Replays from Redis cassette + Response response = embeddingModel.embed("What is Redis?"); + assertNotNull(response.content()); + } + + @Test + void testChat() { + String response = chatModel.generate("Explain Redis in one sentence."); + assertNotNull(response); + } +} +``` + +### VCR Modes + +| Mode | Description | API Key Required | +|------|-------------|------------------| +| `PLAYBACK` | Only use recorded cassettes. Fails if missing. | No | +| `PLAYBACK_OR_RECORD` | Use cassette if available, record if not. | Only for first run | +| `RECORD` | Always call real API and record response. | Yes | +| `OFF` | Bypass VCR, always call real API. | Yes | + +### Environment Variable Override + +Override the VCR mode at runtime without changing code: + +```bash +# Record new cassettes +VCR_MODE=RECORD OPENAI_API_KEY=your-key ./gradlew test + +# Playback only (CI/CD, no API key needed) +VCR_MODE=PLAYBACK ./gradlew test +``` + +### LangChain4J Integration + +```java +import com.redis.vl.test.vcr.VCREmbeddingModel; +import com.redis.vl.test.vcr.VCRChatModel; +import com.redis.vl.test.vcr.VCRMode; + +// Wrap any LangChain4J EmbeddingModel +VCREmbeddingModel vcrEmbedding = new VCREmbeddingModel(openAiEmbeddingModel); +vcrEmbedding.setMode(VCRMode.PLAYBACK_OR_RECORD); +Response response = vcrEmbedding.embed("What is Redis?"); + +// Wrap any LangChain4J ChatLanguageModel +VCRChatModel vcrChat = new VCRChatModel(openAiChatModel); +vcrChat.setMode(VCRMode.PLAYBACK_OR_RECORD); +String response = vcrChat.generate("What is Redis?"); +``` + +### Spring AI Integration + +```java +import com.redis.vl.test.vcr.VCRSpringAIEmbeddingModel; +import com.redis.vl.test.vcr.VCRSpringAIChatModel; +import com.redis.vl.test.vcr.VCRMode; + +// Wrap any Spring AI EmbeddingModel +VCRSpringAIEmbeddingModel vcrEmbedding = new VCRSpringAIEmbeddingModel(openAiEmbeddingModel); +vcrEmbedding.setMode(VCRMode.PLAYBACK_OR_RECORD); +EmbeddingResponse response = vcrEmbedding.embedForResponse(List.of("What is Redis?")); + +// Wrap any Spring AI ChatModel +VCRSpringAIChatModel vcrChat = new VCRSpringAIChatModel(openAiChatModel); +vcrChat.setMode(VCRMode.PLAYBACK_OR_RECORD); +String response = vcrChat.call("What is Redis?"); +``` + +### How It Works + +1. **Container Management**: VCR starts a Redis Stack container with persistence +2. **Model Wrapping**: `@VCRModel` fields are wrapped with VCR proxies +3. **Cassette Storage**: Responses stored as Redis JSON documents +4. **Persistence**: Data saved to `src/test/resources/vcr-data/` via Redis AOF/RDB +5. **Playback**: Subsequent runs load cassettes from persistent storage + +### Demo Projects + +Complete working examples are available: + +- **[LangChain4J VCR Demo](demos/langchain4j-vcr/)** - LangChain4J embedding and chat models +- **[Spring AI VCR Demo](demos/spring-ai-vcr/)** - Spring AI embedding and chat models + +Run the demos without an API key (uses pre-recorded cassettes): + +```bash +./gradlew :demos:langchain4j-vcr:test +./gradlew :demos:spring-ai-vcr:test +``` + +> Learn more about [VCR testing](https://redis.github.io/redis-vl-java/redisvl/current/vcr-testing.html). + ## πŸš€ Why RedisVL? In the age of GenAI, **vector databases** and **LLMs** are transforming information retrieval systems. With emerging and popular frameworks like [LangChain4J](https://github.com/langchain4j/langchain4j) and [Spring AI](https://spring.io/projects/spring-ai), innovation is rapid. Yet, many organizations face the challenge of delivering AI solutions **quickly** and at **scale**. diff --git a/build.gradle.kts b/build.gradle.kts index 8ca1e4e..a04d73e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,9 @@ allprojects { maven { url = uri("https://repo.spring.io/milestone") } + maven { + url = uri("https://repo.spring.io/snapshot") + } } } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index cec90d5..7bba8d8 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -2,6 +2,7 @@ plugins { java `java-library` `maven-publish` + id("io.spring.dependency-management") version "1.1.7" } description = "RedisVL - Vector Library for Java" @@ -65,6 +66,16 @@ dependencies { // Cohere Java SDK for reranking compileOnly("com.cohere:cohere-java:1.8.1") + // VCR Test Utilities (optional - users include what they need for testing) + // JUnit 5 for extension development + compileOnly("org.junit.jupiter:junit-jupiter-api:5.10.2") + // Testcontainers for Redis persistence + compileOnly("org.testcontainers:testcontainers:1.19.7") + compileOnly("org.testcontainers:junit-jupiter:1.19.7") + // ByteBuddy for method interception (future LLM interceptor) + compileOnly("net.bytebuddy:byte-buddy:1.14.12") + compileOnly("net.bytebuddy:byte-buddy-agent:1.14.12") + // Test dependencies for LangChain4J (include in tests to verify integration) testImplementation("dev.langchain4j:langchain4j:0.36.2") testImplementation("dev.langchain4j:langchain4j-open-ai:0.36.2") @@ -73,12 +84,32 @@ dependencies { testImplementation("dev.langchain4j:langchain4j-embeddings-all-minilm-l6-v2:0.36.2") testImplementation("dev.langchain4j:langchain4j-hugging-face:0.36.2") + // Spring AI for VCR testing (optional - users include what they need) + // Version managed by spring-ai-bom (spring-ai-model contains EmbeddingModel interface) + compileOnly("org.springframework.ai:spring-ai-model") + testImplementation("org.springframework.ai:spring-ai-model") + // Cohere for integration tests testImplementation("com.cohere:cohere-java:1.8.1") // Additional test dependencies testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0") testImplementation("org.mockito:mockito-core:5.11.0") + + // VCR test dependencies (to test VCR functionality) + testImplementation("org.testcontainers:testcontainers:1.19.7") + testImplementation("org.testcontainers:junit-jupiter:1.19.7") + testImplementation("net.bytebuddy:byte-buddy:1.14.12") + testImplementation("net.bytebuddy:byte-buddy-agent:1.14.12") +} + +// Spring AI 1.1.0 - BOM for dependency management +val springAiVersion = "1.1.0" + +dependencyManagement { + imports { + mavenBom("org.springframework.ai:spring-ai-bom:$springAiVersion") + } } // Configure test execution diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRCassetteMissingException.java b/core/src/main/java/com/redis/vl/test/vcr/VCRCassetteMissingException.java new file mode 100644 index 0000000..655583d --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRCassetteMissingException.java @@ -0,0 +1,49 @@ +package com.redis.vl.test.vcr; + +/** + * Exception thrown when a VCR cassette is not found during playback mode. + * + *

This exception indicates that the test expected to find a recorded cassette but none was + * available. To fix this, run the test in RECORD or PLAYBACK_OR_RECORD mode first. + */ +public class VCRCassetteMissingException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final String cassetteKey; + private final String testId; + + /** + * Creates a new exception. + * + * @param cassetteKey the key that was not found + * @param testId the test identifier + */ + public VCRCassetteMissingException(String cassetteKey, String testId) { + super( + String.format( + "VCR cassette not found for test '%s'%nCassette key: %s%n" + + "Run with VCRMode.RECORD or VCRMode.PLAYBACK_OR_RECORD to record this interaction", + testId, cassetteKey)); + this.cassetteKey = cassetteKey; + this.testId = testId; + } + + /** + * Gets the cassette key that was not found. + * + * @return the cassette key + */ + public String getCassetteKey() { + return cassetteKey; + } + + /** + * Gets the test identifier. + * + * @return the test ID + */ + public String getTestId() { + return testId; + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRCassetteStore.java b/core/src/main/java/com/redis/vl/test/vcr/VCRCassetteStore.java new file mode 100644 index 0000000..f7e9ead --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRCassetteStore.java @@ -0,0 +1,249 @@ +package com.redis.vl.test.vcr; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.Objects; +import redis.clients.jedis.JedisPooled; +import redis.clients.jedis.json.Path2; + +/** + * Stores and retrieves VCR cassettes (recorded API responses) in Redis. + * + *

Cassettes are stored as Redis JSON documents with the following key format: + * + *

vcr:{type}:{testId}:{callIndex}
+ * + * Where: + * + * + */ +public class VCRCassetteStore { + + private static final String KEY_PREFIX = "vcr"; + private static final Gson GSON = new Gson(); + + private final JedisPooled jedis; + + /** + * Creates a new cassette store. + * + * @param jedis the Redis client + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "JedisPooled is intentionally shared for connection pooling") + public VCRCassetteStore(JedisPooled jedis) { + this.jedis = jedis; + } + + /** + * Formats a cassette key. + * + * @param type the cassette type + * @param testId the test identifier + * @param callIndex the call index (1-based) + * @return the formatted key + */ + public static String formatKey(String type, String testId, int callIndex) { + return String.format("%s:%s:%s:%04d", KEY_PREFIX, type, testId, callIndex); + } + + /** + * Parses a cassette key into its components. + * + * @param key the key to parse + * @return array of [prefix, type, testId, callIndex] or null if invalid + */ + public static String[] parseKey(String key) { + if (key == null) { + return null; + } + String[] parts = key.split(":"); + if (parts.length != 4 || !KEY_PREFIX.equals(parts[0])) { + return null; + } + return parts; + } + + /** + * Creates a cassette JSON object for an embedding. + * + * @param embedding the embedding vector + * @param testId the test identifier + * @param model the model name + * @return the cassette JSON object + */ + public static JsonObject createEmbeddingCassette(float[] embedding, String testId, String model) { + Objects.requireNonNull(embedding, "embedding cannot be null"); + Objects.requireNonNull(testId, "testId cannot be null"); + Objects.requireNonNull(model, "model cannot be null"); + + JsonObject cassette = new JsonObject(); + cassette.addProperty("type", "embedding"); + cassette.addProperty("testId", testId); + cassette.addProperty("model", model); + cassette.addProperty("timestamp", System.currentTimeMillis()); + + JsonArray embeddingArray = new JsonArray(); + for (float value : embedding) { + embeddingArray.add(value); + } + cassette.add("embedding", embeddingArray); + + return cassette; + } + + /** + * Creates a cassette JSON object for batch embeddings. + * + * @param embeddings the embedding vectors + * @param testId the test identifier + * @param model the model name + * @return the cassette JSON object + */ + public static JsonObject createBatchEmbeddingCassette( + float[][] embeddings, String testId, String model) { + Objects.requireNonNull(embeddings, "embeddings cannot be null"); + Objects.requireNonNull(testId, "testId cannot be null"); + Objects.requireNonNull(model, "model cannot be null"); + + JsonObject cassette = new JsonObject(); + cassette.addProperty("type", "batch_embedding"); + cassette.addProperty("testId", testId); + cassette.addProperty("model", model); + cassette.addProperty("timestamp", System.currentTimeMillis()); + + JsonArray embeddingsArray = new JsonArray(); + for (float[] embedding : embeddings) { + JsonArray embeddingArray = new JsonArray(); + for (float value : embedding) { + embeddingArray.add(value); + } + embeddingsArray.add(embeddingArray); + } + cassette.add("embeddings", embeddingsArray); + + return cassette; + } + + /** + * Extracts embedding from a cassette JSON object. + * + * @param cassette the cassette object + * @return the embedding vector or null if not present + */ + public static float[] extractEmbedding(JsonObject cassette) { + if (cassette == null || !cassette.has("embedding")) { + return null; + } + + JsonArray embeddingArray = cassette.getAsJsonArray("embedding"); + float[] embedding = new float[embeddingArray.size()]; + for (int i = 0; i < embeddingArray.size(); i++) { + embedding[i] = embeddingArray.get(i).getAsFloat(); + } + return embedding; + } + + /** + * Extracts batch embeddings from a cassette JSON object. + * + * @param cassette the cassette object + * @return the embedding vectors or null if not present + */ + public static float[][] extractBatchEmbeddings(JsonObject cassette) { + if (cassette == null || !cassette.has("embeddings")) { + return null; + } + + JsonArray embeddingsArray = cassette.getAsJsonArray("embeddings"); + float[][] embeddings = new float[embeddingsArray.size()][]; + + for (int i = 0; i < embeddingsArray.size(); i++) { + JsonArray embeddingArray = embeddingsArray.get(i).getAsJsonArray(); + embeddings[i] = new float[embeddingArray.size()]; + for (int j = 0; j < embeddingArray.size(); j++) { + embeddings[i][j] = embeddingArray.get(j).getAsFloat(); + } + } + return embeddings; + } + + /** + * Stores a cassette in Redis. + * + * @param key the cassette key + * @param cassette the cassette data + */ + public void store(String key, JsonObject cassette) { + if (jedis == null) { + throw new IllegalStateException("Redis client not initialized"); + } + jedis.jsonSet(key, Path2.ROOT_PATH, GSON.toJson(cassette)); + } + + /** + * Retrieves a cassette from Redis. + * + * @param key the cassette key + * @return the cassette data or null if not found + */ + public JsonObject retrieve(String key) { + if (jedis == null) { + return null; + } + Object result = jedis.jsonGet(key, Path2.ROOT_PATH); + if (result == null) { + return null; + } + + // Handle both array and object responses from jsonGet + // Some Jedis versions/configurations return arrays when using ROOT_PATH + String jsonString = result.toString(); + com.google.gson.JsonElement element = + GSON.fromJson(jsonString, com.google.gson.JsonElement.class); + + if (element.isJsonArray()) { + com.google.gson.JsonArray array = element.getAsJsonArray(); + if (array.isEmpty()) { + return null; + } + // Return the first element if it's wrapped in an array + return array.get(0).getAsJsonObject(); + } else if (element.isJsonObject()) { + return element.getAsJsonObject(); + } + + return null; + } + + /** + * Checks if a cassette exists. + * + * @param key the cassette key + * @return true if the cassette exists + */ + public boolean exists(String key) { + if (jedis == null) { + return false; + } + return jedis.exists(key); + } + + /** + * Deletes a cassette. + * + * @param key the cassette key + */ + public void delete(String key) { + if (jedis != null) { + jedis.del(key); + } + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRChatModel.java b/core/src/main/java/com/redis/vl/test/vcr/VCRChatModel.java new file mode 100644 index 0000000..1a36b35 --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRChatModel.java @@ -0,0 +1,264 @@ +package com.redis.vl.test.vcr; + +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.output.Response; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * VCR wrapper for LangChain4J ChatLanguageModel that records and replays LLM responses. + * + *

This class implements the ChatLanguageModel interface, allowing it to be used as a drop-in + * replacement for any LangChain4J chat model. It provides VCR (Video Cassette Recorder) + * functionality to record LLM responses during test execution and replay them in subsequent runs. + * + *

Usage: + * + *

{@code
+ * ChatLanguageModel openAiModel = OpenAiChatModel.builder()
+ *     .apiKey(System.getenv("OPENAI_API_KEY"))
+ *     .build();
+ *
+ * VCRChatModel vcrModel = new VCRChatModel(openAiModel);
+ * vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD);
+ * vcrModel.setTestId("MyTest.testMethod");
+ *
+ * // Use exactly like the original model
+ * Response response = vcrModel.generate(UserMessage.from("Hello"));
+ * }
+ */ +@SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "Delegate is intentionally stored and exposed for VCR functionality") +public final class VCRChatModel implements ChatLanguageModel { + + private final ChatLanguageModel delegate; + private VCRCassetteStore cassetteStore; + private VCRMode mode = VCRMode.PLAYBACK_OR_RECORD; + private String testId = "unknown"; + private final AtomicInteger callCounter = new AtomicInteger(0); + + // In-memory cassette storage for unit tests + private final Map cassettes = new HashMap<>(); + + // Statistics + private int cacheHits = 0; + private int cacheMisses = 0; + private int recordedCount = 0; + + /** + * Creates a new VCRChatModel wrapping the given delegate. + * + * @param delegate The actual ChatLanguageModel to wrap + */ + public VCRChatModel(ChatLanguageModel delegate) { + this.delegate = delegate; + } + + /** + * Creates a new VCRChatModel wrapping the given delegate with Redis storage. + * + * @param delegate The actual ChatLanguageModel to wrap + * @param cassetteStore The cassette store for persistence + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "VCRCassetteStore is intentionally shared") + public VCRChatModel(ChatLanguageModel delegate, VCRCassetteStore cassetteStore) { + this.delegate = delegate; + this.cassetteStore = cassetteStore; + } + + /** + * Sets the VCR mode. + * + * @param mode The VCR mode to use + */ + public void setMode(VCRMode mode) { + this.mode = mode; + } + + /** + * Gets the current VCR mode. + * + * @return The current VCR mode + */ + public VCRMode getMode() { + return mode; + } + + /** + * Sets the test identifier for cassette key generation. + * + * @param testId The test identifier (typically ClassName.methodName) + */ + public void setTestId(String testId) { + this.testId = testId; + } + + /** + * Gets the current test identifier. + * + * @return The current test identifier + */ + public String getTestId() { + return testId; + } + + /** Resets the call counter. Useful when starting a new test method. */ + public void resetCallCounter() { + callCounter.set(0); + } + + /** + * Gets the underlying delegate model. + * + * @return The wrapped ChatLanguageModel + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP", + justification = "Intentional exposure of delegate for advanced use cases") + public ChatLanguageModel getDelegate() { + return delegate; + } + + /** + * Preloads a cassette for testing purposes. + * + * @param key The cassette key + * @param response The response text to cache + */ + public void preloadCassette(String key, String response) { + cassettes.put(key, response); + } + + /** + * Gets the number of cache hits. + * + * @return Cache hit count + */ + public int getCacheHits() { + return cacheHits; + } + + /** + * Gets the number of cache misses. + * + * @return Cache miss count + */ + public int getCacheMisses() { + return cacheMisses; + } + + /** + * Gets the number of recorded responses. + * + * @return Recorded count + */ + public int getRecordedCount() { + return recordedCount; + } + + /** Resets all statistics. */ + public void resetStatistics() { + cacheHits = 0; + cacheMisses = 0; + recordedCount = 0; + } + + @Override + public Response generate(List messages) { + return generateInternal(messages); + } + + @Override + public String generate(String userMessage) { + Response response = generateInternal(userMessage); + return response.content().text(); + } + + private Response generateInternal(Object input) { + if (mode == VCRMode.OFF) { + return callDelegate(input); + } + + String key = formatKey(); + + if (mode.isPlaybackMode()) { + String cached = loadCassette(key); + if (cached != null) { + cacheHits++; + return Response.from(AiMessage.from(cached)); + } + + if (mode == VCRMode.PLAYBACK) { + throw new VCRCassetteMissingException(key, testId); + } + + // PLAYBACK_OR_RECORD - fall through to record + } + + // Record mode or cache miss in PLAYBACK_OR_RECORD + cacheMisses++; + Response response = callDelegate(input); + String responseText = response.content().text(); + saveCassette(key, responseText); + recordedCount++; + + return response; + } + + private String loadCassette(String key) { + // Check in-memory first + String inMemory = cassettes.get(key); + if (inMemory != null) { + return inMemory; + } + + // Check Redis if available + if (cassetteStore != null) { + com.google.gson.JsonObject cassette = cassetteStore.retrieve(key); + if (cassette != null && cassette.has("response")) { + return cassette.get("response").getAsString(); + } + } + + return null; + } + + private void saveCassette(String key, String response) { + // Save to in-memory + cassettes.put(key, response); + + // Save to Redis if available + if (cassetteStore != null) { + com.google.gson.JsonObject cassette = new com.google.gson.JsonObject(); + cassette.addProperty("response", response); + cassette.addProperty("testId", testId); + cassette.addProperty("type", "chat"); + cassetteStore.store(key, cassette); + } + } + + @SuppressWarnings("unchecked") + private Response callDelegate(Object input) { + if (input instanceof List) { + return delegate.generate((List) input); + } else if (input instanceof String) { + String text = delegate.generate((String) input); + return Response.from(AiMessage.from(text)); + } else { + throw new IllegalArgumentException("Unsupported input type: " + input.getClass()); + } + } + + private String formatKey() { + int index = callCounter.incrementAndGet(); + return String.format("vcr:chat:%s:%04d", testId, index); + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRContext.java b/core/src/main/java/com/redis/vl/test/vcr/VCRContext.java new file mode 100644 index 0000000..675a73d --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRContext.java @@ -0,0 +1,460 @@ +package com.redis.vl.test.vcr; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPooled; + +/** + * Manages VCR state and resources throughout a test session. + * + *

VCRContext handles: + * + *

    + *
  • Redis container lifecycle with persistence + *
  • Test context tracking + *
  • Call counter management for cassette key generation + *
  • Statistics collection + *
+ */ +public class VCRContext { + + private final VCRTest config; + private final Path dataDir; + + private GenericContainer redisContainer; + private JedisPooled jedis; + private VCRRegistry registry; + private VCRCassetteStore cassetteStore; + + private String currentTestId; + private VCRMode effectiveMode; + private final List currentCassetteKeys = new ArrayList<>(); + private final Map callCounters = new ConcurrentHashMap<>(); + + // Statistics + private final AtomicLong cacheHits = new AtomicLong(); + private final AtomicLong cacheMisses = new AtomicLong(); + private final AtomicLong apiCalls = new AtomicLong(); + + /** Environment variable name for overriding VCR mode. */ + public static final String VCR_MODE_ENV = "VCR_MODE"; + + /** + * Creates a new VCR context with the given configuration. + * + *

The VCR mode can be overridden via the {@code VCR_MODE} environment variable. Valid values + * are: PLAYBACK, PLAYBACK_OR_RECORD, RECORD, OFF. If the environment variable is set, it takes + * precedence over the annotation's mode setting. + * + * @param config the VCR test configuration + */ + public VCRContext(VCRTest config) { + this.config = config; + this.dataDir = Path.of(config.dataDir()); + this.effectiveMode = resolveMode(config.mode()); + } + + /** + * Resolves the effective VCR mode, checking the environment variable first. + * + * @param annotationMode the mode specified in the annotation + * @return the effective mode (env var takes precedence) + */ + private static VCRMode resolveMode(VCRMode annotationMode) { + String envMode = System.getenv(VCR_MODE_ENV); + if (envMode != null && !envMode.isEmpty()) { + try { + VCRMode mode = VCRMode.valueOf(envMode.toUpperCase()); + System.out.println( + "VCR: Using mode from " + + VCR_MODE_ENV + + " environment variable: " + + mode + + " (annotation was: " + + annotationMode + + ")"); + return mode; + } catch (IllegalArgumentException e) { + System.err.println( + "VCR: Invalid " + + VCR_MODE_ENV + + " value '" + + envMode + + "'. Valid values: PLAYBACK, PLAYBACK_OR_RECORD, RECORD, OFF. Using annotation value: " + + annotationMode); + } + } + return annotationMode; + } + + /** + * Initializes the VCR context, starting Redis and loading existing cassettes. + * + * @throws Exception if initialization fails + */ + public void initialize() throws Exception { + // Ensure data directory exists + Files.createDirectories(dataDir); + + // Start Redis container with persistence + startRedis(); + + // Initialize registry and cassette store + registry = new VCRRegistry(jedis); + cassetteStore = new VCRCassetteStore(jedis); + } + + /** + * Gets the cassette store for storing/retrieving cassettes. + * + * @return the cassette store + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP", + justification = "Callers need direct access to shared cassette store") + public VCRCassetteStore getCassetteStore() { + return cassetteStore; + } + + /** Starts the Redis container with appropriate persistence configuration. */ + @SuppressWarnings("resource") + private void startRedis() { + String redisCommand = buildRedisCommand(); + + // In playback mode, copy data to temp directory to prevent modifications to source files + Path mountPath = dataDir; + if (!effectiveMode.isRecordMode()) { + try { + mountPath = copyDataToTemp(); + } catch (Exception e) { + System.err.println( + "VCR: Failed to copy data to temp directory, using original: " + e.getMessage()); + mountPath = dataDir; + } + } + + redisContainer = + new GenericContainer<>(DockerImageName.parse(config.redisImage())) + .withExposedPorts(6379) + .withFileSystemBind(mountPath.toAbsolutePath().toString(), "/data", BindMode.READ_WRITE) + .withCommand(redisCommand); + + redisContainer.start(); + + String host = redisContainer.getHost(); + Integer port = redisContainer.getFirstMappedPort(); + jedis = new JedisPooled(host, port); + + // Wait for Redis to be ready and load existing data + waitForRedis(); + } + + private String buildRedisCommand() { + StringBuilder cmd = new StringBuilder("redis-stack-server"); + cmd.append(" --dir /data"); + cmd.append(" --dbfilename dump.rdb"); + + if (effectiveMode.isRecordMode()) { + // Enable AOF and periodic saves in record mode + cmd.append(" --appendonly yes"); + cmd.append(" --appendfsync everysec"); + cmd.append(" --save 60 1 --save 300 10"); + } else { + // Disable all persistence in playback mode (read-only) + cmd.append(" --appendonly no"); + cmd.append(" --save \"\""); + } + + return cmd.toString(); + } + + /** + * Copies VCR data to a temporary directory to prevent modifications to source files. Used in + * playback mode to ensure cassette files are not modified. + * + * @return path to the temporary directory containing the copied data + * @throws IOException if copying fails + */ + private Path copyDataToTemp() throws IOException { + Path tempDir = Files.createTempDirectory("vcr-playback-"); + tempDir.toFile().deleteOnExit(); + + Files.walkFileTree( + dataDir, + new SimpleFileVisitor<>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) + throws IOException { + Path targetDir = tempDir.resolve(dataDir.relativize(dir)); + Files.createDirectories(targetDir); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Path targetFile = tempDir.resolve(dataDir.relativize(file)); + Files.copy(file, targetFile, StandardCopyOption.REPLACE_EXISTING); + return FileVisitResult.CONTINUE; + } + }); + + System.out.println( + "VCR: Using temporary copy at " + tempDir + " for playback (read-only protection)"); + return tempDir; + } + + private void waitForRedis() { + for (int i = 0; i < 30; i++) { + try { + jedis.ping(); + long dbSize = jedis.dbSize(); + if (dbSize > 0) { + System.out.println("VCR: Loaded " + dbSize + " keys from persisted data"); + } + return; + } catch (Exception e) { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for Redis", ie); + } + } + } + throw new RuntimeException("Timeout waiting for Redis to be ready"); + } + + /** Shuts down the VCR context and releases resources. */ + public void shutdown() { + if (jedis != null) { + jedis.close(); + jedis = null; + } + if (redisContainer != null) { + redisContainer.stop(); + redisContainer = null; + } + } + + /** Resets call counters for a new test. */ + public void resetCallCounters() { + callCounters.clear(); + currentCassetteKeys.clear(); + } + + /** + * Sets the current test context. + * + * @param testId the unique test identifier + */ + public void setCurrentTest(String testId) { + this.currentTestId = testId; + } + + /** + * Gets the current test ID. + * + * @return the current test ID + */ + public String getCurrentTestId() { + return currentTestId; + } + + /** + * Generates a unique cassette key for the current test and call type. + * + * @param type the type of call (e.g., "llm", "embedding") + * @return the generated cassette key + */ + public String generateCassetteKey(String type) { + String counterKey = currentTestId + ":" + type; + int callIndex = + callCounters.computeIfAbsent(counterKey, k -> new AtomicInteger()).incrementAndGet(); + + String key = String.format("vcr:%s:%s:%04d", type, currentTestId, callIndex); + currentCassetteKeys.add(key); + return key; + } + + /** + * Gets the cassette keys generated for the current test. + * + * @return list of cassette keys + */ + public List getCurrentCassetteKeys() { + return new ArrayList<>(currentCassetteKeys); + } + + /** + * Deletes the specified cassettes. + * + * @param keys the cassette keys to delete + */ + public void deleteCassettes(List keys) { + if (jedis != null && keys != null && !keys.isEmpty()) { + jedis.del(keys.toArray(new String[0])); + } + } + + /** Persists cassettes by triggering a Redis BGSAVE. */ + public void persistCassettes() { + if (jedis == null) { + return; + } + + // Use a separate Jedis connection for BGSAVE since JedisPooled doesn't expose it directly + String host = redisContainer.getHost(); + Integer port = redisContainer.getFirstMappedPort(); + try (Jedis directJedis = new Jedis(host, port)) { + directJedis.bgsave(); + + // Wait for save to complete + long lastSave = directJedis.lastsave(); + for (int i = 0; i < 100; i++) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + if (directJedis.lastsave() != lastSave) { + System.out.println("VCR: Persisted cassettes to disk"); + return; + } + } + System.err.println("VCR: Warning - BGSAVE may not have completed"); + } + } + + /** + * Gets the VCR registry. + * + * @return the registry + */ + public VCRRegistry getRegistry() { + return registry; + } + + /** + * Gets the configured VCR mode. + * + * @return the configured mode + */ + public VCRMode getConfiguredMode() { + return config.mode(); + } + + /** + * Gets the effective VCR mode for the current test. + * + * @return the effective mode + */ + public VCRMode getEffectiveMode() { + return effectiveMode; + } + + /** + * Sets the effective VCR mode. + * + * @param mode the mode to set + */ + public void setEffectiveMode(VCRMode mode) { + this.effectiveMode = mode; + } + + /** + * Gets the Redis client. + * + * @return the Jedis client + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP", + justification = "Callers need direct access to shared Redis connection pool") + public JedisPooled getJedis() { + return jedis; + } + + /** + * Gets the data directory path. + * + * @return the data directory + */ + public Path getDataDir() { + return dataDir; + } + + // Statistics methods + + /** Records a cache hit. */ + public void recordCacheHit() { + cacheHits.incrementAndGet(); + } + + /** Records a cache miss. */ + public void recordCacheMiss() { + cacheMisses.incrementAndGet(); + } + + /** Records an API call. */ + public void recordApiCall() { + apiCalls.incrementAndGet(); + } + + /** Prints VCR statistics to stdout. */ + public void printStatistics() { + long hits = cacheHits.get(); + long misses = cacheMisses.get(); + long total = hits + misses; + double hitRate = total > 0 ? (double) hits / total * 100 : 0; + + System.out.println("=== VCR Statistics ==="); + System.out.printf("Cache Hits: %d%n", hits); + System.out.printf("Cache Misses: %d%n", misses); + System.out.printf("API Calls: %d%n", apiCalls.get()); + System.out.printf("Hit Rate: %.1f%%%n", hitRate); + } + + /** + * Gets the cache hit count. + * + * @return number of cache hits + */ + public long getCacheHits() { + return cacheHits.get(); + } + + /** + * Gets the cache miss count. + * + * @return number of cache misses + */ + public long getCacheMisses() { + return cacheMisses.get(); + } + + /** + * Gets the API call count. + * + * @return number of API calls + */ + public long getApiCalls() { + return apiCalls.get(); + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRDisabled.java b/core/src/main/java/com/redis/vl/test/vcr/VCRDisabled.java new file mode 100644 index 0000000..21d52ae --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRDisabled.java @@ -0,0 +1,39 @@ +package com.redis.vl.test.vcr; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Disables VCR functionality for a specific test method. + * + *

When applied to a test method, this annotation completely disables VCR for that test. All LLM + * calls will go to real APIs and nothing will be recorded or played back. + * + *

Example usage: + * + *

{@code
+ * @VCRTest(mode = VCRMode.PLAYBACK)
+ * class MyLLMIntegrationTest {
+ *
+ *     @Test
+ *     void usesPlayback() {
+ *         // Uses cached responses
+ *     }
+ *
+ *     @Test
+ *     @VCRDisabled
+ *     void bypassesVCR() {
+ *         // VCR is completely disabled - real API calls, no caching
+ *     }
+ * }
+ * }
+ * + * @see VCRTest + * @see VCRRecord + * @see VCRMode#OFF + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface VCRDisabled {} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCREmbeddingInterceptor.java b/core/src/main/java/com/redis/vl/test/vcr/VCREmbeddingInterceptor.java new file mode 100644 index 0000000..66f0ce9 --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCREmbeddingInterceptor.java @@ -0,0 +1,334 @@ +package com.redis.vl.test.vcr; + +import com.google.gson.JsonObject; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Intercepts embedding calls for VCR recording and playback. + * + *

This interceptor captures embedding API calls during test recording and replays them during + * playback, enabling deterministic and fast tests without actual API calls. + * + *

Usage: + * + *

{@code
+ * VCREmbeddingInterceptor interceptor = new VCREmbeddingInterceptor(cassetteStore);
+ * interceptor.setMode(VCRMode.PLAYBACK_OR_RECORD);
+ * interceptor.setTestId("MyTest.testMethod");
+ *
+ * // In your test
+ * float[] embedding = interceptor.embed("text to embed");
+ * }
+ */ +public abstract class VCREmbeddingInterceptor { + + private static final String CASSETTE_TYPE = "embedding"; + + private VCRMode mode = VCRMode.OFF; + private String testId; + private String modelName = "default"; + private final AtomicInteger callCounter = new AtomicInteger(0); + + // In-memory cassette storage for unit tests (null = use Redis) + private VCRCassetteStore cassetteStore; + private final Map inMemoryCassettes = new ConcurrentHashMap<>(); + private final Map inMemoryBatchCassettes = new ConcurrentHashMap<>(); + private final List recordedKeys = new ArrayList<>(); + + // Statistics + private final AtomicLong cacheHits = new AtomicLong(0); + private final AtomicLong cacheMisses = new AtomicLong(0); + + /** Creates a new interceptor without Redis (for unit testing). */ + protected VCREmbeddingInterceptor() { + this.cassetteStore = null; + } + + /** + * Creates a new interceptor with Redis cassette storage. + * + * @param cassetteStore the cassette store + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "VCRCassetteStore is intentionally shared for Redis connection pooling") + public VCREmbeddingInterceptor(VCRCassetteStore cassetteStore) { + this.cassetteStore = cassetteStore; + } + + /** + * Sets the VCR mode. + * + * @param mode the mode + */ + public void setMode(VCRMode mode) { + this.mode = mode; + } + + /** + * Gets the current VCR mode. + * + * @return the mode + */ + public VCRMode getMode() { + return mode; + } + + /** + * Sets the current test identifier. + * + * @param testId the test ID + */ + public void setTestId(String testId) { + this.testId = testId; + this.callCounter.set(0); + } + + /** + * Sets the model name for cassette metadata. + * + * @param modelName the model name + */ + public void setModelName(String modelName) { + this.modelName = modelName; + } + + /** + * Generates a single embedding, intercepting based on VCR mode. + * + * @param text the text to embed + * @return the embedding vector + */ + public float[] embed(String text) { + if (mode == VCRMode.OFF) { + return callRealEmbedding(text); + } + + String cassetteKey = generateCassetteKey(); + + // Try to load from cache in playback modes + if (mode.isPlaybackMode()) { + float[] cached = loadCassette(cassetteKey); + if (cached != null) { + cacheHits.incrementAndGet(); + return cached; + } + + // Strict playback mode - throw if not found + if (mode == VCRMode.PLAYBACK) { + throw new VCRCassetteMissingException(cassetteKey, testId); + } + } + + // Cache miss or record mode - call real API + cacheMisses.incrementAndGet(); + float[] embedding = callRealEmbedding(text); + + // Record if in recording mode + if (mode.isRecordMode() || mode == VCRMode.PLAYBACK_OR_RECORD) { + saveCassette(cassetteKey, embedding); + } + + return embedding; + } + + /** + * Generates batch embeddings, intercepting based on VCR mode. + * + * @param texts the texts to embed + * @return list of embedding vectors + */ + public List embedBatch(List texts) { + if (mode == VCRMode.OFF) { + return callRealBatchEmbedding(texts); + } + + String cassetteKey = generateCassetteKey(); + + // Try to load from cache in playback modes + if (mode.isPlaybackMode()) { + float[][] cached = loadBatchCassette(cassetteKey); + if (cached != null) { + cacheHits.incrementAndGet(); + List result = new ArrayList<>(); + for (float[] embedding : cached) { + result.add(embedding); + } + return result; + } + + // Strict playback mode - throw if not found + if (mode == VCRMode.PLAYBACK) { + throw new VCRCassetteMissingException(cassetteKey, testId); + } + } + + // Cache miss or record mode - call real API + cacheMisses.incrementAndGet(); + List embeddings = callRealBatchEmbedding(texts); + + // Record if in recording mode + if (mode.isRecordMode() || mode == VCRMode.PLAYBACK_OR_RECORD) { + saveBatchCassette(cassetteKey, embeddings.toArray(new float[0][])); + } + + return embeddings; + } + + /** + * Preloads a cassette into the in-memory cache (for testing). + * + * @param key the cassette key + * @param embedding the embedding to cache + */ + public void preloadCassette(String key, float[] embedding) { + inMemoryCassettes.put(key, embedding); + } + + /** + * Preloads a batch cassette into the in-memory cache (for testing). + * + * @param key the cassette key + * @param embeddings the embeddings to cache + */ + public void preloadBatchCassette(String key, float[][] embeddings) { + inMemoryBatchCassettes.put(key, embeddings); + } + + /** + * Gets the number of recorded cassettes. + * + * @return the count + */ + public int getRecordedCount() { + return recordedKeys.size(); + } + + /** + * Gets the recorded cassette keys. + * + * @return list of keys + */ + public List getRecordedKeys() { + return new ArrayList<>(recordedKeys); + } + + /** + * Gets the cache hit count. + * + * @return number of cache hits + */ + public long getCacheHits() { + return cacheHits.get(); + } + + /** + * Gets the cache miss count. + * + * @return number of cache misses + */ + public long getCacheMisses() { + return cacheMisses.get(); + } + + /** Resets statistics. */ + public void resetStatistics() { + cacheHits.set(0); + cacheMisses.set(0); + } + + /** Resets the call counter (call at start of each test). */ + public void resetCallCounter() { + callCounter.set(0); + } + + /** + * Called to perform the actual embedding call. Subclasses must implement this. + * + * @param text the text to embed + * @return the embedding vector + */ + protected abstract float[] callRealEmbedding(String text); + + /** + * Called to perform actual batch embedding call. Subclasses must implement this. + * + * @param texts the texts to embed + * @return list of embedding vectors + */ + protected abstract List callRealBatchEmbedding(List texts); + + private String generateCassetteKey() { + int index = callCounter.incrementAndGet(); + return VCRCassetteStore.formatKey(CASSETTE_TYPE, testId, index); + } + + private float[] loadCassette(String key) { + // Check in-memory first + float[] inMemory = inMemoryCassettes.get(key); + if (inMemory != null) { + return inMemory; + } + + // Check Redis + if (cassetteStore != null) { + JsonObject cassette = cassetteStore.retrieve(key); + if (cassette != null) { + return VCRCassetteStore.extractEmbedding(cassette); + } + } + + return null; + } + + private float[][] loadBatchCassette(String key) { + // Check in-memory first + float[][] inMemory = inMemoryBatchCassettes.get(key); + if (inMemory != null) { + return inMemory; + } + + // Check Redis + if (cassetteStore != null) { + JsonObject cassette = cassetteStore.retrieve(key); + if (cassette != null) { + return VCRCassetteStore.extractBatchEmbeddings(cassette); + } + } + + return null; + } + + private void saveCassette(String key, float[] embedding) { + recordedKeys.add(key); + + // Save to in-memory + inMemoryCassettes.put(key, embedding); + + // Save to Redis if available + if (cassetteStore != null) { + JsonObject cassette = VCRCassetteStore.createEmbeddingCassette(embedding, testId, modelName); + cassetteStore.store(key, cassette); + } + } + + private void saveBatchCassette(String key, float[][] embeddings) { + recordedKeys.add(key); + + // Save to in-memory + inMemoryBatchCassettes.put(key, embeddings); + + // Save to Redis if available + if (cassetteStore != null) { + JsonObject cassette = + VCRCassetteStore.createBatchEmbeddingCassette(embeddings, testId, modelName); + cassetteStore.store(key, cassette); + } + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCREmbeddingModel.java b/core/src/main/java/com/redis/vl/test/vcr/VCREmbeddingModel.java new file mode 100644 index 0000000..1391dd7 --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCREmbeddingModel.java @@ -0,0 +1,376 @@ +package com.redis.vl.test.vcr; + +import com.google.gson.JsonObject; +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.output.Response; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +/** + * VCR-enabled wrapper around a LangChain4J EmbeddingModel. + * + *

This wrapper intercepts embedding calls and routes them through the VCR system for recording + * and playback. + * + *

Usage: + * + *

{@code
+ * // In your test
+ * EmbeddingModel realModel = new AllMiniLmL6V2EmbeddingModel();
+ * VCREmbeddingModel vcrModel = new VCREmbeddingModel(realModel);
+ * vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD);
+ * vcrModel.setTestId("MyTest.testMethod");
+ *
+ * // Use vcrModel instead of realModel
+ * Response response = vcrModel.embed("text");
+ * }
+ */ +public class VCREmbeddingModel implements EmbeddingModel { + + private static final String CASSETTE_TYPE = "embedding"; + + private final EmbeddingModel delegate; + private final int dimensions; + + private VCRMode mode = VCRMode.OFF; + private String testId; + private String modelName = "default"; + private final AtomicInteger callCounter = new AtomicInteger(0); + + // In-memory cassette storage for unit tests (null = use Redis) + private VCRCassetteStore cassetteStore; + private final Map inMemoryCassettes = new ConcurrentHashMap<>(); + private final Map inMemoryBatchCassettes = new ConcurrentHashMap<>(); + private final List recordedKeys = new ArrayList<>(); + + // Statistics + private final AtomicLong cacheHits = new AtomicLong(0); + private final AtomicLong cacheMisses = new AtomicLong(0); + + /** + * Creates a VCR-enabled embedding model wrapper. + * + * @param delegate the real embedding model + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "EmbeddingModel delegate is intentionally shared") + public VCREmbeddingModel(EmbeddingModel delegate) { + this.delegate = delegate; + this.dimensions = detectDimensions(delegate); + } + + /** + * Creates a VCR-enabled embedding model wrapper with Redis storage. + * + * @param delegate the real embedding model + * @param cassetteStore the cassette store + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "EmbeddingModel and VCRCassetteStore are intentionally shared") + public VCREmbeddingModel(EmbeddingModel delegate, VCRCassetteStore cassetteStore) { + this.delegate = delegate; + this.cassetteStore = cassetteStore; + this.dimensions = detectDimensions(delegate); + } + + /** + * Sets the VCR mode. + * + * @param mode the mode + */ + public void setMode(VCRMode mode) { + this.mode = mode; + } + + /** + * Gets the current VCR mode. + * + * @return the mode + */ + public VCRMode getMode() { + return mode; + } + + /** + * Sets the current test identifier. + * + * @param testId the test ID + */ + public void setTestId(String testId) { + this.testId = testId; + this.callCounter.set(0); + } + + /** + * Sets the model name for cassette metadata. + * + * @param modelName the model name + */ + public void setModelName(String modelName) { + this.modelName = modelName; + } + + /** Resets the call counter (call at start of each test). */ + public void resetCallCounter() { + callCounter.set(0); + } + + @Override + public Response embed(String text) { + float[] embedding = embedInternal(text); + return Response.from(Embedding.from(embedding)); + } + + @Override + public Response embed(TextSegment textSegment) { + return embed(textSegment.text()); + } + + @Override + public Response> embedAll(List textSegments) { + List texts = textSegments.stream().map(TextSegment::text).collect(Collectors.toList()); + + List embeddings = embedBatchInternal(texts); + + List result = embeddings.stream().map(Embedding::from).collect(Collectors.toList()); + + return Response.from(result); + } + + @Override + public int dimension() { + return dimensions; + } + + /** + * Gets the cache hit count. + * + * @return number of cache hits + */ + public long getCacheHits() { + return cacheHits.get(); + } + + /** + * Gets the cache miss count. + * + * @return number of cache misses + */ + public long getCacheMisses() { + return cacheMisses.get(); + } + + /** Resets statistics. */ + public void resetStatistics() { + cacheHits.set(0); + cacheMisses.set(0); + } + + /** + * Gets the number of recorded cassettes. + * + * @return the count + */ + public int getRecordedCount() { + return recordedKeys.size(); + } + + /** + * Gets the underlying delegate model. + * + * @return the delegate + */ + public EmbeddingModel getDelegate() { + return delegate; + } + + // Preload methods for testing + + /** + * Preloads a cassette into the in-memory cache (for testing). + * + * @param key the cassette key + * @param embedding the embedding to cache + */ + public void preloadCassette(String key, float[] embedding) { + inMemoryCassettes.put(key, embedding); + } + + /** + * Preloads a batch cassette into the in-memory cache (for testing). + * + * @param key the cassette key + * @param embeddings the embeddings to cache + */ + public void preloadBatchCassette(String key, float[][] embeddings) { + inMemoryBatchCassettes.put(key, embeddings); + } + + // Internal methods + + private float[] embedInternal(String text) { + if (mode == VCRMode.OFF) { + return delegate.embed(text).content().vector(); + } + + String cassetteKey = generateCassetteKey(); + + // Try to load from cache in playback modes + if (mode.isPlaybackMode()) { + float[] cached = loadCassette(cassetteKey); + if (cached != null) { + cacheHits.incrementAndGet(); + return cached; + } + + // Strict playback mode - throw if not found + if (mode == VCRMode.PLAYBACK) { + throw new VCRCassetteMissingException(cassetteKey, testId); + } + } + + // Cache miss or record mode - call real API + cacheMisses.incrementAndGet(); + float[] embedding = delegate.embed(text).content().vector(); + + // Record if in recording mode + if (mode.isRecordMode() || mode == VCRMode.PLAYBACK_OR_RECORD) { + saveCassette(cassetteKey, embedding); + } + + return embedding; + } + + private List embedBatchInternal(List texts) { + if (mode == VCRMode.OFF) { + List segments = + texts.stream().map(TextSegment::from).collect(Collectors.toList()); + return delegate.embedAll(segments).content().stream() + .map(Embedding::vector) + .collect(Collectors.toList()); + } + + String cassetteKey = generateCassetteKey(); + + // Try to load from cache in playback modes + if (mode.isPlaybackMode()) { + float[][] cached = loadBatchCassette(cassetteKey); + if (cached != null) { + cacheHits.incrementAndGet(); + List result = new ArrayList<>(); + for (float[] embedding : cached) { + result.add(embedding); + } + return result; + } + + // Strict playback mode - throw if not found + if (mode == VCRMode.PLAYBACK) { + throw new VCRCassetteMissingException(cassetteKey, testId); + } + } + + // Cache miss or record mode - call real API + cacheMisses.incrementAndGet(); + List segments = texts.stream().map(TextSegment::from).collect(Collectors.toList()); + List embeddings = + delegate.embedAll(segments).content().stream() + .map(Embedding::vector) + .collect(Collectors.toList()); + + // Record if in recording mode + if (mode.isRecordMode() || mode == VCRMode.PLAYBACK_OR_RECORD) { + saveBatchCassette(cassetteKey, embeddings.toArray(new float[0][])); + } + + return embeddings; + } + + private String generateCassetteKey() { + int index = callCounter.incrementAndGet(); + return VCRCassetteStore.formatKey(CASSETTE_TYPE, testId, index); + } + + private float[] loadCassette(String key) { + // Check in-memory first + float[] inMemory = inMemoryCassettes.get(key); + if (inMemory != null) { + return inMemory; + } + + // Check Redis + if (cassetteStore != null) { + JsonObject cassette = cassetteStore.retrieve(key); + if (cassette != null) { + return VCRCassetteStore.extractEmbedding(cassette); + } + } + + return null; + } + + private float[][] loadBatchCassette(String key) { + // Check in-memory first + float[][] inMemory = inMemoryBatchCassettes.get(key); + if (inMemory != null) { + return inMemory; + } + + // Check Redis + if (cassetteStore != null) { + JsonObject cassette = cassetteStore.retrieve(key); + if (cassette != null) { + return VCRCassetteStore.extractBatchEmbeddings(cassette); + } + } + + return null; + } + + private void saveCassette(String key, float[] embedding) { + recordedKeys.add(key); + + // Save to in-memory + inMemoryCassettes.put(key, embedding); + + // Save to Redis if available + if (cassetteStore != null) { + JsonObject cassette = VCRCassetteStore.createEmbeddingCassette(embedding, testId, modelName); + cassetteStore.store(key, cassette); + } + } + + private void saveBatchCassette(String key, float[][] embeddings) { + recordedKeys.add(key); + + // Save to in-memory + inMemoryBatchCassettes.put(key, embeddings); + + // Save to Redis if available + if (cassetteStore != null) { + JsonObject cassette = + VCRCassetteStore.createBatchEmbeddingCassette(embeddings, testId, modelName); + cassetteStore.store(key, cassette); + } + } + + private int detectDimensions(EmbeddingModel model) { + // Try to detect dimensions from the model + try { + return model.dimension(); + } catch (Exception e) { + // Some models don't implement dimension() + return -1; + } + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRExtension.java b/core/src/main/java/com/redis/vl/test/vcr/VCRExtension.java new file mode 100644 index 0000000..f9e03e7 --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRExtension.java @@ -0,0 +1,287 @@ +package com.redis.vl.test.vcr; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestWatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * JUnit 5 extension that provides VCR (Video Cassette Recorder) functionality for recording and + * playing back LLM API calls during tests. + * + *

This extension manages: + * + *

    + *
  • Redis container lifecycle with AOF/RDB persistence + *
  • Cassette storage and retrieval + *
  • Test context and call counter management + *
  • Automatic wrapping of {@code @VCRModel} annotated fields + *
+ * + *

Usage with declarative field wrapping: + * + *

{@code
+ * @VCRTest(mode = VCRMode.PLAYBACK_OR_RECORD)
+ * class MyLLMTest {
+ *
+ *     @VCRModel
+ *     private EmbeddingModel embeddingModel;
+ *
+ *     @VCRModel
+ *     private ChatModel chatModel;
+ *
+ *     @BeforeEach
+ *     void setup() {
+ *         // Initialize models normally - VCR wraps them automatically
+ *         embeddingModel = new OpenAiEmbeddingModel(...);
+ *         chatModel = new OpenAiChatModel(...);
+ *     }
+ *
+ *     @Test
+ *     void testLLMCall() {
+ *         // LLM calls are automatically recorded/replayed
+ *         embeddingModel.embed("Hello");
+ *         chatModel.generate("What is Redis?");
+ *     }
+ * }
+ * }
+ */ +public class VCRExtension + implements BeforeAllCallback, + AfterAllCallback, + BeforeEachCallback, + AfterEachCallback, + TestWatcher { + + private static final Logger LOG = LoggerFactory.getLogger(VCRExtension.class); + + private static final ExtensionContext.Namespace NAMESPACE = + ExtensionContext.Namespace.create(VCRExtension.class); + + private VCRContext context; + + @Override + public void beforeAll(ExtensionContext extensionContext) throws Exception { + // Get VCR configuration from @VCRTest annotation + // Walk up the class hierarchy to find the annotation (handles @Nested test classes) + VCRTest config = findVCRTestAnnotation(extensionContext.getRequiredTestClass()); + + if (config == null) { + // No @VCRTest annotation, use defaults + config = DefaultVCRTest.INSTANCE; + } + + // Create and initialize context + context = new VCRContext(config); + context.initialize(); + + // Store in extension context for access in other callbacks + extensionContext.getStore(NAMESPACE).put("vcr-context", context); + } + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + if (context == null) { + return; + } + + // Get test identifier + String testId = getTestId(extensionContext); + + // Reset call counters for new test + context.resetCallCounters(); + + // Set current test context + context.setCurrentTest(testId); + + // Check for method-level mode overrides + var method = extensionContext.getRequiredTestMethod(); + + VCRMode effectiveMode; + if (method.isAnnotationPresent(VCRDisabled.class)) { + effectiveMode = VCRMode.OFF; + } else if (method.isAnnotationPresent(VCRRecord.class)) { + effectiveMode = VCRMode.RECORD; + } else { + // Use class-level or default mode + effectiveMode = context.getConfiguredMode(); + } + context.setEffectiveMode(effectiveMode); + + // Wrap @VCRModel annotated fields with cassette store + wrapAnnotatedFields(extensionContext, testId, effectiveMode, context.getCassetteStore()); + } + + /** + * Scans the test instance for fields annotated with {@code @VCRModel} and wraps them with VCR + * interceptors. + */ + private void wrapAnnotatedFields( + ExtensionContext extensionContext, + String testId, + VCRMode mode, + VCRCassetteStore cassetteStore) { + + if (mode == VCRMode.OFF) { + return; // Don't wrap if VCR is disabled + } + + Object testInstance = extensionContext.getRequiredTestInstance(); + Class testClass = testInstance.getClass(); + + // Collect all fields including from parent classes + List allFields = new ArrayList<>(); + Class currentClass = testClass; + while (currentClass != null && currentClass != Object.class) { + for (Field field : currentClass.getDeclaredFields()) { + allFields.add(field); + } + currentClass = currentClass.getSuperclass(); + } + + // Also check fields from nested test classes + Class enclosingClass = testClass.getEnclosingClass(); + if (enclosingClass != null) { + // For nested classes, we need to access the outer instance + for (Field field : enclosingClass.getDeclaredFields()) { + if (field.isAnnotationPresent(VCRModel.class)) { + wrapFieldInEnclosingInstance( + testInstance, enclosingClass, field, testId, mode, cassetteStore); + } + } + } + + // Wrap fields in the test instance + for (Field field : allFields) { + if (field.isAnnotationPresent(VCRModel.class)) { + VCRModel annotation = field.getAnnotation(VCRModel.class); + VCRModelWrapper.wrapField( + testInstance, field, testId, mode, annotation.modelName(), cassetteStore); + } + } + } + + /** Wraps a field in the enclosing instance of a nested test class. */ + @SuppressWarnings("java:S3011") // Reflection access is intentional + private void wrapFieldInEnclosingInstance( + Object nestedInstance, + Class enclosingClass, + Field field, + String testId, + VCRMode mode, + VCRCassetteStore cassetteStore) { + try { + // Find the synthetic field that holds reference to the enclosing instance + for (Field syntheticField : nestedInstance.getClass().getDeclaredFields()) { + if (syntheticField.getName().startsWith("this$") + && syntheticField.getType().equals(enclosingClass)) { + syntheticField.setAccessible(true); + Object enclosingInstance = syntheticField.get(nestedInstance); + + if (enclosingInstance != null) { + VCRModel annotation = field.getAnnotation(VCRModel.class); + VCRModelWrapper.wrapField( + enclosingInstance, field, testId, mode, annotation.modelName(), cassetteStore); + } + break; + } + } + } catch (IllegalAccessException e) { + LOG.warn("Failed to access enclosing instance: {}", e.getMessage()); + } + } + + @Override + public void afterEach(ExtensionContext extensionContext) throws Exception { + // Test result handling is done via TestWatcher callbacks + } + + @Override + public void testSuccessful(ExtensionContext extensionContext) { + if (context == null) { + return; + } + + // Only update registry when recording, not in playback mode + if (context.getEffectiveMode().isRecordMode()) { + String testId = getTestId(extensionContext); + context.getRegistry().registerSuccess(testId, context.getCurrentCassetteKeys()); + } + } + + @Override + public void testFailed(ExtensionContext extensionContext, Throwable cause) { + if (context == null) { + return; + } + + // Only update registry and delete cassettes when recording + if (context.getEffectiveMode().isRecordMode()) { + String testId = getTestId(extensionContext); + context.getRegistry().registerFailure(testId, cause.getMessage()); + + // Delete cassettes for failed tests in RECORD mode + context.deleteCassettes(context.getCurrentCassetteKeys()); + } + } + + @Override + public void afterAll(ExtensionContext extensionContext) throws Exception { + if (context == null) { + return; + } + + try { + // Persist cassettes if in record mode + if (context.getEffectiveMode().isRecordMode()) { + context.persistCassettes(); + } + + // Print statistics + context.printStatistics(); + } finally { + // Clean up + context.shutdown(); + } + } + + private String getTestId(ExtensionContext ctx) { + return ctx.getRequiredTestClass().getName() + ":" + ctx.getRequiredTestMethod().getName(); + } + + /** + * Finds the @VCRTest annotation on the given class or any of its enclosing classes. This is + * needed to properly handle @Nested test classes where the annotation is on the outer class. + * + * @param testClass the test class to search + * @return the VCRTest annotation if found, null otherwise + */ + private VCRTest findVCRTestAnnotation(Class testClass) { + Class currentClass = testClass; + + // Walk up the class hierarchy (enclosing classes for nested classes) + while (currentClass != null) { + VCRTest annotation = currentClass.getAnnotation(VCRTest.class); + if (annotation != null) { + return annotation; + } + // Check enclosing class for @Nested test classes + currentClass = currentClass.getEnclosingClass(); + } + + return null; + } + + /** Default VCRTest annotation values for when no annotation is present. */ + @VCRTest + private static class DefaultVCRTest { + static final VCRTest INSTANCE = DefaultVCRTest.class.getAnnotation(VCRTest.class); + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRMode.java b/core/src/main/java/com/redis/vl/test/vcr/VCRMode.java new file mode 100644 index 0000000..fcb8aeb --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRMode.java @@ -0,0 +1,64 @@ +package com.redis.vl.test.vcr; + +/** + * VCR operating modes that determine how LLM calls are handled during tests. + * + *

Inspired by the Python VCR implementation in maestro-langgraph, this enum provides flexible + * options for recording and playing back LLM responses. + */ +public enum VCRMode { + + /** + * Use cached responses only. Fails if no cassette exists for a call. This is the default mode for + * CI/CD environments where API calls should not be made. + */ + PLAYBACK, + + /** + * Always make real API calls and overwrite any existing cassettes. Use this mode when + * re-recording all tests. + */ + RECORD, + + /** + * Only record tests that are not already in the registry. Existing cassettes are played back, new + * tests are recorded. + */ + RECORD_NEW, + + /** Re-record only tests that previously failed. Successful tests use existing cassettes. */ + RECORD_FAILED, + + /** + * Smart mode: use cache if it exists, otherwise record. Good for development when you want + * automatic recording of new tests. + */ + PLAYBACK_OR_RECORD, + + /** + * Disable VCR entirely. All calls go to real APIs, nothing is cached. Use this mode when testing + * real API behavior. + */ + OFF; + + /** + * Checks if this mode can potentially make real API calls and record responses. + * + * @return true if this mode can record new cassettes + */ + public boolean isRecordMode() { + return this == RECORD + || this == RECORD_NEW + || this == RECORD_FAILED + || this == PLAYBACK_OR_RECORD; + } + + /** + * Checks if this mode can use cached responses. + * + * @return true if this mode can play back existing cassettes + */ + public boolean isPlaybackMode() { + return this == PLAYBACK || this == PLAYBACK_OR_RECORD; + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRModel.java b/core/src/main/java/com/redis/vl/test/vcr/VCRModel.java new file mode 100644 index 0000000..29057ba --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRModel.java @@ -0,0 +1,59 @@ +package com.redis.vl.test.vcr; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a field to be automatically wrapped with VCR recording/playback functionality. + * + *

When applied to an {@code EmbeddingModel} or {@code ChatModel} field, the VCR extension will + * automatically wrap the model with the appropriate VCR wrapper after it's initialized. + * + *

Usage: + * + *

{@code
+ * @VCRTest(mode = VCRMode.PLAYBACK_OR_RECORD)
+ * class MyLLMTest {
+ *
+ *     @VCRModel
+ *     private EmbeddingModel embeddingModel;
+ *
+ *     @VCRModel
+ *     private ChatModel chatModel;
+ *
+ *     @BeforeEach
+ *     void setup() {
+ *         // Initialize your models normally - VCR will wrap them automatically
+ *         embeddingModel = new OpenAiEmbeddingModel(...);
+ *         chatModel = new OpenAiChatModel(...);
+ *     }
+ *
+ *     @Test
+ *     void testEmbedding() {
+ *         // Use the model - calls are recorded/replayed transparently
+ *         float[] embedding = embeddingModel.embed("Hello").content();
+ *     }
+ * }
+ * }
+ * + *

Supported model types: + * + *

    + *
  • LangChain4J: {@code dev.langchain4j.model.embedding.EmbeddingModel}, {@code + * dev.langchain4j.model.chat.ChatLanguageModel} + *
  • Spring AI: {@code org.springframework.ai.embedding.EmbeddingModel}, {@code + * org.springframework.ai.chat.model.ChatModel} + *
+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface VCRModel { + + /** + * Optional model name for embedding cache key generation. If not specified, the field name will + * be used. + */ + String modelName() default ""; +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRModelWrapper.java b/core/src/main/java/com/redis/vl/test/vcr/VCRModelWrapper.java new file mode 100644 index 0000000..0667dad --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRModelWrapper.java @@ -0,0 +1,186 @@ +package com.redis.vl.test.vcr; + +import java.lang.reflect.Field; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility class for wrapping model instances with VCR interceptors. + * + *

This class provides methods to wrap various LLM model types (LangChain4J, Spring AI) with + * their corresponding VCR wrappers. + */ +public final class VCRModelWrapper { + + private static final Logger LOG = LoggerFactory.getLogger(VCRModelWrapper.class); + + // Class names for type checking (avoid compile-time dependencies) + private static final String LANGCHAIN4J_EMBEDDING_MODEL = + "dev.langchain4j.model.embedding.EmbeddingModel"; + private static final String LANGCHAIN4J_CHAT_MODEL = + "dev.langchain4j.model.chat.ChatLanguageModel"; + private static final String SPRING_AI_EMBEDDING_MODEL = + "org.springframework.ai.embedding.EmbeddingModel"; + private static final String SPRING_AI_CHAT_MODEL = "org.springframework.ai.chat.model.ChatModel"; + + private VCRModelWrapper() { + // Utility class + } + + /** + * Wraps a model field with the appropriate VCR interceptor. + * + * @param testInstance the test instance containing the field + * @param field the field to wrap + * @param testId the current test identifier + * @param mode the VCR mode + * @param modelName optional model name for the wrapper + * @param cassetteStore the cassette store for persistence + * @return true if the field was wrapped, false otherwise + */ + @SuppressWarnings({"unchecked", "java:S3011"}) // Reflection access is intentional + public static boolean wrapField( + Object testInstance, + Field field, + String testId, + VCRMode mode, + String modelName, + VCRCassetteStore cassetteStore) { + + try { + field.setAccessible(true); + Object model = field.get(testInstance); + + if (model == null) { + LOG.debug("Field {} is null, skipping VCR wrapping", field.getName()); + return false; + } + + String effectiveModelName = modelName.isEmpty() ? field.getName() : modelName; + Object wrapped = wrapModel(model, testId, mode, effectiveModelName, cassetteStore); + + if (wrapped != null && wrapped != model) { + field.set(testInstance, wrapped); + LOG.info("Wrapped field {} with VCR interceptor (mode: {})", field.getName(), mode); + return true; + } + + return false; + } catch (IllegalAccessException e) { + LOG.warn("Failed to wrap field {}: {}", field.getName(), e.getMessage()); + return false; + } + } + + /** + * Wraps a model with the appropriate VCR interceptor based on its type. + * + * @param model the model to wrap + * @param testId the test identifier + * @param mode the VCR mode + * @param modelName the model name for cache keys + * @param cassetteStore the cassette store for persistence + * @return the wrapped model, or null if the model type is not supported + */ + public static Object wrapModel( + Object model, String testId, VCRMode mode, String modelName, VCRCassetteStore cassetteStore) { + Class modelClass = model.getClass(); + + // Check LangChain4J EmbeddingModel + if (implementsInterface(modelClass, LANGCHAIN4J_EMBEDDING_MODEL)) { + return wrapLangChain4JEmbeddingModel(model, testId, mode, modelName, cassetteStore); + } + + // Check LangChain4J ChatLanguageModel + if (implementsInterface(modelClass, LANGCHAIN4J_CHAT_MODEL)) { + return wrapLangChain4JChatModel(model, testId, mode, cassetteStore); + } + + // Check Spring AI EmbeddingModel + if (implementsInterface(modelClass, SPRING_AI_EMBEDDING_MODEL)) { + return wrapSpringAIEmbeddingModel(model, testId, mode, modelName, cassetteStore); + } + + // Check Spring AI ChatModel + if (implementsInterface(modelClass, SPRING_AI_CHAT_MODEL)) { + return wrapSpringAIChatModel(model, testId, mode, cassetteStore); + } + + LOG.warn("Unsupported model type for VCR wrapping: {}", modelClass.getName()); + return null; + } + + @SuppressWarnings("unchecked") + private static Object wrapLangChain4JEmbeddingModel( + Object model, String testId, VCRMode mode, String modelName, VCRCassetteStore cassetteStore) { + try { + var wrapper = + new VCREmbeddingModel( + (dev.langchain4j.model.embedding.EmbeddingModel) model, cassetteStore); + wrapper.setTestId(testId); + wrapper.setMode(mode); + wrapper.setModelName(modelName); + return wrapper; + } catch (NoClassDefFoundError e) { + LOG.debug("LangChain4J not available: {}", e.getMessage()); + return null; + } + } + + @SuppressWarnings("unchecked") + private static Object wrapLangChain4JChatModel( + Object model, String testId, VCRMode mode, VCRCassetteStore cassetteStore) { + try { + var wrapper = + new VCRChatModel((dev.langchain4j.model.chat.ChatLanguageModel) model, cassetteStore); + wrapper.setTestId(testId); + wrapper.setMode(mode); + return wrapper; + } catch (NoClassDefFoundError e) { + LOG.debug("LangChain4J not available: {}", e.getMessage()); + return null; + } + } + + @SuppressWarnings("unchecked") + private static Object wrapSpringAIEmbeddingModel( + Object model, String testId, VCRMode mode, String modelName, VCRCassetteStore cassetteStore) { + try { + var wrapper = + new VCRSpringAIEmbeddingModel( + (org.springframework.ai.embedding.EmbeddingModel) model, cassetteStore); + wrapper.setTestId(testId); + wrapper.setMode(mode); + wrapper.setModelName(modelName); + return wrapper; + } catch (NoClassDefFoundError e) { + LOG.debug("Spring AI not available: {}", e.getMessage()); + return null; + } + } + + @SuppressWarnings("unchecked") + private static Object wrapSpringAIChatModel( + Object model, String testId, VCRMode mode, VCRCassetteStore cassetteStore) { + try { + var wrapper = + new VCRSpringAIChatModel( + (org.springframework.ai.chat.model.ChatModel) model, cassetteStore); + wrapper.setTestId(testId); + wrapper.setMode(mode); + return wrapper; + } catch (NoClassDefFoundError e) { + LOG.debug("Spring AI not available: {}", e.getMessage()); + return null; + } + } + + private static boolean implementsInterface(Class clazz, String interfaceName) { + try { + Class iface = Class.forName(interfaceName); + return iface.isAssignableFrom(clazz); + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRRecord.java b/core/src/main/java/com/redis/vl/test/vcr/VCRRecord.java new file mode 100644 index 0000000..0df0d1d --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRRecord.java @@ -0,0 +1,39 @@ +package com.redis.vl.test.vcr; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Forces a specific test method to use RECORD mode, overriding the class-level VCR mode. + * + *

When applied to a test method, this annotation forces that specific test to always make real + * API calls and record the responses, regardless of the class-level {@link VCRTest#mode()} setting. + * + *

Example usage: + * + *

{@code
+ * @VCRTest(mode = VCRMode.PLAYBACK)
+ * class MyLLMIntegrationTest {
+ *
+ *     @Test
+ *     void usesPlayback() {
+ *         // Uses cached responses
+ *     }
+ *
+ *     @Test
+ *     @VCRRecord
+ *     void forcesRecording() {
+ *         // Always makes real API calls and updates cache
+ *     }
+ * }
+ * }
+ * + * @see VCRTest + * @see VCRDisabled + * @see VCRMode#RECORD + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface VCRRecord {} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRRegistry.java b/core/src/main/java/com/redis/vl/test/vcr/VCRRegistry.java new file mode 100644 index 0000000..92d53eb --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRRegistry.java @@ -0,0 +1,189 @@ +package com.redis.vl.test.vcr; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import redis.clients.jedis.JedisPooled; + +/** + * Tracks which tests have been recorded and their status. + * + *

The registry maintains metadata about each test in Redis, including: + * + *

    + *
  • Recording status (RECORDED, FAILED, MISSING) + *
  • Timestamp of last recording + *
  • Associated cassette keys + *
  • Error messages for failed tests + *
+ */ +public class VCRRegistry { + + private static final String REGISTRY_KEY = "vcr:registry"; + private static final String TESTS_KEY = "vcr:registry:tests"; + + private final JedisPooled jedis; + private final Map localCache = new ConcurrentHashMap<>(); + + /** Recording status for a test. */ + public enum RecordingStatus { + /** Test has been successfully recorded */ + RECORDED, + /** Test recording failed */ + FAILED, + /** Test has no recording */ + MISSING, + /** Test recording is outdated */ + OUTDATED + } + + /** + * Creates a new VCR registry. + * + * @param jedis the Redis client + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "JedisPooled is intentionally shared for connection pooling") + public VCRRegistry(JedisPooled jedis) { + this.jedis = jedis; + } + + /** + * Registers a successful test recording. + * + * @param testId the unique test identifier + * @param cassetteKeys the keys of cassettes recorded for this test + */ + public void registerSuccess(String testId, List cassetteKeys) { + if (jedis == null) { + localCache.put(testId, RecordingStatus.RECORDED); + return; + } + + String testKey = "vcr:test:" + testId; + + Map data = new HashMap<>(); + data.put("status", RecordingStatus.RECORDED.name()); + data.put("recorded_at", Instant.now().toString()); + data.put("cassette_count", String.valueOf(cassetteKeys != null ? cassetteKeys.size() : 0)); + + jedis.hset(testKey, data); + jedis.sadd(TESTS_KEY, testId); + + // Store cassette keys + if (cassetteKeys != null && !cassetteKeys.isEmpty()) { + jedis.sadd(testKey + ":cassettes", cassetteKeys.toArray(new String[0])); + } + + localCache.put(testId, RecordingStatus.RECORDED); + } + + /** + * Registers a failed test recording. + * + * @param testId the unique test identifier + * @param error the error message + */ + public void registerFailure(String testId, String error) { + if (jedis == null) { + localCache.put(testId, RecordingStatus.FAILED); + return; + } + + String testKey = "vcr:test:" + testId; + + Map data = new HashMap<>(); + data.put("status", RecordingStatus.FAILED.name()); + data.put("recorded_at", Instant.now().toString()); + data.put("error", error != null ? error : "Unknown error"); + + jedis.hset(testKey, data); + jedis.sadd(TESTS_KEY, testId); + + localCache.put(testId, RecordingStatus.FAILED); + } + + /** + * Gets the recording status of a test. + * + * @param testId the unique test identifier + * @return the recording status + */ + public RecordingStatus getTestStatus(String testId) { + // Check local cache first + RecordingStatus cached = localCache.get(testId); + if (cached != null) { + return cached; + } + + if (jedis == null) { + return RecordingStatus.MISSING; + } + + String testKey = "vcr:test:" + testId; + String status = jedis.hget(testKey, "status"); + + if (status == null) { + return RecordingStatus.MISSING; + } + + RecordingStatus result = RecordingStatus.valueOf(status); + localCache.put(testId, result); + return result; + } + + /** + * Determines the effective VCR mode for a test based on registry status. + * + * @param testId the unique test identifier + * @param globalMode the global VCR mode + * @return the effective mode to use for this test + */ + public VCRMode determineEffectiveMode(String testId, VCRMode globalMode) { + RecordingStatus status = getTestStatus(testId); + + return switch (globalMode) { + case RECORD_NEW -> status == RecordingStatus.MISSING ? VCRMode.RECORD : VCRMode.PLAYBACK; + + case RECORD_FAILED -> + status == RecordingStatus.FAILED || status == RecordingStatus.MISSING + ? VCRMode.RECORD + : VCRMode.PLAYBACK; + + case PLAYBACK_OR_RECORD -> + status == RecordingStatus.RECORDED ? VCRMode.PLAYBACK : VCRMode.RECORD; + + default -> globalMode; + }; + } + + /** + * Gets all recorded test IDs. + * + * @return set of test IDs + */ + public Set getAllRecordedTests() { + if (jedis == null) { + return localCache.keySet(); + } + return jedis.smembers(TESTS_KEY); + } + + /** + * Gets the cassette keys for a test. + * + * @param testId the unique test identifier + * @return set of cassette keys + */ + public Set getCassetteKeys(String testId) { + if (jedis == null) { + return Set.of(); + } + return jedis.smembers("vcr:test:" + testId + ":cassettes"); + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRSpringAIChatModel.java b/core/src/main/java/com/redis/vl/test/vcr/VCRSpringAIChatModel.java new file mode 100644 index 0000000..4fb9a01 --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRSpringAIChatModel.java @@ -0,0 +1,262 @@ +package com.redis.vl.test.vcr; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.Prompt; + +/** + * VCR wrapper for Spring AI ChatModel that records and replays LLM responses. + * + *

This class implements the ChatModel interface, allowing it to be used as a drop-in replacement + * for any Spring AI chat model. It provides VCR (Video Cassette Recorder) functionality to record + * LLM responses during test execution and replay them in subsequent runs. + * + *

Usage: + * + *

{@code
+ * ChatModel openAiModel = new OpenAiChatModel(openAiApi);
+ *
+ * VCRSpringAIChatModel vcrModel = new VCRSpringAIChatModel(openAiModel);
+ * vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD);
+ * vcrModel.setTestId("MyTest.testMethod");
+ *
+ * // Use exactly like the original model
+ * String response = vcrModel.call("Hello");
+ * }
+ */ +@SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "Delegate is intentionally stored and exposed for VCR functionality") +public final class VCRSpringAIChatModel implements ChatModel { + + private final ChatModel delegate; + private VCRCassetteStore cassetteStore; + private VCRMode mode = VCRMode.PLAYBACK_OR_RECORD; + private String testId = "unknown"; + private final AtomicInteger callCounter = new AtomicInteger(0); + + // In-memory cassette storage for unit tests + private final Map cassettes = new HashMap<>(); + + // Statistics + private int cacheHits = 0; + private int cacheMisses = 0; + private int recordedCount = 0; + + /** + * Creates a new VCRSpringAIChatModel wrapping the given delegate. + * + * @param delegate The actual ChatModel to wrap + */ + public VCRSpringAIChatModel(ChatModel delegate) { + this.delegate = delegate; + } + + /** + * Creates a new VCRSpringAIChatModel wrapping the given delegate with Redis storage. + * + * @param delegate The actual ChatModel to wrap + * @param cassetteStore The cassette store for persistence + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "VCRCassetteStore is intentionally shared") + public VCRSpringAIChatModel(ChatModel delegate, VCRCassetteStore cassetteStore) { + this.delegate = delegate; + this.cassetteStore = cassetteStore; + } + + /** + * Sets the VCR mode. + * + * @param mode The VCR mode to use + */ + public void setMode(VCRMode mode) { + this.mode = mode; + } + + /** + * Gets the current VCR mode. + * + * @return The current VCR mode + */ + public VCRMode getMode() { + return mode; + } + + /** + * Sets the test identifier for cassette key generation. + * + * @param testId The test identifier (typically ClassName.methodName) + */ + public void setTestId(String testId) { + this.testId = testId; + } + + /** + * Gets the current test identifier. + * + * @return The current test identifier + */ + public String getTestId() { + return testId; + } + + /** Resets the call counter. Useful when starting a new test method. */ + public void resetCallCounter() { + callCounter.set(0); + } + + /** + * Gets the underlying delegate model. + * + * @return The wrapped ChatModel + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP", + justification = "Intentional exposure of delegate for advanced use cases") + public ChatModel getDelegate() { + return delegate; + } + + /** + * Preloads a cassette for testing purposes. + * + * @param key The cassette key + * @param response The response text to cache + */ + public void preloadCassette(String key, String response) { + cassettes.put(key, response); + } + + /** + * Gets the number of cache hits. + * + * @return Cache hit count + */ + public int getCacheHits() { + return cacheHits; + } + + /** + * Gets the number of cache misses. + * + * @return Cache miss count + */ + public int getCacheMisses() { + return cacheMisses; + } + + /** + * Gets the number of recorded responses. + * + * @return Recorded count + */ + public int getRecordedCount() { + return recordedCount; + } + + /** Resets all statistics. */ + public void resetStatistics() { + cacheHits = 0; + cacheMisses = 0; + recordedCount = 0; + } + + @Override + public ChatResponse call(Prompt prompt) { + String responseText = + callInternal( + () -> { + ChatResponse response = delegate.call(prompt); + return response.getResult().getOutput().getText(); + }); + Generation generation = new Generation(new AssistantMessage(responseText)); + return new ChatResponse(List.of(generation)); + } + + @Override + public String call(String message) { + return callInternal(() -> delegate.call(message)); + } + + @Override + public String call(Message... messages) { + return callInternal(() -> delegate.call(messages)); + } + + private String callInternal(java.util.function.Supplier delegateCall) { + if (mode == VCRMode.OFF) { + return delegateCall.get(); + } + + String key = formatKey(); + + if (mode.isPlaybackMode()) { + String cached = loadCassette(key); + if (cached != null) { + cacheHits++; + return cached; + } + + if (mode == VCRMode.PLAYBACK) { + throw new VCRCassetteMissingException(key, testId); + } + + // PLAYBACK_OR_RECORD - fall through to record + } + + // Record mode or cache miss in PLAYBACK_OR_RECORD + cacheMisses++; + String response = delegateCall.get(); + saveCassette(key, response); + recordedCount++; + + return response; + } + + private String loadCassette(String key) { + // Check in-memory first + String inMemory = cassettes.get(key); + if (inMemory != null) { + return inMemory; + } + + // Check Redis if available + if (cassetteStore != null) { + com.google.gson.JsonObject cassette = cassetteStore.retrieve(key); + if (cassette != null && cassette.has("response")) { + return cassette.get("response").getAsString(); + } + } + + return null; + } + + private void saveCassette(String key, String response) { + // Save to in-memory + cassettes.put(key, response); + + // Save to Redis if available + if (cassetteStore != null) { + com.google.gson.JsonObject cassette = new com.google.gson.JsonObject(); + cassette.addProperty("response", response); + cassette.addProperty("testId", testId); + cassette.addProperty("type", "chat"); + cassetteStore.store(key, cassette); + } + } + + private String formatKey() { + int index = callCounter.incrementAndGet(); + return String.format("vcr:chat:%s:%04d", testId, index); + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRSpringAIEmbeddingModel.java b/core/src/main/java/com/redis/vl/test/vcr/VCRSpringAIEmbeddingModel.java new file mode 100644 index 0000000..f1fcffd --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRSpringAIEmbeddingModel.java @@ -0,0 +1,380 @@ +package com.redis.vl.test.vcr; + +import com.google.gson.JsonObject; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.IntStream; +import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.Embedding; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.EmbeddingRequest; +import org.springframework.ai.embedding.EmbeddingResponse; + +/** + * VCR-enabled wrapper around a Spring AI EmbeddingModel. + * + *

This wrapper intercepts embedding calls and routes them through the VCR system for recording + * and playback. + * + *

Usage: + * + *

{@code
+ * // In your test
+ * EmbeddingModel realModel = new OpenAiEmbeddingModel(...);
+ * VCRSpringAIEmbeddingModel vcrModel = new VCRSpringAIEmbeddingModel(realModel);
+ * vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD);
+ * vcrModel.setTestId("MyTest.testMethod");
+ *
+ * // Use vcrModel instead of realModel
+ * float[] embedding = vcrModel.embed("text");
+ * }
+ */ +public class VCRSpringAIEmbeddingModel implements EmbeddingModel { + + private static final String CASSETTE_TYPE = "embedding"; + + private final EmbeddingModel delegate; + private final int dimensionSize; + + private VCRMode mode = VCRMode.OFF; + private String testId; + private String modelName = "default"; + private final AtomicInteger callCounter = new AtomicInteger(0); + + // In-memory cassette storage for unit tests (null = use Redis) + private VCRCassetteStore cassetteStore; + private final Map inMemoryCassettes = new ConcurrentHashMap<>(); + private final Map inMemoryBatchCassettes = new ConcurrentHashMap<>(); + private final List recordedKeys = new ArrayList<>(); + + // Statistics + private final AtomicLong cacheHits = new AtomicLong(0); + private final AtomicLong cacheMisses = new AtomicLong(0); + + /** + * Creates a VCR-enabled embedding model wrapper. + * + * @param delegate the real embedding model + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "EmbeddingModel delegate is intentionally shared") + public VCRSpringAIEmbeddingModel(EmbeddingModel delegate) { + this.delegate = delegate; + this.dimensionSize = detectDimensions(delegate); + } + + /** + * Creates a VCR-enabled embedding model wrapper with Redis storage. + * + * @param delegate the real embedding model + * @param cassetteStore the cassette store + */ + @SuppressFBWarnings( + value = "EI_EXPOSE_REP2", + justification = "EmbeddingModel and VCRCassetteStore are intentionally shared") + public VCRSpringAIEmbeddingModel(EmbeddingModel delegate, VCRCassetteStore cassetteStore) { + this.delegate = delegate; + this.cassetteStore = cassetteStore; + this.dimensionSize = detectDimensions(delegate); + } + + /** + * Sets the VCR mode. + * + * @param mode the mode + */ + public void setMode(VCRMode mode) { + this.mode = mode; + } + + /** + * Gets the current VCR mode. + * + * @return the mode + */ + public VCRMode getMode() { + return mode; + } + + /** + * Sets the current test identifier. + * + * @param testId the test ID + */ + public void setTestId(String testId) { + this.testId = testId; + this.callCounter.set(0); + } + + /** + * Sets the model name for cassette metadata. + * + * @param modelName the model name + */ + public void setModelName(String modelName) { + this.modelName = modelName; + } + + /** Resets the call counter (call at start of each test). */ + public void resetCallCounter() { + callCounter.set(0); + } + + @Override + public EmbeddingResponse call(EmbeddingRequest request) { + List texts = request.getInstructions(); + List embeddings = embedBatchInternal(texts); + + List results = + IntStream.range(0, embeddings.size()) + .mapToObj(i -> new Embedding(embeddings.get(i), i)) + .toList(); + + return new EmbeddingResponse(results); + } + + @Override + public float[] embed(String text) { + return embedInternal(text); + } + + @Override + public float[] embed(Document document) { + return embedInternal(document.getText()); + } + + @Override + public List embed(List texts) { + return embedBatchInternal(texts); + } + + @Override + public EmbeddingResponse embedForResponse(List texts) { + return call(new EmbeddingRequest(texts, null)); + } + + @Override + public int dimensions() { + return dimensionSize; + } + + /** + * Gets the cache hit count. + * + * @return number of cache hits + */ + public long getCacheHits() { + return cacheHits.get(); + } + + /** + * Gets the cache miss count. + * + * @return number of cache misses + */ + public long getCacheMisses() { + return cacheMisses.get(); + } + + /** Resets statistics. */ + public void resetStatistics() { + cacheHits.set(0); + cacheMisses.set(0); + } + + /** + * Gets the number of recorded cassettes. + * + * @return the count + */ + public int getRecordedCount() { + return recordedKeys.size(); + } + + /** + * Gets the underlying delegate model. + * + * @return the delegate + */ + public EmbeddingModel getDelegate() { + return delegate; + } + + // Preload methods for testing + + /** + * Preloads a cassette into the in-memory cache (for testing). + * + * @param key the cassette key + * @param embedding the embedding to cache + */ + public void preloadCassette(String key, float[] embedding) { + inMemoryCassettes.put(key, embedding); + } + + /** + * Preloads a batch cassette into the in-memory cache (for testing). + * + * @param key the cassette key + * @param embeddings the embeddings to cache + */ + public void preloadBatchCassette(String key, float[][] embeddings) { + inMemoryBatchCassettes.put(key, embeddings); + } + + // Internal methods + + private float[] embedInternal(String text) { + if (mode == VCRMode.OFF) { + return delegate.embed(text); + } + + String cassetteKey = generateCassetteKey(); + + // Try to load from cache in playback modes + if (mode.isPlaybackMode()) { + float[] cached = loadCassette(cassetteKey); + if (cached != null) { + cacheHits.incrementAndGet(); + return cached; + } + + // Strict playback mode - throw if not found + if (mode == VCRMode.PLAYBACK) { + throw new VCRCassetteMissingException(cassetteKey, testId); + } + } + + // Cache miss or record mode - call real API + cacheMisses.incrementAndGet(); + float[] embedding = delegate.embed(text); + + // Record if in recording mode + if (mode.isRecordMode() || mode == VCRMode.PLAYBACK_OR_RECORD) { + saveCassette(cassetteKey, embedding); + } + + return embedding; + } + + private List embedBatchInternal(List texts) { + if (mode == VCRMode.OFF) { + return delegate.embed(texts); + } + + String cassetteKey = generateCassetteKey(); + + // Try to load from cache in playback modes + if (mode.isPlaybackMode()) { + float[][] cached = loadBatchCassette(cassetteKey); + if (cached != null) { + cacheHits.incrementAndGet(); + List result = new ArrayList<>(); + for (float[] embedding : cached) { + result.add(embedding); + } + return result; + } + + // Strict playback mode - throw if not found + if (mode == VCRMode.PLAYBACK) { + throw new VCRCassetteMissingException(cassetteKey, testId); + } + } + + // Cache miss or record mode - call real API + cacheMisses.incrementAndGet(); + List embeddings = delegate.embed(texts); + + // Record if in recording mode + if (mode.isRecordMode() || mode == VCRMode.PLAYBACK_OR_RECORD) { + saveBatchCassette(cassetteKey, embeddings.toArray(new float[0][])); + } + + return embeddings; + } + + private String generateCassetteKey() { + int index = callCounter.incrementAndGet(); + return VCRCassetteStore.formatKey(CASSETTE_TYPE, testId, index); + } + + private float[] loadCassette(String key) { + // Check in-memory first + float[] inMemory = inMemoryCassettes.get(key); + if (inMemory != null) { + return inMemory; + } + + // Check Redis + if (cassetteStore != null) { + JsonObject cassette = cassetteStore.retrieve(key); + if (cassette != null) { + return VCRCassetteStore.extractEmbedding(cassette); + } + } + + return null; + } + + private float[][] loadBatchCassette(String key) { + // Check in-memory first + float[][] inMemory = inMemoryBatchCassettes.get(key); + if (inMemory != null) { + return inMemory; + } + + // Check Redis + if (cassetteStore != null) { + JsonObject cassette = cassetteStore.retrieve(key); + if (cassette != null) { + return VCRCassetteStore.extractBatchEmbeddings(cassette); + } + } + + return null; + } + + private void saveCassette(String key, float[] embedding) { + recordedKeys.add(key); + + // Save to in-memory + inMemoryCassettes.put(key, embedding); + + // Save to Redis if available + if (cassetteStore != null) { + JsonObject cassette = VCRCassetteStore.createEmbeddingCassette(embedding, testId, modelName); + cassetteStore.store(key, cassette); + } + } + + private void saveBatchCassette(String key, float[][] embeddings) { + recordedKeys.add(key); + + // Save to in-memory + inMemoryBatchCassettes.put(key, embeddings); + + // Save to Redis if available + if (cassetteStore != null) { + JsonObject cassette = + VCRCassetteStore.createBatchEmbeddingCassette(embeddings, testId, modelName); + cassetteStore.store(key, cassette); + } + } + + private int detectDimensions(EmbeddingModel model) { + // Try to detect dimensions from the model + try { + return model.dimensions(); + } catch (Exception e) { + // Some models don't implement dimensions() + return -1; + } + } +} diff --git a/core/src/main/java/com/redis/vl/test/vcr/VCRTest.java b/core/src/main/java/com/redis/vl/test/vcr/VCRTest.java new file mode 100644 index 0000000..3464109 --- /dev/null +++ b/core/src/main/java/com/redis/vl/test/vcr/VCRTest.java @@ -0,0 +1,81 @@ +package com.redis.vl.test.vcr; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Enables VCR (Video Cassette Recorder) functionality for a test class. + * + *

When applied to a test class, this annotation enables automatic recording and playback of LLM + * API calls. This allows tests to run without making actual API calls after initial recording, + * reducing costs and providing deterministic test execution. + * + *

Example usage: + * + *

{@code
+ * @VCRTest(mode = VCRMode.PLAYBACK)
+ * class MyLLMIntegrationTest {
+ *
+ *     @Test
+ *     void testLLMResponse() {
+ *         // LLM calls are automatically intercepted
+ *         String response = chatModel.generate("Hello, world!");
+ *         assertThat(response).isNotEmpty();
+ *     }
+ * }
+ * }
+ * + *

Configuration options: + * + *

    + *
  • {@link #mode()} - The VCR operating mode (default: PLAYBACK_OR_RECORD) + *
  • {@link #dataDir()} - Directory for storing cassettes (default: src/test/resources/vcr-data) + *
  • {@link #redisImage()} - Docker image for Redis container (default: + * redis/redis-stack:latest) + *
+ * + * @see VCRMode + * @see VCRRecord + * @see VCRDisabled + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(VCRExtension.class) +public @interface VCRTest { + + /** + * The VCR operating mode for this test class. + * + * @return the VCR mode to use (default: PLAYBACK_OR_RECORD) + */ + VCRMode mode() default VCRMode.PLAYBACK_OR_RECORD; + + /** + * The directory where VCR cassettes (recorded responses) are stored. + * + *

This directory will contain: + * + *

    + *
  • dump.rdb - Redis RDB snapshot + *
  • appendonlydir/ - Redis AOF segments + *
+ * + *

The directory is relative to the project root unless an absolute path is provided. + * + * @return the data directory path (default: src/test/resources/vcr-data) + */ + String dataDir() default "src/test/resources/vcr-data"; + + /** + * The Docker image to use for the Redis container. + * + *

The image should be a Redis Stack image that includes RediSearch and RedisJSON modules for + * optimal functionality. + * + * @return the Redis Docker image name (default: redis/redis-stack:latest) + */ + String redisImage() default "redis/redis-stack:latest"; +} diff --git a/core/src/test/java/com/redis/vl/test/vcr/VCRAnnotationsTest.java b/core/src/test/java/com/redis/vl/test/vcr/VCRAnnotationsTest.java new file mode 100644 index 0000000..fbc1762 --- /dev/null +++ b/core/src/test/java/com/redis/vl/test/vcr/VCRAnnotationsTest.java @@ -0,0 +1,106 @@ +package com.redis.vl.test.vcr; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Tests for VCR annotations - TDD RED phase. These tests will fail until we implement the + * annotations. + */ +class VCRAnnotationsTest { + + @Test + void vcrTestAnnotationShouldExist() { + assertThat(VCRTest.class).isAnnotation(); + } + + @Test + void vcrTestShouldHaveRuntimeRetention() { + Retention retention = VCRTest.class.getAnnotation(Retention.class); + assertThat(retention).isNotNull(); + assertThat(retention.value()).isEqualTo(RetentionPolicy.RUNTIME); + } + + @Test + void vcrTestShouldTargetTypes() { + Target target = VCRTest.class.getAnnotation(Target.class); + assertThat(target).isNotNull(); + assertThat(target.value()).contains(ElementType.TYPE); + } + + @Test + void vcrTestShouldHaveModeAttribute() throws NoSuchMethodException { + var method = VCRTest.class.getMethod("mode"); + assertThat(method.getReturnType()).isEqualTo(VCRMode.class); + } + + @Test + void vcrTestShouldHaveDefaultModeOfPlaybackOrRecord() throws NoSuchMethodException { + var method = VCRTest.class.getMethod("mode"); + VCRMode defaultValue = (VCRMode) method.getDefaultValue(); + assertThat(defaultValue).isEqualTo(VCRMode.PLAYBACK_OR_RECORD); + } + + @Test + void vcrTestShouldHaveDataDirAttribute() throws NoSuchMethodException { + var method = VCRTest.class.getMethod("dataDir"); + assertThat(method.getReturnType()).isEqualTo(String.class); + } + + @Test + void vcrTestShouldHaveDefaultDataDir() throws NoSuchMethodException { + var method = VCRTest.class.getMethod("dataDir"); + String defaultValue = (String) method.getDefaultValue(); + assertThat(defaultValue).isEqualTo("src/test/resources/vcr-data"); + } + + @Test + void vcrTestShouldHaveRedisImageAttribute() throws NoSuchMethodException { + var method = VCRTest.class.getMethod("redisImage"); + assertThat(method.getReturnType()).isEqualTo(String.class); + } + + @Test + void vcrTestShouldHaveDefaultRedisImage() throws NoSuchMethodException { + var method = VCRTest.class.getMethod("redisImage"); + String defaultValue = (String) method.getDefaultValue(); + assertThat(defaultValue).isEqualTo("redis/redis-stack:latest"); + } + + @Test + void vcrTestShouldBeExtendedWithVCRExtension() { + ExtendWith extendWith = VCRTest.class.getAnnotation(ExtendWith.class); + assertThat(extendWith).isNotNull(); + assertThat(extendWith.value()).contains(VCRExtension.class); + } + + @Test + void vcrRecordAnnotationShouldExist() { + assertThat(VCRRecord.class).isAnnotation(); + } + + @Test + void vcrRecordShouldTargetMethods() { + Target target = VCRRecord.class.getAnnotation(Target.class); + assertThat(target).isNotNull(); + assertThat(target.value()).contains(ElementType.METHOD); + } + + @Test + void vcrDisabledAnnotationShouldExist() { + assertThat(VCRDisabled.class).isAnnotation(); + } + + @Test + void vcrDisabledShouldTargetMethods() { + Target target = VCRDisabled.class.getAnnotation(Target.class); + assertThat(target).isNotNull(); + assertThat(target.value()).contains(ElementType.METHOD); + } +} diff --git a/core/src/test/java/com/redis/vl/test/vcr/VCRCassetteStoreTest.java b/core/src/test/java/com/redis/vl/test/vcr/VCRCassetteStoreTest.java new file mode 100644 index 0000000..71af7fc --- /dev/null +++ b/core/src/test/java/com/redis/vl/test/vcr/VCRCassetteStoreTest.java @@ -0,0 +1,193 @@ +package com.redis.vl.test.vcr; + +import static org.junit.jupiter.api.Assertions.*; + +import com.google.gson.JsonObject; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for VCRCassetteStore. Tests the cassette storage abstraction without requiring a Redis + * instance. + */ +@DisplayName("VCRCassetteStore") +class VCRCassetteStoreTest { + + @Nested + @DisplayName("Cassette Key Format") + class CassetteKeyFormat { + + @Test + @DisplayName("should generate proper cassette key format") + void shouldGenerateProperKeyFormat() { + String key = VCRCassetteStore.formatKey("embedding", "MyTest.testMethod", 1); + assertEquals("vcr:embedding:MyTest.testMethod:0001", key); + } + + @Test + @DisplayName("should pad call index with zeros") + void shouldPadCallIndex() { + assertEquals("vcr:llm:Test.method:0001", VCRCassetteStore.formatKey("llm", "Test.method", 1)); + assertEquals( + "vcr:llm:Test.method:0042", VCRCassetteStore.formatKey("llm", "Test.method", 42)); + assertEquals( + "vcr:llm:Test.method:9999", VCRCassetteStore.formatKey("llm", "Test.method", 9999)); + } + + @Test + @DisplayName("should handle different cassette types") + void shouldHandleDifferentTypes() { + assertEquals("vcr:llm:Test:0001", VCRCassetteStore.formatKey("llm", "Test", 1)); + assertEquals("vcr:embedding:Test:0001", VCRCassetteStore.formatKey("embedding", "Test", 1)); + assertEquals("vcr:chat:Test:0001", VCRCassetteStore.formatKey("chat", "Test", 1)); + } + + @Test + @DisplayName("should parse key components") + void shouldParseKeyComponents() { + String key = "vcr:embedding:MyTest.testMethod:0042"; + String[] parts = VCRCassetteStore.parseKey(key); + + assertEquals(4, parts.length); + assertEquals("vcr", parts[0]); + assertEquals("embedding", parts[1]); + assertEquals("MyTest.testMethod", parts[2]); + assertEquals("0042", parts[3]); + } + } + + @Nested + @DisplayName("Cassette Data Serialization") + class CassetteDataSerialization { + + @Test + @DisplayName("should serialize embedding data") + void shouldSerializeEmbeddingData() { + float[] embedding = new float[] {0.1f, 0.2f, 0.3f, 0.4f, 0.5f}; + String testId = "TestClass.testMethod"; + + JsonObject cassette = VCRCassetteStore.createEmbeddingCassette(embedding, testId, "model-1"); + + assertEquals("embedding", cassette.get("type").getAsString()); + assertEquals(testId, cassette.get("testId").getAsString()); + assertEquals("model-1", cassette.get("model").getAsString()); + assertEquals(5, cassette.getAsJsonArray("embedding").size()); + } + + @Test + @DisplayName("should deserialize embedding data") + void shouldDeserializeEmbeddingData() { + float[] original = new float[] {0.1f, 0.2f, 0.3f, 0.4f, 0.5f}; + JsonObject cassette = + VCRCassetteStore.createEmbeddingCassette(original, "test", "all-minilm-l6-v2"); + + float[] retrieved = VCRCassetteStore.extractEmbedding(cassette); + + assertNotNull(retrieved); + assertEquals(original.length, retrieved.length); + for (int i = 0; i < original.length; i++) { + assertEquals(original[i], retrieved[i], 0.0001f); + } + } + + @Test + @DisplayName("should serialize batch embedding data") + void shouldSerializeBatchEmbeddingData() { + float[][] embeddings = + new float[][] {{0.1f, 0.2f, 0.3f}, {0.4f, 0.5f, 0.6f}, {0.7f, 0.8f, 0.9f}}; + + JsonObject cassette = + VCRCassetteStore.createBatchEmbeddingCassette(embeddings, "test", "model-1"); + + assertEquals("batch_embedding", cassette.get("type").getAsString()); + assertEquals(3, cassette.getAsJsonArray("embeddings").size()); + } + + @Test + @DisplayName("should deserialize batch embedding data") + void shouldDeserializeBatchEmbeddingData() { + float[][] original = + new float[][] {{0.1f, 0.2f, 0.3f}, {0.4f, 0.5f, 0.6f}, {0.7f, 0.8f, 0.9f}}; + + JsonObject cassette = + VCRCassetteStore.createBatchEmbeddingCassette(original, "test", "model-1"); + float[][] retrieved = VCRCassetteStore.extractBatchEmbeddings(cassette); + + assertNotNull(retrieved); + assertEquals(original.length, retrieved.length); + for (int i = 0; i < original.length; i++) { + assertArrayEquals(original[i], retrieved[i], 0.0001f); + } + } + + @Test + @DisplayName("should handle empty embedding array") + void shouldHandleEmptyEmbeddingArray() { + float[] empty = new float[0]; + JsonObject cassette = VCRCassetteStore.createEmbeddingCassette(empty, "test", "model"); + + float[] retrieved = VCRCassetteStore.extractEmbedding(cassette); + assertNotNull(retrieved); + assertEquals(0, retrieved.length); + } + + @Test + @DisplayName("should include metadata in cassette") + void shouldIncludeMetadata() { + float[] embedding = new float[] {0.1f, 0.2f}; + JsonObject cassette = + VCRCassetteStore.createEmbeddingCassette(embedding, "test", "all-minilm-l6-v2"); + + assertTrue(cassette.has("timestamp")); + assertTrue(cassette.get("timestamp").getAsLong() > 0); + } + } + + @Nested + @DisplayName("Null and Edge Cases") + class NullAndEdgeCases { + + @Test + @DisplayName("should throw on null embedding") + void shouldThrowOnNullEmbedding() { + assertThrows( + NullPointerException.class, + () -> VCRCassetteStore.createEmbeddingCassette(null, "test", "model")); + } + + @Test + @DisplayName("should throw on null testId") + void shouldThrowOnNullTestId() { + assertThrows( + NullPointerException.class, + () -> VCRCassetteStore.createEmbeddingCassette(new float[] {0.1f}, null, "model")); + } + + @Test + @DisplayName("should throw on null model") + void shouldThrowOnNullModel() { + assertThrows( + NullPointerException.class, + () -> VCRCassetteStore.createEmbeddingCassette(new float[] {0.1f}, "test", null)); + } + + @Test + @DisplayName("should return null for invalid key format") + void shouldReturnNullForInvalidKey() { + String[] parts = VCRCassetteStore.parseKey("invalid"); + assertNull(parts); + } + + @Test + @DisplayName("should extract null from malformed cassette") + void shouldExtractNullFromMalformedCassette() { + JsonObject malformed = new JsonObject(); + malformed.addProperty("type", "embedding"); + // Missing embedding field + + float[] result = VCRCassetteStore.extractEmbedding(malformed); + assertNull(result); + } + } +} diff --git a/core/src/test/java/com/redis/vl/test/vcr/VCRChatModelTest.java b/core/src/test/java/com/redis/vl/test/vcr/VCRChatModelTest.java new file mode 100644 index 0000000..2b290f6 --- /dev/null +++ b/core/src/test/java/com/redis/vl/test/vcr/VCRChatModelTest.java @@ -0,0 +1,303 @@ +package com.redis.vl.test.vcr; + +import static org.junit.jupiter.api.Assertions.*; + +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.ChatMessage; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.output.Response; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for VCRChatModel. + * + *

These tests demonstrate standalone VCR usage with LangChain4J ChatLanguageModel, without + * requiring any RedisVL components. + */ +@DisplayName("VCRChatModel") +class VCRChatModelTest { + + private VCRChatModel vcrModel; + private MockChatLanguageModel mockDelegate; + + @BeforeEach + void setUp() { + mockDelegate = new MockChatLanguageModel(); + vcrModel = new VCRChatModel(mockDelegate); + vcrModel.setTestId("VCRChatModelTest.test"); + } + + @Nested + @DisplayName("OFF Mode - Direct passthrough") + class OffMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.OFF); + } + + @Test + @DisplayName("should call delegate directly when VCR is OFF") + void shouldCallDelegateDirectly() { + Response response = vcrModel.generate(UserMessage.from("Hello")); + + assertNotNull(response); + assertNotNull(response.content()); + assertTrue(response.content().text().contains("Mock response")); + assertEquals(1, mockDelegate.callCount.get()); + } + + @Test + @DisplayName("should not record when VCR is OFF") + void shouldNotRecordWhenOff() { + vcrModel.generate(UserMessage.from("Hello")); + vcrModel.generate(UserMessage.from("World")); + + assertEquals(2, mockDelegate.callCount.get()); + assertEquals(0, vcrModel.getRecordedCount()); + } + } + + @Nested + @DisplayName("RECORD Mode") + class RecordMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.RECORD); + } + + @Test + @DisplayName("should call delegate and record result") + void shouldCallDelegateAndRecord() { + Response response = vcrModel.generate(UserMessage.from("Test message")); + + assertNotNull(response); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + } + + @Test + @DisplayName("should record multiple calls with incrementing indices") + void shouldRecordMultipleCalls() { + vcrModel.generate(UserMessage.from("Message 1")); + vcrModel.generate(UserMessage.from("Message 2")); + vcrModel.generate(UserMessage.from("Message 3")); + + assertEquals(3, mockDelegate.callCount.get()); + assertEquals(3, vcrModel.getRecordedCount()); + } + } + + @Nested + @DisplayName("PLAYBACK Mode") + class PlaybackMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.PLAYBACK); + } + + @Test + @DisplayName("should return cached response without calling delegate") + void shouldReturnCachedResponse() { + // Pre-load cassette + String cachedResponse = "This is a cached LLM response"; + vcrModel.preloadCassette("vcr:chat:VCRChatModelTest.test:0001", cachedResponse); + + Response response = vcrModel.generate(UserMessage.from("Test")); + + assertNotNull(response); + assertEquals(cachedResponse, response.content().text()); + assertEquals(0, mockDelegate.callCount.get()); + } + + @Test + @DisplayName("should throw when cassette not found in strict PLAYBACK mode") + void shouldThrowWhenCassetteNotFound() { + assertThrows( + VCRCassetteMissingException.class, () -> vcrModel.generate(UserMessage.from("Unknown"))); + } + + @Test + @DisplayName("should track cache hits") + void shouldTrackCacheHits() { + vcrModel.preloadCassette("vcr:chat:VCRChatModelTest.test:0001", "Cached"); + + vcrModel.generate(UserMessage.from("Test")); + + assertEquals(1, vcrModel.getCacheHits()); + assertEquals(0, vcrModel.getCacheMisses()); + } + } + + @Nested + @DisplayName("PLAYBACK_OR_RECORD Mode") + class PlaybackOrRecordMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); + } + + @Test + @DisplayName("should return cached response when available") + void shouldReturnCachedWhenAvailable() { + String cachedResponse = "Cached LLM answer"; + vcrModel.preloadCassette("vcr:chat:VCRChatModelTest.test:0001", cachedResponse); + + Response response = vcrModel.generate(UserMessage.from("Test")); + + assertEquals(cachedResponse, response.content().text()); + assertEquals(0, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getCacheHits()); + } + + @Test + @DisplayName("should call delegate and record on cache miss") + void shouldCallDelegateAndRecordOnMiss() { + Response response = vcrModel.generate(UserMessage.from("Uncached")); + + assertNotNull(response); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + assertEquals(1, vcrModel.getCacheMisses()); + } + + @Test + @DisplayName("should allow subsequent cache hits after recording") + void shouldAllowSubsequentCacheHits() { + // First call - cache miss, records + vcrModel.generate(UserMessage.from("Question")); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getCacheMisses()); + + // Reset counter for second call + vcrModel.resetCallCounter(); + + // Second call - cache hit from recorded value + vcrModel.generate(UserMessage.from("Question")); + assertEquals(1, mockDelegate.callCount.get()); // Still 1, not 2 + assertEquals(1, vcrModel.getCacheHits()); + } + } + + @Nested + @DisplayName("List of Messages") + class ListOfMessages { + + @Test + @DisplayName("should handle list of messages in RECORD mode") + void shouldHandleListOfMessagesInRecordMode() { + vcrModel.setMode(VCRMode.RECORD); + + List messages = List.of(UserMessage.from("First"), UserMessage.from("Second")); + + Response response = vcrModel.generate(messages); + + assertNotNull(response); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + } + + @Test + @DisplayName("should return cached response for list of messages") + void shouldReturnCachedForListOfMessages() { + vcrModel.setMode(VCRMode.PLAYBACK); + vcrModel.preloadCassette("vcr:chat:VCRChatModelTest.test:0001", "List cached response"); + + List messages = List.of(UserMessage.from("Hello")); + + Response response = vcrModel.generate(messages); + + assertEquals("List cached response", response.content().text()); + assertEquals(0, mockDelegate.callCount.get()); + } + } + + @Nested + @DisplayName("String Convenience Method") + class StringConvenienceMethod { + + @Test + @DisplayName("should handle string input in RECORD mode") + void shouldHandleStringInputInRecordMode() { + vcrModel.setMode(VCRMode.RECORD); + + String response = vcrModel.generate("Simple string input"); + + assertNotNull(response); + assertTrue(response.contains("Mock response")); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + } + + @Test + @DisplayName("should return cached response for string input") + void shouldReturnCachedForStringInput() { + vcrModel.setMode(VCRMode.PLAYBACK); + vcrModel.preloadCassette("vcr:chat:VCRChatModelTest.test:0001", "String cached response"); + + String response = vcrModel.generate("Test string"); + + assertEquals("String cached response", response); + assertEquals(0, mockDelegate.callCount.get()); + } + } + + @Nested + @DisplayName("Delegate Access") + class DelegateAccess { + + @Test + @DisplayName("should provide access to underlying delegate") + void shouldProvideAccessToDelegate() { + ChatLanguageModel delegate = vcrModel.getDelegate(); + + assertSame(mockDelegate, delegate); + } + } + + @Nested + @DisplayName("Statistics Reset") + class StatisticsReset { + + @Test + @DisplayName("should reset statistics") + void shouldResetStatistics() { + vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); + vcrModel.generate(UserMessage.from("Test")); // Cache miss + + assertEquals(1, vcrModel.getCacheMisses()); + + vcrModel.resetStatistics(); + + assertEquals(0, vcrModel.getCacheHits()); + assertEquals(0, vcrModel.getCacheMisses()); + } + } + + /** Mock ChatLanguageModel for testing. */ + static class MockChatLanguageModel implements ChatLanguageModel { + AtomicInteger callCount = new AtomicInteger(0); + + @Override + public Response generate(List messages) { + callCount.incrementAndGet(); + String lastMessage = ""; + if (!messages.isEmpty()) { + ChatMessage last = messages.get(messages.size() - 1); + if (last instanceof UserMessage um) { + lastMessage = um.singleText(); + } + } + return Response.from(AiMessage.from("Mock response to: " + lastMessage)); + } + } +} diff --git a/core/src/test/java/com/redis/vl/test/vcr/VCREmbeddingInterceptorTest.java b/core/src/test/java/com/redis/vl/test/vcr/VCREmbeddingInterceptorTest.java new file mode 100644 index 0000000..35fd613 --- /dev/null +++ b/core/src/test/java/com/redis/vl/test/vcr/VCREmbeddingInterceptorTest.java @@ -0,0 +1,258 @@ +package com.redis.vl.test.vcr; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for VCREmbeddingInterceptor. Tests interception logic without requiring a Redis + * instance or actual embedding model. + */ +@DisplayName("VCREmbeddingInterceptor") +class VCREmbeddingInterceptorTest { + + private TestVCREmbeddingInterceptor interceptor; + private MockEmbeddingProvider mockProvider; + + @BeforeEach + void setUp() { + mockProvider = new MockEmbeddingProvider(); + interceptor = new TestVCREmbeddingInterceptor(mockProvider); + } + + @Nested + @DisplayName("Recording Mode") + class RecordingMode { + + @BeforeEach + void setUp() { + interceptor.setMode(VCRMode.RECORD); + interceptor.setTestId("TestClass.testMethod"); + } + + @Test + @DisplayName("should call real provider and record result in RECORD mode") + void shouldCallRealProviderAndRecord() { + float[] result = interceptor.embed("test text"); + + assertNotNull(result); + assertEquals(1, mockProvider.callCount.get()); + assertEquals(1, interceptor.getRecordedCount()); + } + + @Test + @DisplayName("should record multiple calls with incrementing indices") + void shouldRecordMultipleCallsWithIndices() { + interceptor.embed("text 1"); + interceptor.embed("text 2"); + interceptor.embed("text 3"); + + assertEquals(3, mockProvider.callCount.get()); + assertEquals(3, interceptor.getRecordedCount()); + + // Verify keys are sequential + List keys = interceptor.getRecordedKeys(); + assertTrue(keys.get(0).endsWith(":0001")); + assertTrue(keys.get(1).endsWith(":0002")); + assertTrue(keys.get(2).endsWith(":0003")); + } + + @Test + @DisplayName("should record batch embeddings") + void shouldRecordBatchEmbeddings() { + List results = interceptor.embedBatch(List.of("text 1", "text 2")); + + assertNotNull(results); + assertEquals(2, results.size()); + assertEquals(1, interceptor.getRecordedCount()); // Batch recorded as one cassette + } + } + + @Nested + @DisplayName("Playback Mode") + class PlaybackMode { + + @BeforeEach + void setUp() { + interceptor.setMode(VCRMode.PLAYBACK); + interceptor.setTestId("TestClass.testMethod"); + } + + @Test + @DisplayName("should return cached result without calling provider") + void shouldReturnCachedWithoutCallingProvider() { + // Pre-load cache + float[] expected = new float[] {0.1f, 0.2f, 0.3f}; + interceptor.preloadCassette("vcr:embedding:TestClass.testMethod:0001", expected); + + float[] result = interceptor.embed("test text"); + + assertArrayEquals(expected, result); + assertEquals(0, mockProvider.callCount.get()); + } + + @Test + @DisplayName("should throw when cassette not found in strict PLAYBACK mode") + void shouldThrowWhenCassetteNotFound() { + assertThrows( + VCRCassetteMissingException.class, () -> interceptor.embed("text with no cassette")); + } + + @Test + @DisplayName("should return cached batch embeddings") + void shouldReturnCachedBatch() { + // Pre-load batch cassette + float[][] expected = new float[][] {{0.1f, 0.2f}, {0.3f, 0.4f}}; + interceptor.preloadBatchCassette("vcr:embedding:TestClass.testMethod:0001", expected); + + List results = interceptor.embedBatch(List.of("text 1", "text 2")); + + assertEquals(2, results.size()); + assertArrayEquals(expected[0], results.get(0)); + assertArrayEquals(expected[1], results.get(1)); + assertEquals(0, mockProvider.callCount.get()); + } + } + + @Nested + @DisplayName("Playback or Record Mode") + class PlaybackOrRecordMode { + + @BeforeEach + void setUp() { + interceptor.setMode(VCRMode.PLAYBACK_OR_RECORD); + interceptor.setTestId("TestClass.testMethod"); + } + + @Test + @DisplayName("should return cached result when available") + void shouldReturnCachedWhenAvailable() { + float[] cached = new float[] {0.1f, 0.2f, 0.3f}; + interceptor.preloadCassette("vcr:embedding:TestClass.testMethod:0001", cached); + + float[] result = interceptor.embed("test text"); + + assertArrayEquals(cached, result); + assertEquals(0, mockProvider.callCount.get()); + } + + @Test + @DisplayName("should call provider and record when cache miss") + void shouldCallProviderAndRecordOnCacheMiss() { + float[] result = interceptor.embed("uncached text"); + + assertNotNull(result); + assertEquals(1, mockProvider.callCount.get()); + assertEquals(1, interceptor.getRecordedCount()); + } + } + + @Nested + @DisplayName("Off Mode") + class OffMode { + + @Test + @DisplayName("should bypass VCR completely when OFF") + void shouldBypassVCRWhenOff() { + interceptor.setMode(VCRMode.OFF); + + float[] result = interceptor.embed("test"); + + assertEquals(1, mockProvider.callCount.get()); + assertEquals(0, interceptor.getRecordedCount()); + } + } + + @Nested + @DisplayName("Statistics") + class Statistics { + + @BeforeEach + void setUp() { + interceptor.setMode(VCRMode.PLAYBACK_OR_RECORD); + interceptor.setTestId("TestClass.testMethod"); + } + + @Test + @DisplayName("should track cache hits") + void shouldTrackCacheHits() { + interceptor.preloadCassette("vcr:embedding:TestClass.testMethod:0001", new float[] {0.1f}); + interceptor.preloadCassette("vcr:embedding:TestClass.testMethod:0002", new float[] {0.2f}); + + interceptor.embed("text 1"); + interceptor.embed("text 2"); + + assertEquals(2, interceptor.getCacheHits()); + assertEquals(0, interceptor.getCacheMisses()); + } + + @Test + @DisplayName("should track cache misses") + void shouldTrackCacheMisses() { + interceptor.embed("uncached 1"); + interceptor.embed("uncached 2"); + + assertEquals(0, interceptor.getCacheHits()); + assertEquals(2, interceptor.getCacheMisses()); + } + + @Test + @DisplayName("should reset statistics") + void shouldResetStatistics() { + interceptor.embed("text"); + interceptor.resetStatistics(); + + assertEquals(0, interceptor.getCacheHits()); + assertEquals(0, interceptor.getCacheMisses()); + } + } + + // Test helper classes + + /** Mock embedding provider for testing. */ + static class MockEmbeddingProvider { + AtomicInteger callCount = new AtomicInteger(0); + int dimensions = 384; + + float[] embed(String text) { + callCount.incrementAndGet(); + // Return deterministic embedding based on text hash + float[] embedding = new float[dimensions]; + int hash = text.hashCode(); + for (int i = 0; i < dimensions; i++) { + embedding[i] = (float) Math.sin(hash + i) * 0.1f; + } + return embedding; + } + + List embedBatch(List texts) { + callCount.incrementAndGet(); + return texts.stream().map(this::embed).toList(); + } + } + + /** Test implementation with in-memory cassette storage. */ + static class TestVCREmbeddingInterceptor extends VCREmbeddingInterceptor { + private final MockEmbeddingProvider provider; + + TestVCREmbeddingInterceptor(MockEmbeddingProvider provider) { + super(); + this.provider = provider; + } + + @Override + protected float[] callRealEmbedding(String text) { + return provider.embed(text); + } + + @Override + protected List callRealBatchEmbedding(List texts) { + return provider.embedBatch(texts); + } + } +} diff --git a/core/src/test/java/com/redis/vl/test/vcr/VCREmbeddingModelTest.java b/core/src/test/java/com/redis/vl/test/vcr/VCREmbeddingModelTest.java new file mode 100644 index 0000000..c1c0b9b --- /dev/null +++ b/core/src/test/java/com/redis/vl/test/vcr/VCREmbeddingModelTest.java @@ -0,0 +1,312 @@ +package com.redis.vl.test.vcr; + +import static org.junit.jupiter.api.Assertions.*; + +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.output.Response; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for VCREmbeddingModel. + * + *

These tests demonstrate standalone VCR usage with LangChain4J EmbeddingModel, without + * requiring any RedisVL vectorizers or other RedisVL components. + */ +@DisplayName("VCREmbeddingModel") +class VCREmbeddingModelTest { + + private VCREmbeddingModel vcrModel; + private MockLangChain4JEmbeddingModel mockDelegate; + + @BeforeEach + void setUp() { + mockDelegate = new MockLangChain4JEmbeddingModel(); + vcrModel = new VCREmbeddingModel(mockDelegate); + vcrModel.setTestId("VCREmbeddingModelTest.test"); + } + + @Nested + @DisplayName("OFF Mode - Direct passthrough") + class OffMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.OFF); + } + + @Test + @DisplayName("should call delegate directly when VCR is OFF") + void shouldCallDelegateDirectly() { + Response response = vcrModel.embed("hello world"); + + assertNotNull(response); + assertNotNull(response.content()); + assertEquals(384, response.content().vector().length); + assertEquals(1, mockDelegate.callCount.get()); + } + + @Test + @DisplayName("should not record when VCR is OFF") + void shouldNotRecordWhenOff() { + vcrModel.embed("hello"); + vcrModel.embed("world"); + + assertEquals(2, mockDelegate.callCount.get()); + assertEquals(0, vcrModel.getRecordedCount()); + } + } + + @Nested + @DisplayName("RECORD Mode") + class RecordMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.RECORD); + } + + @Test + @DisplayName("should call delegate and record result") + void shouldCallDelegateAndRecord() { + Response response = vcrModel.embed("test text"); + + assertNotNull(response); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + } + + @Test + @DisplayName("should record multiple calls with incrementing indices") + void shouldRecordMultipleCalls() { + vcrModel.embed("text 1"); + vcrModel.embed("text 2"); + vcrModel.embed("text 3"); + + assertEquals(3, mockDelegate.callCount.get()); + assertEquals(3, vcrModel.getRecordedCount()); + } + } + + @Nested + @DisplayName("PLAYBACK Mode") + class PlaybackMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.PLAYBACK); + } + + @Test + @DisplayName("should return cached embedding without calling delegate") + void shouldReturnCachedEmbedding() { + // Pre-load cassette + float[] cached = new float[] {0.1f, 0.2f, 0.3f}; + vcrModel.preloadCassette("vcr:embedding:VCREmbeddingModelTest.test:0001", cached); + + Response response = vcrModel.embed("test text"); + + assertNotNull(response); + assertArrayEquals(cached, response.content().vector()); + assertEquals(0, mockDelegate.callCount.get()); + } + + @Test + @DisplayName("should throw when cassette not found in strict PLAYBACK mode") + void shouldThrowWhenCassetteNotFound() { + assertThrows(VCRCassetteMissingException.class, () -> vcrModel.embed("uncached text")); + } + + @Test + @DisplayName("should track cache hits") + void shouldTrackCacheHits() { + vcrModel.preloadCassette("vcr:embedding:VCREmbeddingModelTest.test:0001", new float[] {0.1f}); + + vcrModel.embed("text"); + + assertEquals(1, vcrModel.getCacheHits()); + assertEquals(0, vcrModel.getCacheMisses()); + } + } + + @Nested + @DisplayName("PLAYBACK_OR_RECORD Mode") + class PlaybackOrRecordMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); + } + + @Test + @DisplayName("should return cached embedding when available") + void shouldReturnCachedWhenAvailable() { + float[] cached = new float[] {0.5f, 0.6f, 0.7f}; + vcrModel.preloadCassette("vcr:embedding:VCREmbeddingModelTest.test:0001", cached); + + Response response = vcrModel.embed("test"); + + assertArrayEquals(cached, response.content().vector()); + assertEquals(0, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getCacheHits()); + } + + @Test + @DisplayName("should call delegate and record on cache miss") + void shouldCallDelegateAndRecordOnMiss() { + Response response = vcrModel.embed("uncached"); + + assertNotNull(response); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + assertEquals(1, vcrModel.getCacheMisses()); + } + + @Test + @DisplayName("should allow subsequent cache hits after recording") + void shouldAllowSubsequentCacheHits() { + // First call - cache miss, records + vcrModel.embed("text"); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getCacheMisses()); + + // Reset counter for second test + vcrModel.resetCallCounter(); + + // Second call - cache hit from recorded value + vcrModel.embed("text"); + assertEquals(1, mockDelegate.callCount.get()); // Still 1, not 2 + assertEquals(1, vcrModel.getCacheHits()); + } + } + + @Nested + @DisplayName("Batch Embedding") + class BatchEmbedding { + + @Test + @DisplayName("should handle batch embeddings in RECORD mode") + void shouldHandleBatchInRecordMode() { + vcrModel.setMode(VCRMode.RECORD); + + List segments = + List.of( + TextSegment.from("text 1"), TextSegment.from("text 2"), TextSegment.from("text 3")); + + Response> response = vcrModel.embedAll(segments); + + assertNotNull(response); + assertEquals(3, response.content().size()); + assertEquals(1, vcrModel.getRecordedCount()); + } + + @Test + @DisplayName("should return cached batch embeddings") + void shouldReturnCachedBatch() { + vcrModel.setMode(VCRMode.PLAYBACK); + + float[][] cached = new float[][] {{0.1f, 0.2f}, {0.3f, 0.4f}}; + vcrModel.preloadBatchCassette("vcr:embedding:VCREmbeddingModelTest.test:0001", cached); + + List segments = List.of(TextSegment.from("text 1"), TextSegment.from("text 2")); + + Response> response = vcrModel.embedAll(segments); + + assertEquals(2, response.content().size()); + assertArrayEquals(cached[0], response.content().get(0).vector()); + assertArrayEquals(cached[1], response.content().get(1).vector()); + assertEquals(0, mockDelegate.callCount.get()); + } + } + + @Nested + @DisplayName("Model Dimension") + class ModelDimension { + + @Test + @DisplayName("should return delegate dimension") + void shouldReturnDelegateDimension() { + assertEquals(384, vcrModel.dimension()); + } + } + + @Nested + @DisplayName("Delegate Access") + class DelegateAccess { + + @Test + @DisplayName("should provide access to underlying delegate") + void shouldProvideAccessToDelegate() { + EmbeddingModel delegate = vcrModel.getDelegate(); + + assertSame(mockDelegate, delegate); + } + } + + @Nested + @DisplayName("Statistics Reset") + class StatisticsReset { + + @Test + @DisplayName("should reset statistics") + void shouldResetStatistics() { + vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); + vcrModel.embed("text"); // Cache miss + + assertEquals(1, vcrModel.getCacheMisses()); + + vcrModel.resetStatistics(); + + assertEquals(0, vcrModel.getCacheHits()); + assertEquals(0, vcrModel.getCacheMisses()); + } + } + + /** Mock LangChain4J EmbeddingModel for testing. */ + static class MockLangChain4JEmbeddingModel implements EmbeddingModel { + AtomicInteger callCount = new AtomicInteger(0); + int dimensions = 384; + + @Override + public Response embed(String text) { + callCount.incrementAndGet(); + float[] vector = generateVector(text); + return Response.from(Embedding.from(vector)); + } + + @Override + public Response embed(TextSegment textSegment) { + return embed(textSegment.text()); + } + + @Override + public Response> embedAll(List textSegments) { + callCount.incrementAndGet(); + List embeddings = + textSegments.stream() + .map(segment -> Embedding.from(generateVector(segment.text()))) + .toList(); + return Response.from(embeddings); + } + + @Override + public int dimension() { + return dimensions; + } + + private float[] generateVector(String text) { + float[] vector = new float[dimensions]; + int hash = text.hashCode(); + for (int i = 0; i < dimensions; i++) { + vector[i] = (float) Math.sin(hash + i) * 0.1f; + } + return vector; + } + } +} diff --git a/core/src/test/java/com/redis/vl/test/vcr/VCRModeTest.java b/core/src/test/java/com/redis/vl/test/vcr/VCRModeTest.java new file mode 100644 index 0000000..540869c --- /dev/null +++ b/core/src/test/java/com/redis/vl/test/vcr/VCRModeTest.java @@ -0,0 +1,81 @@ +package com.redis.vl.test.vcr; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +/** + * Tests for VCRMode enum - TDD RED phase. These tests will fail until we implement the VCRMode + * enum. + */ +class VCRModeTest { + + @Test + void shouldHavePlaybackMode() { + assertThat(VCRMode.PLAYBACK).isNotNull(); + assertThat(VCRMode.PLAYBACK.name()).isEqualTo("PLAYBACK"); + } + + @Test + void shouldHaveRecordMode() { + assertThat(VCRMode.RECORD).isNotNull(); + assertThat(VCRMode.RECORD.name()).isEqualTo("RECORD"); + } + + @Test + void shouldHaveRecordNewMode() { + assertThat(VCRMode.RECORD_NEW).isNotNull(); + assertThat(VCRMode.RECORD_NEW.name()).isEqualTo("RECORD_NEW"); + } + + @Test + void shouldHaveRecordFailedMode() { + assertThat(VCRMode.RECORD_FAILED).isNotNull(); + assertThat(VCRMode.RECORD_FAILED.name()).isEqualTo("RECORD_FAILED"); + } + + @Test + void shouldHavePlaybackOrRecordMode() { + assertThat(VCRMode.PLAYBACK_OR_RECORD).isNotNull(); + assertThat(VCRMode.PLAYBACK_OR_RECORD.name()).isEqualTo("PLAYBACK_OR_RECORD"); + } + + @Test + void shouldHaveOffMode() { + assertThat(VCRMode.OFF).isNotNull(); + assertThat(VCRMode.OFF.name()).isEqualTo("OFF"); + } + + @Test + void shouldHaveSixModes() { + assertThat(VCRMode.values()).hasSize(6); + } + + @Test + void isRecordModeShouldReturnTrueForRecordModes() { + assertThat(VCRMode.RECORD.isRecordMode()).isTrue(); + assertThat(VCRMode.RECORD_NEW.isRecordMode()).isTrue(); + assertThat(VCRMode.RECORD_FAILED.isRecordMode()).isTrue(); + assertThat(VCRMode.PLAYBACK_OR_RECORD.isRecordMode()).isTrue(); + } + + @Test + void isRecordModeShouldReturnFalseForNonRecordModes() { + assertThat(VCRMode.PLAYBACK.isRecordMode()).isFalse(); + assertThat(VCRMode.OFF.isRecordMode()).isFalse(); + } + + @Test + void isPlaybackModeShouldReturnTrueForPlaybackModes() { + assertThat(VCRMode.PLAYBACK.isPlaybackMode()).isTrue(); + assertThat(VCRMode.PLAYBACK_OR_RECORD.isPlaybackMode()).isTrue(); + } + + @Test + void isPlaybackModeShouldReturnFalseForNonPlaybackModes() { + assertThat(VCRMode.RECORD.isPlaybackMode()).isFalse(); + assertThat(VCRMode.RECORD_NEW.isPlaybackMode()).isFalse(); + assertThat(VCRMode.RECORD_FAILED.isPlaybackMode()).isFalse(); + assertThat(VCRMode.OFF.isPlaybackMode()).isFalse(); + } +} diff --git a/core/src/test/java/com/redis/vl/test/vcr/VCRRegistryTest.java b/core/src/test/java/com/redis/vl/test/vcr/VCRRegistryTest.java new file mode 100644 index 0000000..7837890 --- /dev/null +++ b/core/src/test/java/com/redis/vl/test/vcr/VCRRegistryTest.java @@ -0,0 +1,127 @@ +package com.redis.vl.test.vcr; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Unit tests for VCRRegistry with null Redis (local cache only mode). */ +class VCRRegistryTest { + + private VCRRegistry registry; + + @BeforeEach + void setUp() { + // Use null jedis for local-only mode (no Redis required for unit tests) + registry = new VCRRegistry(null); + } + + @Test + void shouldReturnMissingStatusForUnknownTest() { + VCRRegistry.RecordingStatus status = registry.getTestStatus("unknown:test"); + assertThat(status).isEqualTo(VCRRegistry.RecordingStatus.MISSING); + } + + @Test + void shouldRegisterSuccessfulTest() { + String testId = "MyTest:testMethod"; + List cassettes = List.of("vcr:llm:MyTest:testMethod:0001"); + + registry.registerSuccess(testId, cassettes); + + assertThat(registry.getTestStatus(testId)).isEqualTo(VCRRegistry.RecordingStatus.RECORDED); + } + + @Test + void shouldRegisterFailedTest() { + String testId = "MyTest:failingMethod"; + + registry.registerFailure(testId, "Test failed with NPE"); + + assertThat(registry.getTestStatus(testId)).isEqualTo(VCRRegistry.RecordingStatus.FAILED); + } + + @Test + void shouldTrackAllRecordedTests() { + registry.registerSuccess("Test1:method1", List.of()); + registry.registerSuccess("Test2:method2", List.of()); + registry.registerFailure("Test3:method3", "error"); + + assertThat(registry.getAllRecordedTests()) + .containsExactlyInAnyOrder("Test1:method1", "Test2:method2", "Test3:method3"); + } + + // Tests for determineEffectiveMode + + @Test + void recordNewShouldRecordMissingTests() { + VCRMode effective = registry.determineEffectiveMode("missing:test", VCRMode.RECORD_NEW); + assertThat(effective).isEqualTo(VCRMode.RECORD); + } + + @Test + void recordNewShouldPlaybackRecordedTests() { + registry.registerSuccess("recorded:test", List.of()); + + VCRMode effective = registry.determineEffectiveMode("recorded:test", VCRMode.RECORD_NEW); + assertThat(effective).isEqualTo(VCRMode.PLAYBACK); + } + + @Test + void recordFailedShouldRecordFailedTests() { + registry.registerFailure("failed:test", "error"); + + VCRMode effective = registry.determineEffectiveMode("failed:test", VCRMode.RECORD_FAILED); + assertThat(effective).isEqualTo(VCRMode.RECORD); + } + + @Test + void recordFailedShouldRecordMissingTests() { + VCRMode effective = registry.determineEffectiveMode("missing:test", VCRMode.RECORD_FAILED); + assertThat(effective).isEqualTo(VCRMode.RECORD); + } + + @Test + void recordFailedShouldPlaybackRecordedTests() { + registry.registerSuccess("recorded:test", List.of()); + + VCRMode effective = registry.determineEffectiveMode("recorded:test", VCRMode.RECORD_FAILED); + assertThat(effective).isEqualTo(VCRMode.PLAYBACK); + } + + @Test + void playbackOrRecordShouldPlaybackRecordedTests() { + registry.registerSuccess("recorded:test", List.of()); + + VCRMode effective = + registry.determineEffectiveMode("recorded:test", VCRMode.PLAYBACK_OR_RECORD); + assertThat(effective).isEqualTo(VCRMode.PLAYBACK); + } + + @Test + void playbackOrRecordShouldRecordMissingTests() { + VCRMode effective = registry.determineEffectiveMode("missing:test", VCRMode.PLAYBACK_OR_RECORD); + assertThat(effective).isEqualTo(VCRMode.RECORD); + } + + @Test + void playbackModeShouldAlwaysReturnPlayback() { + VCRMode effective = registry.determineEffectiveMode("any:test", VCRMode.PLAYBACK); + assertThat(effective).isEqualTo(VCRMode.PLAYBACK); + } + + @Test + void recordModeShouldAlwaysReturnRecord() { + registry.registerSuccess("recorded:test", List.of()); + + VCRMode effective = registry.determineEffectiveMode("recorded:test", VCRMode.RECORD); + assertThat(effective).isEqualTo(VCRMode.RECORD); + } + + @Test + void offModeShouldAlwaysReturnOff() { + VCRMode effective = registry.determineEffectiveMode("any:test", VCRMode.OFF); + assertThat(effective).isEqualTo(VCRMode.OFF); + } +} diff --git a/core/src/test/java/com/redis/vl/test/vcr/VCRSpringAIChatModelTest.java b/core/src/test/java/com/redis/vl/test/vcr/VCRSpringAIChatModelTest.java new file mode 100644 index 0000000..7a45a10 --- /dev/null +++ b/core/src/test/java/com/redis/vl/test/vcr/VCRSpringAIChatModelTest.java @@ -0,0 +1,310 @@ +package com.redis.vl.test.vcr; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.Prompt; + +/** + * Unit tests for VCRSpringAIChatModel. + * + *

These tests demonstrate standalone VCR usage with Spring AI ChatModel, without requiring any + * RedisVL components. + */ +@DisplayName("VCRSpringAIChatModel") +class VCRSpringAIChatModelTest { + + private VCRSpringAIChatModel vcrModel; + private MockSpringAIChatModel mockDelegate; + + @BeforeEach + void setUp() { + mockDelegate = new MockSpringAIChatModel(); + vcrModel = new VCRSpringAIChatModel(mockDelegate); + vcrModel.setTestId("VCRSpringAIChatModelTest.test"); + } + + @Nested + @DisplayName("OFF Mode - Direct passthrough") + class OffMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.OFF); + } + + @Test + @DisplayName("should call delegate directly when VCR is OFF") + void shouldCallDelegateDirectly() { + String response = vcrModel.call("Hello"); + + assertNotNull(response); + assertTrue(response.contains("Mock response")); + assertEquals(1, mockDelegate.callCount.get()); + } + + @Test + @DisplayName("should not record when VCR is OFF") + void shouldNotRecordWhenOff() { + vcrModel.call("Hello"); + vcrModel.call("World"); + + assertEquals(2, mockDelegate.callCount.get()); + assertEquals(0, vcrModel.getRecordedCount()); + } + } + + @Nested + @DisplayName("RECORD Mode") + class RecordMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.RECORD); + } + + @Test + @DisplayName("should call delegate and record result") + void shouldCallDelegateAndRecord() { + String response = vcrModel.call("Test message"); + + assertNotNull(response); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + } + + @Test + @DisplayName("should record multiple calls with incrementing indices") + void shouldRecordMultipleCalls() { + vcrModel.call("Message 1"); + vcrModel.call("Message 2"); + vcrModel.call("Message 3"); + + assertEquals(3, mockDelegate.callCount.get()); + assertEquals(3, vcrModel.getRecordedCount()); + } + } + + @Nested + @DisplayName("PLAYBACK Mode") + class PlaybackMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.PLAYBACK); + } + + @Test + @DisplayName("should return cached response without calling delegate") + void shouldReturnCachedResponse() { + // Pre-load cassette + String cachedResponse = "This is a cached LLM response"; + vcrModel.preloadCassette("vcr:chat:VCRSpringAIChatModelTest.test:0001", cachedResponse); + + String response = vcrModel.call("Test"); + + assertNotNull(response); + assertEquals(cachedResponse, response); + assertEquals(0, mockDelegate.callCount.get()); + } + + @Test + @DisplayName("should throw when cassette not found in strict PLAYBACK mode") + void shouldThrowWhenCassetteNotFound() { + assertThrows(VCRCassetteMissingException.class, () -> vcrModel.call("Unknown")); + } + + @Test + @DisplayName("should track cache hits") + void shouldTrackCacheHits() { + vcrModel.preloadCassette("vcr:chat:VCRSpringAIChatModelTest.test:0001", "Cached"); + + vcrModel.call("Test"); + + assertEquals(1, vcrModel.getCacheHits()); + assertEquals(0, vcrModel.getCacheMisses()); + } + } + + @Nested + @DisplayName("PLAYBACK_OR_RECORD Mode") + class PlaybackOrRecordMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); + } + + @Test + @DisplayName("should return cached response when available") + void shouldReturnCachedWhenAvailable() { + String cachedResponse = "Cached LLM answer"; + vcrModel.preloadCassette("vcr:chat:VCRSpringAIChatModelTest.test:0001", cachedResponse); + + String response = vcrModel.call("Test"); + + assertEquals(cachedResponse, response); + assertEquals(0, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getCacheHits()); + } + + @Test + @DisplayName("should call delegate and record on cache miss") + void shouldCallDelegateAndRecordOnMiss() { + String response = vcrModel.call("Uncached"); + + assertNotNull(response); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + assertEquals(1, vcrModel.getCacheMisses()); + } + + @Test + @DisplayName("should allow subsequent cache hits after recording") + void shouldAllowSubsequentCacheHits() { + // First call - cache miss, records + vcrModel.call("Question"); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getCacheMisses()); + + // Reset counter for second call + vcrModel.resetCallCounter(); + + // Second call - cache hit from recorded value + vcrModel.call("Question"); + assertEquals(1, mockDelegate.callCount.get()); // Still 1, not 2 + assertEquals(1, vcrModel.getCacheHits()); + } + } + + @Nested + @DisplayName("Prompt API") + class PromptApi { + + @Test + @DisplayName("should handle Prompt in RECORD mode") + void shouldHandlePromptInRecordMode() { + vcrModel.setMode(VCRMode.RECORD); + + Prompt prompt = new Prompt(List.of(new UserMessage("Hello from Prompt"))); + + ChatResponse response = vcrModel.call(prompt); + + assertNotNull(response); + assertNotNull(response.getResult()); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + } + + @Test + @DisplayName("should return cached ChatResponse for Prompt") + void shouldReturnCachedChatResponse() { + vcrModel.setMode(VCRMode.PLAYBACK); + vcrModel.preloadCassette( + "vcr:chat:VCRSpringAIChatModelTest.test:0001", "Prompt cached response"); + + Prompt prompt = new Prompt(List.of(new UserMessage("Test"))); + + ChatResponse response = vcrModel.call(prompt); + + assertEquals("Prompt cached response", response.getResult().getOutput().getText()); + assertEquals(0, mockDelegate.callCount.get()); + } + } + + @Nested + @DisplayName("Message Varargs") + class MessageVarargs { + + @Test + @DisplayName("should handle Message varargs in RECORD mode") + void shouldHandleMessageVarargsInRecordMode() { + vcrModel.setMode(VCRMode.RECORD); + + String response = vcrModel.call(new UserMessage("First"), new UserMessage("Second")); + + assertNotNull(response); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + } + + @Test + @DisplayName("should return cached response for Message varargs") + void shouldReturnCachedForMessageVarargs() { + vcrModel.setMode(VCRMode.PLAYBACK); + vcrModel.preloadCassette("vcr:chat:VCRSpringAIChatModelTest.test:0001", "Varargs cached"); + + String response = vcrModel.call(new UserMessage("Test")); + + assertEquals("Varargs cached", response); + assertEquals(0, mockDelegate.callCount.get()); + } + } + + @Nested + @DisplayName("Delegate Access") + class DelegateAccess { + + @Test + @DisplayName("should provide access to underlying delegate") + void shouldProvideAccessToDelegate() { + ChatModel delegate = vcrModel.getDelegate(); + + assertSame(mockDelegate, delegate); + } + } + + @Nested + @DisplayName("Statistics Reset") + class StatisticsReset { + + @Test + @DisplayName("should reset statistics") + void shouldResetStatistics() { + vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); + vcrModel.call("Test"); // Cache miss + + assertEquals(1, vcrModel.getCacheMisses()); + + vcrModel.resetStatistics(); + + assertEquals(0, vcrModel.getCacheHits()); + assertEquals(0, vcrModel.getCacheMisses()); + } + } + + /** Mock Spring AI ChatModel for testing. */ + static class MockSpringAIChatModel implements ChatModel { + AtomicInteger callCount = new AtomicInteger(0); + + @Override + public ChatResponse call(Prompt prompt) { + callCount.incrementAndGet(); + String lastMessage = ""; + if (prompt.getInstructions() != null && !prompt.getInstructions().isEmpty()) { + Message last = prompt.getInstructions().get(prompt.getInstructions().size() - 1); + lastMessage = last.getText(); + } + Generation generation = + new Generation(new AssistantMessage("Mock response to: " + lastMessage)); + return new ChatResponse(List.of(generation)); + } + + @Override + public String call(String message) { + callCount.incrementAndGet(); + return "Mock response to: " + message; + } + } +} diff --git a/core/src/test/java/com/redis/vl/test/vcr/VCRSpringAIEmbeddingModelTest.java b/core/src/test/java/com/redis/vl/test/vcr/VCRSpringAIEmbeddingModelTest.java new file mode 100644 index 0000000..127564b --- /dev/null +++ b/core/src/test/java/com/redis/vl/test/vcr/VCRSpringAIEmbeddingModelTest.java @@ -0,0 +1,384 @@ +package com.redis.vl.test.vcr; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.Embedding; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.EmbeddingRequest; +import org.springframework.ai.embedding.EmbeddingResponse; + +/** + * Unit tests for VCRSpringAIEmbeddingModel. + * + *

These tests demonstrate standalone VCR usage with Spring AI EmbeddingModel, without requiring + * any RedisVL vectorizers or other RedisVL components. + */ +@DisplayName("VCRSpringAIEmbeddingModel") +class VCRSpringAIEmbeddingModelTest { + + private VCRSpringAIEmbeddingModel vcrModel; + private MockSpringAIEmbeddingModel mockDelegate; + + @BeforeEach + void setUp() { + mockDelegate = new MockSpringAIEmbeddingModel(); + vcrModel = new VCRSpringAIEmbeddingModel(mockDelegate); + vcrModel.setTestId("VCRSpringAIEmbeddingModelTest.test"); + } + + @Nested + @DisplayName("OFF Mode - Direct passthrough") + class OffMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.OFF); + } + + @Test + @DisplayName("should call delegate directly when VCR is OFF") + void shouldCallDelegateDirectly() { + float[] embedding = vcrModel.embed("hello world"); + + assertNotNull(embedding); + assertEquals(384, embedding.length); + assertEquals(1, mockDelegate.callCount.get()); + } + + @Test + @DisplayName("should not record when VCR is OFF") + void shouldNotRecordWhenOff() { + vcrModel.embed("hello"); + vcrModel.embed("world"); + + assertEquals(2, mockDelegate.callCount.get()); + assertEquals(0, vcrModel.getRecordedCount()); + } + } + + @Nested + @DisplayName("RECORD Mode") + class RecordMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.RECORD); + } + + @Test + @DisplayName("should call delegate and record result") + void shouldCallDelegateAndRecord() { + float[] embedding = vcrModel.embed("test text"); + + assertNotNull(embedding); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + } + + @Test + @DisplayName("should record multiple calls with incrementing indices") + void shouldRecordMultipleCalls() { + vcrModel.embed("text 1"); + vcrModel.embed("text 2"); + vcrModel.embed("text 3"); + + assertEquals(3, mockDelegate.callCount.get()); + assertEquals(3, vcrModel.getRecordedCount()); + } + } + + @Nested + @DisplayName("PLAYBACK Mode") + class PlaybackMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.PLAYBACK); + } + + @Test + @DisplayName("should return cached embedding without calling delegate") + void shouldReturnCachedEmbedding() { + // Pre-load cassette + float[] cached = new float[] {0.1f, 0.2f, 0.3f}; + vcrModel.preloadCassette("vcr:embedding:VCRSpringAIEmbeddingModelTest.test:0001", cached); + + float[] embedding = vcrModel.embed("test text"); + + assertNotNull(embedding); + assertArrayEquals(cached, embedding); + assertEquals(0, mockDelegate.callCount.get()); + } + + @Test + @DisplayName("should throw when cassette not found in strict PLAYBACK mode") + void shouldThrowWhenCassetteNotFound() { + assertThrows(VCRCassetteMissingException.class, () -> vcrModel.embed("uncached text")); + } + + @Test + @DisplayName("should track cache hits") + void shouldTrackCacheHits() { + vcrModel.preloadCassette( + "vcr:embedding:VCRSpringAIEmbeddingModelTest.test:0001", new float[] {0.1f}); + + vcrModel.embed("text"); + + assertEquals(1, vcrModel.getCacheHits()); + assertEquals(0, vcrModel.getCacheMisses()); + } + } + + @Nested + @DisplayName("PLAYBACK_OR_RECORD Mode") + class PlaybackOrRecordMode { + + @BeforeEach + void setUp() { + vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); + } + + @Test + @DisplayName("should return cached embedding when available") + void shouldReturnCachedWhenAvailable() { + float[] cached = new float[] {0.5f, 0.6f, 0.7f}; + vcrModel.preloadCassette("vcr:embedding:VCRSpringAIEmbeddingModelTest.test:0001", cached); + + float[] embedding = vcrModel.embed("test"); + + assertArrayEquals(cached, embedding); + assertEquals(0, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getCacheHits()); + } + + @Test + @DisplayName("should call delegate and record on cache miss") + void shouldCallDelegateAndRecordOnMiss() { + float[] embedding = vcrModel.embed("uncached"); + + assertNotNull(embedding); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + assertEquals(1, vcrModel.getCacheMisses()); + } + + @Test + @DisplayName("should allow subsequent cache hits after recording") + void shouldAllowSubsequentCacheHits() { + // First call - cache miss, records + vcrModel.embed("text"); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getCacheMisses()); + + // Reset counter for second test + vcrModel.resetCallCounter(); + + // Second call - cache hit from recorded value + vcrModel.embed("text"); + assertEquals(1, mockDelegate.callCount.get()); // Still 1, not 2 + assertEquals(1, vcrModel.getCacheHits()); + } + } + + @Nested + @DisplayName("Batch Embedding") + class BatchEmbedding { + + @Test + @DisplayName("should handle batch embeddings in RECORD mode") + void shouldHandleBatchInRecordMode() { + vcrModel.setMode(VCRMode.RECORD); + + List texts = List.of("text 1", "text 2", "text 3"); + + List embeddings = vcrModel.embed(texts); + + assertNotNull(embeddings); + assertEquals(3, embeddings.size()); + assertEquals(1, vcrModel.getRecordedCount()); + } + + @Test + @DisplayName("should return cached batch embeddings") + void shouldReturnCachedBatch() { + vcrModel.setMode(VCRMode.PLAYBACK); + + float[][] cached = new float[][] {{0.1f, 0.2f}, {0.3f, 0.4f}}; + vcrModel.preloadBatchCassette( + "vcr:embedding:VCRSpringAIEmbeddingModelTest.test:0001", cached); + + List texts = List.of("text 1", "text 2"); + + List embeddings = vcrModel.embed(texts); + + assertEquals(2, embeddings.size()); + assertArrayEquals(cached[0], embeddings.get(0)); + assertArrayEquals(cached[1], embeddings.get(1)); + assertEquals(0, mockDelegate.callCount.get()); + } + } + + @Nested + @DisplayName("EmbeddingResponse API") + class EmbeddingResponseApi { + + @Test + @DisplayName("should handle embedForResponse in RECORD mode") + void shouldHandleEmbedForResponse() { + vcrModel.setMode(VCRMode.RECORD); + + EmbeddingResponse response = vcrModel.embedForResponse(List.of("text 1", "text 2")); + + assertNotNull(response); + assertEquals(2, response.getResults().size()); + assertEquals(1, vcrModel.getRecordedCount()); + } + + @Test + @DisplayName("should return cached batch via call() method") + void shouldReturnCachedBatchViaCall() { + vcrModel.setMode(VCRMode.PLAYBACK); + + float[][] cached = new float[][] {{0.1f, 0.2f}, {0.3f, 0.4f}}; + vcrModel.preloadBatchCassette( + "vcr:embedding:VCRSpringAIEmbeddingModelTest.test:0001", cached); + + EmbeddingRequest request = new EmbeddingRequest(List.of("text 1", "text 2"), null); + EmbeddingResponse response = vcrModel.call(request); + + assertEquals(2, response.getResults().size()); + assertArrayEquals(cached[0], response.getResults().get(0).getOutput()); + assertEquals(0, mockDelegate.callCount.get()); + } + } + + @Nested + @DisplayName("Document Embedding") + class DocumentEmbedding { + + @Test + @DisplayName("should embed document content") + void shouldEmbedDocument() { + vcrModel.setMode(VCRMode.RECORD); + + Document document = new Document("This is a test document"); + float[] embedding = vcrModel.embed(document); + + assertNotNull(embedding); + assertEquals(384, embedding.length); + assertEquals(1, mockDelegate.callCount.get()); + assertEquals(1, vcrModel.getRecordedCount()); + } + } + + @Nested + @DisplayName("Model Dimension") + class ModelDimension { + + @Test + @DisplayName("should return delegate dimension") + void shouldReturnDelegateDimension() { + assertEquals(384, vcrModel.dimensions()); + } + } + + @Nested + @DisplayName("Delegate Access") + class DelegateAccess { + + @Test + @DisplayName("should provide access to underlying delegate") + void shouldProvideAccessToDelegate() { + EmbeddingModel delegate = vcrModel.getDelegate(); + + assertSame(mockDelegate, delegate); + } + } + + @Nested + @DisplayName("Statistics Reset") + class StatisticsReset { + + @Test + @DisplayName("should reset statistics") + void shouldResetStatistics() { + vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); + vcrModel.embed("text"); // Cache miss + + assertEquals(1, vcrModel.getCacheMisses()); + + vcrModel.resetStatistics(); + + assertEquals(0, vcrModel.getCacheHits()); + assertEquals(0, vcrModel.getCacheMisses()); + } + } + + /** Mock Spring AI EmbeddingModel for testing. */ + static class MockSpringAIEmbeddingModel implements EmbeddingModel { + AtomicInteger callCount = new AtomicInteger(0); + int embeddingDimensions = 384; + + @Override + public EmbeddingResponse call(EmbeddingRequest request) { + callCount.incrementAndGet(); + List embeddings = + request.getInstructions().stream() + .map(text -> new Embedding(generateVector(text), 0)) + .toList(); + return new EmbeddingResponse(embeddings); + } + + @Override + public float[] embed(String text) { + callCount.incrementAndGet(); + return generateVector(text); + } + + @Override + public float[] embed(Document document) { + return embed(document.getText()); + } + + @Override + public List embed(List texts) { + callCount.incrementAndGet(); + return texts.stream().map(this::generateVectorWithoutCount).toList(); + } + + @Override + public EmbeddingResponse embedForResponse(List texts) { + return call(new EmbeddingRequest(texts, null)); + } + + @Override + public int dimensions() { + return embeddingDimensions; + } + + private float[] generateVector(String text) { + float[] vector = new float[embeddingDimensions]; + int hash = text.hashCode(); + for (int i = 0; i < embeddingDimensions; i++) { + vector[i] = (float) Math.sin(hash + i) * 0.1f; + } + return vector; + } + + private float[] generateVectorWithoutCount(String text) { + float[] vector = new float[embeddingDimensions]; + int hash = text.hashCode(); + for (int i = 0; i < embeddingDimensions; i++) { + vector[i] = (float) Math.sin(hash + i) * 0.1f; + } + return vector; + } + } +} diff --git a/demos/langchain4j-vcr/README.md b/demos/langchain4j-vcr/README.md new file mode 100644 index 0000000..36cfb55 --- /dev/null +++ b/demos/langchain4j-vcr/README.md @@ -0,0 +1,177 @@ +# LangChain4J VCR Demo + +This demo shows how to use the VCR (Video Cassette Recorder) test system with LangChain4J models. VCR records LLM/embedding API responses to Redis and replays them in subsequent test runs, enabling fast, deterministic, and cost-effective testing. + +## Features + +- Record and replay LangChain4J `EmbeddingModel` responses +- Record and replay LangChain4J `ChatLanguageModel` responses +- Declarative `@VCRTest` and `@VCRModel` annotations +- Automatic model wrapping via JUnit 5 extension +- Redis-backed persistence with automatic test isolation + +## Quick Start + +### 1. Annotate Your Test Class + +```java +import com.redis.vl.test.vcr.VCRMode; +import com.redis.vl.test.vcr.VCRModel; +import com.redis.vl.test.vcr.VCRTest; + +@VCRTest(mode = VCRMode.PLAYBACK_OR_RECORD) +class MyLangChain4JTest { + + @VCRModel(modelName = "text-embedding-3-small") + private EmbeddingModel embeddingModel = createEmbeddingModel(); + + @VCRModel + private ChatLanguageModel chatModel = createChatModel(); + + // Models must be initialized at field declaration time, + // not in @BeforeEach (VCR wrapping happens before @BeforeEach) +} +``` + +### 2. Use Models Normally + +```java +@Test +void shouldEmbedText() { + // First run: calls real API and records response + // Subsequent runs: replays from Redis cassette + Response response = embeddingModel.embed("What is Redis?"); + + assertNotNull(response.content()); +} + +@Test +void shouldGenerateResponse() { + String response = chatModel.generate("Explain Redis in one sentence."); + + assertNotNull(response); +} +``` + +## VCR Modes + +| Mode | Description | API Key Required | +|------|-------------|------------------| +| `PLAYBACK` | Only use recorded cassettes. Fails if cassette missing. | No | +| `PLAYBACK_OR_RECORD` | Use cassette if available, record if not. | Only for first run | +| `RECORD` | Always call real API and record response. | Yes | +| `OFF` | Bypass VCR, always call real API. | Yes | + +### Setting Mode via Environment Variable + +Override the annotation mode at runtime without changing code: + +```bash +# Record new cassettes +VCR_MODE=RECORD ./gradlew :demos:langchain4j-vcr:test + +# Playback only (CI/CD, no API key needed) +VCR_MODE=PLAYBACK ./gradlew :demos:langchain4j-vcr:test + +# Default behavior from annotation +./gradlew :demos:langchain4j-vcr:test +``` + +## Running the Demo + +### With Pre-recorded Cassettes (No API Key) + +The demo includes pre-recorded cassettes in `src/test/resources/vcr-data/`. Run tests without an API key: + +```bash +./gradlew :demos:langchain4j-vcr:test +``` + +### Recording New Cassettes + +To record fresh cassettes, set your OpenAI API key: + +```bash +OPENAI_API_KEY=your-key VCR_MODE=RECORD ./gradlew :demos:langchain4j-vcr:test +``` + +## How It Works + +1. **Test Setup**: `@VCRTest` annotation triggers the VCR JUnit 5 extension +2. **Container Start**: A Redis Stack container is started with persistence enabled +3. **Model Wrapping**: Fields annotated with `@VCRModel` are wrapped with VCR proxies +4. **Recording**: When a model is called, VCR checks for existing cassette: + - **Cache hit**: Returns recorded response + - **Cache miss**: Calls real API, stores response as cassette +5. **Persistence**: Cassettes are saved to `vcr-data/` directory via Redis persistence +6. **Cleanup**: Container stops, data persists for next run + +## Cassette Storage + +Cassettes are stored in Redis JSON format with keys like: + +``` +vcr:embedding:MyTest.testMethod:0001 +vcr:chat:MyTest.testMethod:0001 +``` + +Data persists to `src/test/resources/vcr-data/` via Redis AOF/RDB. + +## Test Structure + +``` +demos/langchain4j-vcr/ +β”œβ”€β”€ src/test/java/ +β”‚ └── com/redis/vl/demo/vcr/ +β”‚ └── LangChain4JVCRDemoTest.java +└── src/test/resources/ + └── vcr-data/ # Persisted cassettes + β”œβ”€β”€ appendonly.aof + └── dump.rdb +``` + +## Configuration Options + +### @VCRTest Annotation + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `mode` | `PLAYBACK_OR_RECORD` | VCR operating mode | +| `dataDir` | `src/test/resources/vcr-data` | Cassette storage directory | +| `redisImage` | `redis/redis-stack:latest` | Redis Docker image | + +### @VCRModel Annotation + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `modelName` | `""` | Optional model identifier for logging | + +## Best Practices + +1. **Initialize models at field declaration** - Not in `@BeforeEach` +2. **Use dummy API key in PLAYBACK mode** - VCR will use cached responses +3. **Commit cassettes to version control** - Enables reproducible tests +4. **Use specific test names** - Cassette keys include test class and method names +5. **Re-record periodically** - API responses may change over time + +## Troubleshooting + +### Tests fail with "Cassette missing" + +- Ensure cassettes exist in `src/test/resources/vcr-data/` +- Run once with `VCR_MODE=RECORD` and API key to generate cassettes + +### API key required error + +- In `PLAYBACK` mode, use a dummy key: `"vcr-playback-mode"` +- VCR won't call the real API when cassettes exist + +### Tests pass but call real API + +- Verify models are initialized at field declaration, not `@BeforeEach` +- Check that `@VCRModel` annotation is present on model fields + +## See Also + +- [Spring AI VCR Demo](../spring-ai-vcr/README.md) +- [VCR Test System Documentation](../../README.md#-experimental-vcr-test-system) diff --git a/demos/langchain4j-vcr/build.gradle.kts b/demos/langchain4j-vcr/build.gradle.kts new file mode 100644 index 0000000..5d6c572 --- /dev/null +++ b/demos/langchain4j-vcr/build.gradle.kts @@ -0,0 +1,65 @@ +plugins { + java +} + +group = "com.redis.vl.demo" +version = "0.12.0" + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenCentral() +} + +dependencies { + // RedisVL Core (includes VCR support) + implementation(project(":core")) + + // SpotBugs annotations + compileOnly("com.github.spotbugs:spotbugs-annotations:4.8.3") + + // LangChain4J + implementation("dev.langchain4j:langchain4j:0.36.2") + implementation("dev.langchain4j:langchain4j-open-ai:0.36.2") + + // Redis + implementation("redis.clients:jedis:5.2.0") + + // Logging + implementation("org.slf4j:slf4j-api:2.0.16") + runtimeOnly("ch.qos.logback:logback-classic:1.5.15") + + // Testing + testImplementation("org.junit.jupiter:junit-jupiter:5.11.4") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testCompileOnly("com.github.spotbugs:spotbugs-annotations:4.8.3") + + // TestContainers for integration tests + testImplementation("org.testcontainers:testcontainers:1.19.3") + testImplementation("org.testcontainers:junit-jupiter:1.19.3") +} + +tasks.withType { + options.encoding = "UTF-8" + options.compilerArgs.addAll(listOf( + "-parameters", + "-Xlint:all", + "-Xlint:-processing" + )) +} + +tasks.withType { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } + // Pass environment variables to tests + environment("OPENAI_API_KEY", System.getenv("OPENAI_API_KEY") ?: "") +} diff --git a/demos/langchain4j-vcr/src/test/java/com/redis/vl/demo/vcr/LangChain4JVCRDemoTest.java b/demos/langchain4j-vcr/src/test/java/com/redis/vl/demo/vcr/LangChain4JVCRDemoTest.java new file mode 100644 index 0000000..fc51a77 --- /dev/null +++ b/demos/langchain4j-vcr/src/test/java/com/redis/vl/demo/vcr/LangChain4JVCRDemoTest.java @@ -0,0 +1,179 @@ +package com.redis.vl.demo.vcr; + +import static org.junit.jupiter.api.Assertions.*; + +import com.redis.vl.test.vcr.VCRMode; +import com.redis.vl.test.vcr.VCRModel; +import com.redis.vl.test.vcr.VCRTest; +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.model.openai.OpenAiEmbeddingModel; +import dev.langchain4j.model.output.Response; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +/** + * VCR Demo Tests for LangChain4J. + * + *

This demo shows how to use VCR (Video Cassette Recorder) functionality to record and replay + * LLM API calls with LangChain4J. + * + *

How to Use VCR in Your Tests:

+ * + *
    + *
  1. Annotate your test class with {@code @VCRTest} + *
  2. Annotate your model fields with {@code @VCRModel} + *
  3. Initialize your models in {@code @BeforeEach} - VCR wraps them automatically + *
  4. Use the models normally - first run records, subsequent runs replay + *
+ * + *

Benefits:

+ * + *
    + *
  • Fast, deterministic tests that don't call real LLM APIs after recording + *
  • Cost savings by avoiding repeated API calls + *
  • Offline development and CI/CD without API keys + *
  • Recorded data persists across test runs + *
+ */ +// VCR mode choices: +// - PLAYBACK: Uses pre-recorded cassettes only (requires recorded data, no API key needed) +// - PLAYBACK_OR_RECORD: Uses cassettes if available, records if not (needs API key for first run) +// - RECORD: Always records fresh data (always needs API key) +// This demo uses PLAYBACK since cassettes are pre-recorded and committed to the repo +@VCRTest(mode = VCRMode.PLAYBACK) +@DisplayName("LangChain4J VCR Demo") +class LangChain4JVCRDemoTest { + + // Annotate model fields with @VCRModel - VCR wraps them automatically! + // NOTE: Models must be initialized at field declaration time or in @BeforeAll, + // not in @BeforeEach, because VCR wrapping happens before @BeforeEach runs. + @VCRModel(modelName = "text-embedding-3-small") + private EmbeddingModel embeddingModel = createEmbeddingModel(); + + @VCRModel private ChatLanguageModel chatModel = createChatModel(); + + private static String getApiKey() { + String key = System.getenv("OPENAI_API_KEY"); + // In PLAYBACK mode, use a dummy key if none provided (VCR will use cached responses) + return (key == null || key.isEmpty()) ? "vcr-playback-mode" : key; + } + + private static EmbeddingModel createEmbeddingModel() { + return OpenAiEmbeddingModel.builder() + .apiKey(getApiKey()) + .modelName("text-embedding-3-small") + .build(); + } + + private static ChatLanguageModel createChatModel() { + return OpenAiChatModel.builder() + .apiKey(getApiKey()) + .modelName("gpt-4o-mini") + .temperature(0.0) + .build(); + } + + @Nested + @DisplayName("Embedding Model VCR Tests") + class EmbeddingModelTests { + + @Test + @DisplayName("should embed a single text about Redis") + void shouldEmbedSingleText() { + // Use the model - calls are recorded/replayed transparently + Response response = embeddingModel.embed("Redis is an in-memory data store"); + + assertNotNull(response); + assertNotNull(response.content()); + float[] vector = response.content().vector(); + assertNotNull(vector); + assertTrue(vector.length > 0, "Embedding should have dimensions"); + } + + @Test + @DisplayName("should embed text about vector search") + void shouldEmbedVectorSearchText() { + Response response = + embeddingModel.embed("Vector similarity search enables semantic retrieval"); + + assertNotNull(response); + float[] vector = response.content().vector(); + assertTrue(vector.length > 0); + } + + @Test + @DisplayName("should embed multiple related texts") + void shouldEmbedMultipleTexts() { + // Multiple calls - each gets its own cassette key + Response response1 = embeddingModel.embed("What is Redis?"); + Response response2 = embeddingModel.embed("Redis is a database"); + Response response3 = embeddingModel.embed("How does caching work?"); + + assertNotNull(response1.content()); + assertNotNull(response2.content()); + assertNotNull(response3.content()); + + // All embeddings should have the same dimensions + assertEquals(response1.content().vector().length, response2.content().vector().length); + assertEquals(response2.content().vector().length, response3.content().vector().length); + } + } + + @Nested + @DisplayName("Chat Model VCR Tests") + class ChatModelTests { + + @Test + @DisplayName("should answer a question about Redis") + void shouldAnswerRedisQuestion() { + // Use the chat model - calls are recorded/replayed transparently + String response = chatModel.generate("What is Redis in one sentence?"); + + assertNotNull(response); + assertFalse(response.isEmpty()); + } + + @Test + @DisplayName("should explain vector databases") + void shouldExplainVectorDatabases() { + String response = + chatModel.generate("Explain vector databases in two sentences for a developer."); + + assertNotNull(response); + assertTrue(response.length() > 20, "Response should be substantive"); + } + + @Test + @DisplayName("should provide code example") + void shouldProvideCodeExample() { + String response = chatModel.generate("Show a one-line Redis SET command example in Python."); + + assertNotNull(response); + } + } + + @Nested + @DisplayName("Combined RAG-style VCR Tests") + class CombinedTests { + + @Test + @DisplayName("should simulate RAG: embed query then generate answer") + void shouldSimulateRAG() { + // Step 1: Embed the user query (as you would to find relevant documents) + Response queryEmbedding = embeddingModel.embed("How do I use Redis for caching?"); + + assertNotNull(queryEmbedding.content()); + + // Step 2: Generate an answer (simulating after retrieval) + String answer = + chatModel.generate("Based on Redis documentation, explain caching in one sentence."); + + assertNotNull(answer); + assertFalse(answer.isEmpty()); + } + } +} diff --git a/demos/langchain4j-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.base.rdb b/demos/langchain4j-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.base.rdb new file mode 100644 index 0000000..e426f2a Binary files /dev/null and b/demos/langchain4j-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.base.rdb differ diff --git a/demos/langchain4j-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.incr.aof b/demos/langchain4j-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.incr.aof new file mode 100644 index 0000000..17f3f9b --- /dev/null +++ b/demos/langchain4j-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.incr.aof @@ -0,0 +1,1323 @@ +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$85 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T14:38:34.089299Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$76 +com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$93 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T14:38:37.570664Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$84 +com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldProvideCodeExample +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T14:38:39.198555Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$85 +com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +*8 +$4 +HSET +$97 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T14:38:41.226407Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$88 +com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T14:38:43.509065Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$89 +com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +*8 +$4 +HSET +$101 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T14:38:43.933326Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$92 +com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T14:38:44.893460Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$86 +com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$85 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T14:44:43.990472Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$93 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T14:44:46.403421Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T14:44:48.281753Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*8 +$4 +HSET +$97 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T14:44:50.209958Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T14:44:52.588937Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*8 +$4 +HSET +$101 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T14:44:54.398540Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T14:44:56.246056Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$85 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T14:52:14.813395Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$93 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T14:52:17.177657Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T14:52:19.098521Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*8 +$4 +HSET +$97 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T14:52:20.974724Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T14:52:23.337414Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*8 +$4 +HSET +$101 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T14:52:25.185987Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T14:52:27.016329Z +$5 +error +$306 +dev.ai4j.openai4j.OpenAiHttpException: { + "error": { + "message": "Incorrect API key provided: vcr-play*****mode. You can find your API key at https://platform.openai.com/account/api-keys.", + "type": "invalid_request_error", + "param": null, + "code": "invalid_api_key" + } +} + +$6 +status +$6 +FAILED +*2 +$6 +SELECT +$1 +0 +*4 +$8 +JSON.SET +$95 +vcr:embedding:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG:0001 +$1 +$ +$19384 +{"type":"embedding","testId":"com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG","model":"text-embedding-3-small","timestamp":1765638668113,"embedding":[-0.030835615,-0.021801744,0.050975125,-0.03334101,0.032281034,0.03483461,0.01936862,0.025632106,0.005348052,-0.04675932,-0.019994969,-0.0030865727,-0.015333491,-0.009015804,0.017670253,-0.035412777,-0.006329733,0.049120173,-0.017405258,0.02902884,0.03490688,-0.016562099,0.0072993683,0.02001906,-0.006552568,-0.041941255,-0.014972136,0.016574143,0.033726454,-0.06302029,-0.024861215,-0.046470236,-0.044302106,-0.009690333,-0.004236886,0.012015049,0.022584679,-0.014478285,0.023211028,0.03649684,0.004715681,-0.058539487,-0.0018941017,-0.048831087,0.006769381,0.012418563,-0.03644866,-0.037315913,-0.055263203,0.004020073,0.015321447,-0.027246157,-0.063502096,-0.004827099,0.02601755,-0.014719188,0.0059111635,-0.0043513146,-0.0013490581,0.024668492,0.022247415,0.00483011,-0.038183164,0.027607512,0.019549299,0.061430328,0.011533243,1.0539517E-4,-0.058828574,-0.010286569,-0.03126924,-0.0120572075,0.057479516,-0.06634475,0.012171636,-0.015357582,0.041435357,3.6436616E-4,0.01750162,-0.002279547,0.0037550793,-8.115428E-4,-0.019657705,0.007221075,0.011460972,-0.04309759,-0.017718434,0.01297264,0.006100875,-0.0041887052,-0.023500111,0.061960313,-0.035485048,0.006155078,-0.041772623,0.010822578,0.017200492,-0.006546546,0.0076667457,0.04220625,0.006558591,-0.034473255,-0.0076426556,-0.0010200747,0.003983937,-0.013647169,0.038351797,-0.0095698815,-0.03707501,0.0070524425,-0.057383154,-0.03420826,0.017284809,-0.059165835,0.0036436615,0.017995473,8.1003713E-4,-0.009593972,-0.031510144,-0.027824325,0.018513415,-0.0030007511,-0.014887821,-0.055600468,-0.06567022,-0.09477133,0.067019284,-0.057816777,0.042856686,-0.02457213,0.0550223,-0.00928682,0.010443156,-0.036255937,-0.0039447905,-1.9018182E-4,0.015249175,-3.195732E-4,-0.0145505555,0.0066609746,-0.008100372,-0.035894584,0.016321195,0.01994679,-9.6060167E-4,-0.019970879,-0.041242637,-0.032979652,-0.022897853,-9.952316E-4,-0.0062634842,0.02902884,-0.013069001,-0.0209104,-0.032449666,-0.019199988,0.025367111,-0.032305125,-0.01645369,-0.034449164,-0.021753563,-0.007016307,0.0060828067,-0.031871498,-0.013550808,-0.019260215,0.0051854425,-0.0030669994,-0.0037249664,6.0037605E-4,-0.0160562,-0.02223537,0.059695825,0.020199737,0.025343021,-0.0049806745,-0.030185176,0.0039146775,0.050252415,0.01775457,-0.02820977,-0.010997233,-0.008967623,0.042109888,0.015249175,0.011563356,0.0042609763,0.023765106,-0.040134482,0.022090828,-0.03717137,0.057094067,4.8293572E-4,0.027222067,0.05280599,0.0056552035,0.036087304,-0.021524705,-0.056756806,-0.030040635,-0.045458443,0.032473758,-0.023463976,0.002606272,0.008491839,-0.03714728,-0.0076245875,-0.042061705,-0.013297859,0.020296099,-0.046566598,0.020753814,-0.007480046,0.00923864,-0.033316918,-0.008034123,-0.0051884535,-0.0064742747,-0.036015034,0.022813538,-0.041507628,-0.0068476745,0.015321447,-0.028787937,0.020886311,0.02417464,-0.010822578,-0.0408331,0.027848415,0.054010507,0.012310156,-0.016128473,-0.017597983,-0.019284304,0.050975125,0.041868985,-0.013586943,-0.0042248406,0.034641888,-7.516934E-4,-0.020452686,-0.011442904,-0.06417663,-0.024246912,4.8481778E-4,-0.014899866,-0.018814543,-0.048638366,0.044880275,-0.012231862,-0.015598485,-0.02230764,0.019958833,-0.04801202,-0.0088953525,-0.025367111,0.025752557,-0.026210273,0.024018053,-4.023837E-4,-0.0179232,-0.0408331,0.03774954,-0.012466743,0.009864988,0.007323459,-0.052902352,-0.0380868,-0.036255937,-0.039917666,-0.023813285,-0.007263233,0.036183666,-0.036039125,0.059888545,0.015839389,0.020705635,-0.020934492,-0.012960594,0.028378403,-0.039821308,-0.10272114,-0.01712822,9.967372E-4,0.030956067,0.04765066,-1.1809905E-4,0.03177514,0.03664138,-0.021669246,-0.046542507,-0.07453547,-0.05198692,-0.01628506,0.0030323695,0.031799227,-0.03794226,0.0063779135,-0.0063718907,0.018212285,0.02113926,0.0281375,-0.021223575,0.019115672,0.0013573392,-0.0035623568,0.016164608,-0.034545526,0.068464704,0.025583925,-0.051649656,-0.012406517,-0.005170386,-0.011786191,-0.01187653,0.012659466,0.015441898,-0.021729473,0.031004248,-0.0326183,0.043218043,-0.055359565,0.022717176,-0.015490078,0.004044163,-0.035918675,-0.014369879,-0.045771617,-0.015116679,-0.018188195,0.02457213,-0.019537253,0.014996227,0.05121603,0.045097087,-0.031461965,0.011990959,-0.027776144,-0.018344782,-0.01556235,0.0032220809,0.041917164,-0.021295847,7.882053E-4,0.0021063976,0.037773628,-0.030185176,0.04753021,-0.028595215,-0.00758243,-0.0021018807,0.0062153037,-0.018031608,-0.048132468,0.03196786,0.015694845,-0.0014002501,-0.021861969,-0.0667302,-0.02669208,-0.046349782,0.018633867,0.01483964,0.010840646,0.023283299,0.0034599728,0.0109671205,-0.017116176,0.023741014,0.021572886,0.029534739,0.014225337,-0.06138215,-0.020211782,-0.044398468,0.046879772,0.012659466,0.021922195,0.03640048,0.025174389,0.010184185,-0.0043874504,0.005242657,-0.022946034,0.037388183,-0.038929965,0.033895086,0.017706389,-0.061912134,-0.027920686,0.0018278534,0.008148552,0.014020569,0.0051944763,0.014598737,0.017188447,0.0014717682,-0.0281375,0.0052306117,-7.791714E-4,0.0017314921,-0.008082304,-0.03640048,-0.050059695,0.020175647,9.4253395E-4,-0.007474023,-0.026571628,-0.02417464,-0.05333598,0.0066368845,0.007618565,0.021789698,0.010388953,-0.025126208,-0.0025626083,0.023981918,0.0145505555,-0.0117982365,-0.013008775,0.013755575,0.0025460462,-0.06412844,-0.012502878,-0.0028802995,0.039917666,0.014731233,-0.0028802995,-0.020874266,0.007732994,-0.019175898,-0.008262982,0.011635627,0.0370991,-0.003959847,0.03199195,0.0066910875,0.011358588,-0.00854002,-0.032281034,-0.012635375,0.020561092,-0.06456207,0.044591192,-0.060563076,0.009599994,0.021344027,-0.02342784,0.0055287294,0.0468075,-0.0060015023,-0.035292324,-0.032305125,0.01936862,-0.0112802945,-0.01854955,0.011912665,0.016236879,6.9447886E-4,-0.013611034,-0.04088128,0.023114666,-0.012864233,0.012502878,0.09125415,-0.0013053945,0.023644654,0.05646772,0.02746297,-0.036665473,0.008401501,0.043988932,-0.011454949,0.041387178,-0.015755072,-0.0052276,-0.012936505,0.0025219559,0.0023789196,-0.008052191,0.0358464,0.0022675018,0.008720698,-0.03049835,0.017019814,0.075547256,0.027848415,-0.019922698,0.018609775,0.015453943,-0.01710413,0.027197976,0.01752571,-0.01110564,0.0018278534,-0.034039628,0.015767118,-0.00333651,0.016646415,-0.02587301,0.026330724,0.017224582,-0.0062454166,0.072415516,-0.024487814,0.03341328,0.020199737,0.010196229,0.044904366,-0.031100608,0.025005756,0.0066188164,-0.032521937,0.016513918,-0.02596937,-0.011129729,-0.031678777,0.02293399,-0.013081046,-0.011045414,-2.8739005E-4,0.027294338,-0.037532724,0.006745291,0.023945782,0.014490331,-0.021693338,-0.01302082,-0.00461932,-0.0039116666,0.0054986165,-0.017561847,-0.010015552,0.029847912,-0.009148301,-0.030088816,-0.067934714,0.038327705,-0.0067272233,7.1066455E-4,0.0010531988,0.0060255923,-0.015875524,-0.020850176,-0.013177408,0.0046253423,0.013924208,-0.025680285,0.012346291,-0.011978914,-0.0064200712,-0.018489324,-0.006570636,-0.009419316,0.05280599,-0.018826589,0.008184688,-0.011256204,-0.004251942,-0.0028531977,0.01897113,0.015092588,9.447924E-4,0.022946034,-0.0128401425,-0.051553294,0.013731485,-0.04011039,-0.06008127,-0.025656195,-0.042158067,0.0115392655,-0.029342014,-0.011159843,-0.013117182,0.07159644,-0.04300123,-0.017381169,-0.015538259,0.020404505,0.020898357,-0.013225588,0.01735708,-0.002428606,-0.015851434,-0.019513162,0.0088953525,-0.03206422,0.027655693,0.023620563,0.0023879535,-3.6758917E-7,-0.0037219552,-0.0150684975,0.0039026325,0.035990946,0.042712145,0.035364598,0.023548292,0.03047426,-0.009744536,0.0055708876,-0.02333148,-0.04461528,0.0077570844,0.030883797,-0.03363009,0.01411693,0.008823081,-0.04541026,-0.02340375,-0.03134151,-0.041965347,-0.07559544,-0.004583184,-0.013093092,0.015200995,-0.014658962,-0.019212034,0.0020808019,0.034449164,0.008046168,-0.004092344,-0.0040772874,0.018164104,0.008654449,-0.01750162,-0.033678275,-0.038833603,-0.003273273,0.006944036,0.013960343,-0.019826338,-0.030064724,0.016489826,0.0030007511,0.03334101,0.023945782,0.017489575,0.0370991,0.06475479,-0.0378459,0.010039642,0.0037129212,0.04981879,6.8619783E-4,0.017670253,0.024981666,0.030040635,-0.019416802,-0.0393395,-0.0055708876,0.033702362,1.8444155E-4,0.028017048,0.0020130477,0.026908893,-0.011816304,-0.025921188,0.0065947264,-0.004974652,-0.01521304,-0.04377212,0.03290738,-0.013333995,-0.0055287294,0.0040321182,0.014056704,0.0045651165,0.010328727,-0.02830613,-0.018742273,0.0595031,0.025029847,-0.048614275,0.027101615,0.043675758,-0.012033117,-0.022620814,0.009889078,-0.0076667457,0.007751062,-0.030666983,-0.053817786,0.008040146,-0.018115925,-0.008835127,0.016116427,0.0023141769,0.012502878,-0.018308647,0.0013550807,0.0033184423,0.026282543,0.024909396,0.021994466,0.03204013,0.0011224586,0.0020823074,0.014960092,-0.0011141775,0.028498854,0.010533494,6.910912E-4,0.02074177,-0.02384942,-0.030594712,-0.03630412,0.005516684,3.792344E-5,-0.0026830598,-0.036183666,-0.004902381,0.01710413,-0.04018266,-5.781678E-4,-0.0076848133,0.037701357,0.018802498,-0.024788944,0.01745344,0.0321124,-0.023897601,-0.032377396,-0.008184688,0.02669208,-0.015767118,-0.015526214,0.008371388,0.026981164,0.0060286038,0.0333651,0.024584176,0.04914426,-0.016260969,-0.012671511,-0.039194956,0.0028110398,-0.0029706382,-0.023957828,-0.02268104,-0.010599743,-0.040712647,0.010129982,0.013490582,-0.0096722655,0.05189056,0.009804762,0.0033304873,-0.009624084,-0.026282543,0.008004011,0.023391705,0.0128883235,0.0505415,-0.048614275,0.0125269685,0.022066737,-0.016646415,0.02674026,-0.034400985,0.032377396,-0.05126421,0.024331227,-0.023463976,0.023885557,-0.038327705,-0.03577413,0.0015688824,0.008359343,-0.009009781,-0.032160584,-0.010467246,0.02666799,0.0045861956,-0.026812531,-0.012051185,0.03555732,0.0026860712,0.03204013,0.024764853,-0.03943586,-0.0025686307,-0.018031608,-0.025005756,-0.040399473,-0.030570623,-0.0017631106,0.004709658,-0.014586692,-0.030691074,-0.04921653,0.038110893,-0.0108165555,-0.003998994,0.032256946,-0.012231862,-0.050011512,-0.018525459,0.027944777,-0.042037617,-0.018826589,0.0104552,-0.035292324,-0.033051927,0.015706891,-0.007907649,-0.026402995,-0.027342519,-0.02587301,-0.009148301,-0.009533746,0.0012316179,0.009961349,0.01668255,-0.0017450429,0.043218043,-0.018236376,0.01929635,-8.2961057E-4,-0.02340375,-0.018368872,5.303635E-4,-5.7139236E-4,-0.013309904,-0.028113408,-0.051842377,-0.011894598,-0.002253951,-0.03579822,0.0020431606,-0.019681795,0.018742273,0.012105388,0.009599994,-0.002543035,0.016477782,-0.022391956,-0.006311665,0.00480602,0.0027387687,0.004495857,0.0010855702,-0.005835881,0.025005756,-0.040592197,-0.012852188,8.4316137E-4,0.031654686,-0.021645157,0.033678275,0.020247918,0.020404505,0.024246912,0.019344531,0.0037550793,-0.0070825554,-0.011557333,0.015393717,0.012791962,-0.0149119105,0.04095355,0.009455453,-0.0028456696,0.03471416,0.015598485,0.014297607,0.004598241,0.028836118,-0.020175647,-2.9887058E-4,0.01168983,-0.019163853,0.0017480542,0.017597983,0.008829104,0.018007517,0.027125705,0.0016306138,-0.022367867,0.023909647,0.021874014,-0.015321447,-0.03866497,0.0019031357,0.022921944,0.008811036,-0.005257713,0.0028050172,0.028450673,0.018392963,0.032931473,0.0026499357,-0.00758243,0.01556235,-0.009202504,0.01635733,0.015357582,-0.022464227,0.038881782,4.7390186E-4,-0.0016833114,-0.0040471745,-0.004309157,1.00031306E-4,-0.00816662,0.032425575,-0.01638142,-0.00966022,-0.0046915906,0.0128883235,-0.01936862,0.03420826,0.01257515,0.01374353,-0.018838633,-0.005522707,0.020296099,-0.014815549,-0.017381169,-0.056804985,-0.01820024,-0.0054022553,0.053046893,-0.012551059,0.030209268,-0.004809031,-0.0062454166,0.08633972,0.018212285,0.07415002,-0.0147553235,-0.031486053,0.0011096606,0.0053540748,-0.015225085,-0.0064441618,-0.021898106,0.051794197,-0.0071066455,-0.014586692,-0.03257012,0.026475266,-0.009094098,0.004050186,-0.0076547004,0.009401249,-0.0042218296,-0.043555308,-0.017236628,-0.003042909,-0.019187944,0.052131463,-0.0011028851,0.0046825567,0.02828204,-0.0071427813,-0.0070223296,-0.0039749034,0.004242908,0.035388686,0.041941255,-0.03945995,0.002424089,-0.017067995,-0.01078042,-0.009630107,0.05295053,0.0011269755,-0.030763345,0.033702362,0.010641901,0.014562601,0.01516486,0.030185176,-0.016718686,-0.012189704,-0.010539517,0.016538007,-0.018621821,0.027342519,0.02524666,0.011888575,0.006251439,-0.013213543,0.0024451679,-0.013839891,0.0020281042,-0.01591166,-0.014695098,0.0016441647,0.001201505,-0.0039116666,0.0012835626,-0.011370633,-0.017007768,-0.009901123,-0.018706137,-0.009871011,-0.0030700108,-0.023186937,0.024258956,-0.019248169,-0.008666494,-0.010840646,0.05405869,0.0069079003,-0.029052932,3.7114156E-4,0.010629856,0.03649684,0.014731233,0.04015857,-0.06080398,-0.042760327,0.01864591,-0.018718181,-0.0065646134,-0.022885809,-0.014803505,0.044952545,-0.032377396,-0.041170366,0.0051252167,-0.022271505,0.013755575,0.027776144,2.0175647E-4,0.0010449179,0.026113912,-0.019705886,0.0038514405,-0.0058388924,-0.0056160567,-0.023837376,0.058105864,-0.002916435,0.024054188,-0.0018383929,-0.013839891,0.0065345005,-0.04822883,-0.008154575,-0.024957577,0.02158493,0.014779414,-3.2653683E-4,0.009919191,0.027800234,0.02051291,-0.01155131,0.007708904,0.018296601,-0.03572595,0.029149292,0.010431111,-0.017465485,-1.5479916E-4,-0.0057485537,-0.001647176,0.009425339,-0.018983176,-0.013635124,-0.008100372,-0.002954076,-0.018019563,-0.010955075,1.2374522E-4,0.018886814,0.03126924,0.014406014,-0.0021304882,0.03346146,-0.03856861,-0.031630594,-0.0036376389,-0.051071487,-0.028330222,0.010792465,-0.010063733,0.00246173,0.045458443,-0.009172391,0.009491588,0.0062454166,0.063742995,0.011039391,7.351313E-4,-0.0031558324,-0.035918675,0.024788944,-0.025535744,0.0016803001,-0.038303617,-0.0049595954,0.06644111,0.012804007,0.0012662477,0.020621318,0.0026770374,0.01962157,0.01208732,-0.0034057696,-0.019886563,-0.0088953525,0.013129227,-0.012286065,0.015393717,-0.01742935,0.024451679,0.014538511,-0.029245654,-0.008871262,0.025656195,0.019199988,-0.04145945,0.010051688,-0.008630359,0.02337966,-0.020862222,-0.008148552,0.0066067716,-0.019175898,0.0077932198,-0.027101615,-0.007028352,0.035677772,0.0023698856,0.0104552,-0.014044659,0.0041736485,-0.026451176,0.039556313,0.010575652,0.04396484,0.02036837,0.015441898,0.019561343,-0.036665473,0.028041137,0.009118188,0.006582681,0.014128976,-0.0054835603,0.039773125,0.017935246,-0.014454194,0.05704589,-9.7490534E-5,-0.020729724,-0.018802498,-0.009136256,-0.0029345027,-0.035460956,0.041049913,-0.017200492,-0.018417053,-0.0050318665,-0.022921944,-0.018115925,-0.011984936,-0.019729976,0.0065646134,-0.03543687,0.044880275,-0.022343775,-0.029342014,0.0065646134,-0.036689565,-0.021079034,-4.821829E-4,-0.003369634,0.0023186938,0.0119066425,-0.0012602251,0.0030127962,0.038424067,-0.043458946,0.002627351,-0.019958833,0.021018809,0.010394975,-0.008383433,0.005378165,0.021319937,-0.0077871974,-0.0065345005,0.019982925,0.012394472,-0.015622576,0.0064200712,-0.020645408,0.03712319,-0.029149292,-0.015116679,-0.0071066455,0.036785927,0.008503884,0.012195727,0.010214297,0.0043573375,0.0028381413,0.0017856953,0.028547034,0.026258454,0.032377396,0.0026409018,-0.0020762847,-0.015044408,-0.033846907,-0.0013061473,-0.031052427,-0.010262478,-0.033606004,0.03483461,-0.0022419058,-0.027005253,-0.014381924,0.018802498,-0.03269057,-0.006293597,-0.013839891,0.05555229,0.020657454,0.0281375,0.02900475,0.010961098,0.005435379,0.048710637,-0.017706389,0.0071969843,-0.0033967358,0.0062755295,0.067404725,0.03343737,0.047072493,0.07679995,-0.032160584,0.024909396,0.0051462958,-0.006293597,-0.008046168,0.0333651,-0.010250433,0.007347549,-0.01523713,0.015598485,-0.018140014,-0.0027538252,-0.018814543,0.005435379,-0.02303035,0.040134482,-0.0022163098,-0.004035129,-0.033846907,0.020296099,0.010364862,-0.032594208,-0.0049204486,-0.011780169,-0.06138215,-0.013249679,-0.022066737,-0.014406014,0.020958582,-0.018356828,-0.01927226,0.027968867,-4.1518168E-4,0.011497107,-0.0010690081,0.019260215,0.021319937,-0.010882804,-0.012051185,-0.020729724,-8.3638594E-4,-0.04972243,0.0108165555,0.020488821,0.026716169,-0.01962157,0.001264742,-0.004333247,0.04088128,-0.05569683,8.205767E-4,0.021307891,0.008805013,-0.009130233,0.04921653,0.012852188,-0.012719692,-0.009377159,0.037701357,-0.0060557052,0.0110755265,-0.0029104124,-0.008311162,0.010148049,0.01775457,-0.013611034,-3.628605E-4,-0.016393466,0.018802498,-0.0020025081,-0.004766873,-0.013141272,-0.0031648665,0.014899866,0.03406372,0.013081046,0.045241628,-0.03355782,-0.035412777,-0.028908389,0.041290816,0.033148285,0.0063899583,-0.021681292,0.009232617,0.0056913393,-0.023235118,-0.0015395223,-0.011834372,-1.825595E-4,0.0051131714,0.015839389,-0.0059744003,0.016369376,-0.022572635,-0.023235118,0.026402995,-0.0014845663,0.022343775,0.020031104,0.0073535717,0.03786999,0.020898357,-0.01115382,-0.022416048,-0.018272512,0.011208024,0.048686545,-0.020826085,0.0057274746,-0.03266648,-0.027583422,-0.013297859,-0.019477027,0.0128401425,0.011870507,0.021934241,0.01603211,0.014815549,-0.0067934715,0.016393466,0.03702683,-0.007775152,0.019260215,0.03218467,0.018453188,0.0020175646,-0.0022298608,0.019874517,0.0070825554,-0.02895657,0.00797992,0.0060015023,-0.035292324,-0.0067513133,0.020633362,0.015333491,-0.025174389,0.009485565,0.027149796,0.013683304,-0.02221128,0.02755933,-0.023391705,0.010081801,0.0059774118,-0.015321447,0.0032582164,0.0070765326,0.03420826,-0.014430105,-0.02820977,0.010714171,-0.0014695098,-0.027655693,-0.0040471745,0.008196733,-0.0031979906,-0.012346291,0.03736409,-0.017465485,0.036689565,-0.013791711,-0.015225085,-0.04227852,-0.027438879,0.03782181,-0.006974149,0.004074276,0.004324213,0.0358464,0.004809031,0.010388953,-4.1028834E-4,-0.00283513,-0.03628003,-0.010623833,0.0076426556,-0.026330724,0.0147553235,-0.024716673,0.037436362,0.030594712,-0.023801241,-0.014189201,-0.029968364,0.020151556,0.012755827,0.03656911,-0.036785927,-0.027053434,-0.009895101,-0.02828204,-0.012490833,-0.006552568,0.024487814,0.0044115405,-0.013863982,-0.05102331,-0.008419569,0.049312893,0.014044659,-0.018344782,0.035171874,0.0072150524,-0.01187653,-0.033027835,-0.0053631086,-0.036520932,-0.001251944,7.838766E-5,0.035894584,0.02305444,-0.004345292,0.016152563,-0.0052998713,-0.007618565,-0.0014446666,-0.04902381,0.018115925,0.0171764,0.010533494,0.015839389,-0.006582681,0.006281552,0.00264843,0.005013799,0.032859202,-0.038183164,0.051649656,0.033726454,-0.013153317,-0.014369879,0.014538511,-0.009401249,-0.006558591,0.020524956,0.01203914,0.035412777,0.024861215,0.036978647,-0.0082208235,-0.009262729,-0.013827846,0.014369879,-0.029486557,0.028763847,0.013803756,-0.0088953525,0.0055287294,-0.0048873248,-0.017369123,0.004516936,0.004860223,0.031100608,-0.007949807,0.016321195,0.019260215,-0.00190163,-0.0076004975,0.017200492,0.035027333,-0.00480602,0.01864591,-0.010280546,0.0036677518,0.02895657,0.0052276,-0.02604164,-0.022982169,0.0049084034,-0.02746297,0.0069560814,-0.017862976,-0.025415292,-0.012924459,-0.024981666,-0.0321124,-0.01411693,0.027607512,0.010635878,0.014598737,0.0106057655,-0.004146547,-0.028619306,8.687573E-4,-0.02004315,0.002050689,-0.010732239,-0.030546531,0.02967928,-0.051456932,-0.0112260915,0.006281552,0.0160562,0.011262227,-0.012406517,-0.024584176,0.021127215]} +*4 +$8 +JSON.SET +$90 +vcr:chat:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG:0001 +$1 +$ +$260 +{"response":"Caching in Redis involves storing frequently accessed data in memory to improve retrieval speed and reduce the load on underlying data sources.","testId":"com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG","type":"chat"} +*8 +$4 +HSET +$85 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T15:11:09.599099Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*4 +$8 +JSON.SET +$98 +vcr:chat:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldProvideCodeExample:0001 +$1 +$ +$476 +{"response":"Certainly! Here’s a one-line example of a Redis `SET` command in Python using the `redis` library:\n\n```python\nimport redis; r \u003d redis.Redis(); r.set(\u0027my_key\u0027, \u0027my_value\u0027)\n```\n\nMake sure you have the `redis` library installed. You can install it using pip if you haven\u0027t done so:\n\n```bash\npip install redis\n```","testId":"com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldProvideCodeExample","type":"chat"} +*8 +$4 +HSET +$93 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T15:11:12.148359Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*4 +$8 +JSON.SET +$99 +vcr:chat:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion:0001 +$1 +$ +$295 +{"response":"Redis is an open-source, in-memory data structure store used as a database, cache, and message broker, known for its high performance and support for various data types.","testId":"com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion","type":"chat"} +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T15:11:13.567104Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*4 +$8 +JSON.SET +$102 +vcr:chat:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases:0001 +$1 +$ +$538 +{"response":"Vector databases are specialized databases designed to store, index, and query high-dimensional vectors, which are often used to represent complex data like text, images, or audio in a format suitable for machine learning applications. They enable efficient similarity searches and nearest neighbor queries, allowing developers to quickly retrieve relevant data based on vector embeddings generated by models.","testId":"com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases","type":"chat"} +*8 +$4 +HSET +$97 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T15:11:15.929864Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*4 +$8 +JSON.SET +$108 +vcr:embedding:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts:0001 +$1 +$ +$19352 +{"type":"embedding","testId":"com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts","model":"text-embedding-3-small","timestamp":1765638676858,"embedding":[-0.010563402,-0.0010704309,0.013926964,-0.017211739,0.035417397,0.017151136,0.012260335,0.047441375,-0.02530853,-0.041938465,-0.0274903,0.015272389,-0.011636105,-0.021975271,0.04266572,-0.0056938133,-0.0037090087,8.7952596E-4,-0.019648049,0.028726636,0.008733139,-0.007981641,0.012139125,0.010011899,0.0023544934,-0.0034181061,-0.045308087,-0.015587534,0.019866226,-0.07548924,0.01335728,-0.04753834,-0.015854195,0.03866581,0.024484307,0.031247793,0.009611908,-0.034859832,-0.0023378269,0.047950454,-0.016848112,-0.031441726,-0.008411935,-0.027320607,-0.0023863108,-0.0101634115,-0.03711433,-0.028096346,-0.014690584,0.01813293,-0.02892057,-0.013696666,-0.04339298,0.016072372,0.04615655,-0.0033059875,0.008781624,0.03626586,0.01219367,0.027853929,0.019829864,0.0034332573,-0.029453892,0.034011368,0.0062968303,0.043344494,0.0021787395,0.039441552,-0.04186574,0.002104499,-0.02273889,-0.014666341,-0.039902147,-0.06288345,0.034859832,-0.011369445,0.014654221,0.0068968167,0.0025635795,-0.017890513,-0.022629801,0.015526929,-0.011848222,-0.008630112,0.02940541,-0.041550595,-0.014314834,-0.019926831,0.030253874,0.024872176,-0.028314523,-0.00732105,-0.012666386,0.028508458,-0.055271503,-0.03660525,0.028847845,0.011460352,-0.0062725884,0.05396244,-0.0060574417,-0.03696888,-0.030932648,-0.045938376,-0.04365964,-0.034520447,0.031247793,-0.033720464,-0.018581407,0.02904178,-0.03573254,-0.04101727,0.018035963,-0.059392624,-0.003224171,0.01182398,0.01028462,-0.022254052,-0.016629934,-0.04535657,0.029138748,0.018945035,-0.02182982,-0.01952684,-0.066859126,-0.05405941,0.039708212,-0.023296453,0.016011767,-0.022496471,0.069428764,0.02584185,-0.002209042,-0.03403561,-0.0039029438,0.025647916,-0.030035699,-4.6135343E-4,-0.039538518,-0.042326335,-0.037332505,-0.03245989,-0.009030103,-0.0039726393,0.01618146,-0.01323607,-0.02659335,-0.023841895,-0.007902855,0.0048392867,0.007799827,0.020278338,0.0017651124,-0.006490765,-0.049647383,-0.015211784,0.033841673,-0.016617814,-0.0056544202,-0.044362653,-0.01335728,-0.027538784,-0.018799582,-0.039902147,-0.019793501,-0.02156316,0.014726946,-0.023357058,-0.025866093,0.002298434,0.012096701,0.002813574,0.060265332,0.04254451,-0.01156944,-0.0050695846,-0.02105408,0.04339298,0.040799096,-0.0011977007,0.006642277,-0.015066333,-0.015260268,0.035126496,0.022896463,-0.017902633,-0.015611775,0.010072504,-0.02363584,0.012308818,-0.038714293,0.09638574,-0.016860232,0.020254096,0.048604984,0.0054513942,0.027320607,0.0032090198,-0.0585684,-0.03289624,-0.047562584,0.031659905,-0.041720286,-0.009927053,0.01207246,-0.009417973,-0.0104300715,-0.040290017,-0.0138057545,0.013211829,0.017842028,-0.0034332573,0.004227179,-0.011890646,-0.023611598,-0.0013916359,0.017684456,0.012381543,-0.04009608,0.02363584,-0.041696046,0.009490699,0.032653823,-0.0043635396,0.01785415,0.015745105,0.025647916,-0.031781115,0.005448364,0.015102696,-0.0027559996,-0.023284333,-0.017575368,-0.03403561,0.017672336,0.031369,-0.03905368,0.0119815525,0.04765955,-0.0023287362,-0.027514542,-0.012896684,-0.026375173,0.0033999246,0.035296187,0.0022651013,-0.0010924,0.010090685,0.03301745,0.009593727,0.028096346,-0.010357346,0.028799362,-0.07359837,0.007387715,-0.034593172,6.8275E-5,-0.01849656,0.03364774,0.02018137,-0.04252027,-0.052992765,0.020981353,-0.0063392534,0.008593749,0.0074180174,-0.0068968167,-0.018545043,-0.027684236,-0.022508591,-0.027296364,-0.039417308,0.031950805,0.005227157,0.02326009,0.006490765,0.01606025,-0.028508458,-0.031296276,0.024205524,-0.008266483,-0.05779266,-0.008799805,0.02070257,0.0130663775,0.061089557,0.031078098,0.021914667,0.042859655,-0.0036635553,-0.06268952,-0.08639809,-0.033865917,0.01181186,-0.0024938842,-0.020738935,-0.019757139,0.039102163,0.0033938643,0.016920837,0.07413169,0.048750434,-0.024847934,0.0063937977,2.3294939E-4,-0.006702882,0.003996881,-0.043368734,0.031805355,-0.012993651,-0.015199663,0.012472451,-0.019817743,-0.0011022483,-0.023054035,0.01734507,-0.016520847,-0.010830063,0.017369311,-0.04741713,0.031635664,-0.05265338,0.054156374,0.0053241244,-0.02042379,-0.01787839,-0.025769126,-0.04649594,1.3683409E-4,-0.013842118,0.034617417,-0.016775386,0.04300511,0.031781115,0.04312632,-0.08145274,-0.04113848,-0.0027302424,-0.002425704,-0.012327,0.07258021,0.021769214,-0.033575013,-0.021151047,0.031659905,0.023102518,-0.031562936,0.02352675,0.0029696312,0.02865391,0.021344982,-0.038350664,-0.020969233,-0.079367936,0.01104824,-0.0028938754,0.023720687,0.0036635553,-0.045817167,-0.004357479,-0.060556233,0.02081166,-0.013114861,0.0042059673,0.010484616,-0.025357014,-0.019757139,-0.0012234577,-0.0032484129,-0.013660303,-0.0039241556,0.035659816,-0.07078631,-0.033332594,-0.0036847668,0.040386982,0.012145186,0.02916299,0.012369423,0.02401159,0.012423967,0.05061706,-0.001015129,-0.0063937977,0.034641657,-0.0020469243,0.013781513,0.020605603,-0.057986595,-3.7139328E-4,-0.0028984207,-0.016981442,0.02632669,0.0128482,-0.010490676,-0.011708831,-0.054932117,-0.040653646,0.022132844,0.030350843,-7.4278656E-4,-0.019745016,-0.04700502,-0.020545,-0.027029704,0.022363141,0.012139125,-0.016096612,0.003402955,-0.05958656,0.017126894,0.005018071,0.030665986,0.016714782,-0.018411713,-0.017551126,0.022957068,-0.024617637,-0.02016925,0.015769348,0.028799362,-0.0065816725,-0.089694984,-0.01452089,0.008805865,0.09241007,0.04210816,-0.015454203,-0.013769392,0.002787817,0.021405587,-0.0010514918,0.041938465,0.027611509,0.036435556,0.047877725,0.006090774,0.0027317577,-0.017126894,-0.016363274,-0.0021151046,0.027199397,-0.032653823,0.045768682,-0.08227696,0.0150905745,2.992358E-4,0.028823603,0.039223373,0.06462887,0.020375306,-0.025453981,-0.021163167,0.004445356,-0.01604813,-0.049404968,0.029817522,0.02453279,0.0076846783,-0.004208998,-0.04727168,0.016363274,-0.010817942,0.039732452,0.08004671,-0.042083915,0.020351063,0.05192612,0.005896839,-0.0064604627,-0.022157084,0.058132045,-0.0010098261,0.06254407,-0.021514675,0.035635576,0.015539049,0.01873898,0.001233306,0.015381478,-0.008411935,-0.0059574437,-0.001432544,-0.044411138,0.023587355,-0.0028196345,-0.018545043,0.044095993,-0.005845325,0.025647916,0.004087788,0.016726902,0.007896795,-0.011866404,-0.0014810278,-0.008557386,-0.0017817786,-0.04741713,1.2442906E-4,-0.046423215,0.031053858,0.017320829,-0.0467626,0.07345292,-0.019963194,0.019284422,0.005739267,0.006266528,0.03711433,-0.04765955,0.027175155,0.027053947,-0.01873898,0.0041968767,-0.004390812,-0.005493818,-0.0073089288,-0.001399969,0.0015954192,0.005372608,0.021466192,0.021466192,-0.030084182,0.018278383,0.019926831,0.023854017,-0.045647472,0.017963238,-3.8465057E-4,0.01220579,-0.021090442,0.008484661,0.017817788,0.050180707,-0.028484218,-0.030253874,-0.046956535,0.015720863,-0.0010605826,-0.0046756538,0.0056301784,0.029575102,-0.033478048,-0.004378691,-0.016739024,0.03187808,0.015878435,-0.037453715,0.0101634115,-0.003827188,0.034981042,-0.009545243,0.03236292,-0.019175332,0.080628514,-0.019175332,0.0011787617,-0.007569529,0.035684057,0.019890469,1.1524366E-4,0.02659335,0.0037544623,0.021466192,-0.019805621,-0.06089562,-4.5264149E-4,-0.002465097,-0.03827794,-0.044823248,-0.024750967,-0.009896751,-0.020351063,0.0033878037,-0.0071877195,0.070495404,-0.015490565,-0.03650828,0.0074968035,0.0102300765,0.0037817343,-0.013005773,0.011054301,-0.019211695,-0.026641835,0.005121099,0.0068786354,-0.032023534,0.03650828,0.03837491,0.029066022,0.02952662,0.00963009,-6.6333756E-5,-0.0026075179,0.027708476,0.04150211,0.023320695,-0.02130862,0.04353843,-0.01810869,0.020920748,-0.020993475,-0.05357457,0.017139014,0.008484661,-0.0053241244,-0.022605559,-0.012557298,-0.028120589,-0.029623587,-0.008830108,-0.0023363119,-0.05328367,-0.020411668,0.012642144,-0.01438756,-0.020896507,-0.0040544556,-3.638177E-4,-0.03558709,-0.03313866,0.001161338,-0.032290194,-0.0031665964,0.002425704,-0.034302272,-0.032702304,-0.030302359,-0.015308752,0.028605428,-0.009557364,-0.015369357,-0.006260467,-0.0058635063,0.019345026,0.010848245,0.0075755897,0.023660082,0.02438734,0.0570654,0.008854349,-0.016399637,-0.0034574992,0.04225361,-0.0023605537,0.0066240956,1.9507144E-4,0.018557165,-0.0015469354,-0.01349061,-0.041453626,0.049792837,-0.008496782,0.018448075,0.019902589,0.018690495,0.00899374,0.008757382,0.0031817476,-0.0018545043,-0.0044514164,-0.024375217,0.029817522,-0.05444728,0.0043211165,0.0035302248,0.011902766,0.02787817,0.022266174,-0.018811705,-0.02698122,0.043756608,0.031272035,-0.020787418,0.009617968,0.040193047,-0.02312676,-0.002518126,-0.01759961,0.027417574,-0.015575413,-0.030787196,-0.020726813,-0.007181659,-0.0060574417,-0.013987569,0.023320695,0.021041958,-0.014702705,-0.009308885,0.0023211606,0.012787595,0.019490477,0.03839915,0.0010749762,0.051974606,0.029308442,0.018787462,-8.424056E-4,7.662709E-4,0.028629668,-0.03762341,-6.348344E-4,2.6552443E-4,-0.0044302046,0.008236181,-0.045720197,-0.008442237,-0.009702816,-0.0012287607,-0.016617814,0.0040665767,0.045647472,-0.009836146,-0.004415054,0.0149572445,0.03764765,-0.0074968035,-0.010114928,-0.02082378,0.019829864,-0.035005286,-0.045211118,-1.14391405E-4,0.013320917,-0.009181615,0.0056059365,-0.010327044,0.035102252,0.025938818,6.7309116E-4,0.040023357,0.021914667,0.030859923,-0.008733139,-0.021296497,0.02056924,0.0032847757,0.0070422683,-0.011223993,-0.0056089666,-0.0149572445,-0.00732105,-0.021357102,-0.02940541,0.03340532,0.03810825,0.005457455,-0.020254096,-0.038883988,0.0074968035,-0.0064544026,0.042447545,0.023151003,-0.021126805,0.018981397,0.01117551,-0.01091491,0.019648049,-0.03561133,0.034738623,-0.03272655,0.034617417,-0.0038908229,0.0026075179,-0.028363008,-0.041841496,-0.010593705,0.01028462,0.034326512,-0.007896795,-0.010327044,0.01642388,-0.013308796,0.004830196,-0.0019984406,0.023199486,-0.015999645,0.021866182,-0.006696821,-0.04324753,0.03110234,-0.027805444,-0.019078365,-0.028605428,-0.008751322,-0.03226595,-0.038738534,-0.0046332306,-0.005205945,-0.02362372,0.0339144,-0.03071447,-0.024605514,0.012581539,-0.022957068,-0.01349061,0.020593483,0.012017916,0.010520979,-0.005808962,0.025769126,0.019587444,-0.010242198,0.003275685,-0.022654044,-0.01824202,-0.029623587,-0.030108424,0.006515007,0.008763442,0.04831408,0.016945079,0.021611642,0.0035241644,0.0029862975,-0.0045786863,0.0042423303,0.011545199,0.007363473,-0.022823736,0.004327177,0.003687797,0.01616934,0.004063546,-0.027393332,0.004857468,-0.02145407,-0.0737923,-0.0055453316,0.0019151091,-0.0059150206,0.012242153,-0.0136724245,0.0077513433,0.011757315,-0.014739067,-0.047829244,-0.010551281,-0.025429739,0.015102696,-0.0022969188,1.5047015E-4,0.011211873,-0.03236292,0.02557519,0.0049786777,0.049647383,-0.03272655,-6.510272E-5,0.011484594,0.006157439,-0.007078631,0.036435556,-0.007854371,-0.014908761,0.0020575302,0.008624052,0.0057968413,-0.024447944,0.041962706,0.012678507,-0.008145274,0.020460153,0.0010795215,0.010933091,0.010502798,0.03340532,-0.01579359,0.0042029372,0.012520934,-0.008327088,0.012520934,0.018411713,0.054738183,0.017708698,0.04109,-0.0064604627,-0.025647916,7.181659E-4,0.007157417,0.0044089933,-0.04329601,0.019381389,0.059780493,0.03481135,0.0073028686,-0.0013348189,0.038859744,0.00911495,0.044823248,0.0136724245,-0.03083568,0.0043483884,-0.0058816876,0.012775474,0.027563026,-0.01233306,0.03095689,0.019490477,-0.03621738,-0.022254052,-0.016896596,0.039732452,-1.1827389E-4,0.029890247,-0.028072106,-0.004445356,0.0023938864,0.002952965,0.022714648,0.013090619,-0.0104300715,0.016411757,-0.039562758,-0.008218,-0.028605428,-0.037962794,-0.010739156,-0.014933002,-0.0017817786,0.00539685,0.05832598,0.0011446716,0.035441637,0.015308752,0.0141451415,0.062738,0.026932737,0.10162199,5.3445785E-4,-0.025114596,-0.0045392932,0.03325987,-0.02145407,0.014339076,-0.019938951,0.056095727,-0.021999512,-0.008660414,-0.0159148,0.014751188,0.017393554,2.7802415E-4,-0.025793368,0.016642055,-0.004081728,-0.039902147,-0.0023969165,0.011527018,-0.006799849,0.079804294,-0.0149572445,0.013514852,0.0154299615,2.899557E-4,0.02107832,0.01618146,0.0091513125,0.021514675,0.024993386,-0.03789007,-0.016387515,-0.010720975,-0.04327177,-0.005987746,0.039732452,-0.020775298,-0.03687191,0.02841149,0.006611975,0.016205702,0.0038483995,0.044411138,0.009411912,-0.00526352,-0.007939218,0.018411713,-9.6285745E-4,0.029211475,0.047586825,0.03185384,-0.011266417,7.939218E-4,-0.0022393444,-0.010127048,-0.0016590542,-0.03071447,-0.011836101,-0.0038817322,0.005178673,-0.01349061,-0.021635884,-0.0076786177,0.005287762,-0.007399836,-0.013902722,0.027078187,-0.012533056,-0.04431417,0.031635664,-0.016981442,0.015636018,0.0028241798,0.017211739,0.03633859,-0.04084758,-0.012654265,0.029769037,0.031175068,0.049550418,0.030350843,-0.039368823,-0.0035484063,0.01785415,-0.019248059,0.011787618,-0.030035699,-0.004666563,0.044605073,-0.022181327,-0.019563204,0.0059635043,0.0030741743,-0.023841895,-0.0035241644,0.02195103,-0.0120542785,0.011642166,-0.01579359,-0.014096658,-0.010248258,0.020714693,0.023066156,0.055368472,-0.004930194,0.0036696156,0.008908894,-0.006593793,0.0122542735,-0.009005861,-0.016072372,0.0021287408,0.0032059895,0.015866315,-0.025187321,0.0030241753,-0.0060695624,-0.007569529,0.02659335,-0.0014726947,-0.008951317,-0.054786664,-0.0025908516,0.015381478,-0.015623896,-0.014217867,0.008102851,0.010720975,0.023151003,-0.038883988,0.009920992,-0.0102725,9.522516E-4,-0.014799672,-0.0017363252,-9.734633E-4,0.016157217,0.033041693,0.0070846914,0.025866093,0.0016529937,-0.033332594,-0.011302779,-0.008333148,-0.04075061,-0.02671456,-0.0037847646,-0.016120855,0.022435866,0.03696888,-0.01631479,0.022799496,-6.185469E-4,0.06525916,2.2916158E-4,-0.025090354,-0.0043847514,-0.04191422,0.025502466,0.0033605315,0.005624118,-0.014290593,-0.018181415,0.06681064,0.016096612,-0.0070846914,0.03621738,-0.016993564,0.009442215,-0.010502798,-0.01207246,-0.02351463,-0.015175422,0.0034484083,0.0215874,-0.008460418,-0.014872397,0.0292842,0.025623675,-0.02814483,-0.0013280009,0.013733029,0.0046059587,-0.009302824,0.017284466,0.004478689,0.023866137,0.024338854,0.010254318,-0.010236137,0.0102300765,0.030593261,0.0034635596,-0.009569485,0.03864157,0.002839331,0.0018196567,-0.00475141,0.02952662,0.007157417,0.015199663,0.006842273,0.001291638,0.029211475,0.022132844,0.023332816,-0.045574747,0.042835414,-0.0033241687,-0.004536263,0.0062725884,-0.0032544734,0.027635751,0.030011456,-0.029744795,0.07733162,-0.015951162,-0.0051968545,-0.005975625,-0.02671456,0.009387671,-0.033841673,0.017442038,-0.015296631,-0.010260379,0.0029832672,-0.018387472,-0.018945035,0.007169538,-0.024847934,0.019163212,-0.018932914,0.045041427,-0.017005684,-0.051586736,0.018326867,0.01322395,-0.0076240734,5.9089598E-5,-0.005599876,-0.030787196,-0.0210662,-0.0264479,0.03738099,0.018775342,-0.0026666075,0.0068968167,0.0048847403,0.033962883,-0.010611886,-0.002146922,-0.029647827,-0.01580571,0.008442237,-0.040580917,0.039780937,0.013866359,-0.017926876,0.0096906945,0.0059180507,0.03277503,0.0024484305,-0.051198866,-0.0077816457,0.015817832,0.0063634953,0.0022408594,0.019478356,0.010333104,0.027999379,-0.022569196,0.044847492,0.021599522,0.028096346,-0.015781468,-0.0013817876,0.007757404,-0.036411315,-0.010302802,-0.0073695336,-0.012957289,-0.03786583,0.002736303,0.02584185,-0.007527106,-0.00719378,-0.0090119215,-0.004984738,-0.016593572,-0.03313866,0.015454203,0.03507801,0.024035832,0.026229722,-0.0027559996,-0.0016287518,0.06230165,-0.00950282,0.01335728,-0.019720776,0.009072526,0.008521023,0.01798748,0.0367507,0.067586385,-0.0060877437,0.040653646,-0.0054271524,0.03005994,-0.0078058876,0.02387826,-0.022387384,-0.019635929,-0.021793457,-0.013551215,-0.03752644,-0.02427825,-0.03376895,0.004999889,-0.0073028686,0.03132052,-0.009539182,-0.019429872,-0.020751055,0.0039271857,-0.003068114,-0.014108778,-1.3920147E-4,0.016254185,-0.030011456,-0.0077392226,-0.021526795,-0.021248015,0.0037908251,-0.02698122,0.0107027935,0.018460197,0.02105408,0.04164756,0.004545354,0.0014401196,-0.012508813,-0.0039756694,0.017551126,0.0019969253,0.01862989,-0.0015196633,0.036556765,0.014799672,0.011151268,-0.0038877926,0.0039635487,4.6817143E-4,0.02584185,-0.021272255,0.026229722,0.027417574,-0.009036164,0.016157217,0.051101897,-0.01786627,0.012484572,-0.011908827,0.023454025,0.0068362122,-0.022302536,-0.03224171,-0.021757094,0.013866359,0.02979328,-0.052362476,0.009617968,-5.42791E-4,0.01592692,-0.018314745,-0.023975227,0.008290726,-5.734721E-4,-0.015345114,0.027756961,0.01798748,0.028314523,-0.034884077,-0.044580832,-0.012751233,-0.014690584,0.005884718,0.02095711,-0.00938161,0.0028181195,-0.006199863,-0.020217733,-0.008005884,0.008151335,0.028508458,-0.0182299,0.001207549,0.0085392045,0.017514763,-0.008048306,-0.015454203,-0.0069574215,-0.008181637,-0.0075574084,0.0113088405,0.024654,0.04957466,-0.02105408,8.8028354E-4,-0.0285327,0.016726902,0.021017715,0.043489944,-0.014460285,0.027369091,-0.025502466,0.0174784,0.007133175,-0.004442326,0.017429916,-0.025066111,-0.006642277,6.6400046E-4,0.018666252,-0.041186966,7.8880825E-5,0.03532043,-0.013454247,0.021126805,0.011490654,-0.0029075113,0.01027856,0.008018004,-0.0021863151,0.001484058,-0.040580917,-0.01219367,-0.0023863108,-0.03798704,-0.0010386134,0.007399836,0.004057486,-0.02584185,-0.005308973,0.005808962,0.039780937,0.0027559996,0.013636062,-0.025211563,-0.02620548,-0.001952987,0.0016272367,0.0025272167,-0.009896751,0.011399748,-0.013623941,0.0034120455,0.01104824,-0.0048635285,-0.021248015,0.01579359,0.008054367,0.020326823,-0.009563425,0.050859477,-8.651324E-4,0.0063998583,-0.013042135,-0.012545177,-0.026569108,-0.05396244,0.03328411,0.016496604,0.015999645,0.011260357,0.025260046,0.006199863,0.00192723,0.012114883,-0.016605692,-0.02452067,-0.010630067,0.01810869,-0.0018651102,0.03187808,-0.01452089,0.030738713,-0.0025514585,-0.012508813,-0.021502554,-0.015114817,0.031562936,2.1931333E-4,0.01837535,-0.031926565,-0.030665986,-0.021914667,-0.020981353,-0.021369223,-0.0019954103,0.027902411,0.0017242042,-0.0040665767,-0.026666075,-0.022957068,0.029211475,0.025114596,-0.012284576,0.036023445,0.02287222,-0.005057464,-0.009424034,-0.02018137,-0.03626586,-0.009108889,-0.009393731,0.023151003,0.032047775,-0.012630023,-0.015769348,-0.005627148,-0.0120482175,-0.013575457,-0.022641923,0.027926654,0.0031544755,0.01477543,-0.005563513,-0.004687775,0.01078158,-0.007757404,0.01477543,0.02069045,-0.03376895,0.04904134,0.041841496,-0.01901776,-0.02208436,0.030108424,-0.014981486,0.0033999246,0.0017151135,-0.0119815525,0.037332505,0.017454159,0.026811527,-0.034981042,-0.0033696224,-7.772555E-4,0.022363141,-0.036653735,0.004145363,0.033793192,-0.017563248,0.009575546,-0.0029393288,-0.01593904,0.022447987,-0.012448209,0.020278338,-0.0013340614,0.0072907475,0.01219973,-0.006115016,-0.02452067,-0.020096524,0.025769126,0.0023166153,-0.020035919,-0.004275663,0.0058816876,0.0074543804,-0.0033847734,-0.028678153,0.0013992115,0.04945345,-0.044847492,0.00887253,-0.037962794,-0.017672336,-7.977096E-4,-0.008805865,-0.008448298,-0.013345159,0.018205658,0.013987569,-0.0047089867,-0.0059150206,-0.024254007,-0.0071392357,-0.020993475,-0.031272035,0.0036847668,0.012933047,-0.020969233,0.02093287,-0.038059764,0.026641835,0.009927053,0.036774945,-0.010127048,0.008393753,-0.0032938663,0.027078187]} +*4 +$8 +JSON.SET +$108 +vcr:embedding:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts:0002 +$1 +$ +$19400 +{"type":"embedding","testId":"com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts","model":"text-embedding-3-small","timestamp":1765638677058,"embedding":[-0.024119083,0.038244378,0.02622671,-0.0023466556,0.036569443,0.013476258,0.022597682,0.044944122,-0.03386163,-0.047344867,-0.029590541,-0.022151032,0.038160633,-0.010300858,0.015772317,0.015828148,-0.021871876,0.00971463,-0.0438275,0.056305774,0.019554881,-0.006078623,0.011159264,-0.014711523,0.029171808,0.008674774,-0.033135824,-0.008758521,0.035229493,-0.08313267,0.031991284,-0.037713982,0.032689173,0.041677997,0.030511755,0.025766103,0.06621581,-0.028027268,0.0026380247,0.019917784,0.02858558,0.0065217833,-0.005049235,-0.007334825,0.008242083,0.038132716,-0.001835451,-0.023211826,0.017014561,0.027064178,-0.025919639,-0.0043722815,-0.05329089,0.026966475,0.022890797,-0.022723302,-0.013301786,0.030762997,-0.012303803,0.034252446,0.018563878,-0.022458104,-0.005419117,-0.005977429,-0.018424299,0.022109158,-0.015883978,0.04488829,-0.017168097,-0.0013678647,-0.013050545,-0.0017298951,0.0039046952,-0.025975471,0.04402291,-0.012910968,0.03615071,0.02184396,0.005830872,0.010300858,-0.034447856,0.03709984,-0.019191978,-0.06409423,0.021997496,-0.031767957,-0.02166251,-5.9189805E-4,0.032242525,0.028097056,-0.018326595,0.005258602,-0.004745653,-4.9244874E-4,-0.012708579,-0.053625878,0.015423371,0.017279759,-0.0565291,0.043799583,-0.030511755,-0.019066358,-0.019122189,-0.0044944123,0.029227639,-0.021160029,0.016526038,-0.011661744,-0.01982008,0.025375284,-0.07213392,-0.07704707,0.013818225,-0.05239759,-0.003163187,-0.007090564,-0.016177094,-0.0064799096,-0.033331234,-0.029953444,0.01441841,0.0073138885,-0.033247486,-0.0045188386,-0.015451287,-0.02864141,0.028362256,-0.005342349,-0.011885068,-0.03411287,0.06308927,-0.006643914,0.034978252,-0.020978577,-0.03983557,-0.007697728,-0.044804547,-0.0014708034,-0.027385209,-0.05513332,-0.0151163,-0.034447856,-0.0053074546,-0.030009275,2.024154E-5,0.0023274636,-0.008549154,0.018773245,0.028613495,0.012164225,0.026687318,0.007087074,-0.0068114074,6.564529E-4,-0.049633946,-0.0070766057,0.04391125,-0.025682356,0.011536124,-0.035173662,-0.037183587,0.004222235,-6.3159055E-4,-0.03151672,-0.0311259,-0.03391746,-0.041119687,-0.0067485976,-0.045530353,9.779185E-4,-0.033303317,0.018829076,0.030874658,0.02171834,-0.022304568,-0.008472386,0.044748716,0.03651361,0.031935453,-0.0010503246,0.016400417,-0.019750291,0.0120037105,0.014865059,0.029450964,-0.03062342,-0.025305497,-0.026003387,-0.013643752,0.012792326,-0.051727615,0.13120334,-0.01054512,0.023588685,0.04751236,-0.019024486,-0.010419499,0.03182379,-0.06331259,-0.020210898,-3.3346063E-4,0.011389567,0.018815117,-0.022095202,-0.013294807,0.023951588,0.025333412,-0.010028681,-0.019150104,-0.0057715513,0.019191978,-0.0269246,-0.013057524,0.0010363668,0.029004313,0.005328391,0.019471135,0.012883051,-0.012917946,-0.010328773,-0.026966475,0.0045956066,0.06526668,-0.010963853,0.0146556925,0.031712126,0.039528497,-0.046144497,-0.0012117118,0.0070731165,0.010810318,-0.00533537,-0.006752087,-0.036485694,0.030372178,0.014188106,-0.006504336,-0.035731975,0.017852029,-0.018703455,-0.041957155,-0.023644518,-0.04357626,0.035285324,-0.01620501,5.993131E-4,-0.009128403,-0.015353583,0.05479833,0.019861953,-0.0032347208,0.0073138885,0.021578763,-0.067109115,-0.011668723,-0.030288432,-0.0056180153,-0.01499068,0.029171808,0.034699097,-0.020699421,-0.06660663,0.033331234,0.031349223,-0.04712154,-0.013308764,-0.0024548285,-0.0025176387,0.009993787,-0.010307837,-0.041677997,-0.029506795,0.04427415,-0.0363182,0.016037514,0.019164063,-0.0145021565,-0.010056596,0.0032434443,0.045697846,-0.01276441,-0.087320015,-0.0075930445,0.01880116,0.0181591,0.036485694,0.01499068,0.0065008467,0.050694738,0.01092896,-0.04606075,-0.045642015,-0.01688894,-0.022960585,-0.017544957,-0.026910644,-0.01606543,-0.021816045,-0.017642662,0.020783167,0.088269144,0.05513332,-0.032828752,0.019471135,-0.010147322,-9.5349236E-4,0.020825041,-0.0059599816,0.020085277,0.009449433,-0.0149069335,-0.02547299,-8.994059E-4,-0.026561698,-0.03296833,-0.03715567,0.016121263,-0.024761142,0.031991284,-0.04123135,0.012527128,-0.04103594,0.030344263,0.022374358,-0.05075057,-0.026059218,-0.028222676,-0.051029727,0.011982773,-0.012366613,0.05901359,-0.006528762,0.015674612,0.0075302343,0.029953444,-0.049689777,-0.0117734065,-0.004299003,-0.020657547,-0.025640484,0.044106655,-0.001257947,-0.023225784,-1.9778097E-5,0.014976722,0.024119083,-0.024691353,-8.906823E-4,-0.020155067,-0.030148853,-0.01531171,-0.020476095,-0.009512243,-0.058064457,5.6834426E-4,-0.0011837961,0.03017677,0.005691294,-0.024551775,-0.0024059762,-0.033833712,-0.0014716758,0.002629301,0.0039709946,0.037713982,0.008584049,-0.024314491,0.01085917,0.024188872,-0.017070392,-0.0024478496,0.03919351,-0.070458986,0.005977429,0.0036604337,0.02547299,0.010314816,0.041119687,0.0214671,0.054379597,0.0015510608,0.045055788,-0.01606543,-0.025766103,0.04123135,-0.004871273,-0.015214005,0.0046270117,-0.079559475,0.0012431168,0.0017255333,-0.034392025,0.013650731,-0.013518131,0.002877052,-0.052230097,-0.035843637,-0.011319778,0.020392349,0.02642212,0.011515187,-0.017712452,-0.04821025,-0.060186043,-0.011319778,0.038914353,0.0061274753,-0.0033551068,-0.0026205773,-0.072468914,-0.010419499,-0.005070172,0.041873407,0.023909716,-0.008814353,-0.030009275,1.2758304E-4,0.0115989335,0.012422444,0.024314491,0.049354788,0.017349549,-0.07537214,-0.056249943,-0.0044036866,0.07928032,0.012792326,0.0030724613,-0.023239741,-0.034782846,0.0016191051,-0.018982612,0.002990459,-0.0055935895,-0.016009599,0.024705311,0.046758637,-0.0017002348,-0.011724554,-0.05049933,-0.022192907,-0.007502319,-0.029423047,0.025193833,-0.06387091,0.01314825,-0.0054854164,-0.008695711,0.005729678,-0.034559518,0.0205738,-0.031740043,-0.025389243,-0.0029503305,-0.010894065,-0.035536565,-0.008814353,0.027999353,0.012352656,-0.019778207,-0.028306423,0.024286576,-0.028055184,0.04879648,0.100328684,-0.041566335,0.03263334,0.06275428,0.020825041,-0.062475123,-0.022639556,0.035229493,-0.021774173,0.03651361,0.0040093786,-0.0018092801,-0.0044176443,-0.011445398,0.018759286,-0.022695387,0.0043792604,0.023993462,0.006301948,-0.0262965,-0.0066299564,0.050080594,-0.018117229,0.011954858,0.0070835846,0.016735405,0.0033551068,0.020252772,0.0109847905,-0.017209971,-0.013727499,0.02318391,-0.009351728,-0.039416835,0.031963367,-0.036429863,0.02171834,-0.0020151578,-0.033498727,0.087487504,-0.022458104,0.034252446,-0.011571018,-0.0071289474,0.044162486,0.01563274,0.036485694,0.029004313,-0.0020465627,0.007655855,0.0046060747,-0.0056703575,-0.024789058,-0.005265581,-0.013092419,-0.006581104,-0.0014926125,0.007948969,-0.0056319735,-0.0035138768,0.014879017,0.011577997,-0.037016094,0.034392025,-0.0044490495,0.012757432,-0.017182054,-0.00273224,0.016079389,0.019359471,0.0023832947,-0.03830021,-0.021704383,0.021201901,0.0016382971,0.005513332,0.018089311,0.031097984,-0.034308277,0.008102505,-0.0071952473,0.034140784,0.032242525,-0.012806284,0.009309854,0.03341498,0.0068358337,-0.031349223,0.037630234,-0.023490982,0.04234797,-0.038021054,-0.0034318748,-1.8330521E-4,0.015186089,0.0010145578,-0.027482914,0.014376537,0.011480292,0.016777279,-0.011619871,-0.0061135176,0.021229818,-0.024789058,-0.050415583,-0.008660817,-0.023449108,-0.008549154,-0.023588685,-0.021495016,-0.0199457,0.054658756,-0.042068817,-0.031488802,0.022234779,-0.02572423,-0.0036115814,0.0025699805,0.00704869,0.012122352,-0.04282254,0.00781637,0.011919963,-0.030065106,0.021564804,-0.005684315,0.009449433,0.038160633,0.035424903,-0.021522932,0.0041280203,0.009316833,0.06610415,0.030762997,-0.037769813,0.03386163,-0.011968816,0.024091167,-0.004871273,-0.042096734,0.0024722759,0.04770777,-0.013560005,9.1249135E-4,-5.583121E-4,-0.011054579,-0.028725158,-0.016344586,-0.008528218,-0.038774777,-0.045055788,0.00978442,-0.028920567,-0.013448343,-9.814079E-4,-0.021383353,-0.027580617,-8.941717E-4,-0.012855136,-0.0058657667,-0.010391584,0.023155995,-0.03352664,-0.03472701,-0.04282254,0.0027461976,0.032912496,0.013832183,0.003932611,-0.006643914,0.004019847,0.036876515,-0.010621888,0.01245036,0.01924781,0.015939811,0.03227044,0.01556295,0.006110028,-0.013050545,0.05499374,0.0026432588,0.0076837703,0.02312808,0.016121263,-8.579687E-4,-0.027650407,-0.016456248,0.04706571,0.005251623,0.009323812,0.014753398,0.011012706,3.805682E-4,0.033694137,0.012031626,-0.010600951,0.01321106,-7.035605E-4,-0.011968816,-0.025389243,0.0323821,-0.007634918,9.848975E-4,0.017503085,0.017237887,-0.022067286,-0.030093022,0.06331259,-0.0020169024,0.017963693,0.020936703,0.046954047,-0.012931904,-0.023449108,-0.0095052635,0.016679574,-9.5698185E-4,-0.045195363,-0.04759611,0.012938883,-0.010056596,-0.020210898,-0.008367702,0.017893903,0.014306747,-0.0025385753,-0.0030340774,0.009009762,-0.016246881,0.02610109,0.009554116,0.035480734,0.027720196,0.022053327,-0.0018825586,0.010998748,0.027720196,-0.02141127,-0.02819476,9.1859786E-4,-0.021816045,0.020601716,-0.02490072,0.013455322,-0.032884583,-0.008932994,0.0060018552,-9.604713E-4,0.042906284,-0.0023326979,0.00819323,0.0021826513,0.03983557,0.008276977,-0.022123117,-0.023351403,0.015618781,-0.026380247,-0.01942926,0.03126548,0.019150104,-0.026687318,0.0015187834,-0.019317599,0.02312808,-0.0053946907,-0.0045572226,0.014767355,0.030958407,0.017726408,4.1818887E-5,-0.015800232,0.009575052,-0.010552099,-0.011710596,0.005258602,-0.005942534,-0.0302326,-0.021592721,-0.05303965,-0.0293393,0.029925529,0.026408162,0.029925529,-0.0029241596,-0.013064503,0.011550082,0.0011707107,0.024495943,0.0120804785,-0.046870302,-0.01130582,-0.019861953,-0.024593648,0.027273547,0.0032626363,0.040784698,-0.036820684,0.038411874,0.015102343,0.002756666,-0.04547452,-0.05906942,-0.008758521,-0.0050771507,0.027706238,-0.017447254,-0.011431441,-0.011689659,0.022918712,-7.905786E-5,-0.013643752,0.013573963,-0.015004638,0.015800232,0.004023337,-0.036429863,0.012485255,-0.031991284,-0.022248738,-0.0056075472,-0.04611658,-0.013001693,-0.017712452,-0.005628484,-3.8623856E-4,-0.036178622,0.015395456,-0.030288432,0.0017473424,0.005823893,-0.033498727,-0.051839277,0.011208115,0.008011779,-0.008807373,0.046088666,0.053151313,-9.476039E-5,0.014013634,0.014753398,-0.030093022,-0.038160633,-0.039500583,-0.025221748,-0.033024162,3.7031795E-4,0.049829356,0.004386239,0.026031302,0.030595504,0.0066264668,-0.012778368,0.017614746,0.023002459,6.957092E-4,-0.012408487,0.010782402,-0.010112428,0.021369396,0.007732623,-0.019233853,0.010370648,0.011131347,-0.030400094,0.03137714,0.002268143,-0.030288432,0.034559518,-0.020852957,0.004375771,0.031935453,-0.005628484,-0.025082171,-0.024761142,-0.0029171808,0.0068777073,0.00374767,0.00546099,0.012136309,-0.047791515,0.008591027,0.019359471,0.015353583,-0.0051643867,0.0125410855,-0.0016539996,0.038188547,0.011103432,0.024607606,0.010126386,-0.007334825,3.670466E-4,-0.0103497105,-0.025696315,-0.028725158,0.029311385,0.002470531,-0.013671667,-0.015786275,0.025947554,0.008905078,0.0042885346,0.037434828,-0.008995804,0.011912985,0.021383353,-0.005157408,0.0053074546,-0.038718943,0.023211826,0.0098123355,0.030511755,-0.017154139,-0.030400094,-0.018717414,-0.017684536,0.0048433575,-0.030455925,0.014229979,0.058287784,-0.011229052,0.020127151,0.0039291214,0.010719593,0.02019694,0.02445407,0.013162208,-0.03765815,-0.0050038723,-0.01499068,0.01276441,0.02318391,-0.018480131,-1.0141216E-4,-0.016539996,-0.015200047,-0.018773245,0.022625597,0.0044490495,0.014851102,0.026142964,-0.023867842,-0.0058099353,0.0116757015,-0.010901044,0.01149425,0.0032120394,-0.013894992,-1.0043075E-4,-0.028041225,-0.008283956,-0.022374358,-0.02457969,0.009742546,0.018075354,0.018689498,0.018884907,0.086147554,-0.011717576,0.031405054,-0.00940058,0.0080047995,0.059739396,9.002783E-4,0.081066914,-0.0017778751,-0.026547741,0.009247044,0.031070068,0.055747464,0.031237561,-0.01473944,0.027873732,-0.0040372945,-0.019331556,-0.0047910158,-0.012394529,-0.03213086,0.014229979,-0.01924781,-0.01556295,-0.0105241835,-0.039388917,-0.010147322,0.016679574,0.002590917,0.10278526,-0.031963367,-0.0025507885,0.021774173,-8.754159E-4,0.008786437,0.060800187,0.010866149,-0.011640808,0.008116462,-0.0123107815,-0.019806122,-0.017768282,-0.058287784,-0.004860805,-0.0017360017,0.02127169,-0.041845493,0.031488802,-0.016177094,0.027706238,0.017223928,0.024063252,0.0099658705,0.009156318,-0.015172131,0.020852957,0.021760214,0.053625878,0.048126504,0.03606696,-0.03126548,0.0063996525,-0.01531171,0.02751083,0.008556133,-0.014390495,0.021816045,-0.024747184,-0.019778207,-0.0013931631,0.009456411,-0.0094075585,0.013448343,-0.02425866,-0.009526201,-0.0015379754,-0.017684536,-0.028892651,-0.0040372945,-0.02769228,-1.4088656E-4,-0.0042920243,0.023770137,0.031935453,-0.01365771,-0.0027915605,0.014837144,0.013497195,0.027859773,0.021592721,-0.042152565,-0.038439788,0.012136309,-0.010614909,0.016372502,-0.0425713,-0.011075516,0.054602925,-0.017140182,0.010580014,0.016623743,-0.0142718535,-0.012150267,0.005869256,0.009100487,0.03361039,0.0046514375,-0.028124971,-0.014209043,-0.013922908,0.01695873,0.0075302343,0.03869103,-0.026882729,-0.0061693485,0.0012134565,-0.013204081,-0.0043164506,-0.02751083,-0.025333412,0.023169952,0.03405704,-0.0014934848,0.028320381,0.020099236,0.0130993975,-0.0043792604,0.0043653026,0.0072371205,-0.02141127,-0.024663437,0.007767517,0.016414376,-0.017168097,-0.014683608,0.019624671,-0.009240066,-0.016232925,-0.027287504,7.9385E-4,0.008528218,0.0043269186,-0.0058727455,-0.022821007,0.016009599,0.024160957,0.026701277,-0.00654272,-6.8306E-4,0.005583121,-0.042990033,-0.0068358337,0.009316833,-0.013860098,-0.029255554,-0.0055203107,-0.0012753942,0.0040058894,0.024663437,-0.029869698,0.013106377,-0.035704058,0.02674315,0.0070033274,0.009791398,0.02356077,-0.035369072,0.006472931,0.01219214,0.009540158,-0.0056249946,-0.011682681,0.045251194,0.010503246,-0.0062147113,0.029841783,0.014278832,0.004745653,-0.008932994,-0.0040826574,0.013064503,-0.008283956,0.013727499,-0.0051923026,0.014641735,-0.004469986,0.028976398,-0.0018302168,-0.04120343,-0.004459518,0.006853281,0.010363668,-0.029953444,4.946296E-4,-0.022332484,0.033024162,0.011822259,0.023798054,-0.0169029,0.036094878,0.045334943,-0.012562023,-0.03481076,0.024565732,0.016079389,-0.0076070023,-0.017782241,0.0055238004,0.0238818,0.00978442,0.009944934,0.017921818,-0.0034144274,-0.014111338,0.0042710877,-0.041119687,0.057115328,0.0072371205,0.0024827442,-0.025933597,0.0013172677,0.03271709,0.015507119,-0.033387065,0.090111576,-1.3058178E-5,-0.0014838888,0.0031788896,-0.040700953,-0.011752469,-0.0031195688,-0.008542175,-0.022890797,-0.0104474155,0.0013094164,0.014948807,-0.013371575,-0.010887086,-0.0447208,0.01473944,-0.028920567,0.0538492,-0.03260543,-0.027217714,0.025905682,0.008228124,-0.015716486,0.0030811848,-0.028097056,-0.028920567,-0.018884907,-0.027538745,0.011340715,0.018940737,-0.002686877,-0.01060793,-0.007997821,0.042906284,0.0078093903,-0.003601113,-0.03062342,6.141433E-4,0.0030532693,-0.015451287,0.02883682,0.011857153,0.007285973,-0.01524192,-0.017014561,0.0425713,-0.012108394,-0.05404461,-0.018982612,0.013141271,-0.020922747,0.0031527185,-0.0083328085,-0.0016225945,0.027873732,-0.03866311,0.02998136,-8.994059E-4,0.0101961745,-0.022025412,-0.0065601673,-0.012317761,-0.030707166,-0.03550865,-0.0065915724,-0.025124045,-0.032047115,0.013315744,0.0064380364,-0.011982773,0.018075354,0.012052562,-1.412137E-4,-0.022695387,0.018689498,0.022513935,0.040980108,0.009526201,0.01949905,0.041677997,0.011249989,0.053402554,-0.014404452,0.044329982,-9.386622E-4,0.0181591,0.0293393,-8.750888E-5,0.040533457,0.058176123,-0.011787364,0.0028247102,-0.008891121,0.012527128,0.015060469,0.011515187,-0.022248738,-0.0058901925,0.005900661,-0.006015813,-0.026910644,-0.0024844888,-0.03243793,0.0036813705,0.012883051,0.017614746,0.003911674,-0.0025228728,-0.027189799,-4.202607E-4,-0.0051225135,-0.003252168,-0.018200975,0.025305497,-0.022569766,0.0073069097,-0.004218746,-0.05150429,0.018787201,-0.026966475,0.009868166,0.027664365,-0.0100426385,-0.021369396,0.015004638,0.01029388,-0.0046270117,-3.3062545E-4,-0.002610109,-0.034559518,0.0155210765,-0.019289684,0.0110615585,-0.01219214,0.011640808,-0.0030462905,0.016009599,-0.0011340715,0.0067974497,-0.011529145,0.015172131,0.014571946,-0.011187179,0.020476095,0.06990068,-0.01333668,-0.0032922968,0.032745004,-0.0020308602,7.3801883E-4,-0.017279759,-0.033749968,-0.0323821,0.0012527128,0.034308277,-0.01524192,0.04876856,-0.012499212,0.007411593,-0.0139438445,-0.025305497,0.021257734,-0.0047421632,-0.013915929,0.029590541,0.027482914,-0.018340552,-0.010852192,-0.019052401,-0.0044455603,-0.008053652,0.028864736,0.0055098427,-0.023100164,-0.012890031,-0.013622815,-0.037016094,-0.007634918,0.034280363,0.019931741,-0.008905078,0.0050457455,0.03243793,0.04876856,-0.026952516,-0.020922747,-0.016930815,0.0041384883,-0.008974867,-0.019205937,0.02724563,0.054602925,0.029646372,-0.0033830223,-0.026980432,0.02198354,0.024286576,0.026589613,-0.0499131,0.028892651,-0.044385813,0.017852029,0.0038139694,-0.011571018,-0.014348621,-0.005216729,-0.0055586947,0.026198795,0.008346766,-0.050359752,0.016930815,0.037909392,0.007348783,0.03062342,0.02356077,0.024998425,0.00978442,0.02203937,-1.695655E-4,0.029562626,-0.018563878,-0.017824113,0.0017011071,-0.054212105,0.008625922,-0.0031073557,0.01035669,-0.0053772433,0.014697566,0.025919639,0.032242525,0.013071482,0.0012710324,-0.05234176,0.011731533,-0.024789058,-0.008144378,-0.026701277,-0.0070033274,0.0035487714,-0.01747517,-0.0035941342,0.0117734065,0.009100487,0.0078512635,-0.004096615,0.004654927,0.013692604,-0.0049271043,0.048433576,-0.04617241,0.009986808,-0.007292952,0.012924925,-0.02076921,-0.03003719,0.008200209,0.0013652475,0.006092581,-0.0030393114,0.011947879,-0.0013451832,0.0036988177,0.019024486,0.010866149,-0.02820872,0.0018476641,0.011710596,-0.03260543,0.016944772,-0.007837306,0.012973777,7.502319E-4,0.010719593,0.012555043,-0.0062810113,0.017768282,-0.011857153,0.019289684,-0.03263334,-0.009707652,-0.0030846742,-0.023379318,-0.01874533,0.008367702,0.0011052835,-0.012443381,-0.013699583,-0.010977812,-0.0012998204,0.0323821,0.002369337,-0.023351403,0.041315094,9.081295E-4,-0.0064171,-0.01035669,-0.0254032,-0.0323821,0.008981846,0.007690749,0.0011044111,0.04103594,-0.04134301,-0.005862277,0.014934849,-0.027329378,0.0131761655,-0.027594576,0.023993462,-0.003848864,0.013650731,-0.013287828,0.0024234236,0.03257751,0.011145305,0.033554558,0.04033805,0.020978577,0.035285324,0.05248134,-0.009421517,-0.033191655,0.034782846,-0.015437329,0.011284883,-0.002554278,0.023086205,0.020476095,0.020378392,0.018131185,-0.011912985,-0.010133364,0.0033167228,0.018619709,-0.023197867,0.003957037,0.02438428,-0.017182054,-0.017112266,-0.011152284,-0.007292952,-0.016218966,-0.016721448,0.05111347,0.01631667,0.01308544,0.025891723,-0.020936703,-0.018326595,-0.0027357293,0.050527245,0.0060681547,-0.015227962,0.016470207,0.01060793,0.017363505,0.022151032,-0.03659736,0.007893138,0.050080594,-0.032019198,0.015590865,-0.00819323,-0.020545885,-0.007334825,0.002648493,-0.0073208674,-0.024314491,0.012331719,0.011571018,0.011145305,-0.018242847,-0.023518898,0.02699439,-0.023435151,-0.021508973,0.01410436,-0.015548992,-0.025054256,0.034419943,0.007017285,0.031712126,0.0043269186,0.012848157,-0.014020612,0.0028805416,3.552697E-4,0.00762096]} +*4 +$8 +JSON.SET +$108 +vcr:embedding:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts:0003 +$1 +$ +$19308 +{"type":"embedding","testId":"com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts","model":"text-embedding-3-small","timestamp":1765638677256,"embedding":[-0.04252664,-0.031583946,0.023355722,-0.0017751832,0.02920879,0.04521283,0.03636254,-0.02526433,-0.009599597,-0.030283267,0.043035604,-0.017926788,-0.02625398,-0.0052416064,-0.01844989,-0.005750569,-0.025193641,-0.0240202,-0.0122363055,-0.029859131,0.051631413,-0.03650392,0.019114368,0.03834184,-0.011444586,-0.07374301,-0.030339817,0.029717753,0.01811058,-0.055929318,-0.016371626,-0.042215608,0.019241609,-0.03771977,0.024981573,0.009373392,0.022790208,-0.017219895,0.026607426,0.043657668,-0.004980056,0.0016850545,-0.04017976,-0.038963903,0.01634335,0.007528403,-0.044336285,-0.027724316,-0.04148044,0.048888672,0.027611213,0.0018785663,-0.07176371,-0.0044392836,-0.037380464,-0.02074022,0.003315325,0.006227721,-0.025900533,0.031527396,0.003997476,0.018930575,-0.059774812,5.407726E-4,0.0026667512,0.022761932,-8.2397135E-4,-0.006779097,-0.038992178,0.0040999753,0.0024758903,0.022309521,0.05165969,-0.045014903,-0.0021259787,-0.031668775,0.071311295,2.7833885E-4,0.055109322,0.03149912,0.06876648,-0.037012883,0.004806868,-0.057399653,0.040349413,-0.03195153,0.016880589,0.04541076,-0.03879425,0.025617776,0.0038101494,0.06780511,-0.047503162,0.040208034,0.022465037,0.041791473,-0.008984601,0.0075142654,5.226585E-4,0.020909874,0.0017433731,-0.06887958,-0.009762183,-0.03622116,0.014130777,-0.039274935,-0.023709167,-0.0047679883,-0.022295384,-0.031329468,-0.08098158,-0.049256254,0.010073216,-0.02598536,-0.013254231,0.012455442,-0.027851557,0.03786115,-0.010935624,0.0062065143,-0.008772533,0.0043438533,5.708155E-4,-0.047757644,-0.015381976,-0.028120175,0.03183843,-0.07577886,-0.0067578903,-0.043572843,0.038285285,-0.013536988,0.015000255,-0.044619042,-0.030028785,-0.025250193,0.004803333,0.0075566787,-0.00465842,0.027724316,-0.012738199,-0.025292607,0.031923257,0.045976274,-0.011656654,0.025306745,-0.005874275,1.0410094E-4,-0.02027367,-0.01181217,-0.03045292,-0.009634942,0.014074226,0.0013881597,-0.025829844,0.0040222174,0.032319117,0.02409089,-0.010935624,0.027257767,-0.055109322,-0.009019946,0.01975057,-0.025547087,-0.031470846,0.0132118175,0.0145478435,-0.006984096,-0.022083316,-0.061527904,-0.03715426,0.00827064,-0.01825196,0.020132292,0.038539767,-0.028049486,-0.01542439,-0.057597585,0.06294169,-0.006043929,-0.029887406,-0.03263015,-0.026748804,0.018534716,0.02034436,-0.019976776,0.010864935,0.011706136,-0.04970867,0.0388508,-0.043685943,0.010165111,0.027427422,0.026876045,-0.031386018,0.0030820505,0.021489525,0.014731635,0.020952288,-0.017841961,-0.021376424,0.04207423,0.035401165,0.010787177,0.031357743,-0.011854583,0.038992178,0.01319061,-0.024302958,-0.01299268,-0.008956325,0.018492302,0.0387377,-0.003753598,-0.05100935,-0.030141888,-0.013791469,-0.0020305482,-0.038992178,0.0069628893,0.00686039,0.009790459,0.0224509,-0.003055542,-0.00800202,0.0028894222,-0.036079783,-0.05853068,-0.0035397632,-0.010907348,0.01897299,0.017219895,-0.024119165,-0.04405353,0.009458219,0.06260238,-0.023977786,0.00359278,0.032856353,-0.03483565,-0.044845246,-0.0062065143,-0.0561838,0.00794547,-0.035881855,0.0030661453,-0.03229084,-0.043940425,0.04297905,-0.0015905077,-0.0347791,-0.029293617,-0.0125685455,-0.01719162,-0.020485738,-0.02802121,0.024953298,0.05061349,0.02441606,-0.0027003286,-0.009634942,-0.026607426,0.0022938654,-0.015693009,-0.014427672,0.0058707404,-0.012109065,-0.020188844,-0.03200808,-0.023581928,0.009118911,0.024656404,0.03859632,-0.03438324,-6.437138E-4,0.039925277,-0.019269884,-0.027229492,-0.02769604,0.04382732,-0.053186577,-0.04515628,7.541657E-4,-0.016725073,6.030675E-4,-0.007627368,-0.012716993,0.03452462,-0.018506441,-0.012716993,-0.02435951,-0.057314828,-0.04193285,0.009479426,0.00336304,0.02855845,-0.046541788,-0.01917092,0.012306995,0.015693009,0.023426412,-0.006188842,-0.0066978047,0.038511492,0.005139107,-0.023723306,-0.015509217,0.018068166,0.030226715,0.05559001,-0.018690232,-0.0039479933,-0.0104125235,0.00833426,-0.019213332,-0.014067157,0.059322402,-0.05573139,0.070519574,-0.053638987,0.07029337,-0.055759665,-0.0030449387,0.009535977,0.012936129,-0.015042668,-0.012936129,-0.025900533,-0.034863926,0.01076597,0.043261807,0.010652867,0.01975057,-0.010872004,0.016046455,-0.010313558,-0.0142650865,-0.005796517,-0.028770516,-0.03938804,-0.029519822,0.0077263326,-0.023511238,0.019722296,0.041904576,0.025759155,-0.035033584,0.030679125,0.027229492,-0.0055773803,0.010949762,0.013317851,-0.018464027,-0.020485738,0.010327697,0.040971477,0.018082306,-0.0653734,-0.038511492,-0.018676095,-0.017431963,-0.01627266,0.04391215,0.031272914,-0.02836052,0.04023631,-0.012306995,-0.035146683,0.00525221,0.026762942,-0.008207019,0.039925277,-0.04575007,-0.022139866,-0.04871902,0.03263015,0.020825047,0.038624596,0.03511841,0.041961126,0.032319117,-0.0070194406,-0.012172685,-0.033846002,0.07934159,-0.03531634,0.0075001274,-0.022224694,-0.08663672,-0.022139866,-0.043488014,-0.02199849,-0.008284777,-0.0038560976,-0.008362536,0.04555214,0.028954308,0.010165111,0.008058572,-0.013438023,0.02691846,0.027851557,0.008942188,-0.057286553,0.012172685,0.015735423,-0.035231512,-0.023610203,0.028318105,-0.01522646,0.026296394,0.0024723557,0.030707402,-0.031188088,-0.020400912,0.01522646,-0.009988388,0.015254736,0.039303213,0.003087352,0.003997476,-0.019269884,-0.04719213,-0.0015083314,-0.01240596,-0.015961628,-0.014745774,0.0029830856,0.0155233545,-0.049821768,-0.011670792,0.018223684,-0.033789452,0.03786115,-0.0070865955,-0.011211311,0.010440799,0.008447363,0.001936885,-0.03766322,0.008044435,-0.013134059,-0.03511841,-0.009663218,-0.046230756,0.015127495,0.010589247,-0.021546077,0.0028063625,-0.0070936643,-0.02100884,0.021489525,-0.040971477,0.029406719,-0.017573342,-0.027950522,0.022168143,-1.5532301E-5,-0.010483213,0.0030078266,-0.034043934,0.0142650865,-0.025631914,-0.019199194,0.085505694,-0.0026614496,0.050189354,0.038454942,0.016399901,-0.041819748,-0.0034584706,0.028629137,0.0058884127,8.791973E-4,-0.01982126,-0.021602629,0.021376424,-0.020641254,0.003195153,-0.015127495,0.06797476,-0.010016664,0.01574956,-0.017926788,-0.007358749,0.067465805,-0.0016258523,0.0012953802,0.025038125,-0.016894726,-0.038879078,0.002604898,0.045919724,-0.017205758,0.016710933,-0.068427175,0.020655394,-0.0387377,0.03766322,0.0087937405,0.03209291,0.04436456,-0.019778846,0.040010106,-0.033817727,0.018082306,-0.016159559,0.034270138,0.035203237,-0.042356987,0.008617017,0.02119263,-0.017375413,-0.002564252,-0.031046709,-0.03025499,-0.021348147,0.044986624,-0.008708913,-0.030679125,-0.01463267,0.015735423,-0.004845747,-0.024797782,0.03313911,-0.030028785,-0.038087357,-0.044986624,-0.02920879,-0.022917448,0.029491547,-0.038087357,0.008765465,0.022154005,-0.014816463,-0.009592528,-0.034722548,0.0142650865,-0.0013422117,-0.010843728,0.010624591,0.020231258,-0.013028025,0.0204716,-0.015480941,-0.012688717,0.01738955,-0.019595055,0.011649585,-0.0068144416,-0.0011495836,0.0043297154,-0.003799546,0.0036122196,0.02409089,-0.025165366,0.010758901,-0.00623479,-0.009620804,-0.026989147,-0.013544057,0.012321133,0.023907097,0.009953043,-0.006104015,0.017955065,0.025631914,-0.050698314,0.0025324416,-0.013812675,-0.020966426,0.020514015,-0.029039135,-0.0019033075,-0.007705126,0.013918709,0.015848525,0.013339058,0.00416713,0.020726083,0.012038376,-0.0024476147,-0.015579906,7.3649344E-4,-0.014385258,0.041339062,-2.012434E-4,0.0055915182,0.009663218,0.014010605,-0.013565263,-0.028233279,-0.004403939,6.803838E-4,0.013855089,0.043233532,0.016527142,0.024048476,-0.022493312,0.05349761,-0.026437772,-0.010228732,-0.005623328,-0.026805356,0.006241859,0.015537493,-0.051801067,0.048860397,0.010249938,-0.028827067,0.0034531688,-0.0067119426,-0.01607473,-0.033648074,-0.025391571,-0.004379198,-0.016696796,-0.022917448,-0.0030661453,-0.0038490286,0.045382485,-0.011317345,0.0059308265,0.028261553,0.004213078,-1.948372E-4,-0.025716743,3.625032E-4,-0.008567534,-0.0027833884,0.032206014,0.034496345,0.004817471,-0.013035094,0.0035980819,1.5021462E-4,0.011211311,0.026876045,0.018421613,0.015070944,0.022677105,-0.023977786,0.013438023,-0.01437112,0.023666754,0.010115629,0.013876296,0.046428688,0.0062772036,-0.014067157,-0.028205004,-0.04201768,-0.022521589,0.010610454,0.01509922,-0.024670541,0.029548097,-0.016329212,0.001520702,-0.019015403,-3.731066E-4,-0.06718305,-0.042781122,0.0144206025,-0.013876296,0.010214593,0.033563245,0.0016673822,0.011084071,1.5286545E-4,-0.0715375,0.0066377185,0.049906597,0.015254736,-0.03203636,0.027144665,0.021348147,0.007584954,-0.03641909,0.039472867,-0.030537747,0.01929816,-0.033591524,-0.021022977,0.0017610454,-0.025419846,0.00800202,0.03353497,-0.0144206025,0.018082306,-0.023666754,-0.013939916,0.023016414,-0.023313308,0.028643277,0.018308511,0.030170163,-0.014031813,0.005209796,-0.028940171,0.014356983,0.0068674586,-0.0013139361,7.3737703E-4,0.017417826,-0.0468811,-0.039868727,-0.02416158,-0.007867712,-0.026338806,-0.024006063,-0.04114113,0.01299975,-0.00416713,0.0051179,-0.011974756,-0.0064503923,0.02691846,0.04942591,-0.019920224,0.031272914,0.01844989,-0.021814696,-0.04948246,0.01181924,-0.004283767,-0.04049079,0.027073976,-0.008249433,0.054967944,-9.772786E-4,0.015283012,0.0010073215,0.02126332,-0.015311287,-0.021404698,-0.036136333,-0.01699369,-0.024628127,0.0031138605,-0.04770109,-0.011670792,-5.6551385E-4,0.027088113,0.034411516,-0.008115124,0.005132038,0.01194648,0.006676598,-0.02980258,0.0014288061,0.013070439,0.0358253,0.011621309,0.0036334265,-0.036475644,0.008079779,0.023270894,4.7494326E-4,0.031979807,0.01661197,0.031753603,-0.02527847,0.036192887,-0.004969453,0.012045445,-0.02271952,-0.058813438,0.025759155,-0.03551427,-0.022309521,-0.027144665,-0.014858876,-0.00413532,0.0066412534,-0.013070439,0.022846758,0.005835396,0.007853573,0.0040257517,0.045325935,-0.034694273,-0.0075708167,-0.0049199704,-0.032573596,-0.0063584964,0.008715982,0.016244385,-0.016654383,-0.05052866,-0.008426156,-0.032771528,0.02980258,-0.015594044,0.0029989905,0.005789448,-0.0076344367,-0.08822016,-0.0028558448,0.030877056,-0.035231512,-0.009012877,-0.0052062618,-0.02146125,-0.0275688,0.04733351,-0.047022477,-0.016838174,-0.020005053,-0.01962333,-0.03302601,-0.010921486,0.009825803,-0.0032870492,0.0029070945,-0.004082303,0.021348147,-0.036136333,0.02093815,-0.017248172,-0.04102803,0.00718556,-0.0016099472,-0.022323659,0.0040080794,-0.021093667,-0.042554915,-0.016597832,0.029661201,-0.044336285,0.014887152,-0.033591524,0.029717753,0.010652867,0.04589145,0.022408485,0.0126463035,-0.024430199,-0.030961882,0.019397125,-0.01726231,0.009401668,-0.013360265,-1.2215982E-4,0.01917092,-0.026536737,-0.027356733,3.971851E-4,-0.031725325,0.029717753,0.011649585,-0.0061994456,0.029774304,0.018690232,0.016527142,0.014929566,-0.018138856,-0.019934364,0.01844989,-0.0068568555,0.0093097715,0.032856353,0.022818483,0.02106539,0.039897002,0.029180514,-0.012823027,-0.032517046,0.015381976,-0.018619543,0.010292352,0.04057562,-2.301542E-5,-0.014215604,-0.015721284,-0.044675592,0.02428882,0.04077355,0.028275693,9.313306E-4,0.03557082,0.0058318614,-0.022804346,-0.017629893,0.019057816,0.0018750319,-0.012773544,0.031046709,0.02763949,-0.011691999,0.01607473,0.018336786,0.009578391,0.0035556683,0.002995456,0.005853068,0.0037783394,0.03407221,-0.019241609,0.054713465,-0.033789452,-0.028332243,-0.007344611,-0.0012724062,-0.022479175,0.002719768,0.022917448,0.013600607,0.01549508,-0.020514015,0.03347842,-0.0471073,0.031527396,0.020259533,0.023751581,0.009458219,-0.005803586,0.048266605,-0.008051503,-0.0069876304,-0.03203636,-0.03248877,-0.011345621,0.03839839,0.011536483,0.012709923,-0.0183792,-0.012377684,0.05067004,0.03729564,0.05776724,0.0066377185,-0.011388035,0.010101491,0.036588747,-0.008935119,0.010688212,0.015000255,0.025589501,-0.0072809905,-0.008503915,-0.0062206523,0.037125982,-0.04108458,0.011593034,-0.02283262,0.021277457,-6.5210817E-4,-0.029293617,-0.0051603136,-0.025094677,-0.02645191,0.028417071,-0.020754358,0.017347137,0.032743253,0.028586725,-0.018266097,0.008920981,-0.018124718,0.020075742,0.05663621,-0.033506695,0.010313558,0.004471094,-5.0719525E-4,0.0024847265,0.017856099,-0.023737444,-0.011974756,0.006333755,0.038285285,0.021206768,0.009196669,0.022691242,-0.008694775,0.02737087,0.0025677863,0.013353196,-0.023115378,0.03110326,0.060114123,0.012066651,-0.0066059087,-0.020047465,0.024500888,-0.012292857,0.021871248,0.010539765,-0.0049729873,0.017545067,0.005782379,-0.005153245,-0.01384802,-0.014717498,-0.0093097715,0.008878567,-0.020118155,-0.026084326,-0.024302958,-0.011960617,0.0013201213,-0.005757638,-0.0026632168,-0.010716488,0.017106794,0.022111591,-0.03019844,0.008510984,-0.005828327,0.05321485,-0.0028434743,0.027342593,-0.02816259,-0.013268368,0.012674578,-0.038935628,0.04312043,-0.01825196,-0.032319117,0.0139045715,-0.03294118,-0.009960112,-0.0077263326,-0.02730018,-0.007259784,0.027964659,-0.01936885,0.025193641,0.043742497,0.006764959,0.010009595,0.006941682,0.0035680388,-0.007712195,0.021786422,-0.040519066,-0.0056869485,-0.030594299,-0.03045292,-0.014385258,-0.018464027,0.005047211,-0.03130119,0.010143905,0.021305734,0.026140878,0.019057816,0.040971477,0.023256756,-0.030566024,0.003983338,-0.012306995,6.4238836E-4,0.0551376,0.00686039,-0.015678871,-0.005722293,-0.02697501,0.0019139108,-0.014166121,-0.0027003286,-0.00977632,-0.01589094,0.006241859,-0.018661957,-0.006379703,-0.031923257,0.039359763,0.023242619,0.03099016,0.005192124,0.04108458,-0.038907353,-0.008390811,0.02336986,-0.026494324,-0.02724363,0.01870437,-0.009755114,-7.104268E-4,0.006524616,-0.020909874,-0.005520829,0.007931331,0.04566524,0.018775059,0.0019828328,0.02034436,0.009260289,0.014604395,-0.03622116,-0.023779856,-0.010638729,0.014929566,0.035881855,-0.030170163,0.01109114,0.022507451,0.022111591,0.0050825556,0.02657915,-0.008546328,-0.0026473117,0.0010541532,-0.01738955,-0.03090533,0.020104017,-0.03248877,0.019920224,0.011444586,-4.252399E-4,0.0089492565,0.02527847,-4.3275062E-4,-0.041339062,-0.007775815,0.018068166,0.012837164,-0.016230248,-0.014328707,0.034637723,-0.012179755,0.0027321388,-0.043261807,-0.015325425,0.017884376,0.0120101,0.013247162,0.0022567536,0.016654383,-0.037634946,0.014406465,-0.011048727,0.01699369,-0.008199951,0.0050719525,0.0022779605,-0.040405963,0.04148044,-0.013480436,-0.01555163,0.02435951,7.992301E-4,0.020584704,-0.002933603,-0.042300437,0.07278163,-0.008185813,-0.0224509,-0.032828078,-0.028007073,-0.020881599,-0.00715375,0.01542439,-0.049114875,-0.017135069,-0.030877056,0.0073092664,-0.014519568,-0.03065085,0.0051885894,0.023016414,-0.036390815,0.03788943,-0.008157537,0.004322646,0.004661955,-0.035683922,-0.0106952805,-0.02034436,0.00813633,0.0056940173,0.023793995,-0.009748044,-0.030594299,0.030961882,-0.013466299,-0.022168143,-0.034948755,0.032347392,0.0013678366,-0.0057788445,0.023426412,0.027498111,-0.0019121437,0.012773544,0.017205758,0.030141888,-0.002168392,-0.010193387,0.011522344,7.939284E-4,-0.0062842728,-0.017474378,-9.2337804E-4,-3.898953E-4,5.6937966E-5,0.03214946,0.0043014395,0.024840195,-0.008652362,-0.030566024,-0.0025395106,0.010872004,0.061414804,8.6019957E-4,0.013445091,-0.015834387,-0.016371626,-0.026692253,-0.02816259,-0.031188088,-0.007008837,0.035627373,-0.019609192,-0.011585965,-0.023072965,0.01883161,-0.01608887,-0.01509922,-0.019722296,0.025942948,-0.02526433,0.014173191,-0.019906087,0.015056806,-8.548979E-4,0.042809397,-0.013197679,0.0011805102,0.016824037,0.013989398,0.05231003,0.021885386,0.035231512,0.072046466,-0.013105784,0.0023645547,-0.0028293363,-0.020033328,0.001160187,-0.017615756,-0.004456956,-0.010511489,0.0032428685,0.015410252,-0.0051072966,0.02081091,-0.008150469,-0.017686445,-0.012943198,0.016527142,-0.021475388,0.012391822,0.008079779,0.019764708,-0.016258523,-0.014519568,-0.006058067,-0.008723051,-0.02362434,-0.012837164,-0.019863674,-0.031414293,0.016654383,-0.030396368,-0.013028025,-0.0015242365,-0.01738955,-0.02724363,0.016710933,0.0068533206,0.012695786,-0.0058848783,-0.042696293,-0.020980563,-0.019029541,-0.034637723,-0.007952538,0.007910125,0.004004545,-0.02717294,-0.0050295386,0.01358647,0.02690432,-0.04724868,-0.001470336,0.023779856,-0.022139866,-0.0055773803,0.034043934,0.028841205,-0.004488766,-0.013862158,0.034750827,-0.005344106,0.009401668,6.370867E-4,-0.0068285796,0.008956325,4.7162972E-5,0.0020853323,0.011494068,-0.02093815,-0.0029636458,0.0025607173,0.015579906,0.009804596,0.0013978796,0.0265226,0.00984701,5.7655905E-4,0.007627368,-0.008800809,-0.020429187,-0.035090134,0.022931587,0.01254027,0.02730018,-0.008341329,-0.004015148,-0.02553295,0.0062913415,0.013961123,-0.03322394,-0.011112347,0.00945115,0.030141888,8.8228995E-4,0.028205004,-0.014788187,0.016908864,-0.007507196,-0.0045453175,0.01095683,-0.007493058,0.0010002526,-0.004739713,0.040405963,-0.015791973,0.002007574,0.0077687465,-0.0014270388,0.029491547,0.008093917,0.014618533,-0.033846002,-0.027356733,-0.026098464,-0.043629393,0.0024705885,0.008843223,0.027837418,0.043035604,0.04521283,0.0063054794,0.0019739966,0.037917703,-0.0020128759,0.023723306,0.026989147,0.023454687,-0.008051503,0.0204716,0.034185313,-0.014858876,-0.0035326942,-0.023652617,0.011338552,-0.011048727,0.007825297,0.020697806,0.043544564,-0.027469834,-0.012116134,0.021772282,0.0012768242,-0.022281244,0.02527847,-0.0026985614,0.053554162,-0.016131282,-0.012278719,0.007797022,-0.015763698,0.051292107,-0.0016019946,-0.02546226,0.013402678,-0.018124718,-0.011133553,-0.021517802,0.03322394,-0.021220908,-0.006019188,0.019736433,-0.013741986,0.06458168,-0.0019351176,-0.02848776,-0.016414039,-0.012087858,0.023002276,-0.009182531,0.020895736,0.0061428943,0.0316405,-0.00905529,-0.020895736,0.0053122956,-0.010377179,-0.013975261,-0.016852312,0.031442568,-0.046061102,0.0091542555,-0.018266097,1.07911525E-4,0.0070017683,-0.008362536,0.010066146,-0.017700583,-0.02960465,0.002588993,0.03398738,0.003439031,-0.026876045,-0.021164356,0.006899269,-0.041423887,0.0015648828,0.0031845497,0.016173696,0.014717498,-0.0020305482,-0.0132613,0.03839839,0.01463267,-0.018859886,0.020853322,-0.030792229,-0.027554661,-0.009740976,9.631408E-5,-0.03984045,0.033846002,-0.025886396,0.039868727,-0.026466047,-0.012455442,0.027003286,-0.02592881,-0.00672608,0.008207019,-0.017361274,0.012490787,0.015919214,0.00531583,-0.016951278,0.0026985614,-0.013876296,0.0011504672,1.954999E-4,0.05112245,-0.005492553,0.05604242,0.032743253,0.019665744,0.021277457,-0.008058572,-0.02093815,-0.0054713464,0.022465037,0.03209291,-0.018746784,0.027809143,0.018817473,-0.007114871,0.004304974,-0.027328456,0.03353497,-0.008928049,0.041197684,0.0060863425,-0.011232519,-0.02618329,-0.0026861907,-0.009147187,0.01772886,0.023963649,0.044025254,0.029406719,-0.002550114,0.026310531,-0.008772533,0.012759406,-0.0065670293,0.0039692004,0.003930321,0.025518812,-0.0050260043,0.015396114,0.034157038,0.00833426,-0.019199194,-0.013883364,-0.0069346135,-0.026932597,-0.010780107,0.020570565,-0.004188337,-0.017700583,-0.013883364,-0.030283267,0.008129261,0.02744156,-0.004753851,0.050783142,0.005966171,0.006188842,-0.027328456,0.0029972233,0.012200961,-0.0010355972,0.01844989,-0.028629137,-0.013402678,-0.037437018,-0.028120175,-0.017135069,0.022097453,0.0023062362,-0.018054029,-0.015975766,0.027978797]} +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T15:11:17.261013Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*4 +$8 +JSON.SET +$111 +vcr:embedding:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText:0001 +$1 +$ +$19402 +{"type":"embedding","testId":"com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText","model":"text-embedding-3-small","timestamp":1765638678239,"embedding":[-0.036741957,-0.023619829,-0.01989364,-0.023892175,-5.032986E-4,-0.029660959,-0.0053169373,0.022480927,-0.013109748,0.004988884,-0.039242588,-0.040232938,0.01208226,-0.018383358,-3.6925325E-4,-0.012948816,0.018061494,-0.008424158,0.012119398,0.0163779,0.027680261,0.001658835,0.020822093,0.028522057,3.359837E-4,-0.023557931,0.016266486,0.050582085,0.050012637,-0.037187614,-0.024102625,-0.029958064,-0.006393942,-0.0035528778,-0.0084489165,0.07566268,-0.0017826286,-0.0046948744,-0.005307653,0.01521424,0.014211511,0.05521197,0.015573242,-0.025823357,0.010559599,-0.02730888,0.0104667535,-0.020537367,0.0035404984,0.0011002161,0.007910415,-0.05130009,-0.035603054,-0.038177963,0.0069757723,-0.012243193,0.019918399,0.012354607,0.0097611295,0.028224953,-0.03372139,-0.0032619627,-0.0034167047,-0.034860294,-0.05461776,0.025897631,-0.02186196,0.011766586,-0.010386287,-0.030378962,0.022097167,0.008770781,0.045679856,0.0064248904,0.009847785,0.012849781,-0.018470014,0.04681876,-0.0038530775,-0.019980295,0.02301324,0.0057780687,0.04602648,-0.05441969,-0.07011673,-0.022778032,-0.008158002,-0.029017232,-0.022889446,0.0071305144,0.0026398997,0.027927848,0.009866354,-0.020723058,0.040975697,0.028398264,-0.035008844,-0.040629078,-0.0066662882,0.019163257,-0.049566977,-0.0011474125,0.009346421,-0.01736825,1.5580592E-4,-0.015375172,0.020153606,-0.027061293,0.03565257,-0.014904756,-0.04449144,-0.0069510136,-0.005044591,0.04899753,0.053379823,-0.037138097,-0.022505686,0.014001062,0.0029370044,0.06164924,-0.009891113,-0.02126775,-0.024870144,-0.0050167376,-2.9942588E-4,-0.0194975,-0.03525643,-0.03223587,0.004428718,-0.012267951,0.015164723,0.003506455,0.022592342,0.06343187,-0.040678594,-0.02380552,-0.07437523,-0.00878316,-0.028546816,0.0421146,-0.007291446,-0.022381892,-0.012069881,0.026962258,-0.017454905,-0.018569048,-0.010541029,0.01656359,-0.0011489599,0.0050631603,-0.023916934,-0.06719519,-0.011686121,0.002406239,-0.046150275,-0.014496237,0.019831743,-0.026417565,-0.016303625,0.008442727,-0.0421146,-0.007279067,0.044590473,0.020524988,0.006882927,0.03374615,6.893759E-4,0.009476404,-0.027581226,-0.06962155,-0.013963924,-0.042461224,0.030354204,-0.022901826,0.035999194,-0.021688648,0.039366383,-0.047239657,-0.014719065,-0.022678997,-0.019324189,-0.0049053235,0.04971553,0.0057656895,0.068185546,0.023632208,-0.0063134762,0.008888384,0.010082993,0.036222022,0.0016015804,0.05709363,0.061302617,5.435315E-4,0.0022963723,0.0033671875,-0.009315472,0.04560558,-0.0036240593,-0.040827148,0.03193876,-0.045729376,-0.027952606,-0.024053106,-0.055112936,-0.043253504,0.0014151162,-0.016464556,-0.03644485,0.0102129765,0.0030700825,0.018408116,0.005289084,0.008523193,-0.015474207,-0.012447452,-0.0058028274,0.06263959,0.008888384,-0.030750344,-0.031146483,0.007966122,0.03845031,-0.012738367,-0.030205652,0.0036611974,0.037757065,-0.07610834,-0.00307163,-0.049245115,0.07298874,0.010900031,-0.0018955903,-0.0012704324,-0.0072481185,-0.034488913,0.0025362226,-0.022716135,-0.017058766,-0.021490578,-0.0124784,-0.02201051,0.04978981,0.010380098,-0.011419964,0.012825022,-0.009117402,0.03800465,0.037583753,-0.054172102,-0.0012023458,-0.0012224623,0.008424158,-0.03649437,0.042585015,0.011413774,-0.05342934,0.034389876,0.02516725,0.023520794,-0.0052179024,7.694549E-4,0.026269013,0.009853975,-0.060361784,-0.0034383687,-0.009575439,-0.0019853406,-0.0022669712,0.011153808,-0.018098632,0.031493105,0.039688244,-0.032582488,-0.061352134,-0.0057594995,-0.036618162,0.019287052,0.03394422,-0.006870548,-0.003432179,0.0076937755,0.007266687,-0.025377698,0.008436537,0.014161994,-0.020029813,-0.012323658,-0.023496035,-0.02106968,0.033003386,-0.05481583,0.008752211,-0.06452125,-0.039366383,0.036791474,0.040975697,0.006065889,0.019720329,0.036172505,0.07041383,-0.04488758,0.033176698,0.028348746,-0.03711334,0.048081454,-0.04424385,0.018370979,0.007254308,2.187666E-4,-0.018408116,0.0083498815,-0.007180032,6.839599E-4,-0.05402355,-0.05739074,0.05788591,0.017108282,0.021329647,-0.025241526,0.0171578,-0.030898895,0.040480524,-0.0026213306,0.006412511,0.0032217298,-0.055756662,-0.025575768,0.03273104,-0.016155072,0.021515338,0.02673943,0.019547017,-0.005917337,-0.025798596,-0.015053309,-0.04270881,0.041272804,-0.010448184,-0.026838465,-0.03394422,-0.014681927,0.016811177,0.023681726,0.0612531,0.05461776,-0.011741828,0.043946747,-0.0050414964,-0.0066229603,0.015573242,-0.058430605,0.020747816,0.028744886,0.013679199,-0.009278335,-0.016860696,0.009940631,0.030700825,-0.018494772,0.02400359,-0.0094083175,0.03822748,0.01697211,0.0035745418,-0.0027017964,0.0060163713,2.8027655E-4,0.041594665,-0.018581428,-0.049566977,0.010039665,-0.022827549,0.015078067,-0.024833007,0.045382753,-0.019782227,0.033498563,-0.011995605,0.04384771,0.03023041,-0.028224953,-0.030651309,-0.038078927,0.040777627,-0.0123731755,0.0066539086,-0.011933709,-0.032087315,0.019472742,-0.071651764,-0.0059390003,0.033052906,0.03978728,0.014409581,-0.008337502,0.04600172,0.0053912136,0.015226619,0.025006318,-0.0032898162,-0.017652975,-0.002085923,-0.02516725,-0.021837201,0.020921128,5.2418874E-4,0.024870144,0.018445253,0.005521197,0.024424488,0.011166187,0.019819364,-0.020252641,-0.008646986,0.01600652,0.022901826,0.06813603,-0.0018661893,0.0114261545,0.07130514,0.028571576,-0.021143956,-0.015474207,-0.006845789,-0.019757466,0.06546208,0.0038840258,0.027185086,-0.03434036,-0.03607347,0.0072419285,0.04053004,-0.012558866,-0.0530332,-0.032260627,0.012998333,0.02631853,0.014780962,-0.009061695,-0.043773435,-0.025773838,-0.04350109,-0.033374768,0.02829923,-0.012447452,0.0069386344,0.0734344,-0.0040449575,-0.0068210303,-0.02010409,-0.023372242,-0.03958921,0.03802941,0.008919333,-0.04231267,-0.034587946,-0.06986914,0.06694761,-0.030329445,-0.013617301,0.0077371034,-0.047313932,-0.017405387,0.073087774,-0.0031149578,-0.004499899,0.022542825,0.03253297,-0.003429084,0.028175436,-0.03374615,0.011240464,-0.04619979,-0.016080795,0.006236105,0.040480524,-0.025303423,-0.0036147747,-0.0024325452,0.0026832274,-0.026887981,0.02594715,-0.006028751,-0.006709616,0.054865345,0.012571245,-0.036395334,0.0075699817,0.016427418,-0.015325654,-0.034216564,0.028571576,-0.0117232585,-0.035380226,0.008888384,-0.042609774,0.005121962,0.042882122,-0.01970795,0.011159997,-0.02555101,-0.01616745,0.017046386,-0.008430348,0.0011566969,0.0012069881,-0.0010653991,-0.017071145,0.012007984,0.015610379,0.028596334,0.018816635,0.02420166,0.061154064,0.01970795,6.445007E-4,0.0316169,0.0029215303,0.0018599996,0.0074957055,0.02400359,0.0024371876,-0.021181094,-0.019163257,-0.038276996,-0.015090446,-0.023161793,-0.052240923,-0.02340938,0.0035745418,-0.0028457067,0.04060432,0.007334774,-0.023830278,-0.055657625,-0.013208782,0.01598176,-0.032062557,0.057539288,0.010262494,9.586271E-4,-0.021911476,0.014161994,0.038673136,-0.00662915,-0.007662827,-0.017207317,0.012063691,0.029611442,0.009693043,-0.008609848,0.018841393,-0.014719065,-0.001565216,0.016427418,-0.0079970695,-0.037336167,-0.033028148,-0.034612704,-0.025105353,0.015449448,0.035231672,0.017256835,-0.015511345,0.02631853,0.014124855,-0.0031196,0.049467944,-0.0055119125,-0.037732307,-0.010206787,0.019844122,0.0030731775,-0.017826285,0.004920798,0.028744886,-0.035033602,-0.014892376,0.0084489165,-0.028125918,0.046868276,0.02574908,0.0061123115,-0.024053106,-0.038673136,-0.025130112,-0.007718534,0.0265166,-0.002019384,0.043030676,-0.01596938,0.020586884,-0.03845031,0.021007784,0.012614573,0.0033579029,-0.024820628,-0.035429742,-0.009012178,-0.012181295,0.009810647,0.039490175,-0.007928983,0.022889446,0.013592543,0.0021369879,0.024486385,0.015746552,-0.008764591,-0.018544288,-0.015895106,-0.02222096,-0.002615141,0.01365444,0.009278335,-0.030354204,-0.01967081,0.011580897,-0.027383156,-0.017430147,-0.03003234,-0.005595473,0.0015628949,0.026417565,0.0061123115,-0.003918069,-0.048155732,-0.018754737,-0.05030974,-0.0059761386,0.020005055,0.053528376,-0.03914355,0.023619829,0.031270277,-0.007836138,0.0138401305,0.0096063875,0.032285385,-0.01327068,0.04800718,0.0053138426,-0.053181753,-0.009235007,-0.008944091,-0.043649644,0.0044318126,-0.025068214,0.0017640595,-0.019769846,-0.022505686,0.012125588,0.0039056898,0.022740893,-0.0028039261,0.007198601,0.017690113,-0.0067219953,-0.02614522,0.013084989,0.009321662,-0.0149418935,-0.05149816,-0.024350211,0.03414229,0.011760397,0.014347685,0.0076752063,-0.0026693007,0.0059606642,0.014780962,0.025650045,0.022753274,1.8743133E-4,-0.0038159394,0.009185489,-0.010454374,-0.0062670535,1.08319444E-4,-1.9923042E-5,0.046744484,-0.025724322,-0.06313476,0.0041285185,0.020933507,0.009018367,-0.008517004,8.2245405E-4,0.0013508982,-0.037162855,-0.013728716,0.007136704,0.02046309,0.0047041588,-0.0048310473,9.859971E-5,0.016724523,-0.013159265,-0.0033455235,0.031889245,0.031270277,-0.018630944,-0.02824971,-0.0027389345,-0.021304887,-0.008597469,-0.026046185,0.050804917,0.04800718,-0.019064223,-0.013419232,0.009272144,0.014459099,-0.033572838,0.009600198,0.026194736,0.0026213306,-0.015672276,-0.02671467,-0.027086051,0.023496035,-0.058628675,0.0019760563,-0.013394473,-0.0059792334,-0.008999798,-0.002089018,-0.014731444,-0.015387551,-0.006981962,0.013443991,-0.041446116,0.039465416,0.05798495,-0.019014705,-0.011444723,0.021960994,-0.04969077,0.008114674,0.015387551,0.0026801326,-0.004444192,0.027135568,-0.047437727,0.015461828,0.011203325,-0.016724523,0.018383358,-0.006783892,0.006248485,0.009284524,0.007867087,-0.00956306,0.017083524,-0.035627812,0.009631146,-0.0014058317,0.042882122,0.015845587,0.01931181,0.011289981,-0.005774974,0.010002527,0.037558995,-0.003162928,-0.003196971,0.0071490835,-0.019943157,0.021701027,0.019608915,-0.006356804,-0.024919663,0.00994682,0.038920723,0.030180892,0.022097167,-0.012800263,-0.0048279525,0.027036535,-0.014879997,-0.017318731,0.001330008,-0.025922392,-0.004911513,0.023087516,-0.04407054,0.0055676196,0.022381892,-0.03731141,-0.014681927,0.062491037,0.014384822,-0.022976102,4.8279524E-4,0.04971553,-0.003001996,-0.05694508,0.022790411,0.015276137,-0.016934972,0.02106968,0.027977366,0.018247185,-0.028125918,0.018086253,0.028423022,-0.0052581355,-0.019571776,-0.021948615,0.0045370376,-0.010033475,-0.027036535,0.015895106,-0.03141883,-0.013369715,-0.0245978,-0.030700825,0.0037509478,0.024919663,-0.018011976,-0.006146355,0.0031582855,-0.021181094,-0.0026383523,1.4555425E-4,0.0029617632,-6.386979E-4,-0.01385251,0.009699233,0.07120611,0.016043657,-0.03144359,0.02671467,-0.002203527,0.023557931,0.013976303,-0.018779498,-0.02260472,-0.048106212,-0.04053004,0.0448133,-0.017034007,-0.017232077,-0.023099896,0.008758401,0.023954071,0.0015621212,-0.03183973,0.00566356,-0.012137968,0.0010677202,-0.013221162,0.05709363,-0.008479865,0.047264416,-0.002732745,-0.06655147,-0.049294632,-0.0070500486,0.017826285,-0.010838134,-0.018581428,-0.017529182,0.018408116,0.06759133,-0.017442526,0.012998333,0.02319893,-0.035281193,0.039886314,0.026689911,-0.015994139,0.0041996995,-0.04431813,-0.01641504,0.010299632,0.05422162,-0.024139762,-0.021923855,0.041644186,-0.032656766,-0.02810116,0.012775505,9.679116E-4,-0.058529638,1.1980421E-5,0.035231672,-0.0074152397,0.0073966705,0.046447378,-0.016625488,0.0013826203,0.017281594,-0.009569249,0.030775102,3.4623538E-4,0.06140165,0.005149816,-0.030378962,-0.0038530775,0.021292508,0.030279927,0.001824409,-0.023842657,0.05382548,-0.0023056567,0.008752211,0.021738166,-0.0472149,-0.03490981,-0.0026801326,0.02067354,0.031715933,5.357944E-4,-0.005737836,-0.00633514,0.011946088,-0.0125279175,0.033176698,0.017021628,-0.0040666214,-0.01641504,0.014372443,-0.005406688,-0.013716336,0.01772725,0.007056238,0.032062557,-0.0030577031,-0.016489314,-0.008442727,0.04038149,0.0052705146,0.03221111,-0.049666014,0.0421146,0.0031753073,0.0027605984,-0.0021818632,0.024337832,0.019324189,0.024709214,-0.030923655,4.6267878E-4,-0.028571576,-0.015486586,-0.0021385353,-0.0055892835,-0.011648983,0.008337502,-2.3288681E-4,0.014607651,-0.002395407,0.0023195837,0.015053309,0.056251835,0.0049424614,0.017702492,-0.01814815,-0.0011311645,0.0031938762,0.0052024284,-0.020438332,0.016130313,-0.0029168879,0.042411704,0.027531708,-0.036890507,0.007297636,0.022951344,0.0045091836,0.013357335,0.010999066,-0.010157269,0.013988683,0.01577131,0.0047103483,-0.005190049,-0.042411704,0.018680463,0.014669548,0.023830278,-0.045778893,-0.06422415,-0.003196971,0.047734834,0.0018770213,0.012336037,0.041248046,0.005168385,0.005874009,-0.0045153736,-0.03221111,0.021119198,1.438134E-4,0.0065796324,-0.03802941,-0.0033238595,0.009402128,0.0027079862,-0.054716796,0.0045091836,-0.0035869211,-0.046051238,9.795172E-4,-0.032582488,-0.007879466,0.0032279196,0.008238467,-0.013369715,0.00516529,0.041025218,0.018395737,0.015300895,-0.010256304,0.00798469,5.3347327E-4,0.005443826,-0.006765323,0.010646254,0.015659897,0.0023443422,0.0049826945,-0.027754538,-0.016897833,-0.006301097,-0.026194736,0.010064424,-0.0035745418,0.00662915,0.0060875528,0.01617983,-0.011549948,-0.0025501493,0.058430605,-0.018569048,-0.029784752,-5.4159726E-4,0.032186348,0.01462003,-0.012688849,-0.017900562,0.020921128,5.659691E-4,-0.004134708,0.007508085,0.012948816,0.0071862214,0.012775505,-0.013320197,0.01210083,-0.019608915,-0.0034383687,0.03958921,0.040431008,0.0015296253,0.01911374,-0.04075287,0.018507151,-0.015127584,0.0022453074,-0.013419232,-0.002166389,-0.03243394,0.0016789514,0.032384418,0.0049455566,0.024919663,-0.03822748,-0.01990602,-0.02438735,6.8434677E-4,0.0151399635,0.03312718,0.02770502,-0.014100097,-0.004580365,-0.009884923,0.022171443,0.018346218,0.0016975205,-0.014124855,0.026095701,0.032483455,0.016452176,-0.03993583,-0.0013493508,-0.015895106,0.010986687,-0.013815371,0.0047474867,3.8298662E-4,-0.022109546,0.025650045,0.017232077,-0.05130009,0.013184024,0.03283008,0.0050012637,-7.8570284E-4,0.053875,-0.024721593,-0.0081518125,0.0046082186,0.0051714797,-0.0347365,0.033993736,-0.001103311,0.036593404,0.01929943,-0.0102129765,0.0127259875,-0.025068214,0.036147747,-0.010361529,-5.1529106E-4,-0.03213683,-0.011679932,0.03711334,0.0025408647,-0.007966122,0.012391744,0.031121723,0.012961196,-0.007192411,0.01596938,0.013171645,0.009680663,-0.011840863,0.041074734,-0.04446668,-0.0070314794,0.023025619,-0.014310546,-0.02047547,-0.0187176,0.024337832,9.849332E-4,1.0609503E-4,0.0062144413,0.027185086,0.009092644,-0.018086253,0.042188875,0.054964382,-0.0036704817,0.07298874,-0.016613109,-0.026764188,-0.01910136,-0.036940027,-0.0034445585,-0.03493457,-0.0066972366,0.0037757065,0.019460361,-0.0013949997,0.007118135,0.008653176,-0.039638728,0.012026553,0.0296362,0.0041749408,0.030304685,-0.0027621458,0.0041408977,0.014904756,-0.005440731,0.028200194,0.023830278,0.0035435932,-0.023099896,-8.0465875E-4,0.03374615,0.007885655,0.013864889,0.010237735,-0.040777627,-0.022431409,0.0074833264,-0.022121925,0.0073719122,-0.025823357,-0.027828813,0.018309081,-7.315431E-4,0.03726189,-0.024313074,0.01755394,0.05263706,0.048155732,-0.0016433607,0.028571576,-0.021242991,0.013146886,-0.030057099,-0.019212775,0.038103685,-0.0018476202,-0.013394473,0.0012657901,-0.027927848,-0.029165784,-0.011036204,0.02241903,-0.038128447,0.0029122457,-2.9536392E-4,-0.032508213,0.0063227606,-0.010454374,-0.0061773034,-0.067937955,0.02245617,-0.00935261,0.010770048,-0.01851953,0.026120462,-0.012583625,-0.01929943,0.04310495,-0.0073038256,0.026269013,0.054370172,-0.032780558,-0.036568645,-0.012459831,0.05590521,-0.03745996,0.04213936,-0.0048496164,0.046942554,0.021589613,-0.0056264214,0.035627812,0.042461224,-0.021688648,-0.020487849,0.048824217,0.011809914,-0.013592543,-0.0097611295,-0.023112275,-0.037558995,0.024535902,0.007155273,-0.0020658066,0.032285385,0.0056016627,-4.278618E-4,0.0069881515,-0.0051962384,-0.021478198,-0.029586682,0.0071057556,-0.0056790337,0.0032217298,-0.0038716465,0.044367645,0.012973575,-0.012459831,0.04213936,-0.049047045,0.0148181,-0.013382094,0.0011868717,-0.002876655,0.010404857,-0.031468347,0.006536305,0.037336167,-0.021936236,0.005694508,-0.0179377,-0.011661362,0.02844778,-0.0023164887,-0.03154262,0.0071862214,-0.0012936437,0.06120358,-0.00956306,0.026467083,-0.0102129765,0.009742561,-0.026368048,0.014780962,-0.012763126,-0.0063444246,0.012961196,-0.011890381,-0.025402458,0.006734375,0.0027930944,-0.018073874,-0.024733972,-0.036766715,-0.0068767373,-0.011265222,0.012317468,0.055162452,0.016452176,-0.004407054,-2.398502E-4,-0.051002987,0.019683192,-0.010596736,0.02515487,-0.019002326,-0.00740905,0.018061494,-0.010120131,-0.021527717,-0.012490779,-0.008028018,0.01792532,-0.028323987,-0.015276137,-0.01307261,-0.0034600326,-0.043203983,-0.026987016,-0.025328182,0.0041749408,0.01578369,0.0036240593,0.009835405,-0.0065672533,6.4217957E-4,0.0060349405,-0.00478153,-0.0202774,0.030700825,-0.010887652,0.0022499496,-0.01052865,-0.0017717966,0.004236838,0.0066539086,0.02730888,0.0041625616,0.027135568,-0.0150409285,-0.005796638,-0.0023458898,0.013406852,-0.042807844,0.0013857152,-0.0118222935,0.0064248904,0.022109546,-0.0013911312,-0.015697036,0.025377698,-0.0011373542,-0.011871811,0.016675005,0.0027048914,0.008721263,0.004468951,-0.024746351,-0.009389749,6.371021E-6,0.008671746,0.030131375,0.026887981,-0.002030216,-0.008436537,-0.01130855,-0.015622759,-0.034365118,0.0018491676,0.010949548,0.013035472,-0.011549948,0.04461523,-0.041867014,0.033696633,0.014892376,-0.054766312,4.5803652E-4,-0.006301097,-0.03181497,-0.00721098,0.008950281,0.037732307,0.0037695167,-0.0021617466,0.029017232,-0.007836138,-0.015226619,0.023855036,0.030700825,0.024300694,-0.01813577,-0.021020163,-0.028819162,0.026442325,-0.046051238,-0.047115862,-0.0226171,0.026887981,-0.010039665,0.035479262,9.6017454E-4,0.013109748,-0.029883787,0.013827751,0.004168751,0.0017919132,-0.0050600655,0.004270881,-0.019175638,0.03533071,-0.013666819,-0.0039985348,0.017987218,0.0091545405,0.008517004,-0.027457433,-0.012633142,-0.03179021,0.021020163,0.0039861556,-0.012602194,0.001493261,0.03283008,-0.024424488,0.006765323,-0.0115251895,0.018581428,-0.0034816966,-0.00526742,0.019732708,0.015127584,-0.0038159394,0.003314575,0.0139391655,0.014335305,0.02535294,-0.05402355,-0.010268684,-0.027086051,-0.01501617,-0.020512609,0.027185086,0.017492043,0.022171443,0.037336167,0.006771513,-0.016080795,0.0072728773,-0.02477111,0.024610179,-0.0109186005,0.031270277,0.026368048,-0.029685717,-0.029066749,0.012144158,0.01247221,0.03800465,-0.0093092825,0.029561924,0.013394473,0.035776366,-0.03154262,-0.026987016,0.027754538,0.011147618,-0.0059575695,-0.013146886,-0.0070686177,-0.0031211474,-5.238019E-4,0.030775102,0.0354545,0.02943813,-0.005227187,0.013122127,0.003549783,-0.003528119,-0.008659366,0.008919333,0.013629681,-0.024350211,0.008517004,0.018767118,0.0027281027,0.010392477,0.027581226,-0.0033424287,-0.02184958,-0.0028905817,-0.0021060396,-0.020784954,-0.0058554397,0.0133325765,0.008257037,-0.0015288516,1.9545858E-4,0.020042192,0.0108195655,9.640431E-4,-0.005264325,-0.004821763,-0.029586682,-0.014706686,-0.016266486,-0.011772776,-0.005988518,-0.00955687,-0.03552878,-0.02671467,-0.033052906,-0.0035652572,0.0150409285,0.050804917,-8.170381E-4,-0.032681525,-0.027482191,0.0015791428,0.002571813,0.0086036585,-0.03040372,-0.018569048,0.023545552,0.018866153,0.013357335,-0.019262291,-0.011488051,0.040851906,0.032508213,0.015350413,-0.004701064]} +*8 +$4 +HSET +$101 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T15:11:18.242906Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*4 +$8 +JSON.SET +$105 +vcr:embedding:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText:0001 +$1 +$ +$19368 +{"type":"embedding","testId":"com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText","model":"text-embedding-3-small","timestamp":1765638678807,"embedding":[-0.019984918,0.053795878,0.02393721,0.0064696106,0.052147925,0.009231328,0.025962237,0.036590133,-0.031618346,-0.05362829,-0.009636333,-0.008365454,0.031869728,-0.012659907,0.027624154,-0.00293978,-0.009587454,0.017652646,-0.032232836,0.051673092,0.0014908385,-0.0036240993,0.022987543,-0.015152785,0.029411765,-0.006546422,-0.034383554,-0.018071616,0.039020166,-0.08133624,0.02045975,-0.041031227,0.023141166,0.027917435,0.039830178,0.040779844,0.060890455,-0.0322887,-0.001002039,0.028769342,-0.005931931,-0.011521703,-0.011863862,-0.0040186304,0.020836825,0.025948271,0.00698285,-0.0047518294,-0.0029432713,0.04267918,-0.033769064,-0.0037672475,-0.03443942,0.01805765,0.005125412,-0.02481705,-0.018392827,0.017163845,5.926694E-4,0.03357354,0.011954639,-0.0035822021,-0.010474275,0.011277303,-0.007206301,0.04245573,-0.016465561,0.04452265,-0.01776437,0.0060750796,-0.011277303,-0.0035368137,-5.2022235E-4,-0.042930562,0.036869448,-0.022596503,0.0110748,0.015208648,0.0018155411,0.019174907,-0.00742277,0.04692475,-0.009789956,-0.06837607,0.01624211,-0.023113234,-0.02435618,2.6032937E-4,0.02400704,0.029216245,-0.0145243285,0.0113192005,-0.01825317,-0.0027233115,-0.02484498,-0.04005363,7.052679E-4,-0.006651165,-0.047790628,0.047511313,-0.02910452,-0.020278197,-0.02075303,-0.013637506,0.03988604,-0.008882185,0.0143288085,-0.012478353,-0.021800458,0.04131054,-0.055751074,-0.07647617,0.01555779,-0.048293393,0.0031684681,-4.0413244E-4,-0.023197029,0.006570862,-0.014217082,-0.016186247,0.019733535,0.017736439,-0.010523155,0.0043922127,-0.0110259205,-0.036645997,0.021255795,-0.025305849,0.007220267,-0.009294174,0.052147925,0.0019778924,0.036366682,-0.0141263055,-0.0337132,0.002847257,-0.03429976,0.027065527,-0.011528686,-0.06854366,-0.011856879,-0.02520809,0.016004693,-0.009440813,0.016591253,0.0027058544,-0.0015737598,0.04206469,0.021199932,0.01516675,0.025990168,0.0071993186,0.008323558,-0.009608402,-0.02643707,8.252856E-4,0.045137145,-0.018392827,0.0027494973,-0.036645997,-0.040221218,0.013190604,0.00881934,-0.028825205,-0.032372493,-0.014803642,-0.04309815,-0.022624435,-0.041254677,-0.009377968,-0.015278476,0.0022188006,0.01923077,0.02004078,-0.021493213,-0.0070666443,0.027163288,0.03114351,0.025319815,0.010250824,0.003589185,-0.026548795,0.024104798,0.027428634,0.026870007,-0.015906932,-0.023476342,-0.021158036,-0.027889503,0.027791744,-0.05904698,0.12122228,-0.014957265,0.03256801,0.043600917,-0.02293168,-0.00284202,0.021507178,-0.0708899,-0.019021284,-0.004112899,-0.004469024,0.012059382,-0.029411765,0.008616837,0.030054187,0.0037078934,-0.0036345734,-0.023434445,-0.005652617,0.012213005,-0.0054256744,-0.01844869,5.603737E-4,0.020822858,-0.0035996593,0.014510362,0.012108262,0.0023724234,0.012499302,-0.01756885,0.009496676,0.05887939,-0.0050136866,-1.4554878E-4,0.037372213,0.012415508,-0.05566728,0.0065918104,-0.006309005,0.03435562,-0.015236579,-0.009580471,-0.03050109,0.016702978,0.020166472,-0.003934836,-0.04069605,0.022526674,0.0025888917,-0.045220938,-0.016018659,-0.03974638,0.014817608,-0.005862103,0.02393721,-0.021213898,-0.01597676,0.050081,0.02133959,-0.010788503,-0.011954639,0.018309033,-0.06619742,-0.026562762,-0.030417295,0.012897324,0.03290319,0.023755657,0.04968996,-0.034076307,-0.073627174,0.05326518,0.02621362,-0.050583765,-0.021130104,-0.0071923356,0.011423943,0.012115245,-0.0076951007,-0.0303335,-0.04362885,0.037400145,-0.021255795,0.033769064,0.024928775,-0.02143735,-0.014901402,-0.011570583,0.048572704,-0.0019691638,-0.06223116,0.006406765,0.014265963,0.0018871153,0.0418971,0.023252891,-7.0483144E-4,0.030808335,0.0040291045,-0.033796996,-0.05209206,-3.4194143E-4,-0.0058027483,0.008630803,-0.033238366,-0.027596224,-0.013050947,-0.019091113,0.0042176414,0.11004972,0.044355065,-0.043433327,0.015515893,-0.009210379,0.0035245935,-0.0034111224,0.0020389922,0.024970671,0.019468186,-0.039159823,-0.02783364,-4.791981E-4,-0.025864476,-0.033182506,-0.021297693,0.040193286,-0.0107326405,0.034998044,-0.02871348,0.029830735,-0.032651808,0.03427183,0.003503645,-0.024314284,-0.023588067,-0.034383554,-0.04608681,-0.0043572984,-0.0072412156,0.038154293,0.0019813837,0.022065807,0.0025854004,0.037176695,-0.053795878,-0.0022973577,-0.02400704,-0.021102173,-0.036227025,0.035808057,-0.0066895704,-0.013791129,-0.0074786325,0.008100106,0.016256075,-0.02604603,0.02712139,-0.019286633,-0.040332943,-0.0202363,-0.029188313,-0.0036101334,-0.06859952,0.010816434,0.0031545025,0.025417574,-0.009287191,-0.018295066,-0.016130384,-0.04027708,0.0016453341,0.008798392,0.0038300932,0.04988548,-0.006399782,-0.021074241,0.0029607285,0.018322999,-0.012247919,0.0021978521,0.03745601,-0.06005251,0.0024457432,0.002459709,0.025305849,0.017191777,0.052231718,0.028503994,0.05387967,0.0143288085,0.054466233,-0.038210157,-0.010579018,0.056952126,-0.011214457,-0.0067140106,0.0040535447,-0.08234177,0.0016208941,-0.0036520306,-0.0051289033,-8.423063E-4,0.009357019,0.017778337,-0.038266018,-0.050891012,-0.02192615,0.019482153,0.031618346,3.179379E-4,-0.006801296,-0.04703648,-0.05404726,-0.015809173,0.022428915,0.0017107982,0.006291548,0.00837942,-0.07513547,0.008051226,-0.0045388527,0.038377743,0.014140272,-0.006672113,-0.023476342,-0.033433888,-0.014293894,0.009070722,0.04086364,0.02724708,0.0017465854,-0.07401821,-0.08949221,0.0014594157,0.072063014,0.018630244,0.0010788504,-0.009426848,-0.051393777,-0.010376515,-0.007653204,0.0023602033,0.0022030892,0.004549327,0.01578124,0.028741412,0.00943383,-0.0024492347,-0.06005251,-0.027596224,-0.0036555221,-0.03312664,0.047427516,-0.07926931,0.004943858,-0.019468186,0.0010421904,0.01656332,-0.052734483,0.017066086,-0.019565945,-0.014999162,0.005910983,-8.855127E-4,-0.024453942,-0.021968046,0.020739065,0.0038894475,-0.0011024175,-0.04346126,0.0045807497,-0.025668956,0.042539522,0.10708899,-0.037260488,0.048740294,0.042120554,0.038433608,-0.042651247,-0.023895314,0.02969108,-0.0050276523,0.02604603,0.008190883,-0.001032589,-0.004469024,-0.008679682,0.01883973,-0.0047343723,0.007681135,0.025696889,0.0026325346,-0.048824087,0.009070722,0.06340428,-0.022945646,-0.0061414167,0.01633987,0.010418412,-0.022359086,0.012143176,0.022722194,-0.017471092,0.016647115,0.026492933,-0.0012647688,-0.052706555,0.04508128,-0.033210434,0.045444388,0.0151946815,-0.012254902,0.08960393,-0.025417574,0.04692475,-0.0313111,-0.0093709845,0.02452377,0.006089045,0.03290319,0.047539245,-0.015795207,0.005467572,0.0022030892,-0.011249372,-0.021912184,0.020110609,-0.019286633,0.025878442,-0.0028699513,0.018909559,-0.0010465547,-0.0027791744,0.009643316,0.011968605,-0.036199097,0.023476342,-0.010027373,0.0076601864,0.011402994,-0.0017806268,0.010551087,0.036003575,0.0035926765,-0.04871236,-0.021912184,0.026842076,-0.005020669,-0.006965393,0.029970393,0.041031227,-0.033852857,0.014384671,-0.0048426064,0.033210434,0.03156248,0.0053767944,0.00862382,0.025641026,0.013574661,-0.020208368,0.05287414,-0.036785655,0.06837607,-0.021618905,-0.019049214,0.0100553045,0.0057049887,-0.00142712,-0.019007318,0.031758003,0.01597676,0.02131166,-0.014677951,-0.021814425,0.026227586,-0.030584883,-0.037707392,-0.015208648,-0.028392268,-0.02004078,-0.0140355285,-0.003885956,-0.010592984,0.06368359,-0.03413217,-0.028629685,0.016884532,-0.021353556,0.001876641,-0.015613653,6.489686E-4,0.008351489,-0.043042287,0.025710855,0.010160047,-0.028657617,0.014056478,-0.0057887826,0.023266857,0.032540083,0.035389084,-0.022694264,-0.0029066114,0.0070841014,0.046617508,0.02383945,-0.023252891,0.037260488,-0.025641026,0.025194123,-0.0016208941,-0.04251159,-0.0043922127,0.06223116,-0.01175912,0.0045213955,0.0026744315,-0.020306129,-0.033769064,-0.012324731,-0.008491145,-0.06334842,-0.052287582,0.013951735,-0.021088207,-0.005628177,-0.0010832147,-0.022652365,-0.0077928607,7.925535E-4,-0.013029998,-0.006682588,-0.02849003,0.025459472,-0.02663259,-0.027205184,-0.04684096,-3.5416143E-4,0.0081839,4.5934063E-5,4.0915137E-4,0.0013415801,0.0018591839,0.025571197,-0.0044061784,0.02332272,0.012527233,0.020836825,0.040360875,0.011577565,0.021451315,-0.011409977,0.06413049,0.0036066421,0.0071993186,0.039439138,0.029272107,-0.001379113,-0.021130104,-0.030445227,0.043321602,0.00399419,0.017261606,0.012834478,0.009161499,-0.009517625,0.030640746,0.0027425145,0.020306129,-0.0145243285,-0.012946204,0.0032051282,-0.025599128,0.024956707,-2.7691366E-4,0.0141263055,0.021381486,0.026171722,0.0011626446,-0.039215688,0.071951285,-0.0051149377,0.0135537125,0.02332272,0.039411206,-0.017457126,-0.022205463,-0.006399782,0.022428915,0.0010168776,-0.04362885,-0.061169766,0.008945031,-0.00455631,-0.016940394,-0.008637786,0.02143735,0.023434445,4.730881E-4,-0.0025295375,0.018420758,-0.0104393605,0.024216523,0.0074856156,0.027819674,0.038377743,0.016535388,-0.004971789,-0.0037986704,0.0057713254,-0.007653204,-0.017694542,0.0070736273,-0.012534216,0.025920339,-0.017917993,0.022778057,-0.027819674,-0.013469918,-0.0026988715,-0.012729736,0.040975366,-0.009364002,-0.009412882,-0.0044934643,0.052231718,-1.02015074E-4,-0.0042246245,-0.02075303,0.016186247,-0.023881348,-0.014154237,0.033210434,0.019202838,-0.012324731,-0.007960449,-0.007911569,0.032037318,-0.0021524637,-0.0044480753,0.004549327,0.02065527,-0.0046505784,0.012820513,-0.02374169,0.017904028,-0.018937489,-0.007778895,-0.0011547889,-0.016353834,-0.026311379,0.007750964,-0.035584603,-0.01786213,0.0152924415,0.010411429,0.004699458,-9.601419E-4,-0.02234512,0.011556617,0.012932238,0.047986146,0.01702419,-0.03848947,-0.0029048657,-0.014412602,-0.03309871,0.022875817,-8.545263E-4,0.034970112,-0.04765097,0.03332216,0.018183341,-0.0010814689,-0.046980616,-0.04248366,0.0020966008,0.0034390537,0.026004134,-0.023141166,-0.001658427,-0.023238925,0.03234456,0.003697419,-0.0066965534,0.014887436,-0.023210993,0.014971231,0.0033343108,-0.03491425,0.025766717,-0.040249147,-0.0036694878,-0.007171387,-0.050360315,-0.022093737,-0.03631082,-0.028769342,-0.0018993352,-0.033405956,0.02295961,-0.028559856,4.7701594E-4,0.0197475,-0.022792023,-0.054550026,0.014251997,0.0068536676,3.9038496E-4,0.042790905,0.035612535,0.0014445771,-0.001208906,0.027037596,-0.020361992,-0.03670186,-0.051142395,-0.01886766,-0.03474666,-6.0881727E-4,0.036171164,0.017820233,0.008868219,0.01825317,1.6006002E-4,-0.031199375,0.023979107,0.014929334,0.002812343,9.749805E-4,-0.0048426064,-0.0030654713,0.025682922,0.0050800233,-0.013986649,0.02403497,0.0075973407,-0.020557512,0.03069661,0.005921457,-0.034215964,0.03335009,-0.011891793,-0.0054326574,0.036645997,-0.0043747555,-0.029830735,0.0010465547,-0.0065080165,0.022331154,0.008135021,0.0039069047,0.01822524,-0.041115023,-0.005938914,3.7969247E-4,0.018686106,0.009000894,0.019398358,0.00291534,0.03848947,0.016577287,0.029272107,0.007548461,0.010104184,-0.009580471,-0.008058209,-0.024886878,-0.014286911,0.029160382,0.020054746,0.002180395,-0.009692196,0.036785655,0.0015859798,-0.0071329814,0.0438523,0.001002039,0.0051568346,0.026032066,0.012052399,0.0013433258,-0.015823139,0.01352578,0.015962796,0.03251215,-0.013574661,-0.017554885,-0.016912462,-0.0027756828,-0.0061658565,-0.03474666,0.0021838865,0.047232,-0.013225518,0.031059718,-0.012638959,-0.001925521,0.011731188,0.024509804,0.010949109,-0.03589185,-0.0014061715,-0.006012234,0.026059996,0.020669237,-0.002599366,0.005404726,-0.016256075,-0.033545613,-0.010816434,0.01903525,0.0021594465,0.010942126,0.019733535,-0.018630244,0.008931065,2.1384978E-4,-0.004231607,0.02562706,0.012506285,-0.0069130217,0.012918273,-0.009685213,-0.0089171,-0.029774873,-0.020361992,0.008023295,-0.011256354,0.022987543,0.028531926,0.0877046,-0.012366627,0.032791466,-3.227386E-4,0.006769873,0.056393497,0.0013983158,0.07776102,-0.009587454,-0.024565667,0.0037009106,0.03533322,0.034998044,0.017107982,-0.004626138,0.021618905,-1.07852306E-4,-0.031981453,0.006399782,-0.0034966622,-0.011640411,0.02006871,-0.018895593,-0.009496676,-0.012268867,-0.017792301,-0.010348584,-0.009063739,0.0019883665,0.09083291,-0.036645997,0.005750377,0.02124183,-0.005100972,0.012066365,0.049215127,5.896144E-4,-0.015418133,0.0139657,-0.030026255,-0.02533378,-0.0014733814,-0.059158705,0.022526674,0.012974136,5.167309E-4,-0.03885258,0.054550026,-0.00394531,0.030026255,0.012233953,0.02092062,0.012380593,9.1475336E-4,-0.008637786,0.022638401,0.024314284,0.03572426,0.04686889,0.026381208,-0.02114407,0.002101838,-0.017205743,0.013016033,0.013518798,-0.017931959,0.0059424057,-0.019174907,-0.018937489,-0.023406513,-0.009852801,-0.012394559,0.011507737,-0.019216804,0.012645941,0.016353834,-0.018169375,-0.05047204,0.00575736,-0.019146975,-0.027693983,0.002126278,0.030026255,0.048377186,-0.015013128,-0.0021280237,0.0032417881,0.01536227,0.027065527,0.026381208,-0.04745545,-0.025305849,0.018211273,-0.010425395,0.008965979,-0.035752192,-0.0049473494,0.03927155,-0.029802805,0.011004971,0.0045947153,-0.0057713254,-0.017512988,0.0051847664,-0.008512095,0.03413217,0.026492933,-0.026451036,-0.012492319,-0.011842914,0.00580624,0.008197866,0.031785935,-0.02452377,-0.005666583,-0.004371264,-0.019188872,-0.02949556,-0.02396514,-0.003770739,0.011773085,0.02783364,-0.0020180438,0.027288979,0.0099575445,0.025878442,0.0029851685,0.008931065,0.0106348805,-0.0047343723,-0.04050053,0.013099827,0.011654377,-0.01744316,-0.002726803,0.0065918104,-0.010111167,-0.0020808894,-0.02124183,0.019244734,0.0059738285,-4.6130453E-4,-0.02192615,-0.012932238,0.0032557538,0.025808614,0.0351377,-0.0019761466,0.008253729,0.022708228,-0.051226187,0.0022659348,-0.0029607285,-0.019607844,-0.018909559,-0.008903134,0.008267694,-0.004566784,0.016284006,-0.036143232,0.007541478,-0.022359086,0.02822468,0.013874923,-0.013414055,0.01411234,-0.040975366,0.0046540694,0.0044934643,0.0099575445,-0.021800458,1.9093731E-4,0.047343723,0.008463214,-0.022233395,0.02585051,0.018239204,-0.002550486,-0.012659907,-0.015278476,0.014440534,-0.012415508,0.015529859,-0.010572035,0.01867214,0.0028682058,0.02442601,0.014468465,-0.038154293,0.0013799857,0.01011815,-0.0033552595,-0.0118987765,-0.00717837,-0.01795989,0.05304173,0.019188872,0.024509804,-0.002501606,0.039215688,0.036813587,0.0027966315,-0.040025696,0.033182506,0.0019080638,-0.009287191,0.0010893246,-0.0019796381,0.015418133,0.0022153093,0.015124854,0.006790822,-0.014175186,0.0034826966,0.01416122,-0.03552874,0.052455172,-5.7041156E-4,0.008882185,-0.02335065,0.003405885,0.04083571,0.02111614,-0.0279314,0.085302494,-0.0076252725,-0.01795989,0.0048670466,-0.04153399,0.005069549,-0.009475728,-0.009769008,-0.011863862,-0.005369812,-0.0072900956,0.009168482,-0.014279928,-0.023448411,-0.03589185,0.011081783,-0.016730908,0.05644936,-0.033992514,-0.017163845,0.035389084,0.0050765323,-0.002550486,0.0038370762,-0.041170884,-0.047790628,-0.021227865,-0.02111614,0.014342774,0.017596783,-0.0016505712,-0.007848724,-0.012904307,0.037819117,0.0031352998,9.1300765E-4,-0.02092062,-0.012659907,0.01867214,-0.029635215,0.008407352,0.025780683,-0.0026971258,-0.005457097,-0.012185073,0.020627338,0.0084981285,-0.051952403,-0.020613374,0.009175465,-0.01320457,-0.0036520306,-0.008777442,-0.0020128065,0.017512988,-0.038405675,0.034970112,0.006466119,0.0127716325,-0.024998603,-4.6130453E-4,-0.010844367,-0.02910452,-0.035780124,-0.008009329,-0.033042848,-0.031758003,0.0019848752,0.016493492,-0.013253449,0.013958718,0.01766661,-0.0011530431,-0.017806267,-0.002011061,0.012310765,0.03466287,0.005921457,0.024691358,0.03212111,0.021130104,0.05921457,-0.02322496,0.03452321,-0.012219988,0.019565945,0.034076307,-0.008665717,0.03991397,0.05323725,-0.020585442,3.7969247E-4,-3.8056533E-4,0.002175158,0.0084981285,-0.0030864198,-0.0014952028,-0.0089171,0.002957237,0.013951735,-0.035081837,0.0067559076,-0.015082956,-0.0015240071,0.018755935,0.011333166,-0.0027844114,0.006029691,-0.019663706,-5.0189235E-4,-0.01252025,-0.0018033211,-0.01633987,0.02254064,-0.010579018,-0.0057783085,-0.004479498,-0.036562204,0.00872158,-0.031227306,0.018434724,0.038349815,-0.020515613,-0.015697448,0.035165634,0.014084409,0.0077439807,-0.0023881348,-0.013888889,-0.048740294,0.004025613,-0.021995978,0.019579912,0.011954639,-7.633128E-4,-0.0015030585,0.017736439,-0.013176639,-4.1787993E-5,-0.019635774,0.012206022,0.0155857215,-0.0076182894,0.010788503,0.05787386,-0.008505112,-0.011591531,0.021563042,0.0022746634,0.009706162,-0.013602592,-0.031813864,-0.03270767,0.024914809,0.02114407,-0.011521703,0.04223228,-0.020320093,0.013155689,-0.011675325,-0.007366907,0.017261606,-0.0118987765,-0.018658176,0.021088207,0.01822524,-0.01604659,-0.0056630913,-0.03267974,-0.01594883,-0.004591224,0.021577006,0.017820233,-0.025808614,6.600976E-5,-0.015334339,-0.020794928,-0.011242389,0.035863917,0.0130649125,-0.020892687,0.0064346963,0.039606728,0.045500252,-0.021227865,-0.027261047,-0.02413273,8.309592E-4,-0.011095749,-0.019593878,0.03404838,0.04692475,0.01825317,0.011766102,-0.038210157,0.018085582,0.006298531,0.029355902,-0.04186917,0.026646556,-0.04784649,0.024789117,0.0017203997,-0.021521144,-0.016968327,0.002896137,-0.0022449864,0.012213005,0.020417854,-0.06463326,0.0151946815,0.041115023,0.009091671,0.013462935,0.032009386,0.032791466,-0.0030829282,0.01698229,0.008323558,0.022331154,-0.013700352,-0.011926708,0.0065603876,-0.042790905,0.006686079,-0.013798112,0.011877828,-0.0025120804,0.015250545,0.022135634,0.029607285,-0.003510628,0.00575736,-0.049326852,0.014084409,-0.027163288,-0.021283727,-0.010753589,-0.013532763,0.003648539,-0.0015370998,-0.0021646835,0.015446064,0.005575806,0.012415508,0.002604603,0.010160047,0.0151946815,0.009608402,0.04949444,-0.03848947,0.016228143,0.00717837,0.0061449083,-0.010984023,-0.03751187,-9.1213477E-4,0.0144545,-0.0033744622,0.003273211,0.0065010334,0.0057818,0.0060436567,0.019216804,-0.009168482,-0.026478969,-0.0024928774,0.027275013,-0.025892409,0.02721915,-0.00150044,0.020697167,0.0021402435,-7.9997274E-4,0.0023916261,-0.0015467013,0.014677951,-0.004517904,0.018197307,-0.038182225,-0.018951455,-0.00436079,-0.021912184,-0.0070526786,0.011528686,0.013057929,-0.015501928,-0.015823139,0.0046051894,-0.0049997205,0.034634937,0.003199891,-0.029774873,0.039048098,0.007415787,-0.019160941,-0.0058586113,-0.023532204,-0.04290263,0.003351768,0.0027355314,0.02052958,0.029188313,-0.044550583,-7.4061856E-4,3.969314E-4,-0.015683481,0.028308474,-0.04231607,0.021772526,0.0033849366,0.011787051,-0.021702698,0.012122228,0.02913245,0.0019028267,0.013246466,0.03888051,0.021828389,0.043768503,0.05681247,4.3184563E-4,-0.031394895,0.036981173,-0.050974805,0.0069549186,0.004936875,0.01276465,0.019328529,0.055779006,0.022470811,-0.01578124,-0.0046540694,-0.013002067,0.00962935,-0.025682922,0.00595288,0.029830735,-0.012240936,-0.018309033,-0.009231328,-0.0076951007,-0.01578124,-0.01036255,0.048963744,0.015348305,0.01844869,0.020208368,-0.019970952,-0.0040291045,-0.0049892464,0.049131334,0.008030278,-0.009007877,0.007841741,0.011745154,0.02295961,0.004821658,-0.03156248,0.0027460058,0.04806994,-0.041366406,0.014705882,-0.019202838,-0.023448411,-0.011158595,0.0014245014,0.0070945756,-0.027372772,0.020278197,0.02396514,0.017303502,-0.024872912,-0.0057294285,0.014203117,-0.030445227,-0.020864757,-9.69416E-5,-0.005593263,-0.02234512,0.037400145,-0.011912743,0.021297693,-0.0044655325,-0.006120468,-0.0056630913,-0.005813223,0.0015021856,0.0045877327]} +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T15:11:18.810264Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$85 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T15:13:28.379279Z +$5 +error +$82 +Expected a com.google.gson.JsonObject but was com.google.gson.JsonArray; at path $ +$6 +status +$6 +FAILED +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$93 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T15:13:28.949012Z +$5 +error +$82 +Expected a com.google.gson.JsonObject but was com.google.gson.JsonArray; at path $ +$6 +status +$6 +FAILED +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T15:13:29.048627Z +$5 +error +$82 +Expected a com.google.gson.JsonObject but was com.google.gson.JsonArray; at path $ +$6 +status +$6 +FAILED +*8 +$4 +HSET +$97 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T15:13:29.162958Z +$5 +error +$82 +Expected a com.google.gson.JsonObject but was com.google.gson.JsonArray; at path $ +$6 +status +$6 +FAILED +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T15:13:29.708584Z +$5 +error +$82 +Expected a com.google.gson.JsonObject but was com.google.gson.JsonArray; at path $ +$6 +status +$6 +FAILED +*8 +$4 +HSET +$101 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T15:13:29.817103Z +$5 +error +$82 +Expected a com.google.gson.JsonObject but was com.google.gson.JsonArray; at path $ +$6 +status +$6 +FAILED +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T15:13:29.913614Z +$5 +error +$82 +Expected a com.google.gson.JsonObject but was com.google.gson.JsonArray; at path $ +$6 +status +$6 +FAILED +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$85 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T15:15:40.278041Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$93 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T15:15:40.866231Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T15:15:40.974084Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$97 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T15:15:41.078139Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T15:15:41.609164Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$101 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T15:15:41.721662Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T15:15:41.816593Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$85 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T15:17:30.882256Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$93 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T15:17:41.776750Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T15:17:41.870959Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$97 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T15:17:41.975842Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T15:17:42.632137Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$101 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T15:17:42.736800Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T15:17:42.847040Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$85 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T16:28:47.788837Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$93 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T16:28:58.760576Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T16:28:58.857239Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$97 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T16:28:58.963075Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T16:29:10.000357Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$101 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T16:29:10.105840Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.LangChain4JVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T16:29:10.201759Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 diff --git a/demos/langchain4j-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.manifest b/demos/langchain4j-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.manifest new file mode 100644 index 0000000..7f8bb72 --- /dev/null +++ b/demos/langchain4j-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.manifest @@ -0,0 +1,2 @@ +file appendonly.aof.1.base.rdb seq 1 type b +file appendonly.aof.1.incr.aof seq 1 type i diff --git a/demos/langchain4j-vcr/src/test/resources/vcr-data/dump.rdb b/demos/langchain4j-vcr/src/test/resources/vcr-data/dump.rdb new file mode 100644 index 0000000..dbf89da Binary files /dev/null and b/demos/langchain4j-vcr/src/test/resources/vcr-data/dump.rdb differ diff --git a/demos/spring-ai-vcr/README.md b/demos/spring-ai-vcr/README.md new file mode 100644 index 0000000..576d6a3 --- /dev/null +++ b/demos/spring-ai-vcr/README.md @@ -0,0 +1,223 @@ +# Spring AI VCR Demo + +This demo shows how to use the VCR (Video Cassette Recorder) test system with Spring AI models. VCR records LLM/embedding API responses to Redis and replays them in subsequent test runs, enabling fast, deterministic, and cost-effective testing. + +## Features + +- Record and replay Spring AI `EmbeddingModel` responses +- Record and replay Spring AI `ChatModel` responses +- Declarative `@VCRTest` and `@VCRModel` annotations +- Automatic model wrapping via JUnit 5 extension +- Redis-backed persistence with automatic test isolation + +## Quick Start + +### 1. Annotate Your Test Class + +```java +import com.redis.vl.test.vcr.VCRMode; +import com.redis.vl.test.vcr.VCRModel; +import com.redis.vl.test.vcr.VCRTest; + +@VCRTest(mode = VCRMode.PLAYBACK_OR_RECORD) +class MySpringAITest { + + @VCRModel(modelName = "text-embedding-3-small") + private EmbeddingModel embeddingModel = createEmbeddingModel(); + + @VCRModel + private ChatModel chatModel = createChatModel(); + + // Models must be initialized at field declaration time, + // not in @BeforeEach (VCR wrapping happens before @BeforeEach) +} +``` + +### 2. Use Models Normally + +```java +@Test +void shouldEmbedText() { + // First run: calls real API and records response + // Subsequent runs: replays from Redis cassette + EmbeddingResponse response = embeddingModel.embedForResponse( + List.of("What is Redis?") + ); + + assertNotNull(response.getResults().get(0)); +} + +@Test +void shouldGenerateResponse() { + String response = chatModel.call("Explain Redis in one sentence."); + + assertNotNull(response); +} +``` + +## VCR Modes + +| Mode | Description | API Key Required | +|------|-------------|------------------| +| `PLAYBACK` | Only use recorded cassettes. Fails if cassette missing. | No | +| `PLAYBACK_OR_RECORD` | Use cassette if available, record if not. | Only for first run | +| `RECORD` | Always call real API and record response. | Yes | +| `OFF` | Bypass VCR, always call real API. | Yes | + +### Setting Mode via Environment Variable + +Override the annotation mode at runtime without changing code: + +```bash +# Record new cassettes +VCR_MODE=RECORD ./gradlew :demos:spring-ai-vcr:test + +# Playback only (CI/CD, no API key needed) +VCR_MODE=PLAYBACK ./gradlew :demos:spring-ai-vcr:test + +# Default behavior from annotation +./gradlew :demos:spring-ai-vcr:test +``` + +## Running the Demo + +### With Pre-recorded Cassettes (No API Key) + +The demo includes pre-recorded cassettes in `src/test/resources/vcr-data/`. Run tests without an API key: + +```bash +./gradlew :demos:spring-ai-vcr:test +``` + +### Recording New Cassettes + +To record fresh cassettes, set your OpenAI API key: + +```bash +OPENAI_API_KEY=your-key VCR_MODE=RECORD ./gradlew :demos:spring-ai-vcr:test +``` + +## How It Works + +1. **Test Setup**: `@VCRTest` annotation triggers the VCR JUnit 5 extension +2. **Container Start**: A Redis Stack container is started with persistence enabled +3. **Model Wrapping**: Fields annotated with `@VCRModel` are wrapped with VCR proxies +4. **Recording**: When a model is called, VCR checks for existing cassette: + - **Cache hit**: Returns recorded response + - **Cache miss**: Calls real API, stores response as cassette +5. **Persistence**: Cassettes are saved to `vcr-data/` directory via Redis persistence +6. **Cleanup**: Container stops, data persists for next run + +## Cassette Storage + +Cassettes are stored in Redis JSON format with keys like: + +``` +vcr:embedding:MyTest.testMethod:0001 +vcr:chat:MyTest.testMethod:0001 +``` + +Data persists to `src/test/resources/vcr-data/` via Redis AOF/RDB. + +## Test Structure + +``` +demos/spring-ai-vcr/ +β”œβ”€β”€ src/test/java/ +β”‚ └── com/redis/vl/demo/vcr/ +β”‚ └── SpringAIVCRDemoTest.java +└── src/test/resources/ + └── vcr-data/ # Persisted cassettes + β”œβ”€β”€ appendonly.aof + └── dump.rdb +``` + +## Configuration Options + +### @VCRTest Annotation + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `mode` | `PLAYBACK_OR_RECORD` | VCR operating mode | +| `dataDir` | `src/test/resources/vcr-data` | Cassette storage directory | +| `redisImage` | `redis/redis-stack:latest` | Redis Docker image | + +### @VCRModel Annotation + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `modelName` | `""` | Optional model identifier for logging | + +## Spring AI Specifics + +### Supported Model Types + +- `org.springframework.ai.embedding.EmbeddingModel` +- `org.springframework.ai.chat.model.ChatModel` + +### Creating Models for VCR + +```java +private static String getApiKey() { + String key = System.getenv("OPENAI_API_KEY"); + // In PLAYBACK mode, use a dummy key (VCR will use cached responses) + return (key == null || key.isEmpty()) ? "vcr-playback-mode" : key; +} + +private static EmbeddingModel createEmbeddingModel() { + OpenAiApi api = OpenAiApi.builder().apiKey(getApiKey()).build(); + OpenAiEmbeddingOptions options = OpenAiEmbeddingOptions.builder() + .model("text-embedding-3-small") + .build(); + return new OpenAiEmbeddingModel(api, MetadataMode.EMBED, options, + RetryUtils.DEFAULT_RETRY_TEMPLATE); +} + +private static ChatModel createChatModel() { + OpenAiApi api = OpenAiApi.builder().apiKey(getApiKey()).build(); + OpenAiChatOptions options = OpenAiChatOptions.builder() + .model("gpt-4o-mini") + .temperature(0.0) + .build(); + return OpenAiChatModel.builder() + .openAiApi(api) + .defaultOptions(options) + .build(); +} +``` + +## Best Practices + +1. **Initialize models at field declaration** - Not in `@BeforeEach` +2. **Use dummy API key in PLAYBACK mode** - VCR will use cached responses +3. **Commit cassettes to version control** - Enables reproducible tests +4. **Use specific test names** - Cassette keys include test class and method names +5. **Re-record periodically** - API responses may change over time +6. **Set temperature to 0** - For deterministic LLM responses during recording + +## Troubleshooting + +### Tests fail with "Cassette missing" + +- Ensure cassettes exist in `src/test/resources/vcr-data/` +- Run once with `VCR_MODE=RECORD` and API key to generate cassettes + +### API key required error + +- In `PLAYBACK` mode, use a dummy key: `"vcr-playback-mode"` +- VCR won't call the real API when cassettes exist + +### Tests pass but call real API + +- Verify models are initialized at field declaration, not `@BeforeEach` +- Check that `@VCRModel` annotation is present on model fields + +### Spring AI version compatibility + +- VCR wrappers implement Spring AI interfaces +- Test with your specific Spring AI version for compatibility + +## See Also + +- [LangChain4J VCR Demo](../langchain4j-vcr/README.md) +- [VCR Test System Documentation](../../README.md#-experimental-vcr-test-system) diff --git a/demos/spring-ai-vcr/build.gradle.kts b/demos/spring-ai-vcr/build.gradle.kts new file mode 100644 index 0000000..57f5f20 --- /dev/null +++ b/demos/spring-ai-vcr/build.gradle.kts @@ -0,0 +1,68 @@ +plugins { + java +} + +group = "com.redis.vl.demo" +version = "0.12.0" + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenCentral() + maven { + url = uri("https://repo.spring.io/milestone") + } +} + +dependencies { + // RedisVL Core (includes VCR support) + implementation(project(":core")) + + // SpotBugs annotations + compileOnly("com.github.spotbugs:spotbugs-annotations:4.8.3") + + // Spring AI 1.1.0 + implementation(platform("org.springframework.ai:spring-ai-bom:1.1.0")) + implementation("org.springframework.ai:spring-ai-openai") + + // Redis + implementation("redis.clients:jedis:5.2.0") + + // Logging + implementation("org.slf4j:slf4j-api:2.0.16") + runtimeOnly("ch.qos.logback:logback-classic:1.5.15") + + // Testing + testImplementation("org.junit.jupiter:junit-jupiter:5.11.4") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testCompileOnly("com.github.spotbugs:spotbugs-annotations:4.8.3") + + // TestContainers for integration tests + testImplementation("org.testcontainers:testcontainers:1.19.3") + testImplementation("org.testcontainers:junit-jupiter:1.19.3") +} + +tasks.withType { + options.encoding = "UTF-8" + options.compilerArgs.addAll(listOf( + "-parameters", + "-Xlint:all", + "-Xlint:-processing" + )) +} + +tasks.withType { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } + // Pass environment variables to tests + environment("OPENAI_API_KEY", System.getenv("OPENAI_API_KEY") ?: "") +} diff --git a/demos/spring-ai-vcr/src/test/java/com/redis/vl/demo/vcr/SpringAIVCRDemoTest.java b/demos/spring-ai-vcr/src/test/java/com/redis/vl/demo/vcr/SpringAIVCRDemoTest.java new file mode 100644 index 0000000..81c8f93 --- /dev/null +++ b/demos/spring-ai-vcr/src/test/java/com/redis/vl/demo/vcr/SpringAIVCRDemoTest.java @@ -0,0 +1,198 @@ +package com.redis.vl.demo.vcr; + +import static org.junit.jupiter.api.Assertions.*; + +import com.redis.vl.test.vcr.VCRMode; +import com.redis.vl.test.vcr.VCRModel; +import com.redis.vl.test.vcr.VCRTest; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.document.MetadataMode; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.OpenAiEmbeddingModel; +import org.springframework.ai.openai.OpenAiEmbeddingOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.ai.retry.RetryUtils; + +/** + * VCR Demo Tests for Spring AI. + * + *

This demo shows how to use VCR (Video Cassette Recorder) functionality to record and replay + * LLM API calls with Spring AI. + * + *

How to Use VCR in Your Tests:

+ * + *
    + *
  1. Annotate your test class with {@code @VCRTest} + *
  2. Annotate your model fields with {@code @VCRModel} + *
  3. Initialize your models in {@code @BeforeEach} - VCR wraps them automatically + *
  4. Use the models normally - first run records, subsequent runs replay + *
+ * + *

Benefits:

+ * + *
    + *
  • Fast, deterministic tests that don't call real LLM APIs after recording + *
  • Cost savings by avoiding repeated API calls + *
  • Offline development and CI/CD without API keys + *
  • Recorded data persists across test runs + *
+ */ +// VCR mode choices: +// - PLAYBACK: Uses pre-recorded cassettes only (requires recorded data, no API key needed) +// - PLAYBACK_OR_RECORD: Uses cassettes if available, records if not (needs API key for first run) +// - RECORD: Always records fresh data (always needs API key) +// This demo uses PLAYBACK since cassettes are pre-recorded and committed to the repo +@VCRTest(mode = VCRMode.PLAYBACK) +@DisplayName("Spring AI VCR Demo") +class SpringAIVCRDemoTest { + + // Annotate model fields with @VCRModel - VCR wraps them automatically! + // NOTE: Models must be initialized at field declaration time or in @BeforeAll, + // not in @BeforeEach, because VCR wrapping happens before @BeforeEach runs. + @VCRModel(modelName = "text-embedding-3-small") + private EmbeddingModel embeddingModel = createEmbeddingModel(); + + @VCRModel private ChatModel chatModel = createChatModel(); + + private static String getApiKey() { + String key = System.getenv("OPENAI_API_KEY"); + // In PLAYBACK mode, use a dummy key if none provided (VCR will use cached responses) + return (key == null || key.isEmpty()) ? "vcr-playback-mode" : key; + } + + private static OpenAiApi createOpenAiApi() { + return OpenAiApi.builder().apiKey(getApiKey()).build(); + } + + private static EmbeddingModel createEmbeddingModel() { + OpenAiEmbeddingOptions embeddingOptions = + OpenAiEmbeddingOptions.builder().model("text-embedding-3-small").build(); + return new OpenAiEmbeddingModel( + createOpenAiApi(), MetadataMode.EMBED, embeddingOptions, RetryUtils.DEFAULT_RETRY_TEMPLATE); + } + + private static ChatModel createChatModel() { + OpenAiChatOptions chatOptions = + OpenAiChatOptions.builder().model("gpt-4o-mini").temperature(0.0).build(); + return OpenAiChatModel.builder() + .openAiApi(createOpenAiApi()) + .defaultOptions(chatOptions) + .build(); + } + + @Nested + @DisplayName("Embedding Model VCR Tests") + class EmbeddingModelTests { + + @Test + @DisplayName("should embed a single text about Redis") + void shouldEmbedSingleText() { + // Use the model - calls are recorded/replayed transparently + EmbeddingResponse response = + embeddingModel.embedForResponse(List.of("Redis is an in-memory data store")); + + assertNotNull(response); + assertNotNull(response.getResults()); + assertFalse(response.getResults().isEmpty()); + float[] vector = response.getResults().get(0).getOutput(); + assertNotNull(vector); + assertTrue(vector.length > 0, "Embedding should have dimensions"); + } + + @Test + @DisplayName("should embed text about vector search") + void shouldEmbedVectorSearchText() { + EmbeddingResponse response = + embeddingModel.embedForResponse( + List.of("Vector similarity search enables semantic retrieval")); + + assertNotNull(response); + float[] vector = response.getResults().get(0).getOutput(); + assertTrue(vector.length > 0); + } + + @Test + @DisplayName("should embed multiple related texts") + void shouldEmbedMultipleTexts() { + // Multiple calls - each gets its own cassette key + EmbeddingResponse response1 = embeddingModel.embedForResponse(List.of("What is Redis?")); + EmbeddingResponse response2 = embeddingModel.embedForResponse(List.of("Redis is a database")); + EmbeddingResponse response3 = + embeddingModel.embedForResponse(List.of("How does caching work?")); + + assertNotNull(response1.getResults().get(0)); + assertNotNull(response2.getResults().get(0)); + assertNotNull(response3.getResults().get(0)); + + // All embeddings should have the same dimensions + assertEquals( + response1.getResults().get(0).getOutput().length, + response2.getResults().get(0).getOutput().length); + assertEquals( + response2.getResults().get(0).getOutput().length, + response3.getResults().get(0).getOutput().length); + } + } + + @Nested + @DisplayName("Chat Model VCR Tests") + class ChatModelTests { + + @Test + @DisplayName("should answer a question about Redis") + void shouldAnswerRedisQuestion() { + // Use the chat model - calls are recorded/replayed transparently + String response = chatModel.call("What is Redis in one sentence?"); + + assertNotNull(response); + assertFalse(response.isEmpty()); + } + + @Test + @DisplayName("should explain vector databases") + void shouldExplainVectorDatabases() { + String response = + chatModel.call("Explain vector databases in two sentences for a developer."); + + assertNotNull(response); + assertTrue(response.length() > 20, "Response should be substantive"); + } + + @Test + @DisplayName("should provide code example") + void shouldProvideCodeExample() { + String response = chatModel.call("Show a one-line Redis SET command example in Python."); + + assertNotNull(response); + } + } + + @Nested + @DisplayName("Combined RAG-style VCR Tests") + class CombinedTests { + + @Test + @DisplayName("should simulate RAG: embed query then generate answer") + void shouldSimulateRAG() { + // Step 1: Embed the user query (as you would to find relevant documents) + EmbeddingResponse queryEmbedding = + embeddingModel.embedForResponse(List.of("How do I use Redis for caching?")); + + assertNotNull(queryEmbedding.getResults().get(0)); + + // Step 2: Generate an answer (simulating after retrieval) + String answer = + chatModel.call("Based on Redis documentation, explain caching in one sentence."); + + assertNotNull(answer); + assertFalse(answer.isEmpty()); + } + } +} diff --git a/demos/spring-ai-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.base.rdb b/demos/spring-ai-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.base.rdb new file mode 100644 index 0000000..4df9689 Binary files /dev/null and b/demos/spring-ai-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.base.rdb differ diff --git a/demos/spring-ai-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.incr.aof b/demos/spring-ai-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.incr.aof new file mode 100644 index 0000000..d39159d --- /dev/null +++ b/demos/spring-ai-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.1.incr.aof @@ -0,0 +1,809 @@ +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$82 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T14:40:38.988471Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$73 +com.redis.vl.demo.vcr.SpringAIVCRDemoTest$CombinedTests:shouldSimulateRAG +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$90 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T14:40:41.773347Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$81 +com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldProvideCodeExample +*8 +$4 +HSET +$91 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T14:40:42.886995Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$82 +com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T14:40:45.188915Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$85 +com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T14:40:46.917730Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$86 +com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T14:40:47.297784Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$89 +com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +*8 +$4 +HSET +$92 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T14:40:47.651680Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*3 +$4 +SADD +$18 +vcr:registry:tests +$83 +com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +*2 +$6 +SELECT +$1 +0 +*4 +$8 +JSON.SET +$92 +vcr:embedding:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$CombinedTests:shouldSimulateRAG:0001 +$1 +$ +$19390 +{"type":"batch_embedding","testId":"com.redis.vl.demo.vcr.SpringAIVCRDemoTest$CombinedTests:shouldSimulateRAG","model":"text-embedding-3-small","timestamp":1765638701522,"embeddings":[[-0.030835615,-0.021801744,0.050975125,-0.03334101,0.032281034,0.03483461,0.01936862,0.025632106,0.005348052,-0.04675932,-0.019994969,-0.0030865727,-0.015333491,-0.009015804,0.017670253,-0.035412777,-0.006329733,0.049120173,-0.017405258,0.02902884,0.03490688,-0.016562099,0.0072993683,0.02001906,-0.006552568,-0.041941255,-0.014972136,0.016574143,0.033726454,-0.06302029,-0.024861215,-0.046470236,-0.044302106,-0.009690333,-0.004236886,0.012015049,0.022584679,-0.014478285,0.023211028,0.03649684,0.004715681,-0.058539487,-0.0018941017,-0.048831087,0.006769381,0.012418563,-0.03644866,-0.037315913,-0.055263203,0.004020073,0.015321447,-0.027246157,-0.063502096,-0.004827099,0.02601755,-0.014719188,0.0059111635,-0.0043513146,-0.0013490581,0.024668492,0.022247415,0.00483011,-0.038183164,0.027607512,0.019549299,0.061430328,0.011533243,1.0539517E-4,-0.058828574,-0.010286569,-0.03126924,-0.0120572075,0.057479516,-0.06634475,0.012171636,-0.015357582,0.041435357,3.6436616E-4,0.01750162,-0.002279547,0.0037550793,-8.115428E-4,-0.019657705,0.007221075,0.011460972,-0.04309759,-0.017718434,0.01297264,0.006100875,-0.0041887052,-0.023500111,0.061960313,-0.035485048,0.006155078,-0.041772623,0.010822578,0.017200492,-0.006546546,0.0076667457,0.04220625,0.006558591,-0.034473255,-0.0076426556,-0.0010200747,0.003983937,-0.013647169,0.038351797,-0.0095698815,-0.03707501,0.0070524425,-0.057383154,-0.03420826,0.017284809,-0.059165835,0.0036436615,0.017995473,8.1003713E-4,-0.009593972,-0.031510144,-0.027824325,0.018513415,-0.0030007511,-0.014887821,-0.055600468,-0.06567022,-0.09477133,0.067019284,-0.057816777,0.042856686,-0.02457213,0.0550223,-0.00928682,0.010443156,-0.036255937,-0.0039447905,-1.9018182E-4,0.015249175,-3.195732E-4,-0.0145505555,0.0066609746,-0.008100372,-0.035894584,0.016321195,0.01994679,-9.6060167E-4,-0.019970879,-0.041242637,-0.032979652,-0.022897853,-9.952316E-4,-0.0062634842,0.02902884,-0.013069001,-0.0209104,-0.032449666,-0.019199988,0.025367111,-0.032305125,-0.01645369,-0.034449164,-0.021753563,-0.007016307,0.0060828067,-0.031871498,-0.013550808,-0.019260215,0.0051854425,-0.0030669994,-0.0037249664,6.0037605E-4,-0.0160562,-0.02223537,0.059695825,0.020199737,0.025343021,-0.0049806745,-0.030185176,0.0039146775,0.050252415,0.01775457,-0.02820977,-0.010997233,-0.008967623,0.042109888,0.015249175,0.011563356,0.0042609763,0.023765106,-0.040134482,0.022090828,-0.03717137,0.057094067,4.8293572E-4,0.027222067,0.05280599,0.0056552035,0.036087304,-0.021524705,-0.056756806,-0.030040635,-0.045458443,0.032473758,-0.023463976,0.002606272,0.008491839,-0.03714728,-0.0076245875,-0.042061705,-0.013297859,0.020296099,-0.046566598,0.020753814,-0.007480046,0.00923864,-0.033316918,-0.008034123,-0.0051884535,-0.0064742747,-0.036015034,0.022813538,-0.041507628,-0.0068476745,0.015321447,-0.028787937,0.020886311,0.02417464,-0.010822578,-0.0408331,0.027848415,0.054010507,0.012310156,-0.016128473,-0.017597983,-0.019284304,0.050975125,0.041868985,-0.013586943,-0.0042248406,0.034641888,-7.516934E-4,-0.020452686,-0.011442904,-0.06417663,-0.024246912,4.8481778E-4,-0.014899866,-0.018814543,-0.048638366,0.044880275,-0.012231862,-0.015598485,-0.02230764,0.019958833,-0.04801202,-0.0088953525,-0.025367111,0.025752557,-0.026210273,0.024018053,-4.023837E-4,-0.0179232,-0.0408331,0.03774954,-0.012466743,0.009864988,0.007323459,-0.052902352,-0.0380868,-0.036255937,-0.039917666,-0.023813285,-0.007263233,0.036183666,-0.036039125,0.059888545,0.015839389,0.020705635,-0.020934492,-0.012960594,0.028378403,-0.039821308,-0.10272114,-0.01712822,9.967372E-4,0.030956067,0.04765066,-1.1809905E-4,0.03177514,0.03664138,-0.021669246,-0.046542507,-0.07453547,-0.05198692,-0.01628506,0.0030323695,0.031799227,-0.03794226,0.0063779135,-0.0063718907,0.018212285,0.02113926,0.0281375,-0.021223575,0.019115672,0.0013573392,-0.0035623568,0.016164608,-0.034545526,0.068464704,0.025583925,-0.051649656,-0.012406517,-0.005170386,-0.011786191,-0.01187653,0.012659466,0.015441898,-0.021729473,0.031004248,-0.0326183,0.043218043,-0.055359565,0.022717176,-0.015490078,0.004044163,-0.035918675,-0.014369879,-0.045771617,-0.015116679,-0.018188195,0.02457213,-0.019537253,0.014996227,0.05121603,0.045097087,-0.031461965,0.011990959,-0.027776144,-0.018344782,-0.01556235,0.0032220809,0.041917164,-0.021295847,7.882053E-4,0.0021063976,0.037773628,-0.030185176,0.04753021,-0.028595215,-0.00758243,-0.0021018807,0.0062153037,-0.018031608,-0.048132468,0.03196786,0.015694845,-0.0014002501,-0.021861969,-0.0667302,-0.02669208,-0.046349782,0.018633867,0.01483964,0.010840646,0.023283299,0.0034599728,0.0109671205,-0.017116176,0.023741014,0.021572886,0.029534739,0.014225337,-0.06138215,-0.020211782,-0.044398468,0.046879772,0.012659466,0.021922195,0.03640048,0.025174389,0.010184185,-0.0043874504,0.005242657,-0.022946034,0.037388183,-0.038929965,0.033895086,0.017706389,-0.061912134,-0.027920686,0.0018278534,0.008148552,0.014020569,0.0051944763,0.014598737,0.017188447,0.0014717682,-0.0281375,0.0052306117,-7.791714E-4,0.0017314921,-0.008082304,-0.03640048,-0.050059695,0.020175647,9.4253395E-4,-0.007474023,-0.026571628,-0.02417464,-0.05333598,0.0066368845,0.007618565,0.021789698,0.010388953,-0.025126208,-0.0025626083,0.023981918,0.0145505555,-0.0117982365,-0.013008775,0.013755575,0.0025460462,-0.06412844,-0.012502878,-0.0028802995,0.039917666,0.014731233,-0.0028802995,-0.020874266,0.007732994,-0.019175898,-0.008262982,0.011635627,0.0370991,-0.003959847,0.03199195,0.0066910875,0.011358588,-0.00854002,-0.032281034,-0.012635375,0.020561092,-0.06456207,0.044591192,-0.060563076,0.009599994,0.021344027,-0.02342784,0.0055287294,0.0468075,-0.0060015023,-0.035292324,-0.032305125,0.01936862,-0.0112802945,-0.01854955,0.011912665,0.016236879,6.9447886E-4,-0.013611034,-0.04088128,0.023114666,-0.012864233,0.012502878,0.09125415,-0.0013053945,0.023644654,0.05646772,0.02746297,-0.036665473,0.008401501,0.043988932,-0.011454949,0.041387178,-0.015755072,-0.0052276,-0.012936505,0.0025219559,0.0023789196,-0.008052191,0.0358464,0.0022675018,0.008720698,-0.03049835,0.017019814,0.075547256,0.027848415,-0.019922698,0.018609775,0.015453943,-0.01710413,0.027197976,0.01752571,-0.01110564,0.0018278534,-0.034039628,0.015767118,-0.00333651,0.016646415,-0.02587301,0.026330724,0.017224582,-0.0062454166,0.072415516,-0.024487814,0.03341328,0.020199737,0.010196229,0.044904366,-0.031100608,0.025005756,0.0066188164,-0.032521937,0.016513918,-0.02596937,-0.011129729,-0.031678777,0.02293399,-0.013081046,-0.011045414,-2.8739005E-4,0.027294338,-0.037532724,0.006745291,0.023945782,0.014490331,-0.021693338,-0.01302082,-0.00461932,-0.0039116666,0.0054986165,-0.017561847,-0.010015552,0.029847912,-0.009148301,-0.030088816,-0.067934714,0.038327705,-0.0067272233,7.1066455E-4,0.0010531988,0.0060255923,-0.015875524,-0.020850176,-0.013177408,0.0046253423,0.013924208,-0.025680285,0.012346291,-0.011978914,-0.0064200712,-0.018489324,-0.006570636,-0.009419316,0.05280599,-0.018826589,0.008184688,-0.011256204,-0.004251942,-0.0028531977,0.01897113,0.015092588,9.447924E-4,0.022946034,-0.0128401425,-0.051553294,0.013731485,-0.04011039,-0.06008127,-0.025656195,-0.042158067,0.0115392655,-0.029342014,-0.011159843,-0.013117182,0.07159644,-0.04300123,-0.017381169,-0.015538259,0.020404505,0.020898357,-0.013225588,0.01735708,-0.002428606,-0.015851434,-0.019513162,0.0088953525,-0.03206422,0.027655693,0.023620563,0.0023879535,-3.6758917E-7,-0.0037219552,-0.0150684975,0.0039026325,0.035990946,0.042712145,0.035364598,0.023548292,0.03047426,-0.009744536,0.0055708876,-0.02333148,-0.04461528,0.0077570844,0.030883797,-0.03363009,0.01411693,0.008823081,-0.04541026,-0.02340375,-0.03134151,-0.041965347,-0.07559544,-0.004583184,-0.013093092,0.015200995,-0.014658962,-0.019212034,0.0020808019,0.034449164,0.008046168,-0.004092344,-0.0040772874,0.018164104,0.008654449,-0.01750162,-0.033678275,-0.038833603,-0.003273273,0.006944036,0.013960343,-0.019826338,-0.030064724,0.016489826,0.0030007511,0.03334101,0.023945782,0.017489575,0.0370991,0.06475479,-0.0378459,0.010039642,0.0037129212,0.04981879,6.8619783E-4,0.017670253,0.024981666,0.030040635,-0.019416802,-0.0393395,-0.0055708876,0.033702362,1.8444155E-4,0.028017048,0.0020130477,0.026908893,-0.011816304,-0.025921188,0.0065947264,-0.004974652,-0.01521304,-0.04377212,0.03290738,-0.013333995,-0.0055287294,0.0040321182,0.014056704,0.0045651165,0.010328727,-0.02830613,-0.018742273,0.0595031,0.025029847,-0.048614275,0.027101615,0.043675758,-0.012033117,-0.022620814,0.009889078,-0.0076667457,0.007751062,-0.030666983,-0.053817786,0.008040146,-0.018115925,-0.008835127,0.016116427,0.0023141769,0.012502878,-0.018308647,0.0013550807,0.0033184423,0.026282543,0.024909396,0.021994466,0.03204013,0.0011224586,0.0020823074,0.014960092,-0.0011141775,0.028498854,0.010533494,6.910912E-4,0.02074177,-0.02384942,-0.030594712,-0.03630412,0.005516684,3.792344E-5,-0.0026830598,-0.036183666,-0.004902381,0.01710413,-0.04018266,-5.781678E-4,-0.0076848133,0.037701357,0.018802498,-0.024788944,0.01745344,0.0321124,-0.023897601,-0.032377396,-0.008184688,0.02669208,-0.015767118,-0.015526214,0.008371388,0.026981164,0.0060286038,0.0333651,0.024584176,0.04914426,-0.016260969,-0.012671511,-0.039194956,0.0028110398,-0.0029706382,-0.023957828,-0.02268104,-0.010599743,-0.040712647,0.010129982,0.013490582,-0.0096722655,0.05189056,0.009804762,0.0033304873,-0.009624084,-0.026282543,0.008004011,0.023391705,0.0128883235,0.0505415,-0.048614275,0.0125269685,0.022066737,-0.016646415,0.02674026,-0.034400985,0.032377396,-0.05126421,0.024331227,-0.023463976,0.023885557,-0.038327705,-0.03577413,0.0015688824,0.008359343,-0.009009781,-0.032160584,-0.010467246,0.02666799,0.0045861956,-0.026812531,-0.012051185,0.03555732,0.0026860712,0.03204013,0.024764853,-0.03943586,-0.0025686307,-0.018031608,-0.025005756,-0.040399473,-0.030570623,-0.0017631106,0.004709658,-0.014586692,-0.030691074,-0.04921653,0.038110893,-0.0108165555,-0.003998994,0.032256946,-0.012231862,-0.050011512,-0.018525459,0.027944777,-0.042037617,-0.018826589,0.0104552,-0.035292324,-0.033051927,0.015706891,-0.007907649,-0.026402995,-0.027342519,-0.02587301,-0.009148301,-0.009533746,0.0012316179,0.009961349,0.01668255,-0.0017450429,0.043218043,-0.018236376,0.01929635,-8.2961057E-4,-0.02340375,-0.018368872,5.303635E-4,-5.7139236E-4,-0.013309904,-0.028113408,-0.051842377,-0.011894598,-0.002253951,-0.03579822,0.0020431606,-0.019681795,0.018742273,0.012105388,0.009599994,-0.002543035,0.016477782,-0.022391956,-0.006311665,0.00480602,0.0027387687,0.004495857,0.0010855702,-0.005835881,0.025005756,-0.040592197,-0.012852188,8.4316137E-4,0.031654686,-0.021645157,0.033678275,0.020247918,0.020404505,0.024246912,0.019344531,0.0037550793,-0.0070825554,-0.011557333,0.015393717,0.012791962,-0.0149119105,0.04095355,0.009455453,-0.0028456696,0.03471416,0.015598485,0.014297607,0.004598241,0.028836118,-0.020175647,-2.9887058E-4,0.01168983,-0.019163853,0.0017480542,0.017597983,0.008829104,0.018007517,0.027125705,0.0016306138,-0.022367867,0.023909647,0.021874014,-0.015321447,-0.03866497,0.0019031357,0.022921944,0.008811036,-0.005257713,0.0028050172,0.028450673,0.018392963,0.032931473,0.0026499357,-0.00758243,0.01556235,-0.009202504,0.01635733,0.015357582,-0.022464227,0.038881782,4.7390186E-4,-0.0016833114,-0.0040471745,-0.004309157,1.00031306E-4,-0.00816662,0.032425575,-0.01638142,-0.00966022,-0.0046915906,0.0128883235,-0.01936862,0.03420826,0.01257515,0.01374353,-0.018838633,-0.005522707,0.020296099,-0.014815549,-0.017381169,-0.056804985,-0.01820024,-0.0054022553,0.053046893,-0.012551059,0.030209268,-0.004809031,-0.0062454166,0.08633972,0.018212285,0.07415002,-0.0147553235,-0.031486053,0.0011096606,0.0053540748,-0.015225085,-0.0064441618,-0.021898106,0.051794197,-0.0071066455,-0.014586692,-0.03257012,0.026475266,-0.009094098,0.004050186,-0.0076547004,0.009401249,-0.0042218296,-0.043555308,-0.017236628,-0.003042909,-0.019187944,0.052131463,-0.0011028851,0.0046825567,0.02828204,-0.0071427813,-0.0070223296,-0.0039749034,0.004242908,0.035388686,0.041941255,-0.03945995,0.002424089,-0.017067995,-0.01078042,-0.009630107,0.05295053,0.0011269755,-0.030763345,0.033702362,0.010641901,0.014562601,0.01516486,0.030185176,-0.016718686,-0.012189704,-0.010539517,0.016538007,-0.018621821,0.027342519,0.02524666,0.011888575,0.006251439,-0.013213543,0.0024451679,-0.013839891,0.0020281042,-0.01591166,-0.014695098,0.0016441647,0.001201505,-0.0039116666,0.0012835626,-0.011370633,-0.017007768,-0.009901123,-0.018706137,-0.009871011,-0.0030700108,-0.023186937,0.024258956,-0.019248169,-0.008666494,-0.010840646,0.05405869,0.0069079003,-0.029052932,3.7114156E-4,0.010629856,0.03649684,0.014731233,0.04015857,-0.06080398,-0.042760327,0.01864591,-0.018718181,-0.0065646134,-0.022885809,-0.014803505,0.044952545,-0.032377396,-0.041170366,0.0051252167,-0.022271505,0.013755575,0.027776144,2.0175647E-4,0.0010449179,0.026113912,-0.019705886,0.0038514405,-0.0058388924,-0.0056160567,-0.023837376,0.058105864,-0.002916435,0.024054188,-0.0018383929,-0.013839891,0.0065345005,-0.04822883,-0.008154575,-0.024957577,0.02158493,0.014779414,-3.2653683E-4,0.009919191,0.027800234,0.02051291,-0.01155131,0.007708904,0.018296601,-0.03572595,0.029149292,0.010431111,-0.017465485,-1.5479916E-4,-0.0057485537,-0.001647176,0.009425339,-0.018983176,-0.013635124,-0.008100372,-0.002954076,-0.018019563,-0.010955075,1.2374522E-4,0.018886814,0.03126924,0.014406014,-0.0021304882,0.03346146,-0.03856861,-0.031630594,-0.0036376389,-0.051071487,-0.028330222,0.010792465,-0.010063733,0.00246173,0.045458443,-0.009172391,0.009491588,0.0062454166,0.063742995,0.011039391,7.351313E-4,-0.0031558324,-0.035918675,0.024788944,-0.025535744,0.0016803001,-0.038303617,-0.0049595954,0.06644111,0.012804007,0.0012662477,0.020621318,0.0026770374,0.01962157,0.01208732,-0.0034057696,-0.019886563,-0.0088953525,0.013129227,-0.012286065,0.015393717,-0.01742935,0.024451679,0.014538511,-0.029245654,-0.008871262,0.025656195,0.019199988,-0.04145945,0.010051688,-0.008630359,0.02337966,-0.020862222,-0.008148552,0.0066067716,-0.019175898,0.0077932198,-0.027101615,-0.007028352,0.035677772,0.0023698856,0.0104552,-0.014044659,0.0041736485,-0.026451176,0.039556313,0.010575652,0.04396484,0.02036837,0.015441898,0.019561343,-0.036665473,0.028041137,0.009118188,0.006582681,0.014128976,-0.0054835603,0.039773125,0.017935246,-0.014454194,0.05704589,-9.7490534E-5,-0.020729724,-0.018802498,-0.009136256,-0.0029345027,-0.035460956,0.041049913,-0.017200492,-0.018417053,-0.0050318665,-0.022921944,-0.018115925,-0.011984936,-0.019729976,0.0065646134,-0.03543687,0.044880275,-0.022343775,-0.029342014,0.0065646134,-0.036689565,-0.021079034,-4.821829E-4,-0.003369634,0.0023186938,0.0119066425,-0.0012602251,0.0030127962,0.038424067,-0.043458946,0.002627351,-0.019958833,0.021018809,0.010394975,-0.008383433,0.005378165,0.021319937,-0.0077871974,-0.0065345005,0.019982925,0.012394472,-0.015622576,0.0064200712,-0.020645408,0.03712319,-0.029149292,-0.015116679,-0.0071066455,0.036785927,0.008503884,0.012195727,0.010214297,0.0043573375,0.0028381413,0.0017856953,0.028547034,0.026258454,0.032377396,0.0026409018,-0.0020762847,-0.015044408,-0.033846907,-0.0013061473,-0.031052427,-0.010262478,-0.033606004,0.03483461,-0.0022419058,-0.027005253,-0.014381924,0.018802498,-0.03269057,-0.006293597,-0.013839891,0.05555229,0.020657454,0.0281375,0.02900475,0.010961098,0.005435379,0.048710637,-0.017706389,0.0071969843,-0.0033967358,0.0062755295,0.067404725,0.03343737,0.047072493,0.07679995,-0.032160584,0.024909396,0.0051462958,-0.006293597,-0.008046168,0.0333651,-0.010250433,0.007347549,-0.01523713,0.015598485,-0.018140014,-0.0027538252,-0.018814543,0.005435379,-0.02303035,0.040134482,-0.0022163098,-0.004035129,-0.033846907,0.020296099,0.010364862,-0.032594208,-0.0049204486,-0.011780169,-0.06138215,-0.013249679,-0.022066737,-0.014406014,0.020958582,-0.018356828,-0.01927226,0.027968867,-4.1518168E-4,0.011497107,-0.0010690081,0.019260215,0.021319937,-0.010882804,-0.012051185,-0.020729724,-8.3638594E-4,-0.04972243,0.0108165555,0.020488821,0.026716169,-0.01962157,0.001264742,-0.004333247,0.04088128,-0.05569683,8.205767E-4,0.021307891,0.008805013,-0.009130233,0.04921653,0.012852188,-0.012719692,-0.009377159,0.037701357,-0.0060557052,0.0110755265,-0.0029104124,-0.008311162,0.010148049,0.01775457,-0.013611034,-3.628605E-4,-0.016393466,0.018802498,-0.0020025081,-0.004766873,-0.013141272,-0.0031648665,0.014899866,0.03406372,0.013081046,0.045241628,-0.03355782,-0.035412777,-0.028908389,0.041290816,0.033148285,0.0063899583,-0.021681292,0.009232617,0.0056913393,-0.023235118,-0.0015395223,-0.011834372,-1.825595E-4,0.0051131714,0.015839389,-0.0059744003,0.016369376,-0.022572635,-0.023235118,0.026402995,-0.0014845663,0.022343775,0.020031104,0.0073535717,0.03786999,0.020898357,-0.01115382,-0.022416048,-0.018272512,0.011208024,0.048686545,-0.020826085,0.0057274746,-0.03266648,-0.027583422,-0.013297859,-0.019477027,0.0128401425,0.011870507,0.021934241,0.01603211,0.014815549,-0.0067934715,0.016393466,0.03702683,-0.007775152,0.019260215,0.03218467,0.018453188,0.0020175646,-0.0022298608,0.019874517,0.0070825554,-0.02895657,0.00797992,0.0060015023,-0.035292324,-0.0067513133,0.020633362,0.015333491,-0.025174389,0.009485565,0.027149796,0.013683304,-0.02221128,0.02755933,-0.023391705,0.010081801,0.0059774118,-0.015321447,0.0032582164,0.0070765326,0.03420826,-0.014430105,-0.02820977,0.010714171,-0.0014695098,-0.027655693,-0.0040471745,0.008196733,-0.0031979906,-0.012346291,0.03736409,-0.017465485,0.036689565,-0.013791711,-0.015225085,-0.04227852,-0.027438879,0.03782181,-0.006974149,0.004074276,0.004324213,0.0358464,0.004809031,0.010388953,-4.1028834E-4,-0.00283513,-0.03628003,-0.010623833,0.0076426556,-0.026330724,0.0147553235,-0.024716673,0.037436362,0.030594712,-0.023801241,-0.014189201,-0.029968364,0.020151556,0.012755827,0.03656911,-0.036785927,-0.027053434,-0.009895101,-0.02828204,-0.012490833,-0.006552568,0.024487814,0.0044115405,-0.013863982,-0.05102331,-0.008419569,0.049312893,0.014044659,-0.018344782,0.035171874,0.0072150524,-0.01187653,-0.033027835,-0.0053631086,-0.036520932,-0.001251944,7.838766E-5,0.035894584,0.02305444,-0.004345292,0.016152563,-0.0052998713,-0.007618565,-0.0014446666,-0.04902381,0.018115925,0.0171764,0.010533494,0.015839389,-0.006582681,0.006281552,0.00264843,0.005013799,0.032859202,-0.038183164,0.051649656,0.033726454,-0.013153317,-0.014369879,0.014538511,-0.009401249,-0.006558591,0.020524956,0.01203914,0.035412777,0.024861215,0.036978647,-0.0082208235,-0.009262729,-0.013827846,0.014369879,-0.029486557,0.028763847,0.013803756,-0.0088953525,0.0055287294,-0.0048873248,-0.017369123,0.004516936,0.004860223,0.031100608,-0.007949807,0.016321195,0.019260215,-0.00190163,-0.0076004975,0.017200492,0.035027333,-0.00480602,0.01864591,-0.010280546,0.0036677518,0.02895657,0.0052276,-0.02604164,-0.022982169,0.0049084034,-0.02746297,0.0069560814,-0.017862976,-0.025415292,-0.012924459,-0.024981666,-0.0321124,-0.01411693,0.027607512,0.010635878,0.014598737,0.0106057655,-0.004146547,-0.028619306,8.687573E-4,-0.02004315,0.002050689,-0.010732239,-0.030546531,0.02967928,-0.051456932,-0.0112260915,0.006281552,0.0160562,0.011262227,-0.012406517,-0.024584176,0.021127215]]} +*4 +$8 +JSON.SET +$87 +vcr:chat:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$CombinedTests:shouldSimulateRAG:0001 +$1 +$ +$336 +{"response":"Caching in Redis involves storing frequently accessed data in memory to reduce latency and improve the performance of applications by allowing quick retrieval of data without repeatedly querying the underlying data source.","testId":"com.redis.vl.demo.vcr.SpringAIVCRDemoTest$CombinedTests:shouldSimulateRAG","type":"chat"} +*8 +$4 +HSET +$82 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T15:11:42.968193Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*4 +$8 +JSON.SET +$95 +vcr:chat:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldProvideCodeExample:0001 +$1 +$ +$473 +{"response":"Certainly! Here’s a one-line example of a Redis `SET` command in Python using the `redis` library:\n\n```python\nimport redis; r \u003d redis.Redis(); r.set(\u0027my_key\u0027, \u0027my_value\u0027)\n```\n\nMake sure you have the `redis` library installed. You can install it using pip if you haven\u0027t done so:\n\n```bash\npip install redis\n```","testId":"com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldProvideCodeExample","type":"chat"} +*8 +$4 +HSET +$90 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T15:11:45.549462Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*4 +$8 +JSON.SET +$96 +vcr:chat:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion:0001 +$1 +$ +$292 +{"response":"Redis is an open-source, in-memory data structure store used as a database, cache, and message broker, known for its high performance and support for various data types.","testId":"com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion","type":"chat"} +*8 +$4 +HSET +$91 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T15:11:47.286172Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*4 +$8 +JSON.SET +$99 +vcr:chat:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases:0001 +$1 +$ +$508 +{"response":"Vector databases are specialized data storage systems designed to efficiently handle and query high-dimensional vector representations of data, such as embeddings from machine learning models. They enable fast similarity searches and retrievals based on distance metrics, making them ideal for applications like recommendation systems, image search, and natural language processing.","testId":"com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases","type":"chat"} +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T15:11:49.554520Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*4 +$8 +JSON.SET +$105 +vcr:embedding:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts:0001 +$1 +$ +$19371 +{"type":"batch_embedding","testId":"com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts","model":"text-embedding-3-small","timestamp":1765638710416,"embeddings":[[-0.01055897,-0.0010222754,0.013969583,-0.017228749,0.03547523,0.017156053,0.012261248,0.0474457,-0.02529791,-0.04196933,-0.02755146,0.015265981,-0.011649397,-0.021953933,0.04264782,-0.005691424,-0.0037437999,8.6628384E-4,-0.019651921,0.028738813,0.008741591,-0.007948003,0.01214009,0.010019815,0.0023701647,-0.0034257588,-0.045289077,-0.015568877,0.019882122,-0.0754091,0.013388023,-0.047494162,-0.01582331,0.038601123,0.024461916,0.031210449,0.009626049,-0.034869436,-0.0023201867,0.04805149,-0.016853157,-0.031428535,-0.008420521,-0.027333375,-0.0023686502,-0.010165204,-0.03714722,-0.028108789,-0.014684419,0.018113207,-0.028932668,-0.013739383,-0.043399002,0.01602928,0.046161417,-0.0032894553,0.008802171,0.03620218,0.012176436,0.02784224,0.019882122,0.0034651353,-0.029465765,0.03402133,0.006294188,0.043350536,0.0021369343,0.039425,-0.041896634,0.0020536378,-0.022717234,-0.014696535,-0.040006563,-0.062905535,0.034869436,-0.011419196,0.014708651,0.006881807,0.0026094527,-0.017895121,-0.02266877,0.015508298,-0.011831135,-0.0086628385,0.029441534,-0.041581623,-0.014260365,-0.0199427,0.03024118,0.024885971,-0.028278412,-0.0073240357,-0.0126610715,0.028472265,-0.055296775,-0.03661412,0.028859973,0.011455543,-0.0063426513,0.05398826,-0.006048842,-0.036953364,-0.030968131,-0.045870636,-0.043738246,-0.034554426,0.03123468,-0.033754777,-0.01857361,0.029053826,-0.035693318,-0.04097583,0.018028395,-0.0593677,-0.003234934,0.011855367,0.010243958,-0.022268945,-0.016635071,-0.045313306,0.029150752,0.018937085,-0.021844892,-0.019542878,-0.066928,-0.05398826,0.03969155,-0.023274561,0.015992932,-0.022487031,0.06939963,0.02585524,-0.0021869123,-0.03406979,-0.0038952483,0.025588691,-0.030047327,-4.1875432E-4,-0.03954616,-0.042284343,-0.037341073,-0.0324705,-0.00900814,-0.003989146,0.016150437,-0.013242632,-0.02655796,-0.023868239,-0.007893481,0.004840285,0.0077723227,0.020281946,0.001737111,-0.0064940997,-0.049675018,-0.015229633,0.03387594,-0.016622957,-0.005624787,-0.04436827,-0.013327443,-0.02755146,-0.018840158,-0.039909635,-0.019809427,-0.021505646,0.014732882,-0.023359373,-0.025927935,0.002295955,0.012085568,0.002792705,0.060240045,0.04252666,-0.011631223,-0.005082602,-0.021045245,0.04342323,0.040806208,-0.0011669085,0.00666978,-0.015047895,-0.015302328,0.035160217,0.02289897,-0.017919354,-0.01561734,0.0100379875,-0.02366227,0.012352116,-0.038673818,0.096393764,-0.016889505,0.020209251,0.04860882,0.005458194,0.027357606,0.003231905,-0.058543824,-0.032882437,-0.047591086,0.03164662,-0.041751243,-0.009953177,0.012103742,-0.009432195,-0.010455985,-0.040321574,-0.013848425,0.0132062845,0.017810311,-0.0034590773,0.004258724,-0.01190383,-0.023613807,-0.0014114974,0.0177255,0.012406638,-0.04012772,0.02366227,-0.04165432,0.009510948,0.03266435,-0.0043677664,0.01787089,0.015738498,0.025685618,-0.031767778,0.005436991,0.01512059,-0.0027715024,-0.02325033,-0.017567994,-0.03404556,0.017689152,0.031380072,-0.039085757,0.011988641,0.047639553,-0.0023217013,-0.027478766,-0.012915504,-0.026364107,0.0033833531,0.03528138,0.0022474916,-0.0011343472,0.01006222,0.033003595,0.009613933,0.028060326,-0.010371174,0.028811509,-0.07361595,0.0073543256,-0.03457866,9.427652E-5,-0.018488798,0.03360939,0.020185018,-0.04252666,-0.05301899,0.020960433,-0.0063063037,0.008632548,0.007396731,-0.006893923,-0.018500915,-0.027672619,-0.022535495,-0.02728491,-0.039449234,0.03196163,0.0052279923,0.023238216,0.0064940997,0.016077744,-0.02852073,-0.031283144,0.024268063,-0.008232725,-0.05776841,-0.0087718805,0.020730233,0.013048778,0.061063923,0.031089291,0.021929702,0.042841673,-0.003668076,-0.062614754,-0.0864103,-0.033803243,0.011812961,-0.0025094969,-0.020730233,-0.019760964,0.039085757,0.0033954692,0.016901622,0.07414905,0.04877844,-0.02486174,0.006372941,2.4610336E-4,-0.006712185,0.003992175,-0.043399002,0.03179201,-0.013012431,-0.015229633,0.012503565,-0.019809427,-0.0011010285,-0.023080708,0.017362025,-0.016538145,-0.010855808,0.017398372,-0.047421467,0.03167085,-0.052631285,0.05413365,0.0053037168,-0.020451568,-0.017919354,-0.025758313,-0.04647643,1.16047195E-4,-0.013824194,0.03462712,-0.016732,0.042987064,0.03181624,0.043156683,-0.081467025,-0.041193914,-0.0027457562,-0.0024383164,-0.012327885,0.07259822,0.021747964,-0.033536695,-0.02117852,0.031695083,0.023117056,-0.031549692,0.023541111,0.0029320375,0.028666118,0.021336025,-0.038358804,-0.020936202,-0.07943156,0.011049662,-0.002889632,0.023771312,0.0036983655,-0.045822173,-0.004346564,-0.06062775,0.02082716,-0.01307301,0.004183,0.010455985,-0.025346374,-0.01977308,-0.0012282451,-0.0032409918,-0.013654571,-0.003967943,0.03564485,-0.07070814,-0.033294376,-0.0036771628,0.040418502,0.012176436,0.029174985,0.01237029,0.024025746,0.012418754,0.05059582,-0.0010010727,-0.006385057,0.034602888,-0.0020672681,0.013799962,0.020645421,-0.05796226,-3.632864E-4,-0.0029017478,-0.016998548,0.026315643,0.012818578,-0.010486275,-0.0117220925,-0.054909065,-0.040612355,0.022099324,0.03038657,-7.799583E-4,-0.019700384,-0.04705799,-0.020451568,-0.027018363,0.022377988,0.012146147,-0.016138323,0.003407585,-0.059513092,0.017143939,0.0049735596,0.03065312,0.016695652,-0.018416103,-0.017580109,0.02295955,-0.024619423,-0.020172903,0.015762731,0.028763046,-0.0066092005,-0.089754276,-0.014526913,0.0088809235,0.0923713,0.042138953,-0.015423487,-0.013715151,0.002792705,0.021336025,-0.0010979996,0.0419451,0.027599923,0.036396038,0.047881868,0.006051871,0.0027396984,-0.017107591,-0.01640487,-0.00213542,0.02713952,-0.032640122,0.045725245,-0.082242444,0.015096358,2.8888747E-4,0.028787278,0.03918268,0.064650215,0.020403104,-0.025467532,-0.021166403,0.004422288,-0.01605351,-0.049505394,0.029780777,0.024498263,0.007669338,-0.0042072316,-0.047251843,0.016392754,-0.01076494,0.03971578,0.080061585,-0.04211472,0.020378873,0.05185587,0.0059004226,-0.00646381,-0.022184135,0.05805919,-9.912286E-4,0.06251782,-0.021493532,0.03564485,0.015556761,0.018767463,0.0012524768,0.015362907,-0.008450811,-0.0059397994,-0.0014690477,-0.0443925,0.023638038,-0.0028275382,-0.01857361,0.044101723,-0.0058519593,0.025661387,0.0040709283,0.016768347,0.007911655,-0.011885656,-0.0014614753,-0.008571969,-0.0017855745,-0.047397234,1.0326876E-4,-0.046427965,0.031065058,0.01731356,-0.04674298,0.07337363,-0.019979049,0.019312676,0.005694453,0.0063063037,0.037050292,-0.04766378,0.02713952,0.027018363,-0.018731115,0.0042042024,-0.004364738,-0.0055036284,-0.007305862,-0.0013971098,0.0015780905,0.005373383,0.021457184,0.021493532,-0.030023094,0.018234365,0.019906353,0.023783429,-0.04565255,0.018064743,-3.7066953E-4,0.012243074,-0.021093708,0.008462926,0.017846659,0.050062723,-0.028472265,-0.030265411,-0.046936832,0.01570215,-0.0010101596,-0.00467975,0.005688395,0.029514229,-0.033512462,-0.0043768534,-0.016732,0.031864706,0.015896006,-0.03746223,0.010159147,-0.0038528426,0.03494213,-0.0095351795,0.032397803,-0.019203635,0.08059468,-0.019191518,0.0011608506,-0.0075845267,0.03574178,0.01991847,1.12071684E-4,0.02655796,0.003755916,0.021469299,-0.019833658,-0.060918532,-4.6418878E-4,-0.002433773,-0.038334575,-0.044828672,-0.024728466,-0.009947119,-0.020342525,0.0034469615,-0.0071786456,0.07051429,-0.015459835,-0.036492962,0.007481542,0.010225784,0.0037680317,-0.013000315,0.011037546,-0.01921575,-0.026630655,0.005131066,0.0068696914,-0.032082792,0.03646873,0.038358804,0.029078059,0.02953846,0.00968057,-5.8922822E-5,-0.0026139962,0.027745314,0.041460466,0.023359373,-0.021299677,0.043495927,-0.018088976,0.020948317,-0.021033129,-0.053649016,0.017131822,0.008487158,-0.0053188615,-0.022596074,-0.01257626,-0.028108789,-0.029635387,-0.008838518,-0.0023292736,-0.05326131,-0.020427335,0.012624724,-0.014405754,-0.02091197,-0.004076986,-3.5495678E-4,-0.03559639,-0.033100523,0.0011623651,-0.032300875,-0.0031713257,0.0023701647,-0.034287877,-0.03266435,-0.030265411,-0.015314444,0.028641887,-0.00950489,-0.015375023,-0.006263898,-0.0058610463,0.019324793,0.010855808,0.007578469,0.02366227,0.024352873,0.05708992,0.008808228,-0.016429102,-0.0034287877,0.042308573,-0.0023625921,0.0066576637,1.9688268E-4,0.018622072,-0.0014766201,-0.013497065,-0.041484695,0.049820404,-0.008475042,0.018440336,0.019906353,0.018706884,0.009026314,0.008759765,0.003150123,-0.0018476683,-0.0044586356,-0.02438922,0.029780777,-0.054472897,0.0043586795,0.0035650912,0.011867482,0.027914936,0.022268945,-0.018767463,-0.02699413,0.04376248,0.03125891,-0.020815043,0.009607875,0.040224645,-0.023080708,-0.002453461,-0.017616456,0.027454533,-0.015556761,-0.030750046,-0.020730233,-0.0071907616,-0.0060912473,-0.014005931,0.023347257,0.021021012,-0.014708651,-0.009298921,0.002336846,0.01278223,0.019518647,0.03840727,0.0010608948,0.051952798,0.029296143,0.018791694,-6.5690663E-4,7.4323214E-4,0.028641887,-0.037631854,-6.724301E-4,2.5746197E-4,-0.0044404618,0.008263015,-0.045725245,-0.008444753,-0.009692686,-0.0012244588,-0.016647188,0.004086073,0.04565255,-0.009838076,-0.004413201,0.014963084,0.03770455,-0.0074573103,-0.010116741,-0.02085139,0.019821543,-0.034966365,-0.045264844,-1.1188237E-4,0.01333956,-0.0091656465,0.005633874,-0.010353,0.03513599,0.025927935,6.849246E-4,0.040055025,0.021893354,0.030871205,-0.008747649,-0.021336025,0.020536378,0.0033076291,0.0070029655,-0.011267748,-0.0055914684,-0.014926736,-0.0073240357,-0.021348141,-0.029417302,0.033415534,0.038140718,0.005452136,-0.020233482,-0.038916133,0.0074512525,-0.0064395783,0.042453963,0.02310494,-0.021081593,0.018949201,0.011146589,-0.010958794,0.019627688,-0.035620622,0.03474828,-0.032688584,0.03457866,-0.00391948,0.0025700764,-0.028351106,-0.041896634,-0.010625607,0.010304537,0.03433634,-0.007917712,-0.010340884,0.016429102,-0.01327898,0.0048069665,-0.0020203192,0.023177635,-0.016005049,0.021832775,-0.006700069,-0.04325361,0.031137753,-0.027769545,-0.019058244,-0.02854496,-0.008759765,-0.032276645,-0.038746513,-0.0046434025,-0.0052037607,-0.023625921,0.033948634,-0.030677352,-0.024607306,0.0125520285,-0.022947434,-0.013509181,0.020572726,0.011988641,0.010504449,-0.0058034956,0.025782544,0.01956711,-0.010268189,0.003231905,-0.022632422,-0.018210134,-0.029635387,-0.030144254,0.0065304474,0.008765823,0.048293807,0.016950084,0.021590458,0.0035105697,0.0029486967,-0.004594939,0.0042647817,0.01155247,0.007336152,-0.022802044,0.00432839,0.003658989,0.01611409,0.0040588123,-0.027381837,0.0048826905,-0.021469299,-0.073761344,-0.0055732946,0.0019112765,-0.0059125386,0.012230958,-0.013654571,0.007735975,0.011746324,-0.014696535,-0.047833405,-0.01055897,-0.02541907,0.015047895,-0.002264151,1.5532908E-4,0.011188994,-0.032325108,0.02556446,0.004994762,0.049602322,-0.032737046,-6.616394E-5,0.011510065,0.0061821165,-0.0070514292,0.036420267,-0.007820786,-0.014914621,0.0020808985,0.008638606,0.0057944087,-0.024474032,0.041993562,0.012673187,-0.00812974,0.020524263,0.0010813402,0.010904272,0.010480217,0.033464,-0.015811194,0.0041860286,0.012503565,-0.008359942,0.0125520285,0.018355524,0.054715212,0.017701268,0.041072756,-0.006469868,-0.025637155,7.019625E-4,0.0071483557,0.004422288,-0.043350536,0.01936114,0.059803873,0.034796745,0.007287688,-0.0013198713,0.03886767,0.009105067,0.044828672,0.013678803,-0.03082274,0.004319303,-0.005873162,0.01278223,0.027502997,-0.012315769,0.030968131,0.019494414,-0.03620218,-0.022244714,-0.016841043,0.03974001,-1.4084684E-4,0.029877705,-0.028084558,-0.0044737803,0.002418628,0.0029698997,0.022717234,0.013121474,-0.010425695,0.016416987,-0.039594624,-0.008196377,-0.028617656,-0.037971098,-0.010734649,-0.014914621,-0.00174014,0.0053945854,0.05834997,0.0011298037,0.035426766,0.01532656,0.014078626,0.06271168,0.026897203,0.101579346,5.11895E-4,-0.025104057,-0.004519215,0.033270143,-0.021445068,0.014381523,-0.019930586,0.056120653,-0.022050861,-0.008681012,-0.015932353,0.014744999,0.017410487,2.3228371E-4,-0.025806777,0.016647188,-0.0040497254,-0.0398854,-0.0023671356,0.011570644,-0.0068212277,0.0797708,-0.014950968,0.013497065,0.015459835,3.261816E-4,0.02105736,0.01617467,0.009177762,0.021493532,0.02500713,-0.037898403,-0.016441219,-0.010686186,-0.043302074,-0.0059882626,0.03971578,-0.020718116,-0.036832206,0.02839957,0.00663949,0.016198901,0.0038437557,0.044440966,0.00938979,-0.0052401084,-0.007990408,0.018440336,-9.677541E-4,0.02924768,0.047591086,0.031840473,-0.011285922,8.3069346E-4,-0.0022141729,-0.01014703,-0.0016947056,-0.030701583,-0.011867482,-0.0038952483,0.005191645,-0.013484949,-0.021638922,-0.0076814536,0.0052976585,-0.0073724994,-0.013969583,0.027066825,-0.012515681,-0.04431981,0.03164662,-0.016998548,0.015653688,0.0028578278,0.017192401,0.03629911,-0.04083044,-0.012588376,0.029756546,0.031210449,0.049553856,0.03038657,-0.039352305,-0.0035226855,0.017822426,-0.019239983,0.01175844,-0.030047327,-0.00467975,0.044562124,-0.022159904,-0.019579226,0.005961002,0.0031168044,-0.023844007,-0.0035408593,0.021941818,-0.012037104,0.011637281,-0.015786963,-0.0141028585,-0.010243958,0.020718116,0.023080708,0.055345237,-0.004955386,0.0036711048,0.008868808,-0.0066092005,0.0122673055,-0.008996024,-0.016077744,0.002129362,0.0032076733,0.015859658,-0.025225215,0.0030259355,-0.0060851895,-0.007560295,0.02655796,-0.0014539029,-0.008965734,-0.054715212,-0.002594308,0.015387139,-0.015605224,-0.0142119005,0.008123683,0.010746766,0.02310494,-0.03886767,0.009935003,-0.0102803055,9.54881E-4,-0.014757114,-0.0017446835,-9.692686E-4,0.016150437,0.033027828,0.0070877764,0.025879472,0.0016371552,-0.03334284,-0.011304095,-0.008329652,-0.040757746,-0.02670335,-0.0037922633,-0.01617467,0.022450684,0.036977597,-0.016356407,0.022765696,-6.523632E-4,0.065280244,2.631413E-4,-0.025128288,-0.0043738247,-0.041872405,0.025540227,0.0033439766,0.005633874,-0.014308828,-0.018173786,0.06678261,0.016089858,-0.007069603,0.036226414,-0.016986432,0.009468542,-0.010468101,-0.012073452,-0.02351688,-0.01518117,0.0034136428,0.021566227,-0.008493216,-0.014878273,0.029271912,0.025588691,-0.028157253,-0.0013085126,0.013727266,0.004610084,-0.009298921,0.017277213,0.004498012,0.023880355,0.024316527,0.0102379,-0.010243958,0.0102379,0.030580424,0.0034651353,-0.009553353,0.038698047,0.0028290527,0.001793147,-0.0047312425,0.029514229,0.0071968194,0.015205401,0.0068333438,0.0012903388,0.029199217,0.022159904,0.023286678,-0.045604087,0.042890135,-0.0033530635,-0.0045464756,0.0062578404,-0.003237963,0.027648387,0.029950399,-0.029708082,0.077347636,-0.015920237,-0.005167413,-0.0059852335,-0.02667912,0.009407964,-0.03387594,0.017471066,-0.015302328,-0.010286363,0.003009276,-0.01836764,-0.018937085,0.0071847034,-0.024885971,0.019167287,-0.018912854,0.045046758,-0.016986432,-0.051613554,0.018319177,0.0132062845,-0.007590585,6.9003596E-5,-0.0056005553,-0.030750046,-0.021081593,-0.026461033,0.037389535,0.018767463,-0.0026488292,0.006881807,0.004931154,0.033948634,-0.010613491,-0.002126333,-0.029708082,-0.015847541,0.008456868,-0.04058812,0.039788477,0.013884773,-0.017907238,0.009698744,0.0059095095,0.03276128,0.002474664,-0.051225845,-0.0077299173,0.015859658,0.006397173,0.002249006,0.0194823,0.010328769,0.027987631,-0.022571843,0.044901367,0.021590458,0.028133022,-0.015786963,-0.0014077112,0.00780867,-0.0364445,-0.010328769,-0.007378557,-0.012963967,-0.037946865,0.002713952,0.02585524,-0.007566353,-0.0071847034,-0.00900814,-0.0049917335,-0.01658661,-0.033100523,0.0154356025,0.035063293,0.024037862,0.026218716,-0.002748785,-0.0016098946,0.062275507,-0.009462485,0.013375906,-0.019700384,0.009074777,0.008517448,0.018004164,0.036783744,0.06755802,-0.006070045,0.04068505,-0.0054763677,0.030071558,-0.007802612,0.023916703,-0.022377988,-0.019639805,-0.021796428,-0.0135334125,-0.037486464,-0.024316527,-0.033754777,0.0050553414,-0.0072816303,0.031307377,-0.009541238,-0.019470183,-0.020778695,0.00394977,-0.0030713698,-0.0141028585,-1.3052943E-4,0.01623525,-0.030047327,-0.0077662645,-0.021541994,-0.02126333,0.0037680317,-0.027018363,0.010722534,0.018500915,0.021045245,0.04165432,0.004513157,0.0014061967,-0.012503565,-0.003992175,0.017592225,0.002000631,0.018585725,-0.0014955512,0.036541425,0.014805578,0.011140531,-0.0038922192,0.003958856,4.7479017E-4,0.02585524,-0.02132391,0.026242947,0.027454533,-0.009050545,0.016162554,0.05112892,-0.017907238,0.012479333,-0.011922004,0.0234563,0.0068394016,-0.022305293,-0.03222818,-0.02176008,0.013824194,0.029853472,-0.052388966,0.009644222,-5.550577E-4,0.015956584,-0.018319177,-0.023965167,0.008299363,-5.679308E-4,-0.015302328,0.027769545,0.017979933,0.028351106,-0.034820974,-0.044586357,-0.012757998,-0.014708651,0.005876191,0.020960433,-0.0093837315,0.0028290527,-0.0061700004,-0.020245599,-0.00798435,0.008135798,0.028496496,-0.018258598,0.0012403609,0.008559854,0.017483182,-0.008057045,-0.0154356025,-0.006948444,-0.008178203,-0.007590585,0.011298037,0.024667885,0.049529627,-0.021045245,9.0717484E-4,-0.028569192,0.016768347,0.021069476,0.04352016,-0.01441787,0.02740607,-0.025515996,0.017495299,0.007124124,-0.0044737803,0.017434718,-0.025055593,-0.006651606,6.6258595E-4,0.01865842,-0.041193914,4.8250455E-5,0.035305608,-0.013448602,0.021142172,0.011534296,-0.002892661,0.0102379,0.008002524,-0.0021808543,0.0014978229,-0.04053966,-0.01219461,-0.002380766,-0.03799533,-0.0010525652,0.0074088466,0.004067899,-0.025831008,-0.0053006876,0.005800467,0.039812706,0.0027881616,0.013606108,-0.025225215,-0.026170252,-0.0019673123,0.0016083801,0.0025246418,-0.009892598,0.011364674,-0.013618224,0.0034469615,0.011073894,-0.004870575,-0.02126333,0.015786963,0.0080509875,0.020366756,-0.009547296,0.0508866,-8.837004E-4,0.006385057,-0.013036663,-0.0125520285,-0.02655796,-0.05398826,0.033294376,0.016501797,0.016005049,0.011237457,0.025249448,0.006209377,0.0019294503,0.012115858,-0.016550262,-0.024498263,-0.010625607,0.01810109,-0.0018870448,0.03191317,-0.014490565,0.030725814,-0.0025428156,-0.012467218,-0.02152988,-0.0151084745,0.031525463,2.5405438E-4,0.01836764,-0.03191317,-0.030677352,-0.021941818,-0.02097255,-0.021372372,-0.0020294061,0.027866472,0.0017189373,-0.00409516,-0.026654886,-0.02295955,0.029199217,0.025079826,-0.012285479,0.03600833,0.022886856,-0.005094718,-0.009395847,-0.020185018,-0.036250647,-0.009129299,-0.009377673,0.02310494,0.03205856,-0.012624724,-0.015774846,-0.0056369025,-0.012031047,-0.01356976,-0.022644538,0.0279634,0.003141036,0.01476923,-0.0055430047,-0.0046979235,0.010819461,-0.0077723227,0.014793462,0.020681769,-0.03377901,0.049044993,0.04179971,-0.01900978,-0.022099324,0.03009579,-0.014963084,0.0033833531,0.0016886477,-0.012000757,0.03731684,0.017483182,0.026824508,-0.034990598,-0.00339244,-7.9510314E-4,0.02234164,-0.036686815,0.004183,0.033754777,-0.017507413,0.009589701,-0.0029759575,-0.015944468,0.022438569,-0.012479333,0.020245599,-0.0013501609,0.00731192,0.012212784,-0.006130624,-0.024498263,-0.020100208,0.025782544,0.002339875,-0.020027513,-0.004228434,0.0058610463,0.0074512525,-0.0033954692,-0.028666118,0.0013804506,0.04945693,-0.044852905,0.008886982,-0.037922636,-0.01766492,-8.0949074E-4,-0.008790054,-0.008462926,-0.01333956,0.018149555,0.014030163,-0.0047494164,-0.005948886,-0.02428018,-0.0071301823,-0.021008898,-0.03125891,0.0036923075,0.012963967,-0.021008898,0.020899855,-0.038043793,0.02658219,0.009935003,0.03673528,-0.010110683,0.008402348,-0.003310658,0.027091058]]} +*4 +$8 +JSON.SET +$105 +vcr:embedding:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts:0002 +$1 +$ +$19406 +{"type":"batch_embedding","testId":"com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts","model":"text-embedding-3-small","timestamp":1765638710681,"embeddings":[[-0.024119083,0.038244378,0.02622671,-0.0023466556,0.036569443,0.013476258,0.022597682,0.044944122,-0.03386163,-0.047344867,-0.029590541,-0.022151032,0.038160633,-0.010300858,0.015772317,0.015828148,-0.021871876,0.00971463,-0.0438275,0.056305774,0.019554881,-0.006078623,0.011159264,-0.014711523,0.029171808,0.008674774,-0.033135824,-0.008758521,0.035229493,-0.08313267,0.031991284,-0.037713982,0.032689173,0.041677997,0.030511755,0.025766103,0.06621581,-0.028027268,0.0026380247,0.019917784,0.02858558,0.0065217833,-0.005049235,-0.007334825,0.008242083,0.038132716,-0.001835451,-0.023211826,0.017014561,0.027064178,-0.025919639,-0.0043722815,-0.05329089,0.026966475,0.022890797,-0.022723302,-0.013301786,0.030762997,-0.012303803,0.034252446,0.018563878,-0.022458104,-0.005419117,-0.005977429,-0.018424299,0.022109158,-0.015883978,0.04488829,-0.017168097,-0.0013678647,-0.013050545,-0.0017298951,0.0039046952,-0.025975471,0.04402291,-0.012910968,0.03615071,0.02184396,0.005830872,0.010300858,-0.034447856,0.03709984,-0.019191978,-0.06409423,0.021997496,-0.031767957,-0.02166251,-5.9189805E-4,0.032242525,0.028097056,-0.018326595,0.005258602,-0.004745653,-4.9244874E-4,-0.012708579,-0.053625878,0.015423371,0.017279759,-0.0565291,0.043799583,-0.030511755,-0.019066358,-0.019122189,-0.0044944123,0.029227639,-0.021160029,0.016526038,-0.011661744,-0.01982008,0.025375284,-0.07213392,-0.07704707,0.013818225,-0.05239759,-0.003163187,-0.007090564,-0.016177094,-0.0064799096,-0.033331234,-0.029953444,0.01441841,0.0073138885,-0.033247486,-0.0045188386,-0.015451287,-0.02864141,0.028362256,-0.005342349,-0.011885068,-0.03411287,0.06308927,-0.006643914,0.034978252,-0.020978577,-0.03983557,-0.007697728,-0.044804547,-0.0014708034,-0.027385209,-0.05513332,-0.0151163,-0.034447856,-0.0053074546,-0.030009275,2.024154E-5,0.0023274636,-0.008549154,0.018773245,0.028613495,0.012164225,0.026687318,0.007087074,-0.0068114074,6.564529E-4,-0.049633946,-0.0070766057,0.04391125,-0.025682356,0.011536124,-0.035173662,-0.037183587,0.004222235,-6.3159055E-4,-0.03151672,-0.0311259,-0.03391746,-0.041119687,-0.0067485976,-0.045530353,9.779185E-4,-0.033303317,0.018829076,0.030874658,0.02171834,-0.022304568,-0.008472386,0.044748716,0.03651361,0.031935453,-0.0010503246,0.016400417,-0.019750291,0.0120037105,0.014865059,0.029450964,-0.03062342,-0.025305497,-0.026003387,-0.013643752,0.012792326,-0.051727615,0.13120334,-0.01054512,0.023588685,0.04751236,-0.019024486,-0.010419499,0.03182379,-0.06331259,-0.020210898,-3.3346063E-4,0.011389567,0.018815117,-0.022095202,-0.013294807,0.023951588,0.025333412,-0.010028681,-0.019150104,-0.0057715513,0.019191978,-0.0269246,-0.013057524,0.0010363668,0.029004313,0.005328391,0.019471135,0.012883051,-0.012917946,-0.010328773,-0.026966475,0.0045956066,0.06526668,-0.010963853,0.0146556925,0.031712126,0.039528497,-0.046144497,-0.0012117118,0.0070731165,0.010810318,-0.00533537,-0.006752087,-0.036485694,0.030372178,0.014188106,-0.006504336,-0.035731975,0.017852029,-0.018703455,-0.041957155,-0.023644518,-0.04357626,0.035285324,-0.01620501,5.993131E-4,-0.009128403,-0.015353583,0.05479833,0.019861953,-0.0032347208,0.0073138885,0.021578763,-0.067109115,-0.011668723,-0.030288432,-0.0056180153,-0.01499068,0.029171808,0.034699097,-0.020699421,-0.06660663,0.033331234,0.031349223,-0.04712154,-0.013308764,-0.0024548285,-0.0025176387,0.009993787,-0.010307837,-0.041677997,-0.029506795,0.04427415,-0.0363182,0.016037514,0.019164063,-0.0145021565,-0.010056596,0.0032434443,0.045697846,-0.01276441,-0.087320015,-0.0075930445,0.01880116,0.0181591,0.036485694,0.01499068,0.0065008467,0.050694738,0.01092896,-0.04606075,-0.045642015,-0.01688894,-0.022960585,-0.017544957,-0.026910644,-0.01606543,-0.021816045,-0.017642662,0.020783167,0.088269144,0.05513332,-0.032828752,0.019471135,-0.010147322,-9.5349236E-4,0.020825041,-0.0059599816,0.020085277,0.009449433,-0.0149069335,-0.02547299,-8.994059E-4,-0.026561698,-0.03296833,-0.03715567,0.016121263,-0.024761142,0.031991284,-0.04123135,0.012527128,-0.04103594,0.030344263,0.022374358,-0.05075057,-0.026059218,-0.028222676,-0.051029727,0.011982773,-0.012366613,0.05901359,-0.006528762,0.015674612,0.0075302343,0.029953444,-0.049689777,-0.0117734065,-0.004299003,-0.020657547,-0.025640484,0.044106655,-0.001257947,-0.023225784,-1.9778097E-5,0.014976722,0.024119083,-0.024691353,-8.906823E-4,-0.020155067,-0.030148853,-0.01531171,-0.020476095,-0.009512243,-0.058064457,5.6834426E-4,-0.0011837961,0.03017677,0.005691294,-0.024551775,-0.0024059762,-0.033833712,-0.0014716758,0.002629301,0.0039709946,0.037713982,0.008584049,-0.024314491,0.01085917,0.024188872,-0.017070392,-0.0024478496,0.03919351,-0.070458986,0.005977429,0.0036604337,0.02547299,0.010314816,0.041119687,0.0214671,0.054379597,0.0015510608,0.045055788,-0.01606543,-0.025766103,0.04123135,-0.004871273,-0.015214005,0.0046270117,-0.079559475,0.0012431168,0.0017255333,-0.034392025,0.013650731,-0.013518131,0.002877052,-0.052230097,-0.035843637,-0.011319778,0.020392349,0.02642212,0.011515187,-0.017712452,-0.04821025,-0.060186043,-0.011319778,0.038914353,0.0061274753,-0.0033551068,-0.0026205773,-0.072468914,-0.010419499,-0.005070172,0.041873407,0.023909716,-0.008814353,-0.030009275,1.2758304E-4,0.0115989335,0.012422444,0.024314491,0.049354788,0.017349549,-0.07537214,-0.056249943,-0.0044036866,0.07928032,0.012792326,0.0030724613,-0.023239741,-0.034782846,0.0016191051,-0.018982612,0.002990459,-0.0055935895,-0.016009599,0.024705311,0.046758637,-0.0017002348,-0.011724554,-0.05049933,-0.022192907,-0.007502319,-0.029423047,0.025193833,-0.06387091,0.01314825,-0.0054854164,-0.008695711,0.005729678,-0.034559518,0.0205738,-0.031740043,-0.025389243,-0.0029503305,-0.010894065,-0.035536565,-0.008814353,0.027999353,0.012352656,-0.019778207,-0.028306423,0.024286576,-0.028055184,0.04879648,0.100328684,-0.041566335,0.03263334,0.06275428,0.020825041,-0.062475123,-0.022639556,0.035229493,-0.021774173,0.03651361,0.0040093786,-0.0018092801,-0.0044176443,-0.011445398,0.018759286,-0.022695387,0.0043792604,0.023993462,0.006301948,-0.0262965,-0.0066299564,0.050080594,-0.018117229,0.011954858,0.0070835846,0.016735405,0.0033551068,0.020252772,0.0109847905,-0.017209971,-0.013727499,0.02318391,-0.009351728,-0.039416835,0.031963367,-0.036429863,0.02171834,-0.0020151578,-0.033498727,0.087487504,-0.022458104,0.034252446,-0.011571018,-0.0071289474,0.044162486,0.01563274,0.036485694,0.029004313,-0.0020465627,0.007655855,0.0046060747,-0.0056703575,-0.024789058,-0.005265581,-0.013092419,-0.006581104,-0.0014926125,0.007948969,-0.0056319735,-0.0035138768,0.014879017,0.011577997,-0.037016094,0.034392025,-0.0044490495,0.012757432,-0.017182054,-0.00273224,0.016079389,0.019359471,0.0023832947,-0.03830021,-0.021704383,0.021201901,0.0016382971,0.005513332,0.018089311,0.031097984,-0.034308277,0.008102505,-0.0071952473,0.034140784,0.032242525,-0.012806284,0.009309854,0.03341498,0.0068358337,-0.031349223,0.037630234,-0.023490982,0.04234797,-0.038021054,-0.0034318748,-1.8330521E-4,0.015186089,0.0010145578,-0.027482914,0.014376537,0.011480292,0.016777279,-0.011619871,-0.0061135176,0.021229818,-0.024789058,-0.050415583,-0.008660817,-0.023449108,-0.008549154,-0.023588685,-0.021495016,-0.0199457,0.054658756,-0.042068817,-0.031488802,0.022234779,-0.02572423,-0.0036115814,0.0025699805,0.00704869,0.012122352,-0.04282254,0.00781637,0.011919963,-0.030065106,0.021564804,-0.005684315,0.009449433,0.038160633,0.035424903,-0.021522932,0.0041280203,0.009316833,0.06610415,0.030762997,-0.037769813,0.03386163,-0.011968816,0.024091167,-0.004871273,-0.042096734,0.0024722759,0.04770777,-0.013560005,9.1249135E-4,-5.583121E-4,-0.011054579,-0.028725158,-0.016344586,-0.008528218,-0.038774777,-0.045055788,0.00978442,-0.028920567,-0.013448343,-9.814079E-4,-0.021383353,-0.027580617,-8.941717E-4,-0.012855136,-0.0058657667,-0.010391584,0.023155995,-0.03352664,-0.03472701,-0.04282254,0.0027461976,0.032912496,0.013832183,0.003932611,-0.006643914,0.004019847,0.036876515,-0.010621888,0.01245036,0.01924781,0.015939811,0.03227044,0.01556295,0.006110028,-0.013050545,0.05499374,0.0026432588,0.0076837703,0.02312808,0.016121263,-8.579687E-4,-0.027650407,-0.016456248,0.04706571,0.005251623,0.009323812,0.014753398,0.011012706,3.805682E-4,0.033694137,0.012031626,-0.010600951,0.01321106,-7.035605E-4,-0.011968816,-0.025389243,0.0323821,-0.007634918,9.848975E-4,0.017503085,0.017237887,-0.022067286,-0.030093022,0.06331259,-0.0020169024,0.017963693,0.020936703,0.046954047,-0.012931904,-0.023449108,-0.0095052635,0.016679574,-9.5698185E-4,-0.045195363,-0.04759611,0.012938883,-0.010056596,-0.020210898,-0.008367702,0.017893903,0.014306747,-0.0025385753,-0.0030340774,0.009009762,-0.016246881,0.02610109,0.009554116,0.035480734,0.027720196,0.022053327,-0.0018825586,0.010998748,0.027720196,-0.02141127,-0.02819476,9.1859786E-4,-0.021816045,0.020601716,-0.02490072,0.013455322,-0.032884583,-0.008932994,0.0060018552,-9.604713E-4,0.042906284,-0.0023326979,0.00819323,0.0021826513,0.03983557,0.008276977,-0.022123117,-0.023351403,0.015618781,-0.026380247,-0.01942926,0.03126548,0.019150104,-0.026687318,0.0015187834,-0.019317599,0.02312808,-0.0053946907,-0.0045572226,0.014767355,0.030958407,0.017726408,4.1818887E-5,-0.015800232,0.009575052,-0.010552099,-0.011710596,0.005258602,-0.005942534,-0.0302326,-0.021592721,-0.05303965,-0.0293393,0.029925529,0.026408162,0.029925529,-0.0029241596,-0.013064503,0.011550082,0.0011707107,0.024495943,0.0120804785,-0.046870302,-0.01130582,-0.019861953,-0.024593648,0.027273547,0.0032626363,0.040784698,-0.036820684,0.038411874,0.015102343,0.002756666,-0.04547452,-0.05906942,-0.008758521,-0.0050771507,0.027706238,-0.017447254,-0.011431441,-0.011689659,0.022918712,-7.905786E-5,-0.013643752,0.013573963,-0.015004638,0.015800232,0.004023337,-0.036429863,0.012485255,-0.031991284,-0.022248738,-0.0056075472,-0.04611658,-0.013001693,-0.017712452,-0.005628484,-3.8623856E-4,-0.036178622,0.015395456,-0.030288432,0.0017473424,0.005823893,-0.033498727,-0.051839277,0.011208115,0.008011779,-0.008807373,0.046088666,0.053151313,-9.476039E-5,0.014013634,0.014753398,-0.030093022,-0.038160633,-0.039500583,-0.025221748,-0.033024162,3.7031795E-4,0.049829356,0.004386239,0.026031302,0.030595504,0.0066264668,-0.012778368,0.017614746,0.023002459,6.957092E-4,-0.012408487,0.010782402,-0.010112428,0.021369396,0.007732623,-0.019233853,0.010370648,0.011131347,-0.030400094,0.03137714,0.002268143,-0.030288432,0.034559518,-0.020852957,0.004375771,0.031935453,-0.005628484,-0.025082171,-0.024761142,-0.0029171808,0.0068777073,0.00374767,0.00546099,0.012136309,-0.047791515,0.008591027,0.019359471,0.015353583,-0.0051643867,0.0125410855,-0.0016539996,0.038188547,0.011103432,0.024607606,0.010126386,-0.007334825,3.670466E-4,-0.0103497105,-0.025696315,-0.028725158,0.029311385,0.002470531,-0.013671667,-0.015786275,0.025947554,0.008905078,0.0042885346,0.037434828,-0.008995804,0.011912985,0.021383353,-0.005157408,0.0053074546,-0.038718943,0.023211826,0.0098123355,0.030511755,-0.017154139,-0.030400094,-0.018717414,-0.017684536,0.0048433575,-0.030455925,0.014229979,0.058287784,-0.011229052,0.020127151,0.0039291214,0.010719593,0.02019694,0.02445407,0.013162208,-0.03765815,-0.0050038723,-0.01499068,0.01276441,0.02318391,-0.018480131,-1.0141216E-4,-0.016539996,-0.015200047,-0.018773245,0.022625597,0.0044490495,0.014851102,0.026142964,-0.023867842,-0.0058099353,0.0116757015,-0.010901044,0.01149425,0.0032120394,-0.013894992,-1.0043075E-4,-0.028041225,-0.008283956,-0.022374358,-0.02457969,0.009742546,0.018075354,0.018689498,0.018884907,0.086147554,-0.011717576,0.031405054,-0.00940058,0.0080047995,0.059739396,9.002783E-4,0.081066914,-0.0017778751,-0.026547741,0.009247044,0.031070068,0.055747464,0.031237561,-0.01473944,0.027873732,-0.0040372945,-0.019331556,-0.0047910158,-0.012394529,-0.03213086,0.014229979,-0.01924781,-0.01556295,-0.0105241835,-0.039388917,-0.010147322,0.016679574,0.002590917,0.10278526,-0.031963367,-0.0025507885,0.021774173,-8.754159E-4,0.008786437,0.060800187,0.010866149,-0.011640808,0.008116462,-0.0123107815,-0.019806122,-0.017768282,-0.058287784,-0.004860805,-0.0017360017,0.02127169,-0.041845493,0.031488802,-0.016177094,0.027706238,0.017223928,0.024063252,0.0099658705,0.009156318,-0.015172131,0.020852957,0.021760214,0.053625878,0.048126504,0.03606696,-0.03126548,0.0063996525,-0.01531171,0.02751083,0.008556133,-0.014390495,0.021816045,-0.024747184,-0.019778207,-0.0013931631,0.009456411,-0.0094075585,0.013448343,-0.02425866,-0.009526201,-0.0015379754,-0.017684536,-0.028892651,-0.0040372945,-0.02769228,-1.4088656E-4,-0.0042920243,0.023770137,0.031935453,-0.01365771,-0.0027915605,0.014837144,0.013497195,0.027859773,0.021592721,-0.042152565,-0.038439788,0.012136309,-0.010614909,0.016372502,-0.0425713,-0.011075516,0.054602925,-0.017140182,0.010580014,0.016623743,-0.0142718535,-0.012150267,0.005869256,0.009100487,0.03361039,0.0046514375,-0.028124971,-0.014209043,-0.013922908,0.01695873,0.0075302343,0.03869103,-0.026882729,-0.0061693485,0.0012134565,-0.013204081,-0.0043164506,-0.02751083,-0.025333412,0.023169952,0.03405704,-0.0014934848,0.028320381,0.020099236,0.0130993975,-0.0043792604,0.0043653026,0.0072371205,-0.02141127,-0.024663437,0.007767517,0.016414376,-0.017168097,-0.014683608,0.019624671,-0.009240066,-0.016232925,-0.027287504,7.9385E-4,0.008528218,0.0043269186,-0.0058727455,-0.022821007,0.016009599,0.024160957,0.026701277,-0.00654272,-6.8306E-4,0.005583121,-0.042990033,-0.0068358337,0.009316833,-0.013860098,-0.029255554,-0.0055203107,-0.0012753942,0.0040058894,0.024663437,-0.029869698,0.013106377,-0.035704058,0.02674315,0.0070033274,0.009791398,0.02356077,-0.035369072,0.006472931,0.01219214,0.009540158,-0.0056249946,-0.011682681,0.045251194,0.010503246,-0.0062147113,0.029841783,0.014278832,0.004745653,-0.008932994,-0.0040826574,0.013064503,-0.008283956,0.013727499,-0.0051923026,0.014641735,-0.004469986,0.028976398,-0.0018302168,-0.04120343,-0.004459518,0.006853281,0.010363668,-0.029953444,4.946296E-4,-0.022332484,0.033024162,0.011822259,0.023798054,-0.0169029,0.036094878,0.045334943,-0.012562023,-0.03481076,0.024565732,0.016079389,-0.0076070023,-0.017782241,0.0055238004,0.0238818,0.00978442,0.009944934,0.017921818,-0.0034144274,-0.014111338,0.0042710877,-0.041119687,0.057115328,0.0072371205,0.0024827442,-0.025933597,0.0013172677,0.03271709,0.015507119,-0.033387065,0.090111576,-1.3058178E-5,-0.0014838888,0.0031788896,-0.040700953,-0.011752469,-0.0031195688,-0.008542175,-0.022890797,-0.0104474155,0.0013094164,0.014948807,-0.013371575,-0.010887086,-0.0447208,0.01473944,-0.028920567,0.0538492,-0.03260543,-0.027217714,0.025905682,0.008228124,-0.015716486,0.0030811848,-0.028097056,-0.028920567,-0.018884907,-0.027538745,0.011340715,0.018940737,-0.002686877,-0.01060793,-0.007997821,0.042906284,0.0078093903,-0.003601113,-0.03062342,6.141433E-4,0.0030532693,-0.015451287,0.02883682,0.011857153,0.007285973,-0.01524192,-0.017014561,0.0425713,-0.012108394,-0.05404461,-0.018982612,0.013141271,-0.020922747,0.0031527185,-0.0083328085,-0.0016225945,0.027873732,-0.03866311,0.02998136,-8.994059E-4,0.0101961745,-0.022025412,-0.0065601673,-0.012317761,-0.030707166,-0.03550865,-0.0065915724,-0.025124045,-0.032047115,0.013315744,0.0064380364,-0.011982773,0.018075354,0.012052562,-1.412137E-4,-0.022695387,0.018689498,0.022513935,0.040980108,0.009526201,0.01949905,0.041677997,0.011249989,0.053402554,-0.014404452,0.044329982,-9.386622E-4,0.0181591,0.0293393,-8.750888E-5,0.040533457,0.058176123,-0.011787364,0.0028247102,-0.008891121,0.012527128,0.015060469,0.011515187,-0.022248738,-0.0058901925,0.005900661,-0.006015813,-0.026910644,-0.0024844888,-0.03243793,0.0036813705,0.012883051,0.017614746,0.003911674,-0.0025228728,-0.027189799,-4.202607E-4,-0.0051225135,-0.003252168,-0.018200975,0.025305497,-0.022569766,0.0073069097,-0.004218746,-0.05150429,0.018787201,-0.026966475,0.009868166,0.027664365,-0.0100426385,-0.021369396,0.015004638,0.01029388,-0.0046270117,-3.3062545E-4,-0.002610109,-0.034559518,0.0155210765,-0.019289684,0.0110615585,-0.01219214,0.011640808,-0.0030462905,0.016009599,-0.0011340715,0.0067974497,-0.011529145,0.015172131,0.014571946,-0.011187179,0.020476095,0.06990068,-0.01333668,-0.0032922968,0.032745004,-0.0020308602,7.3801883E-4,-0.017279759,-0.033749968,-0.0323821,0.0012527128,0.034308277,-0.01524192,0.04876856,-0.012499212,0.007411593,-0.0139438445,-0.025305497,0.021257734,-0.0047421632,-0.013915929,0.029590541,0.027482914,-0.018340552,-0.010852192,-0.019052401,-0.0044455603,-0.008053652,0.028864736,0.0055098427,-0.023100164,-0.012890031,-0.013622815,-0.037016094,-0.007634918,0.034280363,0.019931741,-0.008905078,0.0050457455,0.03243793,0.04876856,-0.026952516,-0.020922747,-0.016930815,0.0041384883,-0.008974867,-0.019205937,0.02724563,0.054602925,0.029646372,-0.0033830223,-0.026980432,0.02198354,0.024286576,0.026589613,-0.0499131,0.028892651,-0.044385813,0.017852029,0.0038139694,-0.011571018,-0.014348621,-0.005216729,-0.0055586947,0.026198795,0.008346766,-0.050359752,0.016930815,0.037909392,0.007348783,0.03062342,0.02356077,0.024998425,0.00978442,0.02203937,-1.695655E-4,0.029562626,-0.018563878,-0.017824113,0.0017011071,-0.054212105,0.008625922,-0.0031073557,0.01035669,-0.0053772433,0.014697566,0.025919639,0.032242525,0.013071482,0.0012710324,-0.05234176,0.011731533,-0.024789058,-0.008144378,-0.026701277,-0.0070033274,0.0035487714,-0.01747517,-0.0035941342,0.0117734065,0.009100487,0.0078512635,-0.004096615,0.004654927,0.013692604,-0.0049271043,0.048433576,-0.04617241,0.009986808,-0.007292952,0.012924925,-0.02076921,-0.03003719,0.008200209,0.0013652475,0.006092581,-0.0030393114,0.011947879,-0.0013451832,0.0036988177,0.019024486,0.010866149,-0.02820872,0.0018476641,0.011710596,-0.03260543,0.016944772,-0.007837306,0.012973777,7.502319E-4,0.010719593,0.012555043,-0.0062810113,0.017768282,-0.011857153,0.019289684,-0.03263334,-0.009707652,-0.0030846742,-0.023379318,-0.01874533,0.008367702,0.0011052835,-0.012443381,-0.013699583,-0.010977812,-0.0012998204,0.0323821,0.002369337,-0.023351403,0.041315094,9.081295E-4,-0.0064171,-0.01035669,-0.0254032,-0.0323821,0.008981846,0.007690749,0.0011044111,0.04103594,-0.04134301,-0.005862277,0.014934849,-0.027329378,0.0131761655,-0.027594576,0.023993462,-0.003848864,0.013650731,-0.013287828,0.0024234236,0.03257751,0.011145305,0.033554558,0.04033805,0.020978577,0.035285324,0.05248134,-0.009421517,-0.033191655,0.034782846,-0.015437329,0.011284883,-0.002554278,0.023086205,0.020476095,0.020378392,0.018131185,-0.011912985,-0.010133364,0.0033167228,0.018619709,-0.023197867,0.003957037,0.02438428,-0.017182054,-0.017112266,-0.011152284,-0.007292952,-0.016218966,-0.016721448,0.05111347,0.01631667,0.01308544,0.025891723,-0.020936703,-0.018326595,-0.0027357293,0.050527245,0.0060681547,-0.015227962,0.016470207,0.01060793,0.017363505,0.022151032,-0.03659736,0.007893138,0.050080594,-0.032019198,0.015590865,-0.00819323,-0.020545885,-0.007334825,0.002648493,-0.0073208674,-0.024314491,0.012331719,0.011571018,0.011145305,-0.018242847,-0.023518898,0.02699439,-0.023435151,-0.021508973,0.01410436,-0.015548992,-0.025054256,0.034419943,0.007017285,0.031712126,0.0043269186,0.012848157,-0.014020612,0.0028805416,3.552697E-4,0.00762096]]} +*4 +$8 +JSON.SET +$105 +vcr:embedding:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts:0003 +$1 +$ +$19314 +{"type":"batch_embedding","testId":"com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts","model":"text-embedding-3-small","timestamp":1765638710947,"embeddings":[[-0.04252664,-0.031583946,0.023355722,-0.0017751832,0.02920879,0.04521283,0.03636254,-0.02526433,-0.009599597,-0.030283267,0.043035604,-0.017926788,-0.02625398,-0.0052416064,-0.01844989,-0.005750569,-0.025193641,-0.0240202,-0.0122363055,-0.029859131,0.051631413,-0.03650392,0.019114368,0.03834184,-0.011444586,-0.07374301,-0.030339817,0.029717753,0.01811058,-0.055929318,-0.016371626,-0.042215608,0.019241609,-0.03771977,0.024981573,0.009373392,0.022790208,-0.017219895,0.026607426,0.043657668,-0.004980056,0.0016850545,-0.04017976,-0.038963903,0.01634335,0.007528403,-0.044336285,-0.027724316,-0.04148044,0.048888672,0.027611213,0.0018785663,-0.07176371,-0.0044392836,-0.037380464,-0.02074022,0.003315325,0.006227721,-0.025900533,0.031527396,0.003997476,0.018930575,-0.059774812,5.407726E-4,0.0026667512,0.022761932,-8.2397135E-4,-0.006779097,-0.038992178,0.0040999753,0.0024758903,0.022309521,0.05165969,-0.045014903,-0.0021259787,-0.031668775,0.071311295,2.7833885E-4,0.055109322,0.03149912,0.06876648,-0.037012883,0.004806868,-0.057399653,0.040349413,-0.03195153,0.016880589,0.04541076,-0.03879425,0.025617776,0.0038101494,0.06780511,-0.047503162,0.040208034,0.022465037,0.041791473,-0.008984601,0.0075142654,5.226585E-4,0.020909874,0.0017433731,-0.06887958,-0.009762183,-0.03622116,0.014130777,-0.039274935,-0.023709167,-0.0047679883,-0.022295384,-0.031329468,-0.08098158,-0.049256254,0.010073216,-0.02598536,-0.013254231,0.012455442,-0.027851557,0.03786115,-0.010935624,0.0062065143,-0.008772533,0.0043438533,5.708155E-4,-0.047757644,-0.015381976,-0.028120175,0.03183843,-0.07577886,-0.0067578903,-0.043572843,0.038285285,-0.013536988,0.015000255,-0.044619042,-0.030028785,-0.025250193,0.004803333,0.0075566787,-0.00465842,0.027724316,-0.012738199,-0.025292607,0.031923257,0.045976274,-0.011656654,0.025306745,-0.005874275,1.0410094E-4,-0.02027367,-0.01181217,-0.03045292,-0.009634942,0.014074226,0.0013881597,-0.025829844,0.0040222174,0.032319117,0.02409089,-0.010935624,0.027257767,-0.055109322,-0.009019946,0.01975057,-0.025547087,-0.031470846,0.0132118175,0.0145478435,-0.006984096,-0.022083316,-0.061527904,-0.03715426,0.00827064,-0.01825196,0.020132292,0.038539767,-0.028049486,-0.01542439,-0.057597585,0.06294169,-0.006043929,-0.029887406,-0.03263015,-0.026748804,0.018534716,0.02034436,-0.019976776,0.010864935,0.011706136,-0.04970867,0.0388508,-0.043685943,0.010165111,0.027427422,0.026876045,-0.031386018,0.0030820505,0.021489525,0.014731635,0.020952288,-0.017841961,-0.021376424,0.04207423,0.035401165,0.010787177,0.031357743,-0.011854583,0.038992178,0.01319061,-0.024302958,-0.01299268,-0.008956325,0.018492302,0.0387377,-0.003753598,-0.05100935,-0.030141888,-0.013791469,-0.0020305482,-0.038992178,0.0069628893,0.00686039,0.009790459,0.0224509,-0.003055542,-0.00800202,0.0028894222,-0.036079783,-0.05853068,-0.0035397632,-0.010907348,0.01897299,0.017219895,-0.024119165,-0.04405353,0.009458219,0.06260238,-0.023977786,0.00359278,0.032856353,-0.03483565,-0.044845246,-0.0062065143,-0.0561838,0.00794547,-0.035881855,0.0030661453,-0.03229084,-0.043940425,0.04297905,-0.0015905077,-0.0347791,-0.029293617,-0.0125685455,-0.01719162,-0.020485738,-0.02802121,0.024953298,0.05061349,0.02441606,-0.0027003286,-0.009634942,-0.026607426,0.0022938654,-0.015693009,-0.014427672,0.0058707404,-0.012109065,-0.020188844,-0.03200808,-0.023581928,0.009118911,0.024656404,0.03859632,-0.03438324,-6.437138E-4,0.039925277,-0.019269884,-0.027229492,-0.02769604,0.04382732,-0.053186577,-0.04515628,7.541657E-4,-0.016725073,6.030675E-4,-0.007627368,-0.012716993,0.03452462,-0.018506441,-0.012716993,-0.02435951,-0.057314828,-0.04193285,0.009479426,0.00336304,0.02855845,-0.046541788,-0.01917092,0.012306995,0.015693009,0.023426412,-0.006188842,-0.0066978047,0.038511492,0.005139107,-0.023723306,-0.015509217,0.018068166,0.030226715,0.05559001,-0.018690232,-0.0039479933,-0.0104125235,0.00833426,-0.019213332,-0.014067157,0.059322402,-0.05573139,0.070519574,-0.053638987,0.07029337,-0.055759665,-0.0030449387,0.009535977,0.012936129,-0.015042668,-0.012936129,-0.025900533,-0.034863926,0.01076597,0.043261807,0.010652867,0.01975057,-0.010872004,0.016046455,-0.010313558,-0.0142650865,-0.005796517,-0.028770516,-0.03938804,-0.029519822,0.0077263326,-0.023511238,0.019722296,0.041904576,0.025759155,-0.035033584,0.030679125,0.027229492,-0.0055773803,0.010949762,0.013317851,-0.018464027,-0.020485738,0.010327697,0.040971477,0.018082306,-0.0653734,-0.038511492,-0.018676095,-0.017431963,-0.01627266,0.04391215,0.031272914,-0.02836052,0.04023631,-0.012306995,-0.035146683,0.00525221,0.026762942,-0.008207019,0.039925277,-0.04575007,-0.022139866,-0.04871902,0.03263015,0.020825047,0.038624596,0.03511841,0.041961126,0.032319117,-0.0070194406,-0.012172685,-0.033846002,0.07934159,-0.03531634,0.0075001274,-0.022224694,-0.08663672,-0.022139866,-0.043488014,-0.02199849,-0.008284777,-0.0038560976,-0.008362536,0.04555214,0.028954308,0.010165111,0.008058572,-0.013438023,0.02691846,0.027851557,0.008942188,-0.057286553,0.012172685,0.015735423,-0.035231512,-0.023610203,0.028318105,-0.01522646,0.026296394,0.0024723557,0.030707402,-0.031188088,-0.020400912,0.01522646,-0.009988388,0.015254736,0.039303213,0.003087352,0.003997476,-0.019269884,-0.04719213,-0.0015083314,-0.01240596,-0.015961628,-0.014745774,0.0029830856,0.0155233545,-0.049821768,-0.011670792,0.018223684,-0.033789452,0.03786115,-0.0070865955,-0.011211311,0.010440799,0.008447363,0.001936885,-0.03766322,0.008044435,-0.013134059,-0.03511841,-0.009663218,-0.046230756,0.015127495,0.010589247,-0.021546077,0.0028063625,-0.0070936643,-0.02100884,0.021489525,-0.040971477,0.029406719,-0.017573342,-0.027950522,0.022168143,-1.5532301E-5,-0.010483213,0.0030078266,-0.034043934,0.0142650865,-0.025631914,-0.019199194,0.085505694,-0.0026614496,0.050189354,0.038454942,0.016399901,-0.041819748,-0.0034584706,0.028629137,0.0058884127,8.791973E-4,-0.01982126,-0.021602629,0.021376424,-0.020641254,0.003195153,-0.015127495,0.06797476,-0.010016664,0.01574956,-0.017926788,-0.007358749,0.067465805,-0.0016258523,0.0012953802,0.025038125,-0.016894726,-0.038879078,0.002604898,0.045919724,-0.017205758,0.016710933,-0.068427175,0.020655394,-0.0387377,0.03766322,0.0087937405,0.03209291,0.04436456,-0.019778846,0.040010106,-0.033817727,0.018082306,-0.016159559,0.034270138,0.035203237,-0.042356987,0.008617017,0.02119263,-0.017375413,-0.002564252,-0.031046709,-0.03025499,-0.021348147,0.044986624,-0.008708913,-0.030679125,-0.01463267,0.015735423,-0.004845747,-0.024797782,0.03313911,-0.030028785,-0.038087357,-0.044986624,-0.02920879,-0.022917448,0.029491547,-0.038087357,0.008765465,0.022154005,-0.014816463,-0.009592528,-0.034722548,0.0142650865,-0.0013422117,-0.010843728,0.010624591,0.020231258,-0.013028025,0.0204716,-0.015480941,-0.012688717,0.01738955,-0.019595055,0.011649585,-0.0068144416,-0.0011495836,0.0043297154,-0.003799546,0.0036122196,0.02409089,-0.025165366,0.010758901,-0.00623479,-0.009620804,-0.026989147,-0.013544057,0.012321133,0.023907097,0.009953043,-0.006104015,0.017955065,0.025631914,-0.050698314,0.0025324416,-0.013812675,-0.020966426,0.020514015,-0.029039135,-0.0019033075,-0.007705126,0.013918709,0.015848525,0.013339058,0.00416713,0.020726083,0.012038376,-0.0024476147,-0.015579906,7.3649344E-4,-0.014385258,0.041339062,-2.012434E-4,0.0055915182,0.009663218,0.014010605,-0.013565263,-0.028233279,-0.004403939,6.803838E-4,0.013855089,0.043233532,0.016527142,0.024048476,-0.022493312,0.05349761,-0.026437772,-0.010228732,-0.005623328,-0.026805356,0.006241859,0.015537493,-0.051801067,0.048860397,0.010249938,-0.028827067,0.0034531688,-0.0067119426,-0.01607473,-0.033648074,-0.025391571,-0.004379198,-0.016696796,-0.022917448,-0.0030661453,-0.0038490286,0.045382485,-0.011317345,0.0059308265,0.028261553,0.004213078,-1.948372E-4,-0.025716743,3.625032E-4,-0.008567534,-0.0027833884,0.032206014,0.034496345,0.004817471,-0.013035094,0.0035980819,1.5021462E-4,0.011211311,0.026876045,0.018421613,0.015070944,0.022677105,-0.023977786,0.013438023,-0.01437112,0.023666754,0.010115629,0.013876296,0.046428688,0.0062772036,-0.014067157,-0.028205004,-0.04201768,-0.022521589,0.010610454,0.01509922,-0.024670541,0.029548097,-0.016329212,0.001520702,-0.019015403,-3.731066E-4,-0.06718305,-0.042781122,0.0144206025,-0.013876296,0.010214593,0.033563245,0.0016673822,0.011084071,1.5286545E-4,-0.0715375,0.0066377185,0.049906597,0.015254736,-0.03203636,0.027144665,0.021348147,0.007584954,-0.03641909,0.039472867,-0.030537747,0.01929816,-0.033591524,-0.021022977,0.0017610454,-0.025419846,0.00800202,0.03353497,-0.0144206025,0.018082306,-0.023666754,-0.013939916,0.023016414,-0.023313308,0.028643277,0.018308511,0.030170163,-0.014031813,0.005209796,-0.028940171,0.014356983,0.0068674586,-0.0013139361,7.3737703E-4,0.017417826,-0.0468811,-0.039868727,-0.02416158,-0.007867712,-0.026338806,-0.024006063,-0.04114113,0.01299975,-0.00416713,0.0051179,-0.011974756,-0.0064503923,0.02691846,0.04942591,-0.019920224,0.031272914,0.01844989,-0.021814696,-0.04948246,0.01181924,-0.004283767,-0.04049079,0.027073976,-0.008249433,0.054967944,-9.772786E-4,0.015283012,0.0010073215,0.02126332,-0.015311287,-0.021404698,-0.036136333,-0.01699369,-0.024628127,0.0031138605,-0.04770109,-0.011670792,-5.6551385E-4,0.027088113,0.034411516,-0.008115124,0.005132038,0.01194648,0.006676598,-0.02980258,0.0014288061,0.013070439,0.0358253,0.011621309,0.0036334265,-0.036475644,0.008079779,0.023270894,4.7494326E-4,0.031979807,0.01661197,0.031753603,-0.02527847,0.036192887,-0.004969453,0.012045445,-0.02271952,-0.058813438,0.025759155,-0.03551427,-0.022309521,-0.027144665,-0.014858876,-0.00413532,0.0066412534,-0.013070439,0.022846758,0.005835396,0.007853573,0.0040257517,0.045325935,-0.034694273,-0.0075708167,-0.0049199704,-0.032573596,-0.0063584964,0.008715982,0.016244385,-0.016654383,-0.05052866,-0.008426156,-0.032771528,0.02980258,-0.015594044,0.0029989905,0.005789448,-0.0076344367,-0.08822016,-0.0028558448,0.030877056,-0.035231512,-0.009012877,-0.0052062618,-0.02146125,-0.0275688,0.04733351,-0.047022477,-0.016838174,-0.020005053,-0.01962333,-0.03302601,-0.010921486,0.009825803,-0.0032870492,0.0029070945,-0.004082303,0.021348147,-0.036136333,0.02093815,-0.017248172,-0.04102803,0.00718556,-0.0016099472,-0.022323659,0.0040080794,-0.021093667,-0.042554915,-0.016597832,0.029661201,-0.044336285,0.014887152,-0.033591524,0.029717753,0.010652867,0.04589145,0.022408485,0.0126463035,-0.024430199,-0.030961882,0.019397125,-0.01726231,0.009401668,-0.013360265,-1.2215982E-4,0.01917092,-0.026536737,-0.027356733,3.971851E-4,-0.031725325,0.029717753,0.011649585,-0.0061994456,0.029774304,0.018690232,0.016527142,0.014929566,-0.018138856,-0.019934364,0.01844989,-0.0068568555,0.0093097715,0.032856353,0.022818483,0.02106539,0.039897002,0.029180514,-0.012823027,-0.032517046,0.015381976,-0.018619543,0.010292352,0.04057562,-2.301542E-5,-0.014215604,-0.015721284,-0.044675592,0.02428882,0.04077355,0.028275693,9.313306E-4,0.03557082,0.0058318614,-0.022804346,-0.017629893,0.019057816,0.0018750319,-0.012773544,0.031046709,0.02763949,-0.011691999,0.01607473,0.018336786,0.009578391,0.0035556683,0.002995456,0.005853068,0.0037783394,0.03407221,-0.019241609,0.054713465,-0.033789452,-0.028332243,-0.007344611,-0.0012724062,-0.022479175,0.002719768,0.022917448,0.013600607,0.01549508,-0.020514015,0.03347842,-0.0471073,0.031527396,0.020259533,0.023751581,0.009458219,-0.005803586,0.048266605,-0.008051503,-0.0069876304,-0.03203636,-0.03248877,-0.011345621,0.03839839,0.011536483,0.012709923,-0.0183792,-0.012377684,0.05067004,0.03729564,0.05776724,0.0066377185,-0.011388035,0.010101491,0.036588747,-0.008935119,0.010688212,0.015000255,0.025589501,-0.0072809905,-0.008503915,-0.0062206523,0.037125982,-0.04108458,0.011593034,-0.02283262,0.021277457,-6.5210817E-4,-0.029293617,-0.0051603136,-0.025094677,-0.02645191,0.028417071,-0.020754358,0.017347137,0.032743253,0.028586725,-0.018266097,0.008920981,-0.018124718,0.020075742,0.05663621,-0.033506695,0.010313558,0.004471094,-5.0719525E-4,0.0024847265,0.017856099,-0.023737444,-0.011974756,0.006333755,0.038285285,0.021206768,0.009196669,0.022691242,-0.008694775,0.02737087,0.0025677863,0.013353196,-0.023115378,0.03110326,0.060114123,0.012066651,-0.0066059087,-0.020047465,0.024500888,-0.012292857,0.021871248,0.010539765,-0.0049729873,0.017545067,0.005782379,-0.005153245,-0.01384802,-0.014717498,-0.0093097715,0.008878567,-0.020118155,-0.026084326,-0.024302958,-0.011960617,0.0013201213,-0.005757638,-0.0026632168,-0.010716488,0.017106794,0.022111591,-0.03019844,0.008510984,-0.005828327,0.05321485,-0.0028434743,0.027342593,-0.02816259,-0.013268368,0.012674578,-0.038935628,0.04312043,-0.01825196,-0.032319117,0.0139045715,-0.03294118,-0.009960112,-0.0077263326,-0.02730018,-0.007259784,0.027964659,-0.01936885,0.025193641,0.043742497,0.006764959,0.010009595,0.006941682,0.0035680388,-0.007712195,0.021786422,-0.040519066,-0.0056869485,-0.030594299,-0.03045292,-0.014385258,-0.018464027,0.005047211,-0.03130119,0.010143905,0.021305734,0.026140878,0.019057816,0.040971477,0.023256756,-0.030566024,0.003983338,-0.012306995,6.4238836E-4,0.0551376,0.00686039,-0.015678871,-0.005722293,-0.02697501,0.0019139108,-0.014166121,-0.0027003286,-0.00977632,-0.01589094,0.006241859,-0.018661957,-0.006379703,-0.031923257,0.039359763,0.023242619,0.03099016,0.005192124,0.04108458,-0.038907353,-0.008390811,0.02336986,-0.026494324,-0.02724363,0.01870437,-0.009755114,-7.104268E-4,0.006524616,-0.020909874,-0.005520829,0.007931331,0.04566524,0.018775059,0.0019828328,0.02034436,0.009260289,0.014604395,-0.03622116,-0.023779856,-0.010638729,0.014929566,0.035881855,-0.030170163,0.01109114,0.022507451,0.022111591,0.0050825556,0.02657915,-0.008546328,-0.0026473117,0.0010541532,-0.01738955,-0.03090533,0.020104017,-0.03248877,0.019920224,0.011444586,-4.252399E-4,0.0089492565,0.02527847,-4.3275062E-4,-0.041339062,-0.007775815,0.018068166,0.012837164,-0.016230248,-0.014328707,0.034637723,-0.012179755,0.0027321388,-0.043261807,-0.015325425,0.017884376,0.0120101,0.013247162,0.0022567536,0.016654383,-0.037634946,0.014406465,-0.011048727,0.01699369,-0.008199951,0.0050719525,0.0022779605,-0.040405963,0.04148044,-0.013480436,-0.01555163,0.02435951,7.992301E-4,0.020584704,-0.002933603,-0.042300437,0.07278163,-0.008185813,-0.0224509,-0.032828078,-0.028007073,-0.020881599,-0.00715375,0.01542439,-0.049114875,-0.017135069,-0.030877056,0.0073092664,-0.014519568,-0.03065085,0.0051885894,0.023016414,-0.036390815,0.03788943,-0.008157537,0.004322646,0.004661955,-0.035683922,-0.0106952805,-0.02034436,0.00813633,0.0056940173,0.023793995,-0.009748044,-0.030594299,0.030961882,-0.013466299,-0.022168143,-0.034948755,0.032347392,0.0013678366,-0.0057788445,0.023426412,0.027498111,-0.0019121437,0.012773544,0.017205758,0.030141888,-0.002168392,-0.010193387,0.011522344,7.939284E-4,-0.0062842728,-0.017474378,-9.2337804E-4,-3.898953E-4,5.6937966E-5,0.03214946,0.0043014395,0.024840195,-0.008652362,-0.030566024,-0.0025395106,0.010872004,0.061414804,8.6019957E-4,0.013445091,-0.015834387,-0.016371626,-0.026692253,-0.02816259,-0.031188088,-0.007008837,0.035627373,-0.019609192,-0.011585965,-0.023072965,0.01883161,-0.01608887,-0.01509922,-0.019722296,0.025942948,-0.02526433,0.014173191,-0.019906087,0.015056806,-8.548979E-4,0.042809397,-0.013197679,0.0011805102,0.016824037,0.013989398,0.05231003,0.021885386,0.035231512,0.072046466,-0.013105784,0.0023645547,-0.0028293363,-0.020033328,0.001160187,-0.017615756,-0.004456956,-0.010511489,0.0032428685,0.015410252,-0.0051072966,0.02081091,-0.008150469,-0.017686445,-0.012943198,0.016527142,-0.021475388,0.012391822,0.008079779,0.019764708,-0.016258523,-0.014519568,-0.006058067,-0.008723051,-0.02362434,-0.012837164,-0.019863674,-0.031414293,0.016654383,-0.030396368,-0.013028025,-0.0015242365,-0.01738955,-0.02724363,0.016710933,0.0068533206,0.012695786,-0.0058848783,-0.042696293,-0.020980563,-0.019029541,-0.034637723,-0.007952538,0.007910125,0.004004545,-0.02717294,-0.0050295386,0.01358647,0.02690432,-0.04724868,-0.001470336,0.023779856,-0.022139866,-0.0055773803,0.034043934,0.028841205,-0.004488766,-0.013862158,0.034750827,-0.005344106,0.009401668,6.370867E-4,-0.0068285796,0.008956325,4.7162972E-5,0.0020853323,0.011494068,-0.02093815,-0.0029636458,0.0025607173,0.015579906,0.009804596,0.0013978796,0.0265226,0.00984701,5.7655905E-4,0.007627368,-0.008800809,-0.020429187,-0.035090134,0.022931587,0.01254027,0.02730018,-0.008341329,-0.004015148,-0.02553295,0.0062913415,0.013961123,-0.03322394,-0.011112347,0.00945115,0.030141888,8.8228995E-4,0.028205004,-0.014788187,0.016908864,-0.007507196,-0.0045453175,0.01095683,-0.007493058,0.0010002526,-0.004739713,0.040405963,-0.015791973,0.002007574,0.0077687465,-0.0014270388,0.029491547,0.008093917,0.014618533,-0.033846002,-0.027356733,-0.026098464,-0.043629393,0.0024705885,0.008843223,0.027837418,0.043035604,0.04521283,0.0063054794,0.0019739966,0.037917703,-0.0020128759,0.023723306,0.026989147,0.023454687,-0.008051503,0.0204716,0.034185313,-0.014858876,-0.0035326942,-0.023652617,0.011338552,-0.011048727,0.007825297,0.020697806,0.043544564,-0.027469834,-0.012116134,0.021772282,0.0012768242,-0.022281244,0.02527847,-0.0026985614,0.053554162,-0.016131282,-0.012278719,0.007797022,-0.015763698,0.051292107,-0.0016019946,-0.02546226,0.013402678,-0.018124718,-0.011133553,-0.021517802,0.03322394,-0.021220908,-0.006019188,0.019736433,-0.013741986,0.06458168,-0.0019351176,-0.02848776,-0.016414039,-0.012087858,0.023002276,-0.009182531,0.020895736,0.0061428943,0.0316405,-0.00905529,-0.020895736,0.0053122956,-0.010377179,-0.013975261,-0.016852312,0.031442568,-0.046061102,0.0091542555,-0.018266097,1.07911525E-4,0.0070017683,-0.008362536,0.010066146,-0.017700583,-0.02960465,0.002588993,0.03398738,0.003439031,-0.026876045,-0.021164356,0.006899269,-0.041423887,0.0015648828,0.0031845497,0.016173696,0.014717498,-0.0020305482,-0.0132613,0.03839839,0.01463267,-0.018859886,0.020853322,-0.030792229,-0.027554661,-0.009740976,9.631408E-5,-0.03984045,0.033846002,-0.025886396,0.039868727,-0.026466047,-0.012455442,0.027003286,-0.02592881,-0.00672608,0.008207019,-0.017361274,0.012490787,0.015919214,0.00531583,-0.016951278,0.0026985614,-0.013876296,0.0011504672,1.954999E-4,0.05112245,-0.005492553,0.05604242,0.032743253,0.019665744,0.021277457,-0.008058572,-0.02093815,-0.0054713464,0.022465037,0.03209291,-0.018746784,0.027809143,0.018817473,-0.007114871,0.004304974,-0.027328456,0.03353497,-0.008928049,0.041197684,0.0060863425,-0.011232519,-0.02618329,-0.0026861907,-0.009147187,0.01772886,0.023963649,0.044025254,0.029406719,-0.002550114,0.026310531,-0.008772533,0.012759406,-0.0065670293,0.0039692004,0.003930321,0.025518812,-0.0050260043,0.015396114,0.034157038,0.00833426,-0.019199194,-0.013883364,-0.0069346135,-0.026932597,-0.010780107,0.020570565,-0.004188337,-0.017700583,-0.013883364,-0.030283267,0.008129261,0.02744156,-0.004753851,0.050783142,0.005966171,0.006188842,-0.027328456,0.0029972233,0.012200961,-0.0010355972,0.01844989,-0.028629137,-0.013402678,-0.037437018,-0.028120175,-0.017135069,0.022097453,0.0023062362,-0.018054029,-0.015975766,0.027978797]]} +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T15:11:50.952308Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*4 +$8 +JSON.SET +$108 +vcr:embedding:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText:0001 +$1 +$ +$19408 +{"type":"batch_embedding","testId":"com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText","model":"text-embedding-3-small","timestamp":1765638711334,"embeddings":[[-0.036741957,-0.023619829,-0.01989364,-0.023892175,-5.032986E-4,-0.029660959,-0.0053169373,0.022480927,-0.013109748,0.004988884,-0.039242588,-0.040232938,0.01208226,-0.018383358,-3.6925325E-4,-0.012948816,0.018061494,-0.008424158,0.012119398,0.0163779,0.027680261,0.001658835,0.020822093,0.028522057,3.359837E-4,-0.023557931,0.016266486,0.050582085,0.050012637,-0.037187614,-0.024102625,-0.029958064,-0.006393942,-0.0035528778,-0.0084489165,0.07566268,-0.0017826286,-0.0046948744,-0.005307653,0.01521424,0.014211511,0.05521197,0.015573242,-0.025823357,0.010559599,-0.02730888,0.0104667535,-0.020537367,0.0035404984,0.0011002161,0.007910415,-0.05130009,-0.035603054,-0.038177963,0.0069757723,-0.012243193,0.019918399,0.012354607,0.0097611295,0.028224953,-0.03372139,-0.0032619627,-0.0034167047,-0.034860294,-0.05461776,0.025897631,-0.02186196,0.011766586,-0.010386287,-0.030378962,0.022097167,0.008770781,0.045679856,0.0064248904,0.009847785,0.012849781,-0.018470014,0.04681876,-0.0038530775,-0.019980295,0.02301324,0.0057780687,0.04602648,-0.05441969,-0.07011673,-0.022778032,-0.008158002,-0.029017232,-0.022889446,0.0071305144,0.0026398997,0.027927848,0.009866354,-0.020723058,0.040975697,0.028398264,-0.035008844,-0.040629078,-0.0066662882,0.019163257,-0.049566977,-0.0011474125,0.009346421,-0.01736825,1.5580592E-4,-0.015375172,0.020153606,-0.027061293,0.03565257,-0.014904756,-0.04449144,-0.0069510136,-0.005044591,0.04899753,0.053379823,-0.037138097,-0.022505686,0.014001062,0.0029370044,0.06164924,-0.009891113,-0.02126775,-0.024870144,-0.0050167376,-2.9942588E-4,-0.0194975,-0.03525643,-0.03223587,0.004428718,-0.012267951,0.015164723,0.003506455,0.022592342,0.06343187,-0.040678594,-0.02380552,-0.07437523,-0.00878316,-0.028546816,0.0421146,-0.007291446,-0.022381892,-0.012069881,0.026962258,-0.017454905,-0.018569048,-0.010541029,0.01656359,-0.0011489599,0.0050631603,-0.023916934,-0.06719519,-0.011686121,0.002406239,-0.046150275,-0.014496237,0.019831743,-0.026417565,-0.016303625,0.008442727,-0.0421146,-0.007279067,0.044590473,0.020524988,0.006882927,0.03374615,6.893759E-4,0.009476404,-0.027581226,-0.06962155,-0.013963924,-0.042461224,0.030354204,-0.022901826,0.035999194,-0.021688648,0.039366383,-0.047239657,-0.014719065,-0.022678997,-0.019324189,-0.0049053235,0.04971553,0.0057656895,0.068185546,0.023632208,-0.0063134762,0.008888384,0.010082993,0.036222022,0.0016015804,0.05709363,0.061302617,5.435315E-4,0.0022963723,0.0033671875,-0.009315472,0.04560558,-0.0036240593,-0.040827148,0.03193876,-0.045729376,-0.027952606,-0.024053106,-0.055112936,-0.043253504,0.0014151162,-0.016464556,-0.03644485,0.0102129765,0.0030700825,0.018408116,0.005289084,0.008523193,-0.015474207,-0.012447452,-0.0058028274,0.06263959,0.008888384,-0.030750344,-0.031146483,0.007966122,0.03845031,-0.012738367,-0.030205652,0.0036611974,0.037757065,-0.07610834,-0.00307163,-0.049245115,0.07298874,0.010900031,-0.0018955903,-0.0012704324,-0.0072481185,-0.034488913,0.0025362226,-0.022716135,-0.017058766,-0.021490578,-0.0124784,-0.02201051,0.04978981,0.010380098,-0.011419964,0.012825022,-0.009117402,0.03800465,0.037583753,-0.054172102,-0.0012023458,-0.0012224623,0.008424158,-0.03649437,0.042585015,0.011413774,-0.05342934,0.034389876,0.02516725,0.023520794,-0.0052179024,7.694549E-4,0.026269013,0.009853975,-0.060361784,-0.0034383687,-0.009575439,-0.0019853406,-0.0022669712,0.011153808,-0.018098632,0.031493105,0.039688244,-0.032582488,-0.061352134,-0.0057594995,-0.036618162,0.019287052,0.03394422,-0.006870548,-0.003432179,0.0076937755,0.007266687,-0.025377698,0.008436537,0.014161994,-0.020029813,-0.012323658,-0.023496035,-0.02106968,0.033003386,-0.05481583,0.008752211,-0.06452125,-0.039366383,0.036791474,0.040975697,0.006065889,0.019720329,0.036172505,0.07041383,-0.04488758,0.033176698,0.028348746,-0.03711334,0.048081454,-0.04424385,0.018370979,0.007254308,2.187666E-4,-0.018408116,0.0083498815,-0.007180032,6.839599E-4,-0.05402355,-0.05739074,0.05788591,0.017108282,0.021329647,-0.025241526,0.0171578,-0.030898895,0.040480524,-0.0026213306,0.006412511,0.0032217298,-0.055756662,-0.025575768,0.03273104,-0.016155072,0.021515338,0.02673943,0.019547017,-0.005917337,-0.025798596,-0.015053309,-0.04270881,0.041272804,-0.010448184,-0.026838465,-0.03394422,-0.014681927,0.016811177,0.023681726,0.0612531,0.05461776,-0.011741828,0.043946747,-0.0050414964,-0.0066229603,0.015573242,-0.058430605,0.020747816,0.028744886,0.013679199,-0.009278335,-0.016860696,0.009940631,0.030700825,-0.018494772,0.02400359,-0.0094083175,0.03822748,0.01697211,0.0035745418,-0.0027017964,0.0060163713,2.8027655E-4,0.041594665,-0.018581428,-0.049566977,0.010039665,-0.022827549,0.015078067,-0.024833007,0.045382753,-0.019782227,0.033498563,-0.011995605,0.04384771,0.03023041,-0.028224953,-0.030651309,-0.038078927,0.040777627,-0.0123731755,0.0066539086,-0.011933709,-0.032087315,0.019472742,-0.071651764,-0.0059390003,0.033052906,0.03978728,0.014409581,-0.008337502,0.04600172,0.0053912136,0.015226619,0.025006318,-0.0032898162,-0.017652975,-0.002085923,-0.02516725,-0.021837201,0.020921128,5.2418874E-4,0.024870144,0.018445253,0.005521197,0.024424488,0.011166187,0.019819364,-0.020252641,-0.008646986,0.01600652,0.022901826,0.06813603,-0.0018661893,0.0114261545,0.07130514,0.028571576,-0.021143956,-0.015474207,-0.006845789,-0.019757466,0.06546208,0.0038840258,0.027185086,-0.03434036,-0.03607347,0.0072419285,0.04053004,-0.012558866,-0.0530332,-0.032260627,0.012998333,0.02631853,0.014780962,-0.009061695,-0.043773435,-0.025773838,-0.04350109,-0.033374768,0.02829923,-0.012447452,0.0069386344,0.0734344,-0.0040449575,-0.0068210303,-0.02010409,-0.023372242,-0.03958921,0.03802941,0.008919333,-0.04231267,-0.034587946,-0.06986914,0.06694761,-0.030329445,-0.013617301,0.0077371034,-0.047313932,-0.017405387,0.073087774,-0.0031149578,-0.004499899,0.022542825,0.03253297,-0.003429084,0.028175436,-0.03374615,0.011240464,-0.04619979,-0.016080795,0.006236105,0.040480524,-0.025303423,-0.0036147747,-0.0024325452,0.0026832274,-0.026887981,0.02594715,-0.006028751,-0.006709616,0.054865345,0.012571245,-0.036395334,0.0075699817,0.016427418,-0.015325654,-0.034216564,0.028571576,-0.0117232585,-0.035380226,0.008888384,-0.042609774,0.005121962,0.042882122,-0.01970795,0.011159997,-0.02555101,-0.01616745,0.017046386,-0.008430348,0.0011566969,0.0012069881,-0.0010653991,-0.017071145,0.012007984,0.015610379,0.028596334,0.018816635,0.02420166,0.061154064,0.01970795,6.445007E-4,0.0316169,0.0029215303,0.0018599996,0.0074957055,0.02400359,0.0024371876,-0.021181094,-0.019163257,-0.038276996,-0.015090446,-0.023161793,-0.052240923,-0.02340938,0.0035745418,-0.0028457067,0.04060432,0.007334774,-0.023830278,-0.055657625,-0.013208782,0.01598176,-0.032062557,0.057539288,0.010262494,9.586271E-4,-0.021911476,0.014161994,0.038673136,-0.00662915,-0.007662827,-0.017207317,0.012063691,0.029611442,0.009693043,-0.008609848,0.018841393,-0.014719065,-0.001565216,0.016427418,-0.0079970695,-0.037336167,-0.033028148,-0.034612704,-0.025105353,0.015449448,0.035231672,0.017256835,-0.015511345,0.02631853,0.014124855,-0.0031196,0.049467944,-0.0055119125,-0.037732307,-0.010206787,0.019844122,0.0030731775,-0.017826285,0.004920798,0.028744886,-0.035033602,-0.014892376,0.0084489165,-0.028125918,0.046868276,0.02574908,0.0061123115,-0.024053106,-0.038673136,-0.025130112,-0.007718534,0.0265166,-0.002019384,0.043030676,-0.01596938,0.020586884,-0.03845031,0.021007784,0.012614573,0.0033579029,-0.024820628,-0.035429742,-0.009012178,-0.012181295,0.009810647,0.039490175,-0.007928983,0.022889446,0.013592543,0.0021369879,0.024486385,0.015746552,-0.008764591,-0.018544288,-0.015895106,-0.02222096,-0.002615141,0.01365444,0.009278335,-0.030354204,-0.01967081,0.011580897,-0.027383156,-0.017430147,-0.03003234,-0.005595473,0.0015628949,0.026417565,0.0061123115,-0.003918069,-0.048155732,-0.018754737,-0.05030974,-0.0059761386,0.020005055,0.053528376,-0.03914355,0.023619829,0.031270277,-0.007836138,0.0138401305,0.0096063875,0.032285385,-0.01327068,0.04800718,0.0053138426,-0.053181753,-0.009235007,-0.008944091,-0.043649644,0.0044318126,-0.025068214,0.0017640595,-0.019769846,-0.022505686,0.012125588,0.0039056898,0.022740893,-0.0028039261,0.007198601,0.017690113,-0.0067219953,-0.02614522,0.013084989,0.009321662,-0.0149418935,-0.05149816,-0.024350211,0.03414229,0.011760397,0.014347685,0.0076752063,-0.0026693007,0.0059606642,0.014780962,0.025650045,0.022753274,1.8743133E-4,-0.0038159394,0.009185489,-0.010454374,-0.0062670535,1.08319444E-4,-1.9923042E-5,0.046744484,-0.025724322,-0.06313476,0.0041285185,0.020933507,0.009018367,-0.008517004,8.2245405E-4,0.0013508982,-0.037162855,-0.013728716,0.007136704,0.02046309,0.0047041588,-0.0048310473,9.859971E-5,0.016724523,-0.013159265,-0.0033455235,0.031889245,0.031270277,-0.018630944,-0.02824971,-0.0027389345,-0.021304887,-0.008597469,-0.026046185,0.050804917,0.04800718,-0.019064223,-0.013419232,0.009272144,0.014459099,-0.033572838,0.009600198,0.026194736,0.0026213306,-0.015672276,-0.02671467,-0.027086051,0.023496035,-0.058628675,0.0019760563,-0.013394473,-0.0059792334,-0.008999798,-0.002089018,-0.014731444,-0.015387551,-0.006981962,0.013443991,-0.041446116,0.039465416,0.05798495,-0.019014705,-0.011444723,0.021960994,-0.04969077,0.008114674,0.015387551,0.0026801326,-0.004444192,0.027135568,-0.047437727,0.015461828,0.011203325,-0.016724523,0.018383358,-0.006783892,0.006248485,0.009284524,0.007867087,-0.00956306,0.017083524,-0.035627812,0.009631146,-0.0014058317,0.042882122,0.015845587,0.01931181,0.011289981,-0.005774974,0.010002527,0.037558995,-0.003162928,-0.003196971,0.0071490835,-0.019943157,0.021701027,0.019608915,-0.006356804,-0.024919663,0.00994682,0.038920723,0.030180892,0.022097167,-0.012800263,-0.0048279525,0.027036535,-0.014879997,-0.017318731,0.001330008,-0.025922392,-0.004911513,0.023087516,-0.04407054,0.0055676196,0.022381892,-0.03731141,-0.014681927,0.062491037,0.014384822,-0.022976102,4.8279524E-4,0.04971553,-0.003001996,-0.05694508,0.022790411,0.015276137,-0.016934972,0.02106968,0.027977366,0.018247185,-0.028125918,0.018086253,0.028423022,-0.0052581355,-0.019571776,-0.021948615,0.0045370376,-0.010033475,-0.027036535,0.015895106,-0.03141883,-0.013369715,-0.0245978,-0.030700825,0.0037509478,0.024919663,-0.018011976,-0.006146355,0.0031582855,-0.021181094,-0.0026383523,1.4555425E-4,0.0029617632,-6.386979E-4,-0.01385251,0.009699233,0.07120611,0.016043657,-0.03144359,0.02671467,-0.002203527,0.023557931,0.013976303,-0.018779498,-0.02260472,-0.048106212,-0.04053004,0.0448133,-0.017034007,-0.017232077,-0.023099896,0.008758401,0.023954071,0.0015621212,-0.03183973,0.00566356,-0.012137968,0.0010677202,-0.013221162,0.05709363,-0.008479865,0.047264416,-0.002732745,-0.06655147,-0.049294632,-0.0070500486,0.017826285,-0.010838134,-0.018581428,-0.017529182,0.018408116,0.06759133,-0.017442526,0.012998333,0.02319893,-0.035281193,0.039886314,0.026689911,-0.015994139,0.0041996995,-0.04431813,-0.01641504,0.010299632,0.05422162,-0.024139762,-0.021923855,0.041644186,-0.032656766,-0.02810116,0.012775505,9.679116E-4,-0.058529638,1.1980421E-5,0.035231672,-0.0074152397,0.0073966705,0.046447378,-0.016625488,0.0013826203,0.017281594,-0.009569249,0.030775102,3.4623538E-4,0.06140165,0.005149816,-0.030378962,-0.0038530775,0.021292508,0.030279927,0.001824409,-0.023842657,0.05382548,-0.0023056567,0.008752211,0.021738166,-0.0472149,-0.03490981,-0.0026801326,0.02067354,0.031715933,5.357944E-4,-0.005737836,-0.00633514,0.011946088,-0.0125279175,0.033176698,0.017021628,-0.0040666214,-0.01641504,0.014372443,-0.005406688,-0.013716336,0.01772725,0.007056238,0.032062557,-0.0030577031,-0.016489314,-0.008442727,0.04038149,0.0052705146,0.03221111,-0.049666014,0.0421146,0.0031753073,0.0027605984,-0.0021818632,0.024337832,0.019324189,0.024709214,-0.030923655,4.6267878E-4,-0.028571576,-0.015486586,-0.0021385353,-0.0055892835,-0.011648983,0.008337502,-2.3288681E-4,0.014607651,-0.002395407,0.0023195837,0.015053309,0.056251835,0.0049424614,0.017702492,-0.01814815,-0.0011311645,0.0031938762,0.0052024284,-0.020438332,0.016130313,-0.0029168879,0.042411704,0.027531708,-0.036890507,0.007297636,0.022951344,0.0045091836,0.013357335,0.010999066,-0.010157269,0.013988683,0.01577131,0.0047103483,-0.005190049,-0.042411704,0.018680463,0.014669548,0.023830278,-0.045778893,-0.06422415,-0.003196971,0.047734834,0.0018770213,0.012336037,0.041248046,0.005168385,0.005874009,-0.0045153736,-0.03221111,0.021119198,1.438134E-4,0.0065796324,-0.03802941,-0.0033238595,0.009402128,0.0027079862,-0.054716796,0.0045091836,-0.0035869211,-0.046051238,9.795172E-4,-0.032582488,-0.007879466,0.0032279196,0.008238467,-0.013369715,0.00516529,0.041025218,0.018395737,0.015300895,-0.010256304,0.00798469,5.3347327E-4,0.005443826,-0.006765323,0.010646254,0.015659897,0.0023443422,0.0049826945,-0.027754538,-0.016897833,-0.006301097,-0.026194736,0.010064424,-0.0035745418,0.00662915,0.0060875528,0.01617983,-0.011549948,-0.0025501493,0.058430605,-0.018569048,-0.029784752,-5.4159726E-4,0.032186348,0.01462003,-0.012688849,-0.017900562,0.020921128,5.659691E-4,-0.004134708,0.007508085,0.012948816,0.0071862214,0.012775505,-0.013320197,0.01210083,-0.019608915,-0.0034383687,0.03958921,0.040431008,0.0015296253,0.01911374,-0.04075287,0.018507151,-0.015127584,0.0022453074,-0.013419232,-0.002166389,-0.03243394,0.0016789514,0.032384418,0.0049455566,0.024919663,-0.03822748,-0.01990602,-0.02438735,6.8434677E-4,0.0151399635,0.03312718,0.02770502,-0.014100097,-0.004580365,-0.009884923,0.022171443,0.018346218,0.0016975205,-0.014124855,0.026095701,0.032483455,0.016452176,-0.03993583,-0.0013493508,-0.015895106,0.010986687,-0.013815371,0.0047474867,3.8298662E-4,-0.022109546,0.025650045,0.017232077,-0.05130009,0.013184024,0.03283008,0.0050012637,-7.8570284E-4,0.053875,-0.024721593,-0.0081518125,0.0046082186,0.0051714797,-0.0347365,0.033993736,-0.001103311,0.036593404,0.01929943,-0.0102129765,0.0127259875,-0.025068214,0.036147747,-0.010361529,-5.1529106E-4,-0.03213683,-0.011679932,0.03711334,0.0025408647,-0.007966122,0.012391744,0.031121723,0.012961196,-0.007192411,0.01596938,0.013171645,0.009680663,-0.011840863,0.041074734,-0.04446668,-0.0070314794,0.023025619,-0.014310546,-0.02047547,-0.0187176,0.024337832,9.849332E-4,1.0609503E-4,0.0062144413,0.027185086,0.009092644,-0.018086253,0.042188875,0.054964382,-0.0036704817,0.07298874,-0.016613109,-0.026764188,-0.01910136,-0.036940027,-0.0034445585,-0.03493457,-0.0066972366,0.0037757065,0.019460361,-0.0013949997,0.007118135,0.008653176,-0.039638728,0.012026553,0.0296362,0.0041749408,0.030304685,-0.0027621458,0.0041408977,0.014904756,-0.005440731,0.028200194,0.023830278,0.0035435932,-0.023099896,-8.0465875E-4,0.03374615,0.007885655,0.013864889,0.010237735,-0.040777627,-0.022431409,0.0074833264,-0.022121925,0.0073719122,-0.025823357,-0.027828813,0.018309081,-7.315431E-4,0.03726189,-0.024313074,0.01755394,0.05263706,0.048155732,-0.0016433607,0.028571576,-0.021242991,0.013146886,-0.030057099,-0.019212775,0.038103685,-0.0018476202,-0.013394473,0.0012657901,-0.027927848,-0.029165784,-0.011036204,0.02241903,-0.038128447,0.0029122457,-2.9536392E-4,-0.032508213,0.0063227606,-0.010454374,-0.0061773034,-0.067937955,0.02245617,-0.00935261,0.010770048,-0.01851953,0.026120462,-0.012583625,-0.01929943,0.04310495,-0.0073038256,0.026269013,0.054370172,-0.032780558,-0.036568645,-0.012459831,0.05590521,-0.03745996,0.04213936,-0.0048496164,0.046942554,0.021589613,-0.0056264214,0.035627812,0.042461224,-0.021688648,-0.020487849,0.048824217,0.011809914,-0.013592543,-0.0097611295,-0.023112275,-0.037558995,0.024535902,0.007155273,-0.0020658066,0.032285385,0.0056016627,-4.278618E-4,0.0069881515,-0.0051962384,-0.021478198,-0.029586682,0.0071057556,-0.0056790337,0.0032217298,-0.0038716465,0.044367645,0.012973575,-0.012459831,0.04213936,-0.049047045,0.0148181,-0.013382094,0.0011868717,-0.002876655,0.010404857,-0.031468347,0.006536305,0.037336167,-0.021936236,0.005694508,-0.0179377,-0.011661362,0.02844778,-0.0023164887,-0.03154262,0.0071862214,-0.0012936437,0.06120358,-0.00956306,0.026467083,-0.0102129765,0.009742561,-0.026368048,0.014780962,-0.012763126,-0.0063444246,0.012961196,-0.011890381,-0.025402458,0.006734375,0.0027930944,-0.018073874,-0.024733972,-0.036766715,-0.0068767373,-0.011265222,0.012317468,0.055162452,0.016452176,-0.004407054,-2.398502E-4,-0.051002987,0.019683192,-0.010596736,0.02515487,-0.019002326,-0.00740905,0.018061494,-0.010120131,-0.021527717,-0.012490779,-0.008028018,0.01792532,-0.028323987,-0.015276137,-0.01307261,-0.0034600326,-0.043203983,-0.026987016,-0.025328182,0.0041749408,0.01578369,0.0036240593,0.009835405,-0.0065672533,6.4217957E-4,0.0060349405,-0.00478153,-0.0202774,0.030700825,-0.010887652,0.0022499496,-0.01052865,-0.0017717966,0.004236838,0.0066539086,0.02730888,0.0041625616,0.027135568,-0.0150409285,-0.005796638,-0.0023458898,0.013406852,-0.042807844,0.0013857152,-0.0118222935,0.0064248904,0.022109546,-0.0013911312,-0.015697036,0.025377698,-0.0011373542,-0.011871811,0.016675005,0.0027048914,0.008721263,0.004468951,-0.024746351,-0.009389749,6.371021E-6,0.008671746,0.030131375,0.026887981,-0.002030216,-0.008436537,-0.01130855,-0.015622759,-0.034365118,0.0018491676,0.010949548,0.013035472,-0.011549948,0.04461523,-0.041867014,0.033696633,0.014892376,-0.054766312,4.5803652E-4,-0.006301097,-0.03181497,-0.00721098,0.008950281,0.037732307,0.0037695167,-0.0021617466,0.029017232,-0.007836138,-0.015226619,0.023855036,0.030700825,0.024300694,-0.01813577,-0.021020163,-0.028819162,0.026442325,-0.046051238,-0.047115862,-0.0226171,0.026887981,-0.010039665,0.035479262,9.6017454E-4,0.013109748,-0.029883787,0.013827751,0.004168751,0.0017919132,-0.0050600655,0.004270881,-0.019175638,0.03533071,-0.013666819,-0.0039985348,0.017987218,0.0091545405,0.008517004,-0.027457433,-0.012633142,-0.03179021,0.021020163,0.0039861556,-0.012602194,0.001493261,0.03283008,-0.024424488,0.006765323,-0.0115251895,0.018581428,-0.0034816966,-0.00526742,0.019732708,0.015127584,-0.0038159394,0.003314575,0.0139391655,0.014335305,0.02535294,-0.05402355,-0.010268684,-0.027086051,-0.01501617,-0.020512609,0.027185086,0.017492043,0.022171443,0.037336167,0.006771513,-0.016080795,0.0072728773,-0.02477111,0.024610179,-0.0109186005,0.031270277,0.026368048,-0.029685717,-0.029066749,0.012144158,0.01247221,0.03800465,-0.0093092825,0.029561924,0.013394473,0.035776366,-0.03154262,-0.026987016,0.027754538,0.011147618,-0.0059575695,-0.013146886,-0.0070686177,-0.0031211474,-5.238019E-4,0.030775102,0.0354545,0.02943813,-0.005227187,0.013122127,0.003549783,-0.003528119,-0.008659366,0.008919333,0.013629681,-0.024350211,0.008517004,0.018767118,0.0027281027,0.010392477,0.027581226,-0.0033424287,-0.02184958,-0.0028905817,-0.0021060396,-0.020784954,-0.0058554397,0.0133325765,0.008257037,-0.0015288516,1.9545858E-4,0.020042192,0.0108195655,9.640431E-4,-0.005264325,-0.004821763,-0.029586682,-0.014706686,-0.016266486,-0.011772776,-0.005988518,-0.00955687,-0.03552878,-0.02671467,-0.033052906,-0.0035652572,0.0150409285,0.050804917,-8.170381E-4,-0.032681525,-0.027482191,0.0015791428,0.002571813,0.0086036585,-0.03040372,-0.018569048,0.023545552,0.018866153,0.013357335,-0.019262291,-0.011488051,0.040851906,0.032508213,0.015350413,-0.004701064]]} +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T15:11:51.337248Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*4 +$8 +JSON.SET +$102 +vcr:embedding:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText:0001 +$1 +$ +$19374 +{"type":"batch_embedding","testId":"com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText","model":"text-embedding-3-small","timestamp":1765638712187,"embeddings":[[-0.019984918,0.053795878,0.02393721,0.0064696106,0.052147925,0.009231328,0.025962237,0.036590133,-0.031618346,-0.05362829,-0.009636333,-0.008365454,0.031869728,-0.012659907,0.027624154,-0.00293978,-0.009587454,0.017652646,-0.032232836,0.051673092,0.0014908385,-0.0036240993,0.022987543,-0.015152785,0.029411765,-0.006546422,-0.034383554,-0.018071616,0.039020166,-0.08133624,0.02045975,-0.041031227,0.023141166,0.027917435,0.039830178,0.040779844,0.060890455,-0.0322887,-0.001002039,0.028769342,-0.005931931,-0.011521703,-0.011863862,-0.0040186304,0.020836825,0.025948271,0.00698285,-0.0047518294,-0.0029432713,0.04267918,-0.033769064,-0.0037672475,-0.03443942,0.01805765,0.005125412,-0.02481705,-0.018392827,0.017163845,5.926694E-4,0.03357354,0.011954639,-0.0035822021,-0.010474275,0.011277303,-0.007206301,0.04245573,-0.016465561,0.04452265,-0.01776437,0.0060750796,-0.011277303,-0.0035368137,-5.2022235E-4,-0.042930562,0.036869448,-0.022596503,0.0110748,0.015208648,0.0018155411,0.019174907,-0.00742277,0.04692475,-0.009789956,-0.06837607,0.01624211,-0.023113234,-0.02435618,2.6032937E-4,0.02400704,0.029216245,-0.0145243285,0.0113192005,-0.01825317,-0.0027233115,-0.02484498,-0.04005363,7.052679E-4,-0.006651165,-0.047790628,0.047511313,-0.02910452,-0.020278197,-0.02075303,-0.013637506,0.03988604,-0.008882185,0.0143288085,-0.012478353,-0.021800458,0.04131054,-0.055751074,-0.07647617,0.01555779,-0.048293393,0.0031684681,-4.0413244E-4,-0.023197029,0.006570862,-0.014217082,-0.016186247,0.019733535,0.017736439,-0.010523155,0.0043922127,-0.0110259205,-0.036645997,0.021255795,-0.025305849,0.007220267,-0.009294174,0.052147925,0.0019778924,0.036366682,-0.0141263055,-0.0337132,0.002847257,-0.03429976,0.027065527,-0.011528686,-0.06854366,-0.011856879,-0.02520809,0.016004693,-0.009440813,0.016591253,0.0027058544,-0.0015737598,0.04206469,0.021199932,0.01516675,0.025990168,0.0071993186,0.008323558,-0.009608402,-0.02643707,8.252856E-4,0.045137145,-0.018392827,0.0027494973,-0.036645997,-0.040221218,0.013190604,0.00881934,-0.028825205,-0.032372493,-0.014803642,-0.04309815,-0.022624435,-0.041254677,-0.009377968,-0.015278476,0.0022188006,0.01923077,0.02004078,-0.021493213,-0.0070666443,0.027163288,0.03114351,0.025319815,0.010250824,0.003589185,-0.026548795,0.024104798,0.027428634,0.026870007,-0.015906932,-0.023476342,-0.021158036,-0.027889503,0.027791744,-0.05904698,0.12122228,-0.014957265,0.03256801,0.043600917,-0.02293168,-0.00284202,0.021507178,-0.0708899,-0.019021284,-0.004112899,-0.004469024,0.012059382,-0.029411765,0.008616837,0.030054187,0.0037078934,-0.0036345734,-0.023434445,-0.005652617,0.012213005,-0.0054256744,-0.01844869,5.603737E-4,0.020822858,-0.0035996593,0.014510362,0.012108262,0.0023724234,0.012499302,-0.01756885,0.009496676,0.05887939,-0.0050136866,-1.4554878E-4,0.037372213,0.012415508,-0.05566728,0.0065918104,-0.006309005,0.03435562,-0.015236579,-0.009580471,-0.03050109,0.016702978,0.020166472,-0.003934836,-0.04069605,0.022526674,0.0025888917,-0.045220938,-0.016018659,-0.03974638,0.014817608,-0.005862103,0.02393721,-0.021213898,-0.01597676,0.050081,0.02133959,-0.010788503,-0.011954639,0.018309033,-0.06619742,-0.026562762,-0.030417295,0.012897324,0.03290319,0.023755657,0.04968996,-0.034076307,-0.073627174,0.05326518,0.02621362,-0.050583765,-0.021130104,-0.0071923356,0.011423943,0.012115245,-0.0076951007,-0.0303335,-0.04362885,0.037400145,-0.021255795,0.033769064,0.024928775,-0.02143735,-0.014901402,-0.011570583,0.048572704,-0.0019691638,-0.06223116,0.006406765,0.014265963,0.0018871153,0.0418971,0.023252891,-7.0483144E-4,0.030808335,0.0040291045,-0.033796996,-0.05209206,-3.4194143E-4,-0.0058027483,0.008630803,-0.033238366,-0.027596224,-0.013050947,-0.019091113,0.0042176414,0.11004972,0.044355065,-0.043433327,0.015515893,-0.009210379,0.0035245935,-0.0034111224,0.0020389922,0.024970671,0.019468186,-0.039159823,-0.02783364,-4.791981E-4,-0.025864476,-0.033182506,-0.021297693,0.040193286,-0.0107326405,0.034998044,-0.02871348,0.029830735,-0.032651808,0.03427183,0.003503645,-0.024314284,-0.023588067,-0.034383554,-0.04608681,-0.0043572984,-0.0072412156,0.038154293,0.0019813837,0.022065807,0.0025854004,0.037176695,-0.053795878,-0.0022973577,-0.02400704,-0.021102173,-0.036227025,0.035808057,-0.0066895704,-0.013791129,-0.0074786325,0.008100106,0.016256075,-0.02604603,0.02712139,-0.019286633,-0.040332943,-0.0202363,-0.029188313,-0.0036101334,-0.06859952,0.010816434,0.0031545025,0.025417574,-0.009287191,-0.018295066,-0.016130384,-0.04027708,0.0016453341,0.008798392,0.0038300932,0.04988548,-0.006399782,-0.021074241,0.0029607285,0.018322999,-0.012247919,0.0021978521,0.03745601,-0.06005251,0.0024457432,0.002459709,0.025305849,0.017191777,0.052231718,0.028503994,0.05387967,0.0143288085,0.054466233,-0.038210157,-0.010579018,0.056952126,-0.011214457,-0.0067140106,0.0040535447,-0.08234177,0.0016208941,-0.0036520306,-0.0051289033,-8.423063E-4,0.009357019,0.017778337,-0.038266018,-0.050891012,-0.02192615,0.019482153,0.031618346,3.179379E-4,-0.006801296,-0.04703648,-0.05404726,-0.015809173,0.022428915,0.0017107982,0.006291548,0.00837942,-0.07513547,0.008051226,-0.0045388527,0.038377743,0.014140272,-0.006672113,-0.023476342,-0.033433888,-0.014293894,0.009070722,0.04086364,0.02724708,0.0017465854,-0.07401821,-0.08949221,0.0014594157,0.072063014,0.018630244,0.0010788504,-0.009426848,-0.051393777,-0.010376515,-0.007653204,0.0023602033,0.0022030892,0.004549327,0.01578124,0.028741412,0.00943383,-0.0024492347,-0.06005251,-0.027596224,-0.0036555221,-0.03312664,0.047427516,-0.07926931,0.004943858,-0.019468186,0.0010421904,0.01656332,-0.052734483,0.017066086,-0.019565945,-0.014999162,0.005910983,-8.855127E-4,-0.024453942,-0.021968046,0.020739065,0.0038894475,-0.0011024175,-0.04346126,0.0045807497,-0.025668956,0.042539522,0.10708899,-0.037260488,0.048740294,0.042120554,0.038433608,-0.042651247,-0.023895314,0.02969108,-0.0050276523,0.02604603,0.008190883,-0.001032589,-0.004469024,-0.008679682,0.01883973,-0.0047343723,0.007681135,0.025696889,0.0026325346,-0.048824087,0.009070722,0.06340428,-0.022945646,-0.0061414167,0.01633987,0.010418412,-0.022359086,0.012143176,0.022722194,-0.017471092,0.016647115,0.026492933,-0.0012647688,-0.052706555,0.04508128,-0.033210434,0.045444388,0.0151946815,-0.012254902,0.08960393,-0.025417574,0.04692475,-0.0313111,-0.0093709845,0.02452377,0.006089045,0.03290319,0.047539245,-0.015795207,0.005467572,0.0022030892,-0.011249372,-0.021912184,0.020110609,-0.019286633,0.025878442,-0.0028699513,0.018909559,-0.0010465547,-0.0027791744,0.009643316,0.011968605,-0.036199097,0.023476342,-0.010027373,0.0076601864,0.011402994,-0.0017806268,0.010551087,0.036003575,0.0035926765,-0.04871236,-0.021912184,0.026842076,-0.005020669,-0.006965393,0.029970393,0.041031227,-0.033852857,0.014384671,-0.0048426064,0.033210434,0.03156248,0.0053767944,0.00862382,0.025641026,0.013574661,-0.020208368,0.05287414,-0.036785655,0.06837607,-0.021618905,-0.019049214,0.0100553045,0.0057049887,-0.00142712,-0.019007318,0.031758003,0.01597676,0.02131166,-0.014677951,-0.021814425,0.026227586,-0.030584883,-0.037707392,-0.015208648,-0.028392268,-0.02004078,-0.0140355285,-0.003885956,-0.010592984,0.06368359,-0.03413217,-0.028629685,0.016884532,-0.021353556,0.001876641,-0.015613653,6.489686E-4,0.008351489,-0.043042287,0.025710855,0.010160047,-0.028657617,0.014056478,-0.0057887826,0.023266857,0.032540083,0.035389084,-0.022694264,-0.0029066114,0.0070841014,0.046617508,0.02383945,-0.023252891,0.037260488,-0.025641026,0.025194123,-0.0016208941,-0.04251159,-0.0043922127,0.06223116,-0.01175912,0.0045213955,0.0026744315,-0.020306129,-0.033769064,-0.012324731,-0.008491145,-0.06334842,-0.052287582,0.013951735,-0.021088207,-0.005628177,-0.0010832147,-0.022652365,-0.0077928607,7.925535E-4,-0.013029998,-0.006682588,-0.02849003,0.025459472,-0.02663259,-0.027205184,-0.04684096,-3.5416143E-4,0.0081839,4.5934063E-5,4.0915137E-4,0.0013415801,0.0018591839,0.025571197,-0.0044061784,0.02332272,0.012527233,0.020836825,0.040360875,0.011577565,0.021451315,-0.011409977,0.06413049,0.0036066421,0.0071993186,0.039439138,0.029272107,-0.001379113,-0.021130104,-0.030445227,0.043321602,0.00399419,0.017261606,0.012834478,0.009161499,-0.009517625,0.030640746,0.0027425145,0.020306129,-0.0145243285,-0.012946204,0.0032051282,-0.025599128,0.024956707,-2.7691366E-4,0.0141263055,0.021381486,0.026171722,0.0011626446,-0.039215688,0.071951285,-0.0051149377,0.0135537125,0.02332272,0.039411206,-0.017457126,-0.022205463,-0.006399782,0.022428915,0.0010168776,-0.04362885,-0.061169766,0.008945031,-0.00455631,-0.016940394,-0.008637786,0.02143735,0.023434445,4.730881E-4,-0.0025295375,0.018420758,-0.0104393605,0.024216523,0.0074856156,0.027819674,0.038377743,0.016535388,-0.004971789,-0.0037986704,0.0057713254,-0.007653204,-0.017694542,0.0070736273,-0.012534216,0.025920339,-0.017917993,0.022778057,-0.027819674,-0.013469918,-0.0026988715,-0.012729736,0.040975366,-0.009364002,-0.009412882,-0.0044934643,0.052231718,-1.02015074E-4,-0.0042246245,-0.02075303,0.016186247,-0.023881348,-0.014154237,0.033210434,0.019202838,-0.012324731,-0.007960449,-0.007911569,0.032037318,-0.0021524637,-0.0044480753,0.004549327,0.02065527,-0.0046505784,0.012820513,-0.02374169,0.017904028,-0.018937489,-0.007778895,-0.0011547889,-0.016353834,-0.026311379,0.007750964,-0.035584603,-0.01786213,0.0152924415,0.010411429,0.004699458,-9.601419E-4,-0.02234512,0.011556617,0.012932238,0.047986146,0.01702419,-0.03848947,-0.0029048657,-0.014412602,-0.03309871,0.022875817,-8.545263E-4,0.034970112,-0.04765097,0.03332216,0.018183341,-0.0010814689,-0.046980616,-0.04248366,0.0020966008,0.0034390537,0.026004134,-0.023141166,-0.001658427,-0.023238925,0.03234456,0.003697419,-0.0066965534,0.014887436,-0.023210993,0.014971231,0.0033343108,-0.03491425,0.025766717,-0.040249147,-0.0036694878,-0.007171387,-0.050360315,-0.022093737,-0.03631082,-0.028769342,-0.0018993352,-0.033405956,0.02295961,-0.028559856,4.7701594E-4,0.0197475,-0.022792023,-0.054550026,0.014251997,0.0068536676,3.9038496E-4,0.042790905,0.035612535,0.0014445771,-0.001208906,0.027037596,-0.020361992,-0.03670186,-0.051142395,-0.01886766,-0.03474666,-6.0881727E-4,0.036171164,0.017820233,0.008868219,0.01825317,1.6006002E-4,-0.031199375,0.023979107,0.014929334,0.002812343,9.749805E-4,-0.0048426064,-0.0030654713,0.025682922,0.0050800233,-0.013986649,0.02403497,0.0075973407,-0.020557512,0.03069661,0.005921457,-0.034215964,0.03335009,-0.011891793,-0.0054326574,0.036645997,-0.0043747555,-0.029830735,0.0010465547,-0.0065080165,0.022331154,0.008135021,0.0039069047,0.01822524,-0.041115023,-0.005938914,3.7969247E-4,0.018686106,0.009000894,0.019398358,0.00291534,0.03848947,0.016577287,0.029272107,0.007548461,0.010104184,-0.009580471,-0.008058209,-0.024886878,-0.014286911,0.029160382,0.020054746,0.002180395,-0.009692196,0.036785655,0.0015859798,-0.0071329814,0.0438523,0.001002039,0.0051568346,0.026032066,0.012052399,0.0013433258,-0.015823139,0.01352578,0.015962796,0.03251215,-0.013574661,-0.017554885,-0.016912462,-0.0027756828,-0.0061658565,-0.03474666,0.0021838865,0.047232,-0.013225518,0.031059718,-0.012638959,-0.001925521,0.011731188,0.024509804,0.010949109,-0.03589185,-0.0014061715,-0.006012234,0.026059996,0.020669237,-0.002599366,0.005404726,-0.016256075,-0.033545613,-0.010816434,0.01903525,0.0021594465,0.010942126,0.019733535,-0.018630244,0.008931065,2.1384978E-4,-0.004231607,0.02562706,0.012506285,-0.0069130217,0.012918273,-0.009685213,-0.0089171,-0.029774873,-0.020361992,0.008023295,-0.011256354,0.022987543,0.028531926,0.0877046,-0.012366627,0.032791466,-3.227386E-4,0.006769873,0.056393497,0.0013983158,0.07776102,-0.009587454,-0.024565667,0.0037009106,0.03533322,0.034998044,0.017107982,-0.004626138,0.021618905,-1.07852306E-4,-0.031981453,0.006399782,-0.0034966622,-0.011640411,0.02006871,-0.018895593,-0.009496676,-0.012268867,-0.017792301,-0.010348584,-0.009063739,0.0019883665,0.09083291,-0.036645997,0.005750377,0.02124183,-0.005100972,0.012066365,0.049215127,5.896144E-4,-0.015418133,0.0139657,-0.030026255,-0.02533378,-0.0014733814,-0.059158705,0.022526674,0.012974136,5.167309E-4,-0.03885258,0.054550026,-0.00394531,0.030026255,0.012233953,0.02092062,0.012380593,9.1475336E-4,-0.008637786,0.022638401,0.024314284,0.03572426,0.04686889,0.026381208,-0.02114407,0.002101838,-0.017205743,0.013016033,0.013518798,-0.017931959,0.0059424057,-0.019174907,-0.018937489,-0.023406513,-0.009852801,-0.012394559,0.011507737,-0.019216804,0.012645941,0.016353834,-0.018169375,-0.05047204,0.00575736,-0.019146975,-0.027693983,0.002126278,0.030026255,0.048377186,-0.015013128,-0.0021280237,0.0032417881,0.01536227,0.027065527,0.026381208,-0.04745545,-0.025305849,0.018211273,-0.010425395,0.008965979,-0.035752192,-0.0049473494,0.03927155,-0.029802805,0.011004971,0.0045947153,-0.0057713254,-0.017512988,0.0051847664,-0.008512095,0.03413217,0.026492933,-0.026451036,-0.012492319,-0.011842914,0.00580624,0.008197866,0.031785935,-0.02452377,-0.005666583,-0.004371264,-0.019188872,-0.02949556,-0.02396514,-0.003770739,0.011773085,0.02783364,-0.0020180438,0.027288979,0.0099575445,0.025878442,0.0029851685,0.008931065,0.0106348805,-0.0047343723,-0.04050053,0.013099827,0.011654377,-0.01744316,-0.002726803,0.0065918104,-0.010111167,-0.0020808894,-0.02124183,0.019244734,0.0059738285,-4.6130453E-4,-0.02192615,-0.012932238,0.0032557538,0.025808614,0.0351377,-0.0019761466,0.008253729,0.022708228,-0.051226187,0.0022659348,-0.0029607285,-0.019607844,-0.018909559,-0.008903134,0.008267694,-0.004566784,0.016284006,-0.036143232,0.007541478,-0.022359086,0.02822468,0.013874923,-0.013414055,0.01411234,-0.040975366,0.0046540694,0.0044934643,0.0099575445,-0.021800458,1.9093731E-4,0.047343723,0.008463214,-0.022233395,0.02585051,0.018239204,-0.002550486,-0.012659907,-0.015278476,0.014440534,-0.012415508,0.015529859,-0.010572035,0.01867214,0.0028682058,0.02442601,0.014468465,-0.038154293,0.0013799857,0.01011815,-0.0033552595,-0.0118987765,-0.00717837,-0.01795989,0.05304173,0.019188872,0.024509804,-0.002501606,0.039215688,0.036813587,0.0027966315,-0.040025696,0.033182506,0.0019080638,-0.009287191,0.0010893246,-0.0019796381,0.015418133,0.0022153093,0.015124854,0.006790822,-0.014175186,0.0034826966,0.01416122,-0.03552874,0.052455172,-5.7041156E-4,0.008882185,-0.02335065,0.003405885,0.04083571,0.02111614,-0.0279314,0.085302494,-0.0076252725,-0.01795989,0.0048670466,-0.04153399,0.005069549,-0.009475728,-0.009769008,-0.011863862,-0.005369812,-0.0072900956,0.009168482,-0.014279928,-0.023448411,-0.03589185,0.011081783,-0.016730908,0.05644936,-0.033992514,-0.017163845,0.035389084,0.0050765323,-0.002550486,0.0038370762,-0.041170884,-0.047790628,-0.021227865,-0.02111614,0.014342774,0.017596783,-0.0016505712,-0.007848724,-0.012904307,0.037819117,0.0031352998,9.1300765E-4,-0.02092062,-0.012659907,0.01867214,-0.029635215,0.008407352,0.025780683,-0.0026971258,-0.005457097,-0.012185073,0.020627338,0.0084981285,-0.051952403,-0.020613374,0.009175465,-0.01320457,-0.0036520306,-0.008777442,-0.0020128065,0.017512988,-0.038405675,0.034970112,0.006466119,0.0127716325,-0.024998603,-4.6130453E-4,-0.010844367,-0.02910452,-0.035780124,-0.008009329,-0.033042848,-0.031758003,0.0019848752,0.016493492,-0.013253449,0.013958718,0.01766661,-0.0011530431,-0.017806267,-0.002011061,0.012310765,0.03466287,0.005921457,0.024691358,0.03212111,0.021130104,0.05921457,-0.02322496,0.03452321,-0.012219988,0.019565945,0.034076307,-0.008665717,0.03991397,0.05323725,-0.020585442,3.7969247E-4,-3.8056533E-4,0.002175158,0.0084981285,-0.0030864198,-0.0014952028,-0.0089171,0.002957237,0.013951735,-0.035081837,0.0067559076,-0.015082956,-0.0015240071,0.018755935,0.011333166,-0.0027844114,0.006029691,-0.019663706,-5.0189235E-4,-0.01252025,-0.0018033211,-0.01633987,0.02254064,-0.010579018,-0.0057783085,-0.004479498,-0.036562204,0.00872158,-0.031227306,0.018434724,0.038349815,-0.020515613,-0.015697448,0.035165634,0.014084409,0.0077439807,-0.0023881348,-0.013888889,-0.048740294,0.004025613,-0.021995978,0.019579912,0.011954639,-7.633128E-4,-0.0015030585,0.017736439,-0.013176639,-4.1787993E-5,-0.019635774,0.012206022,0.0155857215,-0.0076182894,0.010788503,0.05787386,-0.008505112,-0.011591531,0.021563042,0.0022746634,0.009706162,-0.013602592,-0.031813864,-0.03270767,0.024914809,0.02114407,-0.011521703,0.04223228,-0.020320093,0.013155689,-0.011675325,-0.007366907,0.017261606,-0.0118987765,-0.018658176,0.021088207,0.01822524,-0.01604659,-0.0056630913,-0.03267974,-0.01594883,-0.004591224,0.021577006,0.017820233,-0.025808614,6.600976E-5,-0.015334339,-0.020794928,-0.011242389,0.035863917,0.0130649125,-0.020892687,0.0064346963,0.039606728,0.045500252,-0.021227865,-0.027261047,-0.02413273,8.309592E-4,-0.011095749,-0.019593878,0.03404838,0.04692475,0.01825317,0.011766102,-0.038210157,0.018085582,0.006298531,0.029355902,-0.04186917,0.026646556,-0.04784649,0.024789117,0.0017203997,-0.021521144,-0.016968327,0.002896137,-0.0022449864,0.012213005,0.020417854,-0.06463326,0.0151946815,0.041115023,0.009091671,0.013462935,0.032009386,0.032791466,-0.0030829282,0.01698229,0.008323558,0.022331154,-0.013700352,-0.011926708,0.0065603876,-0.042790905,0.006686079,-0.013798112,0.011877828,-0.0025120804,0.015250545,0.022135634,0.029607285,-0.003510628,0.00575736,-0.049326852,0.014084409,-0.027163288,-0.021283727,-0.010753589,-0.013532763,0.003648539,-0.0015370998,-0.0021646835,0.015446064,0.005575806,0.012415508,0.002604603,0.010160047,0.0151946815,0.009608402,0.04949444,-0.03848947,0.016228143,0.00717837,0.0061449083,-0.010984023,-0.03751187,-9.1213477E-4,0.0144545,-0.0033744622,0.003273211,0.0065010334,0.0057818,0.0060436567,0.019216804,-0.009168482,-0.026478969,-0.0024928774,0.027275013,-0.025892409,0.02721915,-0.00150044,0.020697167,0.0021402435,-7.9997274E-4,0.0023916261,-0.0015467013,0.014677951,-0.004517904,0.018197307,-0.038182225,-0.018951455,-0.00436079,-0.021912184,-0.0070526786,0.011528686,0.013057929,-0.015501928,-0.015823139,0.0046051894,-0.0049997205,0.034634937,0.003199891,-0.029774873,0.039048098,0.007415787,-0.019160941,-0.0058586113,-0.023532204,-0.04290263,0.003351768,0.0027355314,0.02052958,0.029188313,-0.044550583,-7.4061856E-4,3.969314E-4,-0.015683481,0.028308474,-0.04231607,0.021772526,0.0033849366,0.011787051,-0.021702698,0.012122228,0.02913245,0.0019028267,0.013246466,0.03888051,0.021828389,0.043768503,0.05681247,4.3184563E-4,-0.031394895,0.036981173,-0.050974805,0.0069549186,0.004936875,0.01276465,0.019328529,0.055779006,0.022470811,-0.01578124,-0.0046540694,-0.013002067,0.00962935,-0.025682922,0.00595288,0.029830735,-0.012240936,-0.018309033,-0.009231328,-0.0076951007,-0.01578124,-0.01036255,0.048963744,0.015348305,0.01844869,0.020208368,-0.019970952,-0.0040291045,-0.0049892464,0.049131334,0.008030278,-0.009007877,0.007841741,0.011745154,0.02295961,0.004821658,-0.03156248,0.0027460058,0.04806994,-0.041366406,0.014705882,-0.019202838,-0.023448411,-0.011158595,0.0014245014,0.0070945756,-0.027372772,0.020278197,0.02396514,0.017303502,-0.024872912,-0.0057294285,0.014203117,-0.030445227,-0.020864757,-9.69416E-5,-0.005593263,-0.02234512,0.037400145,-0.011912743,0.021297693,-0.0044655325,-0.006120468,-0.0056630913,-0.005813223,0.0015021856,0.0045877327]]} +*8 +$4 +HSET +$92 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T15:11:52.189948Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$82 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T15:16:07.397497Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$90 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T15:16:08.018728Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$91 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T15:16:08.183004Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T15:16:08.366720Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T15:16:08.921030Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T15:16:09.047021Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$92 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T15:16:09.170774Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$82 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T15:17:56.955247Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$90 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T15:17:57.547607Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$91 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T15:17:57.678289Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T15:17:57.811669Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T15:18:08.697106Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T15:18:08.853537Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$92 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T15:18:08.991919Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$82 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$CombinedTests:shouldSimulateRAG +$11 +recorded_at +$27 +2025-12-13T16:29:32.730330Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$90 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldProvideCodeExample +$11 +recorded_at +$27 +2025-12-13T16:29:33.420368Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$91 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldAnswerRedisQuestion +$11 +recorded_at +$27 +2025-12-13T16:29:33.559709Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$94 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$ChatModelTests:shouldExplainVectorDatabases +$11 +recorded_at +$27 +2025-12-13T16:29:33.716833Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*2 +$6 +SELECT +$1 +0 +*8 +$4 +HSET +$95 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedMultipleTexts +$11 +recorded_at +$27 +2025-12-13T16:29:44.741562Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$98 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedVectorSearchText +$11 +recorded_at +$27 +2025-12-13T16:29:44.894743Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 +*8 +$4 +HSET +$92 +vcr:test:com.redis.vl.demo.vcr.SpringAIVCRDemoTest$EmbeddingModelTests:shouldEmbedSingleText +$11 +recorded_at +$27 +2025-12-13T16:29:45.019495Z +$6 +status +$8 +RECORDED +$14 +cassette_count +$1 +0 diff --git a/demos/spring-ai-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.manifest b/demos/spring-ai-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.manifest new file mode 100644 index 0000000..7f8bb72 --- /dev/null +++ b/demos/spring-ai-vcr/src/test/resources/vcr-data/appendonlydir/appendonly.aof.manifest @@ -0,0 +1,2 @@ +file appendonly.aof.1.base.rdb seq 1 type b +file appendonly.aof.1.incr.aof seq 1 type i diff --git a/demos/spring-ai-vcr/src/test/resources/vcr-data/dump.rdb b/demos/spring-ai-vcr/src/test/resources/vcr-data/dump.rdb new file mode 100644 index 0000000..6ffec66 Binary files /dev/null and b/demos/spring-ai-vcr/src/test/resources/vcr-data/dump.rdb differ diff --git a/docs/content/modules/ROOT/nav.adoc b/docs/content/modules/ROOT/nav.adoc index 221c0b5..a946fcc 100644 --- a/docs/content/modules/ROOT/nav.adoc +++ b/docs/content/modules/ROOT/nav.adoc @@ -17,6 +17,9 @@ .API Reference * xref:api-reference.adoc[Javadoc API Reference] +.Experimental +* xref:vcr-testing.adoc[VCR Test System] + .Resources * https://github.com/redis/redis-vl-java[GitHub^] * https://github.com/redis/redis-vl-python[Python Version^] diff --git a/docs/content/modules/ROOT/pages/vcr-testing.adoc b/docs/content/modules/ROOT/pages/vcr-testing.adoc new file mode 100644 index 0000000..132f9c1 --- /dev/null +++ b/docs/content/modules/ROOT/pages/vcr-testing.adoc @@ -0,0 +1,667 @@ += VCR Test System +:navtitle: VCR Testing +:description: Record and replay LLM/embedding API calls for deterministic, fast, and cost-effective testing +:page-toclevels: 3 +:experimental: + +CAUTION: This feature is experimental and the API may change in future releases. + +== Overview + +The VCR (Video Cassette Recorder) test system provides a way to record and replay LLM and embedding API calls during testing. This approach offers several benefits: + +* **Deterministic tests** - Replay recorded responses for consistent test results +* **Cost reduction** - Avoid repeated API calls during test runs +* **Speed improvement** - Playback from local Redis is much faster than API calls +* **Offline testing** - Run tests without network access after initial recording + +The VCR system uses Redis for cassette storage with AOF/RDB persistence, allowing recorded data to be committed to version control and shared across team members. + +== Quick Start + +=== Add Dependencies + +To use the VCR test utilities, add the following test dependencies alongside RedisVL: + +[source,xml] +---- + + + org.junit.jupiter + junit-jupiter + 5.10.2 + test + + + org.testcontainers + testcontainers + 1.19.7 + test + + + org.testcontainers + junit-jupiter + 1.19.7 + test + +---- + +[source,groovy] +---- +// Gradle +testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' +testImplementation 'org.testcontainers:testcontainers:1.19.7' +testImplementation 'org.testcontainers:junit-jupiter:1.19.7' +---- + +=== Basic Usage + +The recommended approach uses `@VCRTest` on the test class and `@VCRModel` on model fields for automatic wrapping: + +[source,java] +---- +import com.redis.vl.test.vcr.VCRTest; +import com.redis.vl.test.vcr.VCRMode; +import com.redis.vl.test.vcr.VCRModel; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.chat.ChatLanguageModel; +import org.junit.jupiter.api.Test; + +@VCRTest(mode = VCRMode.PLAYBACK_OR_RECORD) +public class MyLLMTest { + + // Models are automatically wrapped by VCR - initialize at field declaration + @VCRModel(modelName = "text-embedding-3-small") + private EmbeddingModel embeddingModel = createEmbeddingModel(); + + @VCRModel + private ChatLanguageModel chatModel = createChatModel(); + + @Test + void testEmbedding() { + // First run: Records API response to Redis + // Subsequent runs: Replays from Redis cassette + Response response = embeddingModel.embed("What is Redis?"); + assertNotNull(response.content()); + } + + @Test + void testChat() { + String response = chatModel.generate("Explain Redis in one sentence."); + assertNotNull(response); + } + + private static EmbeddingModel createEmbeddingModel() { + String key = System.getenv("OPENAI_API_KEY"); + if (key == null) key = "vcr-playback-mode"; // Dummy key for playback + return OpenAiEmbeddingModel.builder() + .apiKey(key) + .modelName("text-embedding-3-small") + .build(); + } + + private static ChatLanguageModel createChatModel() { + String key = System.getenv("OPENAI_API_KEY"); + if (key == null) key = "vcr-playback-mode"; + return OpenAiChatModel.builder() + .apiKey(key) + .modelName("gpt-4o-mini") + .build(); + } +} +---- + +IMPORTANT: Models must be initialized at field declaration time, not in `@BeforeEach`. The VCR extension wraps `@VCRModel` fields before `@BeforeEach` methods run. + +== VCR Modes + +The VCR system supports six modes to control recording and playback behavior: + +[cols="1,3"] +|=== +|Mode |Description + +|`PLAYBACK` +|Always replay from cassettes. Fails if cassette not found. + +|`RECORD` +|Always record new cassettes, overwriting existing ones. + +|`RECORD_NEW` +|Record only if cassette is missing; otherwise replay. + +|`RECORD_FAILED` +|Re-record cassettes for previously failed tests; replay successful ones. + +|`PLAYBACK_OR_RECORD` +|Replay if cassette exists; record if not. Best for general use. + +|`OFF` +|Disable VCR entirely - all API calls go through normally. +|=== + +=== Recommended Modes + +* **Development**: Use `PLAYBACK_OR_RECORD` for convenience +* **CI/CD**: Use `PLAYBACK` to ensure tests are deterministic +* **Initial setup**: Use `RECORD` to capture all cassettes + +=== Environment Variable Override + +Override the VCR mode at runtime using the `VCR_MODE` environment variable. This takes precedence over the annotation mode: + +[source,bash] +---- +# Force RECORD mode to capture new cassettes +VCR_MODE=RECORD OPENAI_API_KEY=your-key ./gradlew test + +# Force PLAYBACK mode (no API key required) +VCR_MODE=PLAYBACK ./gradlew test + +# Force OFF mode to bypass VCR +VCR_MODE=OFF OPENAI_API_KEY=your-key ./gradlew test +---- + +Valid values: `PLAYBACK`, `PLAYBACK_OR_RECORD`, `RECORD`, `RECORD_NEW`, `RECORD_FAILED`, `OFF` + +== Configuration + +=== Data Directory + +Configure where cassette data is stored: + +[source,java] +---- +@VCRTest( + mode = VCRMode.PLAYBACK_OR_RECORD, + dataDir = "src/test/resources/vcr-data" // default +) +public class MyTest { + // ... +} +---- + +The data directory contains Redis persistence files (RDB/AOF) that can be committed to version control. + +=== Redis Image + +Specify a custom Redis image: + +[source,java] +---- +@VCRTest( + mode = VCRMode.PLAYBACK, + redisImage = "redis/redis-stack:7.2.0-v6" // default +) +public class MyTest { + // ... +} +---- + +== Method-Level Overrides + +=== @VCRRecord + +Force recording for a specific test method: + +[source,java] +---- +@VCRTest(mode = VCRMode.PLAYBACK) +public class MyTest { + + @Test + void normalTest() { + // Uses PLAYBACK mode from class annotation + } + + @Test + @VCRRecord + void alwaysRecordThisTest() { + // Forces RECORD mode for this test only + } +} +---- + +=== @VCRDisabled + +Disable VCR for a specific test: + +[source,java] +---- +@VCRTest(mode = VCRMode.PLAYBACK_OR_RECORD) +public class MyTest { + + @Test + @VCRDisabled + void testWithRealAPI() { + // VCR bypassed - makes real API calls + } +} +---- + +== VCR Registry + +The VCR Registry tracks the recording status of each test: + +[cols="1,3"] +|=== +|Status |Description + +|`RECORDED` +|Test has a valid cassette recording + +|`FAILED` +|Previous recording attempt failed + +|`MISSING` +|No cassette exists for this test + +|`OUTDATED` +|Cassette exists but may need re-recording +|=== + +Smart modes like `RECORD_NEW` and `RECORD_FAILED` use registry status to make intelligent decisions about when to record. + +== Architecture + +The VCR system consists of several components: + +* **VCRExtension** - JUnit 5 extension that manages the VCR lifecycle +* **VCRContext** - Manages Redis container, call counters, and statistics +* **VCRRegistry** - Tracks recording status for each test +* **VCRCassetteStore** - Stores/retrieves cassettes in Redis using JSON format + +=== Cassette Key Format + +Cassettes are stored in Redis with keys following this pattern: + +---- +vcr:{type}:{testId}:{callIndex} +---- + +For example: +---- +vcr:llm:MyTest.testGeneration:0001 +vcr:embedding:MyTest.testEmbedding:0001 +---- + +== Statistics + +The VCR system tracks statistics during test execution: + +* **Cache Hits** - Number of successful cassette replays +* **Cache Misses** - Number of cassettes not found (triggering API calls in record mode) +* **API Calls** - Number of actual API calls made + +== Best Practices + +=== Version Control + +Commit your `vcr-data/` directory to version control: + +[source] +---- +src/test/resources/vcr-data/ +β”œβ”€β”€ dump.rdb # RDB snapshot +└── appendonlydir/ # AOF segments +---- + +=== CI/CD Integration + +Use strict `PLAYBACK` mode in CI to ensure deterministic tests: + +[source,java] +---- +@VCRTest(mode = VCRMode.PLAYBACK) +public class CITest { + // Tests will fail if cassettes are missing +} +---- + +=== Updating Cassettes + +When API responses change, re-record cassettes using the `VCR_MODE` environment variable: + +[source,bash] +---- +# Run tests in RECORD mode to update all cassettes +VCR_MODE=RECORD ./gradlew test +---- + +=== Sensitive Data + +Be mindful of what gets recorded: + +* API responses may contain sensitive information +* Consider filtering or redacting sensitive data +* Use `.gitignore` patterns if needed + +== Example: Testing with SemanticCache + +[source,java] +---- +import com.redis.vl.test.vcr.VCRTest; +import com.redis.vl.test.vcr.VCRMode; +import com.redis.vl.extensions.cache.llm.SemanticCache; +import org.junit.jupiter.api.Test; + +@VCRTest(mode = VCRMode.PLAYBACK_OR_RECORD) +public class SemanticCacheVCRTest { + + @Test + void testCacheWithRecordedEmbeddings() { + // Embeddings are recorded on first run + SemanticCache cache = new SemanticCache(config, jedis); + + cache.store("What is Redis?", "Redis is an in-memory data store..."); + + CacheResult result = cache.check("Tell me about Redis"); + assertTrue(result.isHit()); + } +} +---- + +== Troubleshooting + +=== Cassette Not Found + +If you see "cassette not found" errors in PLAYBACK mode: + +1. Ensure cassettes were recorded (run in RECORD mode first) +2. Check the data directory path matches +3. Verify Redis persistence files exist + +=== Inconsistent Results + +If test results vary between runs: + +1. Ensure you're using a fixed VCR mode +2. Check for non-deterministic test logic +3. Verify cassette data wasn't corrupted + +=== Container Issues + +If Redis container fails to start: + +1. Ensure Docker is running +2. Check port availability +3. Verify the Redis image exists + +== Framework Integration + +The VCR system provides drop-in wrappers for popular AI frameworks. These wrappers work standalone without requiring any other RedisVL components. + +=== LangChain4J + +==== Embedding Model + +Use `VCREmbeddingModel` to wrap any LangChain4J `EmbeddingModel`: + +[source,java] +---- +import com.redis.vl.test.vcr.VCREmbeddingModel; +import com.redis.vl.test.vcr.VCRMode; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.openai.OpenAiEmbeddingModel; +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.model.output.Response; + +// Create your LangChain4J embedding model +EmbeddingModel openAiModel = OpenAiEmbeddingModel.builder() + .apiKey(System.getenv("OPENAI_API_KEY")) + .modelName("text-embedding-3-small") + .build(); + +// Wrap with VCR for recording/playback +VCREmbeddingModel vcrModel = new VCREmbeddingModel(openAiModel); +vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); +vcrModel.setTestId("MyTest.testEmbedding"); + +// Use exactly like the original model - VCR handles caching transparently +Response response = vcrModel.embed("What is Redis?"); +float[] vector = response.content().vector(); + +// Batch embeddings also supported +List segments = List.of( + TextSegment.from("Document chunk 1"), + TextSegment.from("Document chunk 2") +); +Response> batchResponse = vcrModel.embedAll(segments); +---- + +The `VCREmbeddingModel` implements the full `dev.langchain4j.model.embedding.EmbeddingModel` interface, so it can be used anywhere a LangChain4J embedding model is expected. + +==== Supported Methods + +* `embed(String text)` - Single text embedding +* `embed(TextSegment segment)` - TextSegment embedding +* `embedAll(List segments)` - Batch embedding +* `dimension()` - Returns embedding dimensions + +==== Chat Model + +Use `VCRChatModel` to wrap any LangChain4J `ChatLanguageModel`: + +[source,java] +---- +import com.redis.vl.test.vcr.VCRChatModel; +import com.redis.vl.test.vcr.VCRMode; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.model.output.Response; + +// Create your LangChain4J chat model +ChatLanguageModel openAiModel = OpenAiChatModel.builder() + .apiKey(System.getenv("OPENAI_API_KEY")) + .modelName("gpt-4o-mini") + .build(); + +// Wrap with VCR for recording/playback +VCRChatModel vcrModel = new VCRChatModel(openAiModel); +vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); +vcrModel.setTestId("MyTest.testChat"); + +// Use exactly like the original model - VCR handles caching transparently +Response response = vcrModel.generate(UserMessage.from("What is Redis?")); +String answer = response.content().text(); + +// Simple string convenience method +String simpleAnswer = vcrModel.generate("Tell me about Redis Vector Library"); + +// Multiple messages +Response chatResponse = vcrModel.generate( + UserMessage.from("What is Redis?"), + UserMessage.from("How does it handle vectors?") +); +---- + +The `VCRChatModel` implements the full `dev.langchain4j.model.chat.ChatLanguageModel` interface, so it can be used anywhere a LangChain4J chat model is expected. + +==== Supported Chat Methods + +* `generate(ChatMessage... messages)` - Generate from varargs messages +* `generate(List messages)` - Generate from list of messages +* `generate(String text)` - Simple string convenience method + +=== Spring AI + +==== Embedding Model + +Use `VCRSpringAIEmbeddingModel` to wrap any Spring AI `EmbeddingModel`: + +[source,java] +---- +import com.redis.vl.test.vcr.VCRSpringAIEmbeddingModel; +import com.redis.vl.test.vcr.VCRMode; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.ai.openai.OpenAiEmbeddingModel; +import org.springframework.ai.document.Document; + +// Create your Spring AI embedding model +EmbeddingModel openAiModel = new OpenAiEmbeddingModel(openAiApi); + +// Wrap with VCR for recording/playback +VCRSpringAIEmbeddingModel vcrModel = new VCRSpringAIEmbeddingModel(openAiModel); +vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); +vcrModel.setTestId("MyTest.testSpringAIEmbedding"); + +// Use exactly like the original model +float[] vector = vcrModel.embed("What is Redis?"); + +// Batch embeddings +List vectors = vcrModel.embed(List.of("text 1", "text 2")); + +// Document embedding +Document doc = new Document("Document content here"); +float[] docVector = vcrModel.embed(doc); + +// Full EmbeddingResponse API +EmbeddingResponse response = vcrModel.embedForResponse(List.of("query text")); +---- + +The `VCRSpringAIEmbeddingModel` implements the full `org.springframework.ai.embedding.EmbeddingModel` interface for seamless integration with Spring AI applications. + +==== Supported Methods + +* `embed(String text)` - Single text embedding returning `float[]` +* `embed(Document document)` - Document embedding +* `embed(List texts)` - Batch embedding returning `List` +* `embedForResponse(List texts)` - Full `EmbeddingResponse` +* `call(EmbeddingRequest request)` - Standard Spring AI call pattern +* `dimensions()` - Returns embedding dimensions + +==== Chat Model + +Use `VCRSpringAIChatModel` to wrap any Spring AI `ChatModel`: + +[source,java] +---- +import com.redis.vl.test.vcr.VCRSpringAIChatModel; +import com.redis.vl.test.vcr.VCRMode; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.openai.OpenAiChatModel; + +// Create your Spring AI chat model +ChatModel openAiModel = new OpenAiChatModel(openAiApi); + +// Wrap with VCR for recording/playback +VCRSpringAIChatModel vcrModel = new VCRSpringAIChatModel(openAiModel); +vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); +vcrModel.setTestId("MyTest.testChat"); + +// Use exactly like the original model - VCR handles caching transparently +String response = vcrModel.call("What is Redis?"); + +// With Prompt object +Prompt prompt = new Prompt(List.of(new UserMessage("Explain vector search"))); +ChatResponse chatResponse = vcrModel.call(prompt); +String answer = chatResponse.getResult().getOutput().getText(); + +// Multiple messages +String multiResponse = vcrModel.call( + new UserMessage("What is Redis?"), + new UserMessage("How does it handle vectors?") +); +---- + +The `VCRSpringAIChatModel` implements the full `org.springframework.ai.chat.model.ChatModel` interface for seamless integration with Spring AI applications. + +==== Supported Chat Methods + +* `call(String message)` - Simple string convenience method +* `call(Message... messages)` - Generate from varargs messages +* `call(Prompt prompt)` - Full Prompt/ChatResponse API + +=== Common VCR Operations + +Both wrappers share common VCR functionality: + +[source,java] +---- +// Set VCR mode +vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); + +// Set test identifier (for cassette key generation) +vcrModel.setTestId("MyTestClass.testMethod"); + +// Reset call counter between tests +vcrModel.resetCallCounter(); + +// Get statistics +int hits = vcrModel.getCacheHits(); +int misses = vcrModel.getCacheMisses(); +int recorded = vcrModel.getRecordedCount(); + +// Reset statistics +vcrModel.resetStatistics(); + +// Access underlying delegate +EmbeddingModel delegate = vcrModel.getDelegate(); +---- + +=== Using with JUnit 5 + +Combine VCR wrappers with the `@VCRTest` annotation for a complete testing solution: + +[source,java] +---- +import com.redis.vl.test.vcr.VCRTest; +import com.redis.vl.test.vcr.VCRMode; +import com.redis.vl.test.vcr.VCREmbeddingModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +@VCRTest(mode = VCRMode.PLAYBACK_OR_RECORD) +public class EmbeddingServiceTest { + + private VCREmbeddingModel vcrModel; + + @BeforeEach + void setUp() { + EmbeddingModel realModel = OpenAiEmbeddingModel.builder() + .apiKey(System.getenv("OPENAI_API_KEY")) + .build(); + vcrModel = new VCREmbeddingModel(realModel); + vcrModel.setMode(VCRMode.PLAYBACK_OR_RECORD); + } + + @Test + void testSemanticSearch() { + vcrModel.setTestId("EmbeddingServiceTest.testSemanticSearch"); + + // First run: calls OpenAI API and records response + // Subsequent runs: replays from Redis cassette + Response response = vcrModel.embed("search query"); + + assertNotNull(response); + assertEquals(1536, response.content().vector().length); + } + + @Test + void testBatchEmbedding() { + vcrModel.setTestId("EmbeddingServiceTest.testBatchEmbedding"); + + List docs = List.of( + TextSegment.from("Document 1"), + TextSegment.from("Document 2"), + TextSegment.from("Document 3") + ); + + Response> response = vcrModel.embedAll(docs); + + assertEquals(3, response.content().size()); + } +} +---- + +== See Also + +* xref:llmcache.adoc[LLM Cache] +* xref:vectorizers.adoc[Vectorizers] +* xref:getting-started.adoc[Getting Started] diff --git a/docs/design/EMBEDDINGS_CACHE_ENHANCEMENT.md b/docs/design/EMBEDDINGS_CACHE_ENHANCEMENT.md new file mode 100644 index 0000000..dd54eef --- /dev/null +++ b/docs/design/EMBEDDINGS_CACHE_ENHANCEMENT.md @@ -0,0 +1,356 @@ +# EmbeddingsCache Enhancement Plan + +## Overview + +This document outlines the plan to enhance the Java `EmbeddingsCache` to achieve feature parity with the Python `redis-vl-python` implementation. + +## Current State Analysis + +### Python Implementation (redis-vl-python) + +| Feature | Implementation | +|---------|----------------| +| **Key Generation** | `SHA256(text:model_name)` - combined hash | +| **Storage** | Redis HASH with structured fields | +| **Fields Stored** | text, model_name, embedding, inserted_at, metadata | +| **TTL Behavior** | Refreshed on retrieval (LRU-like) | +| **Metadata** | Full JSON serialization support | +| **Batch Returns** | Ordered lists matching input order | +| **Async Support** | Full async/await variants | + +### Java Implementation (Current) + +| Feature | Implementation | +|---------|----------------| +| **Key Generation** | `model:SHA256(text)` - separate hash | +| **Storage** | Raw byte array via SET/GET | +| **Fields Stored** | embedding vector only | +| **TTL Behavior** | Set once at write, no refresh | +| **Metadata** | None | +| **Batch Returns** | Unordered Maps | +| **Async Support** | Synchronous only | + +## Enhancement Plan + +### Phase 1: Core Data Model + +#### 1.1 Create CacheEntry Class + +```java +package com.redis.vl.extensions.cache; + +import java.time.Instant; +import java.util.Map; + +@Data +@Builder +public class EmbeddingCacheEntry { + private String entryId; // Unique identifier (hash) + private String text; // Original text that was embedded + private String modelName; // Embedding model name + private float[] embedding; // The embedding vector + private Instant insertedAt; // When the entry was cached + private Map metadata; // Optional user metadata +} +``` + +#### 1.2 Update Key Generation + +Change from `model:hash(text)` to `hash(text:model)` for Python compatibility: + +```java +private String generateKey(String text, String modelName) { + String combined = text + ":" + modelName; + byte[] hash = MessageDigest.getInstance("SHA-256") + .digest(combined.getBytes(StandardCharsets.UTF_8)); + String entryId = bytesToHex(hash); + return cacheName + ":" + entryId; +} +``` + +### Phase 2: Storage Enhancement + +#### 2.1 Switch to Redis HASH Storage + +Replace `SET`/`GET` with `HSET`/`HGETALL`: + +```java +public void set(String text, String modelName, float[] embedding, + Map metadata) { + String key = generateKey(text, modelName); + + Map fields = new HashMap<>(); + fields.put("entry_id", extractEntryId(key)); + fields.put("text", text); + fields.put("model_name", modelName); + fields.put("embedding", serializeEmbedding(embedding)); + fields.put("inserted_at", String.valueOf(Instant.now().toEpochMilli())); + + if (metadata != null && !metadata.isEmpty()) { + fields.put("metadata", objectMapper.writeValueAsString(metadata)); + } + + jedis.hset(key, fields); + + if (ttl > 0) { + jedis.expire(key, ttl); + } +} +``` + +#### 2.2 Implement Structured Retrieval + +```java +public Optional get(String text, String modelName) { + String key = generateKey(text, modelName); + Map fields = jedis.hgetAll(key); + + if (fields.isEmpty()) { + return Optional.empty(); + } + + // Refresh TTL on access (LRU behavior) + if (ttl > 0) { + jedis.expire(key, ttl); + } + + return Optional.of(deserializeEntry(fields)); +} + +private EmbeddingCacheEntry deserializeEntry(Map fields) { + return EmbeddingCacheEntry.builder() + .entryId(fields.get("entry_id")) + .text(fields.get("text")) + .modelName(fields.get("model_name")) + .embedding(deserializeEmbedding(fields.get("embedding"))) + .insertedAt(Instant.ofEpochMilli(Long.parseLong(fields.get("inserted_at")))) + .metadata(fields.containsKey("metadata") + ? objectMapper.readValue(fields.get("metadata"), Map.class) + : null) + .build(); +} +``` + +### Phase 3: TTL Enhancement + +#### 3.1 TTL Refresh on Access + +Add TTL refresh in all retrieval methods: + +```java +public Optional get(String text, String modelName) { + String key = generateKey(text, modelName); + Map fields = jedis.hgetAll(key); + + if (fields.isEmpty()) { + return Optional.empty(); + } + + // LRU-like behavior: refresh TTL on access + refreshTTL(key); + + return Optional.of(deserializeEntry(fields)); +} + +private void refreshTTL(String key) { + if (ttl > 0) { + jedis.expire(key, ttl); + } +} +``` + +### Phase 4: Batch Operations Enhancement + +#### 4.1 Ordered Batch Retrieval + +Return `List>` to preserve order: + +```java +public List> mget(List texts, String modelName) { + List keys = texts.stream() + .map(text -> generateKey(text, modelName)) + .collect(Collectors.toList()); + + List> results = new ArrayList<>(texts.size()); + + try (Pipeline pipeline = jedis.pipelined()) { + List>> responses = new ArrayList<>(); + + for (String key : keys) { + responses.add(pipeline.hgetAll(key)); + } + pipeline.sync(); + + // Refresh TTL for hits + for (int i = 0; i < keys.size(); i++) { + Map fields = responses.get(i).get(); + if (!fields.isEmpty()) { + refreshTTL(keys.get(i)); + results.add(Optional.of(deserializeEntry(fields))); + } else { + results.add(Optional.empty()); + } + } + } + + return results; +} +``` + +#### 4.2 Batch Existence Check + +```java +public List mexists(List texts, String modelName) { + List keys = texts.stream() + .map(text -> generateKey(text, modelName)) + .collect(Collectors.toList()); + + try (Pipeline pipeline = jedis.pipelined()) { + List> responses = keys.stream() + .map(pipeline::exists) + .collect(Collectors.toList()); + pipeline.sync(); + + return responses.stream() + .map(Response::get) + .collect(Collectors.toList()); + } +} +``` + +### Phase 5: Backward Compatibility + +#### 5.1 Simple API Methods + +Maintain simple methods for users who don't need full features: + +```java +// Simple API (returns just embedding) +public Optional getEmbedding(String text, String modelName) { + return get(text, modelName).map(EmbeddingCacheEntry::getEmbedding); +} + +// Simple API (no metadata) +public void set(String text, String modelName, float[] embedding) { + set(text, modelName, embedding, null); +} + +// Legacy Map-based batch (for backward compat) +public Map mgetAsMap(List texts, String modelName) { + List> results = mget(texts, modelName); + Map map = new LinkedHashMap<>(); + for (int i = 0; i < texts.size(); i++) { + results.get(i).ifPresent(entry -> map.put(texts.get(i), entry.getEmbedding())); + } + return map; +} +``` + +### Phase 6: Embedding Serialization + +#### 6.1 JSON-Compatible Float Array Serialization + +```java +private String serializeEmbedding(float[] embedding) { + // Store as JSON array for cross-language compatibility + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < embedding.length; i++) { + if (i > 0) sb.append(","); + sb.append(embedding[i]); + } + sb.append("]"); + return sb.toString(); +} + +private float[] deserializeEmbedding(String serialized) { + // Parse JSON array + String content = serialized.substring(1, serialized.length() - 1); + String[] parts = content.split(","); + float[] result = new float[parts.length]; + for (int i = 0; i < parts.length; i++) { + result[i] = Float.parseFloat(parts[i].trim()); + } + return result; +} +``` + +## API Summary + +### Enhanced EmbeddingsCache API + +```java +public class EmbeddingsCache { + // Constructor + public EmbeddingsCache(String name, JedisPooled client, long ttlSeconds); + + // Full API (with metadata) + public void set(String text, String modelName, float[] embedding, + Map metadata); + public void set(String text, String modelName, float[] embedding, + Map metadata, long ttlOverride); + public Optional get(String text, String modelName); + + // Simple API (just embeddings) + public void set(String text, String modelName, float[] embedding); + public Optional getEmbedding(String text, String modelName); + + // Batch operations (ordered) + public List> mget(List texts, String modelName); + public List mexists(List texts, String modelName); + public void mdrop(List texts, String modelName); + + // Legacy batch (unordered map - backward compat) + public Map mgetAsMap(List texts, String modelName); + + // Management + public boolean exists(String text, String modelName); + public boolean drop(String text, String modelName); + public void clear(); + + // Configuration + public void setTTL(long ttlSeconds); + public long getTTL(); +} +``` + +## Testing Strategy + +### Unit Tests + +1. Test key generation matches Python format +2. Test serialization/deserialization of embeddings +3. Test metadata JSON handling +4. Test TTL refresh behavior + +### Integration Tests + +1. Test HASH storage structure in Redis +2. Test batch operation ordering +3. Test TTL expiration and refresh +4. Test cross-language compatibility (read Python-cached entries) + +## Migration Path + +1. **v1.0**: Add new methods alongside existing ones +2. **v1.1**: Deprecate old methods +3. **v2.0**: Remove deprecated methods + +## Implementation Priority + +| Priority | Feature | Effort | +|----------|---------|--------| +| P0 | CacheEntry class | Low | +| P0 | HASH storage | Medium | +| P1 | TTL refresh | Low | +| P1 | Ordered batch returns | Medium | +| P2 | Metadata support | Low | +| P2 | Cross-language key compat | Low | + +## Success Criteria + +- [ ] All Python API methods have Java equivalents +- [ ] TTL refreshed on cache access +- [ ] Batch operations preserve input order +- [ ] Metadata can be stored and retrieved +- [ ] Existing tests continue to pass +- [ ] New tests cover all enhanced functionality diff --git a/docs/design/VCR_TEST_SYSTEM.md b/docs/design/VCR_TEST_SYSTEM.md new file mode 100644 index 0000000..5b6db4f --- /dev/null +++ b/docs/design/VCR_TEST_SYSTEM.md @@ -0,0 +1,861 @@ +# VCR Test System Design for RedisVL Java + +## Overview + +This document outlines a design for implementing a VCR (Video Cassette Recorder) test system for JUnit 5 (and potentially TestNG) that enables recording and replaying LLM API calls and embedding computations during test execution. + +The design is inspired by the Python implementation in `maestro-langgraph` and adapted for Java idioms, leveraging RedisVL's existing `EmbeddingsCache` infrastructure. + +## Goals + +1. **Zero API Costs in CI** - Tests run without LLM API calls after initial recording +2. **Deterministic Tests** - Same responses every run for reproducibility +3. **Fast Execution** - No network latency (cached responses) +4. **Transparent Integration** - Minimal test code changes via annotations +5. **Redis-Native Storage** - Leverage Redis AOF/RDB for persistent cassettes +6. **Framework Agnostic Core** - Support JUnit 5 primarily, TestNG optionally + +## Architecture Overview + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Test Execution β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ @VCRTest(mode = PLAYBACK) β”‚ +β”‚ class MyIntegrationTest { β”‚ +β”‚ @Test void testRAGQuery() { ... } β”‚ +β”‚ } β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ VCRExtension (JUnit 5 Extension) β”‚ +β”‚ β€’ BeforeAllCallback: Start Redis, load cassettes β”‚ +β”‚ β€’ BeforeEachCallback: Set test context, reset counters β”‚ +β”‚ β€’ AfterEachCallback: Register test result, persist cassettes β”‚ +β”‚ β€’ AfterAllCallback: BGSAVE, stop Redis β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ VCR Interceptor Layer β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ LLMInterceptor β”‚ β”‚EmbeddingIntercepβ”‚ β”‚ RedisVCRStore β”‚ β”‚ +β”‚ β”‚ (ByteBuddy/ β”‚ β”‚tor (ByteBuddy) β”‚ β”‚ (Redis + RDB) β”‚ β”‚ +β”‚ β”‚ MockServer) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β–Ό β–Ό β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚ +β”‚ β”‚ VCRCassette β”‚β”‚ +β”‚ β”‚ β€’ generateKey(testId, callIndex) β”‚β”‚ +β”‚ β”‚ β€’ store(key, response) β”‚β”‚ +β”‚ β”‚ β€’ retrieve(key) β†’ Optional β”‚β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Redis Storage Layer β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ Keys: β”‚ +β”‚ vcr:cassette:{testClass}:{testMethod}:{callIndex} β”‚ +β”‚ vcr:registry:{testClass}:{testMethod} β”‚ +β”‚ vcr:embedding:{testClass}:{testMethod}:{model}:{callIndex} β”‚ +β”‚ β”‚ +β”‚ Persistence: β”‚ +β”‚ tests/vcr-data/dump.rdb (RDB snapshot) β”‚ +β”‚ tests/vcr-data/appendonly/ (AOF segments) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Component Design + +### 1. VCR Annotations + +```java +package com.redis.vl.test.vcr; + +/** + * Class-level annotation to enable VCR for all tests in the class. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(VCRExtension.class) +public @interface VCRTest { + VCRMode mode() default VCRMode.PLAYBACK; + String dataDir() default "src/test/resources/vcr-data"; +} + +/** + * Method-level annotation to override class-level VCR mode. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface VCRMode { + VCRMode value(); +} + +/** + * Skip VCR for specific test. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface VCRDisabled { +} + +/** + * VCR operating modes. + */ +public enum VCRMode { + /** Use cached only, fail if missing */ + PLAYBACK, + + /** Always call API, overwrite cache */ + RECORD, + + /** Only record tests not in registry */ + RECORD_NEW, + + /** Re-record only failed tests */ + RECORD_FAILED, + + /** Use cache if exists, else record */ + PLAYBACK_OR_RECORD, + + /** Disable VCR entirely */ + OFF +} +``` + +### 2. JUnit 5 Extension + +```java +package com.redis.vl.test.vcr; + +import org.junit.jupiter.api.extension.*; + +public class VCRExtension implements + BeforeAllCallback, + AfterAllCallback, + BeforeEachCallback, + AfterEachCallback, + TestWatcher { + + private static final ExtensionContext.Namespace NAMESPACE = + ExtensionContext.Namespace.create(VCRExtension.class); + + private VCRContext context; + + @Override + public void beforeAll(ExtensionContext ctx) throws Exception { + // 1. Get VCR configuration from @VCRTest annotation + VCRTest config = ctx.getRequiredTestClass() + .getAnnotation(VCRTest.class); + + // 2. Start Redis container with persistence volume + context = new VCRContext(config); + context.startRedis(); + + // 3. Load existing cassettes from RDB/AOF + context.loadCassettes(); + + // 4. Install interceptors + context.installInterceptors(); + + // 5. Store in extension context + ctx.getStore(NAMESPACE).put("vcr-context", context); + } + + @Override + public void beforeEach(ExtensionContext ctx) throws Exception { + // 1. Get test identifier + String testId = getTestId(ctx); + + // 2. Reset call counters + context.resetCallCounters(); + + // 3. Set current test context (for key generation) + context.setCurrentTest(testId); + + // 4. Check for method-level mode override + VCRMode methodMode = ctx.getRequiredTestMethod() + .getAnnotation(VCRMode.class); + if (methodMode != null) { + context.setEffectiveMode(methodMode.value()); + } + } + + @Override + public void afterEach(ExtensionContext ctx) throws Exception { + // Handled by TestWatcher methods + } + + @Override + public void testSuccessful(ExtensionContext ctx) { + String testId = getTestId(ctx); + context.getRegistry().registerSuccess(testId, + context.getCurrentCassetteKeys()); + } + + @Override + public void testFailed(ExtensionContext ctx, Throwable cause) { + String testId = getTestId(ctx); + context.getRegistry().registerFailure(testId, cause.getMessage()); + + // Optionally delete cassettes for failed tests in RECORD mode + if (context.getEffectiveMode() == VCRMode.RECORD) { + context.deleteCassettes(context.getCurrentCassetteKeys()); + } + } + + @Override + public void afterAll(ExtensionContext ctx) throws Exception { + // 1. Trigger Redis BGSAVE + if (context.isRecordMode()) { + context.persistCassettes(); + } + + // 2. Print statistics + context.printStatistics(); + + // 3. Restore original methods + context.uninstallInterceptors(); + + // 4. Stop Redis + context.stopRedis(); + } + + private String getTestId(ExtensionContext ctx) { + return ctx.getRequiredTestClass().getName() + ":" + + ctx.getRequiredTestMethod().getName(); + } +} +``` + +### 3. VCR Context (State Management) + +```java +package com.redis.vl.test.vcr; + +public class VCRContext { + private final VCRTest config; + private final Path dataDir; + private RedisContainer redisContainer; + private JedisPooled jedis; + private VCRRegistry registry; + private LLMInterceptor llmInterceptor; + private EmbeddingInterceptor embeddingInterceptor; + + private String currentTestId; + private VCRMode effectiveMode; + private List currentCassetteKeys = new ArrayList<>(); + private Map callCounters = new ConcurrentHashMap<>(); + + // Statistics + private AtomicLong cacheHits = new AtomicLong(); + private AtomicLong cacheMisses = new AtomicLong(); + private AtomicLong apiCalls = new AtomicLong(); + + public VCRContext(VCRTest config) { + this.config = config; + this.dataDir = Path.of(config.dataDir()); + this.effectiveMode = config.mode(); + } + + public void startRedis() { + // Ensure data directory exists + Files.createDirectories(dataDir); + + // Start Redis with volume mount + redisContainer = new RedisContainer(DockerImageName.parse("redis/redis-stack:latest")) + .withFileSystemBind(dataDir.toAbsolutePath().toString(), "/data", + BindMode.READ_WRITE) + .withCommand(buildRedisCommand()); + + redisContainer.start(); + + jedis = new JedisPooled(redisContainer.getHost(), + redisContainer.getFirstMappedPort()); + } + + private String buildRedisCommand() { + StringBuilder cmd = new StringBuilder("redis-stack-server"); + cmd.append(" --appendonly yes"); + cmd.append(" --appendfsync everysec"); + cmd.append(" --dir /data"); + cmd.append(" --dbfilename dump.rdb"); + + if (isRecordMode()) { + cmd.append(" --save '60 1' --save '300 10'"); + } else { + cmd.append(" --save ''"); // Disable saves in playback + } + + return cmd.toString(); + } + + public void installInterceptors() { + llmInterceptor = new LLMInterceptor(this); + llmInterceptor.install(); + + embeddingInterceptor = new EmbeddingInterceptor(this); + embeddingInterceptor.install(); + } + + public String generateCassetteKey(String type) { + int callIndex = callCounters + .computeIfAbsent(currentTestId + ":" + type, k -> new AtomicInteger()) + .incrementAndGet(); + + String key = String.format("vcr:%s:%s:%04d", type, currentTestId, callIndex); + currentCassetteKeys.add(key); + return key; + } + + public void persistCassettes() { + // Trigger BGSAVE and wait for completion + jedis.bgsave(); + + long lastSave = jedis.lastsave(); + while (jedis.lastsave() == lastSave) { + Thread.sleep(100); + } + } + + public void printStatistics() { + long hits = cacheHits.get(); + long misses = cacheMisses.get(); + long total = hits + misses; + double hitRate = total > 0 ? (double) hits / total * 100 : 0; + + System.out.println("=== VCR Statistics ==="); + System.out.printf("Cache Hits: %d%n", hits); + System.out.printf("Cache Misses: %d%n", misses); + System.out.printf("API Calls: %d%n", apiCalls.get()); + System.out.printf("Hit Rate: %.1f%%%n", hitRate); + } + + // Getters, setters... +} +``` + +### 4. LLM Interceptor (ByteBuddy) + +```java +package com.redis.vl.test.vcr; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.agent.ByteBuddyAgent; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; + +/** + * Intercepts LLM API calls using ByteBuddy instrumentation. + * Supports LangChain4J ChatLanguageModel and similar interfaces. + */ +public class LLMInterceptor { + private final VCRContext context; + private static VCRContext staticContext; // For Advice access + + public LLMInterceptor(VCRContext context) { + this.context = context; + } + + public void install() { + staticContext = context; + ByteBuddyAgent.install(); + + // Intercept LangChain4J ChatLanguageModel.generate() + new ByteBuddy() + .redefine(OpenAiChatModel.class) + .visit(Advice.to(ChatModelAdvice.class) + .on(named("generate"))) + .make() + .load(OpenAiChatModel.class.getClassLoader(), + ClassReloadingStrategy.fromInstalledAgent()); + + // Similarly for other LLM providers... + } + + public void uninstall() { + // Restore original classes + staticContext = null; + } + + public static class ChatModelAdvice { + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + public static Object onEnter( + @Advice.AllArguments Object[] args, + @Advice.Origin String method) { + + if (staticContext == null) return null; + + String key = staticContext.generateCassetteKey("llm"); + + // Try to get from cache + Optional cached = staticContext.getCassette(key); + if (cached.isPresent()) { + staticContext.recordCacheHit(); + // Return cached response (skip original method) + return deserializeResponse(cached.get()); + } + + if (staticContext.getEffectiveMode() == VCRMode.PLAYBACK) { + throw new VCRCassetteMissingException( + "No cassette found for key: " + key); + } + + return null; // Proceed with original method + } + + @Advice.OnMethodExit + public static void onExit( + @Advice.Enter Object cachedResult, + @Advice.Return(readOnly = false) Object result) { + + if (staticContext == null) return; + + if (cachedResult != null) { + // Use cached result + result = cachedResult; + } else { + // Store new result + String key = staticContext.getLastGeneratedKey(); + staticContext.storeCassette(key, serializeResponse(result)); + staticContext.recordApiCall(); + } + } + } +} +``` + +### 5. Embedding Interceptor + +```java +package com.redis.vl.test.vcr; + +/** + * Intercepts embedding generation calls. + * Integrates with RedisVL's EmbeddingsCache for storage. + */ +public class EmbeddingInterceptor { + private final VCRContext context; + private final EmbeddingsCache embeddingsCache; + + public EmbeddingInterceptor(VCRContext context) { + this.context = context; + this.embeddingsCache = new EmbeddingsCache( + "vcr-embeddings", + context.getJedis(), + TimeUnit.DAYS.toSeconds(7) // 7-day TTL + ); + } + + public void install() { + // Intercept SentenceTransformers, LangChain4J EmbeddingModel, etc. + // Similar ByteBuddy pattern as LLMInterceptor + } + + /** + * Called by advice when embedding is requested. + */ + public float[] intercept(String text, String modelName) { + String key = context.generateCassetteKey("embedding"); + + // Check cache first + Optional cached = embeddingsCache.getEmbedding(text, modelName); + if (cached.isPresent()) { + context.recordCacheHit(); + return cached.get(); + } + + if (context.getEffectiveMode() == VCRMode.PLAYBACK) { + throw new VCRCassetteMissingException( + "No embedding cassette for: " + text.substring(0, 50) + "..."); + } + + // Will be called after actual embedding is computed + return null; + } + + public void storeEmbedding(String text, String modelName, float[] embedding) { + embeddingsCache.set(text, modelName, embedding); + context.recordApiCall(); + } +} +``` + +### 6. VCR Registry + +```java +package com.redis.vl.test.vcr; + +/** + * Tracks which tests have been recorded and their status. + */ +public class VCRRegistry { + private final JedisPooled jedis; + private static final String REGISTRY_KEY = "vcr:registry"; + private static final String TESTS_KEY = "vcr:registry:tests"; + + public enum RecordingStatus { + RECORDED, FAILED, MISSING, OUTDATED + } + + public void registerSuccess(String testId, List cassetteKeys) { + String testKey = "vcr:test:" + testId; + + Map data = Map.of( + "status", RecordingStatus.RECORDED.name(), + "recorded_at", Instant.now().toString(), + "cassette_count", String.valueOf(cassetteKeys.size()) + ); + + jedis.hset(testKey, data); + jedis.sadd(TESTS_KEY, testId); + + // Store cassette keys + if (!cassetteKeys.isEmpty()) { + jedis.sadd(testKey + ":cassettes", cassetteKeys.toArray(new String[0])); + } + } + + public void registerFailure(String testId, String error) { + String testKey = "vcr:test:" + testId; + + Map data = Map.of( + "status", RecordingStatus.FAILED.name(), + "recorded_at", Instant.now().toString(), + "error", error != null ? error : "Unknown error" + ); + + jedis.hset(testKey, data); + jedis.sadd(TESTS_KEY, testId); + } + + public RecordingStatus getTestStatus(String testId) { + String testKey = "vcr:test:" + testId; + String status = jedis.hget(testKey, "status"); + + if (status == null) { + return RecordingStatus.MISSING; + } + return RecordingStatus.valueOf(status); + } + + public VCRMode determineEffectiveMode(String testId, VCRMode globalMode) { + RecordingStatus status = getTestStatus(testId); + + return switch (globalMode) { + case RECORD_NEW -> status == RecordingStatus.MISSING + ? VCRMode.RECORD : VCRMode.PLAYBACK; + + case RECORD_FAILED -> status == RecordingStatus.FAILED || + status == RecordingStatus.MISSING + ? VCRMode.RECORD : VCRMode.PLAYBACK; + + case PLAYBACK_OR_RECORD -> status == RecordingStatus.RECORDED + ? VCRMode.PLAYBACK : VCRMode.RECORD; + + default -> globalMode; + }; + } + + public Set getAllRecordedTests() { + return jedis.smembers(TESTS_KEY); + } +} +``` + +### 7. Cassette Storage + +```java +package com.redis.vl.test.vcr; + +/** + * Stores and retrieves cassette data from Redis. + */ +public class VCRCassetteStore { + private final JedisPooled jedis; + private final ObjectMapper objectMapper; + + public void store(String key, Object response) { + // Serialize response to JSON + Map cassette = new HashMap<>(); + cassette.put("type", response.getClass().getName()); + cassette.put("timestamp", Instant.now().toString()); + cassette.put("data", serializeResponse(response)); + + jedis.jsonSet(key, Path2.ROOT_PATH, cassette); + } + + public Optional retrieve(String key, Class responseType) { + Object data = jedis.jsonGet(key); + if (data == null) { + return Optional.empty(); + } + + Map cassette = (Map) data; + return Optional.of(deserializeResponse( + (Map) cassette.get("data"), + responseType + )); + } + + private Map serializeResponse(Object response) { + // Handle different response types: + // - LangChain4J AiMessage + // - OpenAI ChatCompletionResponse + // - Anthropic Message + // etc. + + if (response instanceof AiMessage aiMsg) { + return Map.of( + "content", aiMsg.text(), + "toolExecutionRequests", serializeToolRequests(aiMsg) + ); + } + + // Generic fallback + return objectMapper.convertValue(response, Map.class); + } +} +``` + +## Usage Examples + +### Basic Usage + +```java +@VCRTest(mode = VCRMode.PLAYBACK) +class RAGServiceIntegrationTest { + + @Test + void testQueryWithContext() { + // LLM calls are automatically intercepted + RAGService rag = new RAGService(embedder, chatModel, index); + + String response = rag.query("What is Redis?"); + + assertThat(response).contains("in-memory"); + // This test uses cached LLM responses - no API calls! + } +} +``` + +### Recording New Tests + +```java +@VCRTest(mode = VCRMode.RECORD_NEW) +class NewFeatureTest { + + @Test + void testNewFeature() { + // First run: makes real API calls and records them + // Subsequent runs: uses cached responses + ChatLanguageModel llm = createChatModel(); + String response = llm.generate("Hello, world!"); + + assertThat(response).isNotEmpty(); + } +} +``` + +### Method-Level Override + +```java +@VCRTest(mode = VCRMode.PLAYBACK) +class MixedModeTest { + + @Test + void usesPlayback() { + // Uses cached responses + } + + @Test + @VCRMode(VCRMode.RECORD) + void forcesRecording() { + // Always makes real API calls and updates cache + } + + @Test + @VCRDisabled + void noVCR() { + // VCR completely disabled - real API calls, no caching + } +} +``` + +### Programmatic Control + +```java +@VCRTest +class AdvancedTest { + + @RegisterExtension + static VCRExtension vcr = VCRExtension.builder() + .mode(VCRMode.PLAYBACK_OR_RECORD) + .dataDir("src/test/resources/custom-vcr-data") + .redisImage("redis/redis-stack:7.4") + .enableInteractionLogging(true) + .build(); + + @Test + void testWithCustomConfig() { + // Test code... + } +} +``` + +## Redis Data Structure + +### Key Patterns + +``` +vcr:llm:{testClass}:{testMethod}:{callIndex} β†’ JSON (cassette) +vcr:embedding:{testClass}:{testMethod}:{model}:{callIndex} β†’ HASH (embedding) +vcr:test:{testId} β†’ HASH (test metadata) +vcr:test:{testId}:cassettes β†’ SET (cassette keys) +vcr:registry:tests β†’ SET (all test IDs) +``` + +### Cassette JSON Format + +```json +{ + "type": "dev.langchain4j.data.message.AiMessage", + "timestamp": "2025-12-13T10:30:00Z", + "data": { + "content": "Redis is an in-memory data structure store...", + "toolExecutionRequests": [] + } +} +``` + +### Persistence Files + +``` +src/test/resources/vcr-data/ +β”œβ”€β”€ dump.rdb # RDB snapshot +└── appendonlydir/ + β”œβ”€β”€ appendonly.aof.manifest + β”œβ”€β”€ appendonly.aof.1.base.rdb + └── appendonly.aof.1.incr.aof +``` + +## Build Configuration + +### Gradle Dependencies + +```kotlin +// build.gradle.kts +dependencies { + testImplementation("com.redis.vl:redisvl-vcr:0.12.0") + + // Required for ByteBuddy instrumentation + testImplementation("net.bytebuddy:byte-buddy:1.14.+") + testImplementation("net.bytebuddy:byte-buddy-agent:1.14.+") + + // Testcontainers for Redis + testImplementation("org.testcontainers:testcontainers:1.19.+") +} +``` + +### JVM Arguments (for ByteBuddy agent) + +```kotlin +tasks.test { + jvmArgs("-XX:+AllowRedefinitionToAddDeleteMethods") +} +``` + +## CLI / Management Tools + +```bash +# View VCR statistics +./gradlew vcrStats + +# List unrecorded tests +./gradlew vcrListUnrecorded + +# Clean failed test cassettes +./gradlew vcrCleanFailed + +# Export cassettes to JSON (for backup/migration) +./gradlew vcrExport --output=vcr-backup.json + +# Import cassettes from JSON +./gradlew vcrImport --input=vcr-backup.json +``` + +## Alternative: MockServer Approach + +Instead of ByteBuddy instrumentation, use WireMock/MockServer for HTTP interception: + +```java +@VCRTest(strategy = InterceptionStrategy.MOCK_SERVER) +class HttpBasedVCRTest { + // Intercepts at HTTP level + // Simpler but only works for HTTP-based LLM APIs +} +``` + +**Pros:** +- No bytecode manipulation +- Works with any HTTP client +- Easier debugging + +**Cons:** +- Doesn't work with local models (ONNX, etc.) +- More complex URL matching + +## TestNG Support + +```java +package com.redis.vl.test.vcr.testng; + +import org.testng.annotations.*; + +@Listeners(VCRTestNGListener.class) +@VCRTest(mode = VCRMode.PLAYBACK) +public class TestNGExample { + + @Test + public void testWithVCR() { + // Same functionality as JUnit 5 + } +} +``` + +## Implementation Phases + +| Phase | Scope | Effort | +|-------|-------|--------| +| **1** | Core VCRContext + Redis persistence | 2 days | +| **2** | JUnit 5 Extension + annotations | 2 days | +| **3** | LLM Interceptor (LangChain4J) | 3 days | +| **4** | Embedding Interceptor (integration with EmbeddingsCache) | 1 day | +| **5** | Registry + smart modes | 2 days | +| **6** | CLI tools + statistics | 1 day | +| **7** | TestNG support (optional) | 1 day | +| **8** | Documentation + examples | 1 day | + +## Success Criteria + +- [ ] Tests run in CI without API keys +- [ ] 95%+ cache hit rate after initial recording +- [ ] Test execution <100ms per cached LLM call +- [ ] Cassettes persist across CI runs (via committed vcr-data/) +- [ ] Seamless integration with existing RedisVL tests +- [ ] Clear error messages when cassettes are missing + +## References + +- [maestro-langgraph VCR Implementation](../../../maestro-langgraph/tests/utils/) +- [VCR.py (Python HTTP recording)](https://vcrpy.readthedocs.io/) +- [WireMock](https://wiremock.org/) +- [ByteBuddy](https://bytebuddy.net/) +- [JUnit 5 Extension Model](https://junit.org/junit5/docs/current/user-guide/#extensions) diff --git a/settings.gradle.kts b/settings.gradle.kts index d2e4526..26cfa26 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,11 +6,15 @@ include("docs") // Demos include("demos:rag-multimodal") +include("demos:langchain4j-vcr") +include("demos:spring-ai-vcr") // Configure module locations project(":core").projectDir = file("core") project(":docs").projectDir = file("docs") project(":demos:rag-multimodal").projectDir = file("demos/rag-multimodal") +project(":demos:langchain4j-vcr").projectDir = file("demos/langchain4j-vcr") +project(":demos:spring-ai-vcr").projectDir = file("demos/spring-ai-vcr") // Enable build cache for faster builds buildCache {