❯ Guillaume Laforge

The power of large context windows for your documentation efforts

My colleague Jaana Dogan was pointing at the Anthropic’s MCP (Model Context Protocol) documentation pages which were describing how to build MCP servers and clients. The interesting twist was about preparing the documentation in order to have Claude assist you in building those MCP servers & clients, rather than clearly documenting how to do so.

No more tutorials:

  • You discuss with the reference documentation.
  • Your chat with the LLM becomes the tutorial!

LLM-powered documentation chats become your tailored tutorial, for your very own specific need and requirements. Not only LLMs can assist you authoring articles, documentation, reports, but LLMs can craft explanations that help you achieve a particular goal for which there’s not already a tutorial or how-to guide available.

Also, sometimes, you overlook some key paragraph of section when browsing through the documentation, and you miss the key information that would have helped you fix the problem at hand. This happened to me recently while using an Obsidian plugin: I needed to configure the plugin in a certain way, and I had the impression it wasn’t possible, but the plugin author pointed me at the key paragraph that I somehow glanced over. Had I asked the question to an LLM-powered chat that has all the reference documentation in its context, maybe it would have found that paragraph I missed?

Here come the large context window LLMs…

As you may already know, I contribute to the LangChain4j open source project, which provides integrations with various LLMs (like Gemini) or vector databases. I hope to start working on an additional module to integrate the new unified Gemini SDK. The advantage of this new SDK is that you can call both Gemini flavors: the one provided by Google Cloud’s Vertex AI offering, as well as DeepMind’s Google AI version. One SDK to rule them all!

I’m also interested in potentially creating a new vector store module for Google Cloud Firestore, which recently added vector calculation support in its Java client library. It would be neat to be able to use Firestore for RAG (Retrieval Augmented Generation) scenarios, taking advantage of the document database, its filtering capabilities, and its ability to do vector similarity searches.

LangChain4j’s documentation provides some explanations on how to contribute new integrations, but I was interested in trying this approach of chatting with the reference documentation to guide my steps in creating a new LLM module, and a new vector store implementation module.

Fortunately, my favorite large language model, Gemini, has a huge context window of up to 2 million tokens! This should be enough to ingurgitate the whole sources and documentation pages of the project.

Additionally, I discovered gitingest a great online tool that allows you to convert a Github project and all its sources into one gigantic text file… that you can then feed to an LLM. Basically, you take the URL of a Github repository, and you replace the github.com part with gitingest.com. For example, for LangChain4j, the URL becomes: https://gitingest.com/langchain4j/langchain4j. Then, a download button lets you get the whole content of the repository.

The prompt

For this experiment, I didn’t code anything, I simply used Google AI Studio.

I selected Gemini 2.0 Pro experimental:

Then, I loaded the big text file of the LangChain4j project, created by gitingest, and look at the number of tokens:

Yes, the whole repository and documentation is almost 2 million tokens long! Only Gemini can ingest that many tokens!

Time to ask my question:

I’d like to create a new language module for LangChain4j. How should I do that? What are the key steps to follow to propose a new model?

The response

Click to reveal the detailed answer

Okay, let’s outline the steps to create a new language model module for LangChain4j and propose its inclusion. Based on the provided file structure, you’ll be focusing on creating a new module similar to the existing ones (e.g., langchain4j-open-ai, langchain4j-ollama, etc.). Here’s a breakdown of the process, referencing the structure you’ve provided:

Key Steps and Considerations

  1. Understand the Abstractions and SPI: LangChain4j, like its Python counterpart, is built around core abstractions. You need to understand these to implement your integration correctly. The core abstractions you must implement are:

    • ChatLanguageModel / StreamingChatLanguageModel: For conversational models (like ChatGPT, Gemini). Implement ChatLanguageModel for synchronous responses, and StreamingChatLanguageModel if the model supports streaming responses token by token.
    • LanguageModel / StreamingLanguageModel: For models with a simpler text-in, text-out interface (less common these days).
    • EmbeddingModel: If the model provider offers embedding capabilities.
    • ModerationModel: If the model provider offers content moderation.
    • ScoringModel: If the model provider offers scoring/ranking capabilities.
    • Builder Factories: You’ll also need to create builder factories (SPIs) for each model type you implement. These are how users will construct your model classes. See examples like AzureOpenAiChatModelBuilderFactory. These are registered using the Java ServiceLoader mechanism (the META-INF/services files).
  2. Choose a Module Structure (and Repository):

    • Community Repo (Preferred for new integrations): Start your integration in the langchain4j-community repository. This is the recommended approach for new contributions. It allows for easier initial review and iteration before considering a move to the core langchain4j repository. Clone this repo, don’t fork the main langchain4j repo directly.
    • Main langchain4j Repo (For Core Integrations): If your integration is with a very widely used and well-established model provider (like OpenAI, Google, etc.), and you are confident in its stability and long-term maintenance, you might propose it for the main repo. However, start in langchain4j-community first.
    • Module Naming: Follow the pattern: langchain4j-{provider-name} (e.g., langchain4j-my-llm).
    • Directory Structure: Create a directory structure mirroring the existing modules (see langchain4j-open-ai or langchain4j-ollama as good examples):
      langchain4j-{provider-name}/
          pom.xml  (Your module's Maven build file)
          src/
              main/
                  java/
                      dev/
                          langchain4j/
                              model/
                                  {providername}/  (e.g., myllm)
                                      {ProviderName}ChatModel.java  (Your implementation)
                                      internal/ (API client and related classes)
                                      spi/      (Builder factory for your model)
                                          {ProviderName}ChatModelBuilderFactory.java
                  resources/
                      META-INF/
                          services/
                              (Files to register your builder factory, see examples)
              test/
                  java/
                      dev/
                          langchain4j/
                              model/
                                  {providername}/
                                      {ProviderName}ChatModelIT.java (Integration tests)
      
  3. Implement the API Client:

    • Official SDK (Preferred): If the LLM provider has an official Java SDK, use it. This is usually the best approach for stability, performance, and access to all features. See langchain4j-bedrock for an example using an official SDK.
    • HTTP Client (If no SDK): If there’s no official SDK, use the JDK’s built-in java.net.http.HttpClient (available since Java 11). This minimizes external dependencies. Avoid adding new dependencies unless absolutely necessary. See http-clients/langchain4j-http-client-jdk for how LangChain4j wraps this. Avoid using the older okhttp3 directly if possible, prefer langchain4j-http-client-jdk (or langchain4j-http-client-spring-restclient if building a Spring Boot starter).
    • JSON Handling: Use Jackson for JSON serialization/deserialization, as it’s already a dependency.
    • Error Handling: Make sure to handle HTTP errors (non-2xx responses) appropriately. Throw a dev.langchain4j.exception.HttpException for these.
    • Request/Response Logging: Implement logging for requests and responses (see langchain4j-anthropic for a complete example). This is very helpful for debugging.
  4. Implement the Model Interface(s):

    • Implement ChatLanguageModel, StreamingChatLanguageModel, EmbeddingModel, etc., as appropriate, based on the provider’s capabilities.
    • Use the Builder pattern for your model classes to allow for flexible configuration.
    • Make sure your implementation handles request/response mapping and error handling correctly.
    • Implement TokenCountEstimator if possible, so the TokenWindowChatMemory can calculate the token usage. Implement DimensionAwareEmbeddingModel to report the output dimension from the embedding model.
  5. Write Tests:

    • Unit Tests: Create unit tests for any complex logic, utility methods, and request/response mappers.
    • Integration Tests (ITs): Create integration tests (e.g., MyLlmChatModelIT.java) that interact with the real LLM provider’s API. These are crucial for ensuring your integration works correctly.
      • Use environment variables (e.g., MYLLM_API_KEY) to store API keys and other secrets. Do not hardcode them.
      • Use @EnabledIfEnvironmentVariable to skip the tests if the required environment variables are not set.
      • Extend AbstractChatModelIT, AbstractStreamingChatModelIT, AbstractEmbeddingModelIT, and/or AbstractScoringModelIT to get a set of basic tests.
      • Test all relevant features of the model (e.g., text generation, streaming, different parameters, tool use, JSON mode).
      • Add test for concurrent requests if possible.
      • Consider adding a test for the Tokenizer interface (see examples in langchain4j-core).
      • Add @RetryingTest if model response is inconsistent
  6. Add to BOM (Bill of Materials): Add your new module to langchain4j-bom/pom.xml. This helps users manage dependencies.

  7. Documentation:

    • Update README.md: Add your integration to the list of supported models and embedding stores.
    • Create Markdown Documentation: Create Markdown files in the docs/docs/integrations/ directory, following the structure of existing integrations. You’ll need:
      • A main file (e.g., my-llm.md).
      • An entry in docs/docs/integrations/language-models/index.md and in docs/sidebars.js.
      • An entry in _category_.json files in docs/docs/integrations/language-models and docs/docs/integrations/embedding-stores
    • Examples (Highly Recommended): Create a simple example in the langchain4j-examples repository. This is very helpful for users.
  8. General Guidelines (from CONTRIBUTING.md):

    • Java 17: Maintain compatibility with Java 17.
    • Minimal Dependencies: Avoid adding new dependencies if possible. If necessary, try to use libraries already present. Run mvn dependency:analyze to check.
    • Backwards Compatibility: Avoid breaking changes. If necessary, deprecate old methods/fields instead of removing them.
    • Naming Conventions: Follow existing naming conventions.
    • No Lombok: Avoid using Lombok in new code; remove it from existing code if you touch it.
    • Javadoc: Add Javadoc where needed.
    • Code Style: Run make lint and make format before committing.
    • Large Features: Discuss large features with maintainers (@langchain4j) before implementation.
  9. Open a Pull Request (Draft First):

    • Open a draft PR in the langchain4j-community repository.
    • Fill out all sections of the PR template.
    • Once the PR is reviewed and approved, you will be asked to finalize it (add documentation, examples, etc.).

Example Code Snippets (Illustrative)

// MyLlmChatModel.java (in the appropriate package)
public class MyLlmChatModel implements ChatLanguageModel {

    private final MyLlmClient client; // Your custom client
    private final String modelName;

    @Builder
    public MyLlmChatModel(String baseUrl, String apiKey, String modelName, Duration timeout) {
        this.client = new MyLlmClient(baseUrl, apiKey, timeout); // Your custom client
        this.modelName = modelName;
    }

    @Override
    public Response<AiMessage> generate(List<ChatMessage> messages) {
        // 1. Convert Langchain4j messages to MyLLM's request format
        // 2. Call client.chat(...)
        // 3. Convert MyLLM's response to LangChain4j's Response<AiMessage>
        // 4. Handle errors (throw HttpException)
        return null; // TODO: Implement
    }

    // ... other methods, builder, etc. ...
}


// MyLlmClient.java (in a suitable package, e.g., dev.langchain4j.model.myllm.internal)
class MyLlmClient {

    // ... implementation using java.net.http.HttpClient ...
}

// MyLlmChatModelBuilderFactory.java (in a suitable package, e.g., dev.langchain4j.model.myllm.spi)
public class MyLlmChatModelBuilderFactory implements Supplier<MyLlmChatModel.Builder> {

    @Override
    public MyLlmChatModel.Builder get() {
        return MyLlmChatModel.builder();
    }
}

// META-INF/services/dev.langchain4j.model.chat.spi.ChatLanguageModelBuilderFactory (resource file)
// Add a line with the fully qualified name of your factory:
// dev.langchain4j.model.myllm.spi.MyLlmChatModelBuilderFactory

Key Points and Common Mistakes

  • Thorough Testing: Integration tests are absolutely essential. Test with real API calls.
  • Error Handling: Handle HTTP errors (non-2xx responses) from the LLM provider’s API.
  • Token Usage: If the LLM provider gives you token usage information, include it in the Response.
  • Finish Reason: If the LLM provider gives you a finish reason (e.g., “stop”, “length”), include it in the Response.
  • Consistency: Maintain consistency with existing LangChain4j modules in terms of naming, structure, and coding style.
  • Documentation: Clear, concise, and accurate documentation is critical. Follow the patterns you see in existing modules.
  • SPI Registration: Remember to create the src/main/resources/META-INF/services/ files to register your model implementation as a service.

This comprehensive guide, combined with the provided file structure and examples, should give you a strong foundation for contributing your new language model integration to LangChain4j. Remember to start in the langchain4j-community repository for initial development and review. Good luck!

The verdict

Honestly, this is really good! It follows the advice in the contributing guidelines. But since it has access to the whole code base, it understands pretty well the various steps to follow, the classes to implement, etc.

A few nits though: for example, it confirms we shouldn’t use Lombok for creating builders, but the example code shows a @Builder annotation from Lombok, because it’s still used in several modules. Or there’s this reference to the make tool, although the project doesn’t use it at all. The code suggests implement the generate() methods, but newer models should now implement the new chat() method instead, but Gemini can’t really know about this, as this is fairly new and not many models implement those interfaces for now.

But other than that, it’s excellent!

Gemini created a contributing guide by looking at almost 2 million tokens of code and documentation. But this doesn’t end here, you can continue asking other questions, to get more insight into particular aspects of the development of a new module, and Gemini would continue the discussion, guiding you and helping you in your task. I found that quite impressive.

Let’s continue the conversation with the code base and documentation

As I was interested in creating a new embedding store for Firestore, I then added in the context the documentation of the Java SDK for Firestore. And asked the following question:

I would also like to create a new embedding store for the Google Cloud Firestore database. What are the steps to create a new LangChain4j embedding store module?

Let’s see what it suggested:

Click to reveal the detailed answer

Okay, here’s a breakdown of how to create a new LangChain4j embedding store module for Google Cloud Firestore, along with the key steps and considerations, mirroring the structure of existing modules like langchain4j-milvus.

Project Structure

Your project structure should follow the established pattern. I’ll create a simplified version based on the most relevant parts from the provided file listing. The full structure would be much larger (like the main langchain4j project), but this captures the essentials:

langchain4j/
└── langchain4j-embedding-store-google-firestore/  (or similar name)
    ├── pom.xml                                     (Your module's Maven build file)
    └── src/
        └── main/
            └── java/
                └── dev/
                    └── langchain4j/
                        └── store/
                            └── embedding/
                                └── google/
                                    └── firestore/
                                        └── GoogleFirestoreEmbeddingStore.java  (Main class)
                                        └── (Any other supporting classes, like request/response objects)
        └── test/
            └── java/
                └── dev/
                    └── langchain4j/
                        └── store/
                            └── embedding/
                                └── google/
                                    └── firestore/
                                        └── GoogleFirestoreEmbeddingStoreIT.java (Integration test)

Steps to Create the Module

  1. Project Setup (Maven Module):

    • Create a new directory named langchain4j-embedding-store-google-firestore (or a similar, descriptive name) within the langchain4j parent directory.
    • Create a pom.xml file inside this new directory. This file will define your module as a Maven project and specify its dependencies. Use langchain4j-milvus/pom.xml as a guide, but adapt it:
      • Parent: Set the parent to langchain4j-parent, as shown in your example.
      • ArtifactId: Use langchain4j-embedding-store-google-firestore.
      • Name and Description: Update these appropriately.
      • Dependencies: Include langchain4j-core as a compile-time dependency. Add the Google Cloud Firestore Java client library as a dependency:
        <dependency>
            <groupId>com.google.cloud</groupId>
            <artifactId>google-cloud-firestore</artifactId>
            <version>YOUR_VERSION_HERE</version> <!-- Lookup the latest version -->
        </dependency>
        
        Add any other necessary dependencies, such as logging (SLF4J), Lombok (if desired, but try to avoid in new code), etc. Minimize dependencies as much as possible.
      • Test Dependencies: Include JUnit, AssertJ, and any mocking libraries (like Mockito) in the test scope.
      • Licenses: Ensure your module has the correct Apache 2.0 license information in the POM.
  2. Implement EmbeddingStore<TextSegment>:

    • Create GoogleFirestoreEmbeddingStore.java in the dev.langchain4j.store.embedding.google.firestore package.
    • Implement the EmbeddingStore<TextSegment> interface from langchain4j-core. This is the crucial part. You’ll need to implement the following methods, mapping them to Firestore operations:
      • add(Embedding embedding): Adds a single embedding to the store, generating a unique ID.
      • add(String id, Embedding embedding): Adds an embedding with a specified ID.
      • add(Embedding embedding, TextSegment textSegment): Adds an embedding with associated text and metadata.
      • addAll(List<Embedding> embeddings): Adds multiple embeddings.
      • addAll(List<String> ids, List<Embedding> embeddings, List<TextSegment> textSegments): Adds multiple embeddings with associated IDs and text segments.
      • removeAll(Collection<String> ids): Delete embeddings by id
      • removeAll(): Deletes everything
      • removeAll(Filter filter): Deletes records using the condition filter
      • search(EmbeddingSearchRequest request): Find related embeddings
      • findRelevant(Embedding referenceEmbedding, int maxResults, double minScore): Finds relevant embeddings.
    • Considerations for Firestore Implementation:
      • Data Model: How will you store the embeddings (as float arrays) and associated data (text, metadata)? Firestore uses a NoSQL document model. You’ll likely store each TextSegment and its Embedding as a document in a collection.
      • Metadata: Firestore supports storing metadata as document fields. You’ll need a way to map TextSegment metadata (which is a Map<String, String>) to Firestore document fields. You have a few options here, mirroring what existing modules do:
        • Individual Columns (Preferred): Each metadata key becomes a separate field in the document. This is efficient for querying but requires knowing the metadata keys in advance. The Mariadb integration uses this approach with MetadataColumDefinition.
        • Single JSON Field: Store all metadata as a single JSON string. This is flexible but less efficient for filtering. The Chroma integration uses this.
        • Mixed Approach: Common metadata fields (like “source” or “document_id”) could be separate fields, and a catch-all “metadata” field could store the rest as JSON.
      • Vector Search: Firestore now has native Vector Search, which is excellent! You’ll use this for the findRelevant method. The key will be understanding how to map LangChain4j’s EmbeddingSearchRequest (which includes maxResults and minScore) to the appropriate Firestore vector search query.
      • Filtering: Implement filtering based on metadata using Firestore’s query capabilities. LangChain4j’s Filter interface needs to be translated into a Firestore query. Look at existing implementations like ChromaMetadataFilterMapper for inspiration, but adapt it to Firestore’s query syntax.
      • Error Handling: Wrap Firestore exceptions in RuntimeException or a more specific custom exception.
      • Configuration: Use a Builder pattern (like OpenAiChatModel does) to allow users to configure:
        • Firestore project ID, database, collection name
        • Credentials (API key, service account)
        • Timeout settings
        • Maximum number of results (maxResults)
        • Minimum relevance score (minScore)
        • Field names for text, embedding, and metadata (if configurable)
        • Possibly options for automatically creating the collection/indexes.
      • Indexing: You must create the necessary index in Firestore for vector search to work. The documentation you provided explains how to do this with the gcloud CLI. The code should, ideally, at least check if the index exists. It could even attempt to create it, but this would require more permissions.
      • Concurrency: if there is any concurrency, the code should be thread-safe.
  • Implement GoogleFirestoreEmbeddingModel: You should also implement the EmbeddingModel interface, by adapting the google API to the langchain4j interface.
  1. Create SPI Builder Factory:

    • Create a GoogleFirestoreEmbeddingStoreBuilderFactory class that implements Supplier<GoogleFirestoreEmbeddingStore.Builder>.
    • Create a file named META-INF/services/dev.langchain4j.spi.store.embedding.EmbeddingStoreFactory in src/main/resources.
    • Add a single line to this file containing the fully qualified name of your factory class (e.g., dev.langchain4j.store.embedding.google.firestore.GoogleFirestoreEmbeddingStoreBuilderFactory).
  2. Write Integration Tests:

    • Create GoogleFirestoreEmbeddingStoreIT.java (and potentially ...WithFilteringIT.java, ...WithRemovalIT.java, etc., mirroring the existing modules).
    • Extend EmbeddingStoreIT (or EmbeddingStoreWithFilteringIT, etc.) to inherit a basic set of tests.
    • Implement the abstract methods (like embeddingStore()) to provide instances of your store and a compatible embedding model.
    • Add tests specific to Google Cloud Firestore features and limitations.
    • Use @EnabledIfEnvironmentVariable to conditionally run the tests only when the necessary environment variables (credentials) are set. See OllamaChatModelIT for an example.
  3. Add to BOM (Bill of Materials): Add your new module to langchain4j-bom/pom.xml to manage its version consistently.

  4. Documentation:

    • Add the new model in all the index.md files
    • Write a guide in docs/docs/integrations/embedding-stores
    • Create a README file

Code Example (Conceptual)

// In your GoogleFirestoreEmbeddingStore.java
package dev.langchain4j.store.embedding.google.firestore;

import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingStore;
import lombok.Builder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

// ... other imports ...

public class GoogleFirestoreEmbeddingStore implements EmbeddingStore<TextSegment> {

    private static final Logger log = LoggerFactory.getLogger(GoogleFirestoreEmbeddingStore.class);

    // ... (fields for Firestore client, collection name, etc.) ...

    @Builder
    public GoogleFirestoreEmbeddingStore(...) {
        // ... (initialize Firestore client, etc.) ...
    }

    @Override
    public String add(Embedding embedding) {
        // ... implementation ...
    }

    @Override
    public void add(String id, Embedding embedding) {
        // ... implementation ...
    }

    @Override
    public String add(Embedding embedding, TextSegment textSegment) {
        // ... implementation ...
    }

    @Override
    public List<String> addAll(List<Embedding> embeddings) {
        // ... implementation ...
    }

    @Override
    public void addAll(List<String> ids, List<Embedding> embeddings, List<TextSegment> embedded) {
        // ... implementation ...
    }

    @Override
    public List<EmbeddingMatch<TextSegment>> findRelevant(Embedding referenceEmbedding, int maxResults, double minScore) {
        // ... implementation using Firestore vector search ...
        // ... map Firestore results to EmbeddingMatch objects ...
    }

    // ... other methods ...
}

// In a separate file: GoogleFirestoreEmbeddingStoreBuilderFactory.java
package dev.langchain4j.store.embedding.google.firestore;

import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.spi.store.embedding.EmbeddingStoreFactory;

public class GoogleFirestoreEmbeddingStoreBuilderFactory implements EmbeddingStoreFactory {
    @Override
    public GoogleFirestoreEmbeddingStore.Builder get() {
       return GoogleFirestoreEmbeddingStore.builder();
    }
}

Key Improvements and Considerations from the examples:

  • Error Handling: Wrap Firestore API calls in try-catch blocks and throw RuntimeException (or a more specific custom exception) to handle potential errors.
  • Logging: Use SLF4J (log.debug(), log.info(), log.warn(), log.error()) for logging. This is crucial for debugging and monitoring.
  • Configuration: The Builder should allow configuration of all necessary parameters (endpoint, credentials, collection name, timeouts, etc.). Use ensureNotNull and ensureNotBlank to validate.
  • Dependency Management: The pom.xml should correctly specify the Google Cloud Firestore client library as a dependency.
  • Testcontainers: Consider using Testcontainers for integration tests to spin up a Firestore emulator. This will make your tests more reliable and portable. (See examples in the provided code, such as for CassandraEmbeddingStoreDockerIT).
  • Metadata: Implement proper handling of metadata, including mapping it to/from Firestore document fields. Decide on your strategy (separate fields vs. JSON).
  • Filtering: Implement filtering based on metadata using Firestore’s query capabilities. You will likely need a helper class like ChromaMetadataFilterMapper to translate Filter objects into Firestore queries.
  • Service Provider Interface (SPI): The spi package and META-INF/services file are critical for LangChain4j to discover your implementation. This is how AiServices (and other parts of the framework) dynamically load components. Without this, your module won’t be usable.
  • Return type: The findRelevant method should take a {@link dev.langchain4j.store.embedding.EmbeddingSearchRequest} and return a {@link dev.langchain4j.store.embedding.EmbeddingSearchResult}. This would ensure that we have consistency through all embedding stores and allow advanced filtering and reranking features.
  • Interface segregation: Introduce a EmbeddingStoreWithFiltering interface, that extends EmbeddingStore and adds a removeAll(Filter filter) method.
  • Metadata handling: Implement the MetadataHandler interface, or extend one of its base classes if it’s a good fit.
  • Support Vector Search API in different database modes: Support vector search in the different modes of Cosmos DB, implementing the search in each of them.
  • Implement the close() method: You should close the connection to Firestore in the close() method.
  • Test your embedding store against the EmbeddingStoreWithFilteringIT and EmbeddingStoreWithRemovalIT: The provided langchain4j-core contains those abstract test classes that you can use to test your embedding store.
  • Add documentation: Create Markdown files in the docs/docs/integrations/embedding-stores directory, following the structure of existing integrations.
  • Add an example: to the examples repository.

By following these steps and adapting the provided example code, you can create a robust and well-integrated LangChain4j module for Google Cloud Firestore.

Second verdict

Again, it’s pretty spot on and acurate! Still a mention to Lombok, or a strange reference to Cosmos DB, but I generally like how it advises me to look at existing modules, telling me that this particular one may be a good inspiration to get my implementation right.

I’ve never implemented an embedding store so far, and I think newer models should implement a search() method instead of findRelevant(). So I asked how to implement this method, using the documentation of the Firestore Java SDK, and a minute later, it suggested a concrete implementation. I won’t copy the output again, as I don’t want to make this article too long (it already is). I haven’t tried this implementation, but the code and explanations seemed pretty convincing, so when I get some time, I’ll try to see if it’s correct.

Conclusion

Only Gemini proposes a 2 million token context window. And I’m glad it does, because the LangChain4j projects is close to that limit! With models with smaller windows, I would have had to be way more selective, and send in the prompt just the right types of artifacts (ie. just the LLM modules, or just the embedding store implementations). Thanks to the huge window, I was able to feed the whole repository in its entirety!

Does it mean it’s the end to writing proper tutorials or how-to guides? Certainly not. But I find that very interesting that I’m able to have this kind of highly detailed conversation with the LLM, without having to understand all the tiny little details of the underlying project, as the model is able to grok it for me, and distills just the right level of information for me to do the task I asked about.

What’s very interesting is that I can continue the conversation to go in various directions, or zoom on some specific aspects, which may not necessarily be covered by existing tutorials or guides. It’s as if I was pair programming with the founder of the project.