CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-langchain4j--langchain4j

Build LLM-powered applications in Java with support for chatbots, agents, RAG, tools, and much more

Overview
Eval results
Files

chains.mddocs/

Chains (Deprecated)

Legacy chain API for sequential processing. Chains are deprecated in favor of AiServices, which provides a more flexible and powerful API.

CRITICAL DEPRECATION NOTICE: This API is deprecated and should NOT be used for new development. All chain functionality has been superseded by AiServices. This documentation is maintained only for legacy code migration purposes.

Deprecation Status

  • Deprecated Since: Version 0.20.0
  • Removal Timeline: These APIs are marked for removal in a future major version
  • Migration Path: Use AiServices for all new development (see detailed migration guide below)
  • Support Status: Bug fixes only, no new features

Capabilities

Chain Interface

Functional interface representing a chain step.

package dev.langchain4j.chain;

/**
 * Functional interface representing a chain step that takes an input and produces an output
 * Deprecated in favor of AiServices
 * @deprecated Use AiServices instead
 */
@Deprecated
public interface Chain<Input, Output> {
    /**
     * Execute the chain step
     * @param input Input to process
     * @return Output from processing
     */
    Output execute(Input input);
}

Thread Safety:

  • Implementations MUST be thread-safe if shared across threads
  • The interface itself does not enforce thread safety
  • State management is implementation-dependent

Common Pitfalls:

  • DO NOT share mutable state between execute() calls without synchronization
  • DO NOT assume execute() is idempotent unless explicitly designed
  • DO NOT use for long-running operations without timeout handling

Exception Handling:

  • execute() can throw any RuntimeException
  • No checked exceptions are declared
  • Error handling is implementation-specific

Performance Notes:

  • Simple functional interface has minimal overhead
  • Performance depends entirely on implementation
  • No built-in caching or optimization

Related APIs:

  • AiServices - Modern replacement with better features
  • ConversationalChain - Concrete chain implementation
  • ConversationalRetrievalChain - RAG-enabled chain implementation

ConversationalChain

Chain for conversing with a ChatModel while maintaining memory.

package dev.langchain4j.chain;

/**
 * A chain for conversing with a ChatModel while maintaining a memory of the conversation
 * Includes a default ChatMemory (message window with max 10 messages)
 * Deprecated in favor of AiServices
 * @deprecated Use AiServices with chatMemory configuration instead
 */
@Deprecated
public class ConversationalChain implements Chain<String, String> {
    /**
     * Create builder for configuring chain
     * @return Builder instance
     */
    public static ConversationalChainBuilder builder();

    /**
     * Execute chain with user message
     * @param userMessage User message to process
     * @return Response from chat model
     */
    public String execute(String userMessage);
}

Thread Safety:

  • NOT thread-safe by default due to shared ChatMemory state
  • Each thread should have its own ConversationalChain instance
  • Concurrent calls to execute() will corrupt conversation history
  • Use AiServices with @MemoryId for proper multi-user support

Common Pitfalls:

  • DO NOT share a single instance across multiple conversations/users
  • DO NOT call execute() concurrently from multiple threads
  • DO NOT forget to configure ChatMemory - default is limited to 10 messages
  • DO NOT use for stateless operations (use ChatModel directly instead)

Edge Cases:

  • Empty or null userMessage may cause NPE
  • Very long conversations will lose old messages (based on memory window)
  • Memory is not persisted - lost when chain is garbage collected
  • No built-in error recovery for failed ChatModel calls

Exception Handling:

  • Throws RuntimeException if ChatModel call fails
  • No retry logic built-in
  • Memory state may be corrupted after exception

Performance Notes:

  • Each execute() call makes one ChatModel API request
  • Memory lookups are O(1) but message serialization adds overhead
  • Default 10-message window is a reasonable balance for most use cases
  • Consider custom ChatMemory implementation for large contexts

Replacement with AiServices:

// OLD (Deprecated):
ConversationalChain chain = ConversationalChain.builder()
    .chatModel(chatModel)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
    .build();

// NEW (Recommended):
interface Assistant {
    String chat(String message);
}
Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
    .build();

Related APIs:

  • AiServices - Modern replacement
  • ChatMemory - Memory management
  • MessageWindowChatMemory - Default memory implementation
  • ChatModel - Underlying model interface

ConversationalChain Builder

/**
 * Builder for ConversationalChain
 */
public class ConversationalChainBuilder {
    /**
     * Set the chat model
     * @param chatModel Chat model to use
     * @return Builder instance
     */
    public ConversationalChainBuilder chatModel(ChatModel chatModel);

    /**
     * Set the chat memory
     * @param chatMemory Chat memory instance
     * @return Builder instance
     */
    public ConversationalChainBuilder chatMemory(ChatMemory chatMemory);

    /**
     * Build the chain
     * @return ConversationalChain instance
     */
    public ConversationalChain build();
}

Thread Safety:

  • Builder is NOT thread-safe
  • Build once per thread or synchronize externally
  • The built chain inherits thread safety from ChatMemory implementation

Common Pitfalls:

  • DO NOT call build() multiple times - creates independent chains with separate memories
  • DO NOT forget to set chatModel - will throw NPE on build()
  • DO NOT share builder instance across threads
  • DO NOT reuse ChatMemory instance across multiple chains

Exception Handling:

  • build() throws IllegalStateException if chatModel is null
  • No validation for chatMemory (uses default if not set)

Related APIs:

  • ConversationalChain - Built chain instance
  • ChatModel - Required model parameter
  • ChatMemory - Optional memory parameter

ConversationalRetrievalChain

Chain for conversing with a ChatModel based on retrieved information.

package dev.langchain4j.chain;

/**
 * A chain for conversing with a ChatModel based on information retrieved by a ContentRetriever
 * Includes default ChatMemory (message window with max 10 messages)
 * Supports RAG with RetrievalAugmentor
 * Deprecated in favor of AiServices with contentRetriever/retrievalAugmentor
 * @deprecated Use AiServices with contentRetriever or retrievalAugmentor instead
 */
@Deprecated
public class ConversationalRetrievalChain implements Chain<String, String> {
    /**
     * Create builder for configuring chain
     * @return Builder instance
     */
    public static Builder builder();

    /**
     * Execute chain with query
     * @param query Query to process
     * @return Response from chat model with retrieved context
     */
    public String execute(String query);
}

Thread Safety:

  • NOT thread-safe due to shared ChatMemory state
  • ContentRetriever and RetrievalAugmentor must be thread-safe
  • Each user/conversation needs separate instance
  • Use AiServices with @MemoryId for proper multi-user RAG

Common Pitfalls:

  • DO NOT share across users/conversations
  • DO NOT call execute() concurrently
  • DO NOT set both contentRetriever and retrievalAugmentor (use one or the other)
  • DO NOT forget that retrieval happens on every execute() call (performance impact)
  • DO NOT use for non-RAG scenarios (use ConversationalChain instead)

Edge Cases:

  • Empty retrieval results still call ChatModel (may produce poor answers)
  • Retrieval failures propagate as RuntimeException
  • Large retrieved contexts may exceed ChatModel token limits
  • Memory window may evict important context from earlier in conversation

Exception Handling:

  • Throws RuntimeException if ContentRetriever fails
  • Throws RuntimeException if ChatModel call fails
  • No automatic retry or fallback logic
  • Memory state may be corrupted after exception

Performance Notes:

  • Each execute() performs: retrieval → memory lookup → ChatModel call
  • Retrieval is typically the slowest part (embedding + vector search)
  • Consider caching retrieval results for repeated queries
  • Default 10-message window impacts context size sent to model
  • Large retrieved contexts increase token usage and cost

Retrieval Process:

  1. Query is used to retrieve relevant content
  2. Retrieved content is formatted into prompt
  3. Conversation memory is added to prompt
  4. Combined prompt sent to ChatModel
  5. Response is stored in memory and returned

Replacement with AiServices:

// OLD (Deprecated):
ConversationalRetrievalChain chain = ConversationalRetrievalChain.builder()
    .chatModel(chatModel)
    .contentRetriever(contentRetriever)
    .build();

// NEW (Recommended):
interface Assistant {
    String chat(String message);
}
Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .contentRetriever(contentRetriever)
    .build();

Related APIs:

  • AiServices - Modern replacement with better RAG support
  • ContentRetriever - Basic retrieval interface
  • RetrievalAugmentor - Advanced retrieval with re-ranking
  • ChatMemory - Conversation history management
  • EmbeddingStore - Vector storage for retrieval

ConversationalRetrievalChain Builder

/**
 * Builder for ConversationalRetrievalChain
 */
public class Builder {
    /**
     * Set the chat model
     * @param chatModel Chat model to use
     * @return Builder instance
     */
    public Builder chatModel(ChatModel chatModel);

    /**
     * Set the chat memory
     * @param chatMemory Chat memory instance
     * @return Builder instance
     */
    public Builder chatMemory(ChatMemory chatMemory);

    /**
     * Set content retriever for RAG
     * @param contentRetriever Content retriever instance
     * @return Builder instance
     */
    public Builder contentRetriever(ContentRetriever contentRetriever);

    /**
     * Set retrieval augmentor for RAG
     * @param retrievalAugmentor Retrieval augmentor instance
     * @return Builder instance
     */
    public Builder retrievalAugmentor(RetrievalAugmentor retrievalAugmentor);

    /**
     * Build the chain
     * @return ConversationalRetrievalChain instance
     */
    public ConversationalRetrievalChain build();
}

Thread Safety:

  • Builder is NOT thread-safe
  • Build once per thread or synchronize externally

Common Pitfalls:

  • DO NOT set both contentRetriever and retrievalAugmentor (mutually exclusive)
  • DO NOT forget to set chatModel (required)
  • DO NOT forget to set contentRetriever OR retrievalAugmentor (one is required)
  • DO NOT share builder across threads
  • DO NOT call build() multiple times if sharing ChatMemory instance

Exception Handling:

  • build() throws IllegalStateException if chatModel is null
  • build() throws IllegalStateException if neither contentRetriever nor retrievalAugmentor set
  • build() throws IllegalStateException if BOTH contentRetriever and retrievalAugmentor set

Configuration Priority:

  • retrievalAugmentor takes precedence over contentRetriever if both somehow set
  • Default ChatMemory is MessageWindowChatMemory with 10 messages
  • No default prompt template (uses model's default behavior)

Related APIs:

  • ConversationalRetrievalChain - Built chain instance
  • ContentRetriever - Simple retrieval option
  • RetrievalAugmentor - Advanced retrieval option with re-ranking

Usage Examples (Deprecated)

Simple Conversational Chain

import dev.langchain4j.chain.ConversationalChain;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;

// Deprecated approach
ConversationalChain chain = ConversationalChain.builder()
    .chatModel(chatModel)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
    .build();

String response1 = chain.execute("Hello, my name is Alice");
String response2 = chain.execute("What is my name?");
// Response: "Your name is Alice"

Recommended alternative using AiServices:

import dev.langchain4j.service.AiServices;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;

interface Assistant {
    String chat(String message);
}

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
    .build();

String response1 = assistant.chat("Hello, my name is Alice");
String response2 = assistant.chat("What is my name?");
// Response: "Your name is Alice"

Why AiServices is Better:

  • Type-safe interface definition
  • Better IDE support with autocompletion
  • Easier to mock for testing
  • Supports annotations for advanced features
  • Consistent API with other LangChain4j features

Conversational Retrieval Chain

import dev.langchain4j.chain.ConversationalRetrievalChain;
import dev.langchain4j.rag.content.retriever.ContentRetriever;

// Deprecated approach
ConversationalRetrievalChain chain = ConversationalRetrievalChain.builder()
    .chatModel(chatModel)
    .contentRetriever(contentRetriever)
    .build();

String response = chain.execute("What does the documentation say about configuration?");

Recommended alternative using AiServices:

import dev.langchain4j.service.AiServices;

interface Assistant {
    String chat(String message);
}

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .contentRetriever(contentRetriever)
    .build();

String response = assistant.chat("What does the documentation say about configuration?");

Additional AiServices Benefits for RAG:

  • Support for @SystemMessage to guide RAG behavior
  • Access to retrieved sources via Result<T>
  • Built-in content moderation with @Moderate
  • Streaming support for RAG responses

Custom Chain Implementation

import dev.langchain4j.chain.Chain;

// Deprecated approach - custom chain
class SummarizationChain implements Chain<String, String> {
    private final ChatModel chatModel;

    public SummarizationChain(ChatModel chatModel) {
        this.chatModel = chatModel;
    }

    @Override
    public String execute(String longText) {
        String prompt = "Summarize the following text:\n\n" + longText;
        return chatModel.generate(prompt);
    }
}

Chain<String, String> chain = new SummarizationChain(chatModel);
String summary = chain.execute(longText);

Recommended alternative using AiServices:

import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.UserMessage;

interface Summarizer {
    @UserMessage("Summarize the following text:\n\n{{text}}")
    String summarize(@V("text") String text);
}

Summarizer summarizer = AiServices.create(Summarizer.class, chatModel);
String summary = summarizer.summarize(longText);

Why This is Better:

  • Prompt template in annotation (more maintainable)
  • No boilerplate class implementation
  • Type-safe method signature
  • Can add multiple summarization methods with different strategies
  • Template variables prevent prompt injection

Chaining Multiple Steps

import dev.langchain4j.chain.Chain;

// Deprecated approach - manual chaining
Chain<String, String> extractChain = input -> {
    // Extract key information
    return chatModel.generate("Extract key facts from: " + input);
};

Chain<String, String> summarizeChain = facts -> {
    // Summarize facts
    return chatModel.generate("Summarize these facts: " + facts);
};

String input = "Long document text...";
String facts = extractChain.execute(input);
String summary = summarizeChain.execute(facts);

Recommended alternative using AiServices:

import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.UserMessage;

interface DocumentProcessor {
    @UserMessage("Extract key facts from the following text:\n\n{{text}}")
    String extractFacts(@V("text") String text);

    @UserMessage("Summarize these facts:\n\n{{facts}}")
    String summarize(@V("facts") String facts);
}

DocumentProcessor processor = AiServices.create(DocumentProcessor.class, chatModel);
String input = "Long document text...";
String facts = processor.extractFacts(input);
String summary = processor.summarize(facts);

Benefits of Method-Based Approach:

  • Clear separation of concerns
  • Each method can have different configuration
  • Easier to test individual steps
  • Better code organization
  • Can add validation, logging, etc. via AOP

Comprehensive Migration Guide

Migration Strategy

  1. Identify Chain Usage: Search codebase for Chain, ConversationalChain, ConversationalRetrievalChain
  2. Analyze Requirements: Understand what each chain does
  3. Design Interface: Create AiService interface with equivalent methods
  4. Update Configuration: Convert builder calls to AiServices.builder()
  5. Test Thoroughly: Ensure behavior matches original chain
  6. Remove Chain Code: Delete deprecated chain usage

From ConversationalChain to AiServices

Before (Deprecated):

ConversationalChain chain = ConversationalChain.builder()
    .chatModel(chatModel)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
    .build();

String response = chain.execute("Hello");

After (Recommended):

interface Assistant {
    String chat(String message);
}

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
    .build();

String response = assistant.chat("Hello");

Migration Checklist:

  • Create interface with method(s) matching chain behavior
  • Convert ConversationalChain.builder() to AiServices.builder()
  • Keep same chatModel configuration
  • Keep same chatMemory configuration
  • Replace chain.execute() calls with interface method calls
  • Update tests to use interface
  • Verify conversation history works correctly

From ConversationalRetrievalChain to AiServices with RAG

Before (Deprecated):

ConversationalRetrievalChain chain = ConversationalRetrievalChain.builder()
    .chatModel(chatModel)
    .contentRetriever(contentRetriever)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
    .build();

String response = chain.execute("Query about documentation");

After (Recommended):

interface Assistant {
    String chat(String message);
}

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .contentRetriever(contentRetriever)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
    .build();

String response = assistant.chat("Query about documentation");

Migration Checklist:

  • Create interface with RAG-enabled method(s)
  • Convert builder to AiServices.builder()
  • Keep same chatModel configuration
  • Keep same contentRetriever or retrievalAugmentor
  • Keep same chatMemory configuration
  • Replace execute() calls with method calls
  • Update tests to verify RAG behavior
  • Consider adding Result<T> return type to access sources

Enhanced RAG with Result Type:

interface Assistant {
    Result<String> chat(String message);
}

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .contentRetriever(contentRetriever)
    .build();

Result<String> result = assistant.chat("Query");
String response = result.content();
List<Content> sources = result.sources(); // Access retrieved content!
TokenUsage tokenUsage = result.tokenUsage();

From Custom Chain to AiServices

Before (Deprecated):

class MyCustomChain implements Chain<InputType, OutputType> {
    private final ChatModel chatModel;
    private final SomeService service;

    @Override
    public OutputType execute(InputType input) {
        // Custom logic
        String prompt = buildPrompt(input);
        String response = chatModel.generate(prompt);
        return parseResponse(response);
    }
}

After (Recommended):

interface MyService {
    @UserMessage("{{customPrompt}}")
    @SystemMessage("You are a helpful assistant specialized in...")
    OutputType process(@V("customPrompt") String prompt);
}

// Use OutputParser for custom type conversion
MyService service = AiServices.builder(MyService.class)
    .chatModel(chatModel)
    .build();

// Custom logic in application code
OutputType result = service.process(buildPrompt(input));

For Complex Parsing:

interface MyService {
    @UserMessage("Process this input: {{input}}")
    String processRaw(@V("input") InputType input);
}

// Custom parser implementation
class MyOutputParser implements OutputParser<OutputType> {
    @Override
    public OutputType parse(String text) {
        // Custom parsing logic
        return parseResponse(text);
    }

    @Override
    public FormatInstructions formatInstructions() {
        return new FormatInstructions("Return data in format...");
    }
}

MyService service = AiServices.builder(MyService.class)
    .chatModel(chatModel)
    .outputParser(new MyOutputParser())
    .build();

Benefits of AiServices over Chains

  1. Type Safety: Define methods with specific return types (POJOs, primitives, collections)

    • Chains are limited to String → String
    • AiServices support any type with automatic parsing
  2. Annotations: Use @SystemMessage, @UserMessage, @V for templates

    • More maintainable than string concatenation
    • Prevents prompt injection vulnerabilities
    • Better IDE support
  3. Tools: Easily integrate function calling

    • Chains have no tool support
    • AiServices integrate seamlessly with @Tool methods
  4. Streaming: Support for streaming responses via TokenStream

    • Chains don't support streaming
    • Critical for responsive UIs
  5. Moderation: Built-in content moderation with @Moderate

    • Must implement manually with chains
    • Automatic safety checks
  6. Memory Management: Per-user memory with @MemoryId

    • Chains require manual instance management per user
    • AiServices handle multi-user scenarios automatically
  7. Result Metadata: Access token usage, sources, tool executions via Result<T>

    • Chains only return final output
    • Critical for cost tracking and debugging
  8. Flexibility: Create any interface you need without implementing Chain

    • Chains require class implementation
    • Interfaces are more flexible and testable
  9. Better Testing: Easier to mock interfaces for testing

    • Can use standard mocking frameworks
    • No need to extend concrete classes
  10. Modern API: More intuitive and aligned with current best practices

    • Leverages Java's strengths (interfaces, annotations, generics)
    • Better integration with modern frameworks (Spring, Quarkus, etc.)

Advanced Migration Scenarios

Multi-Step Chain to Multi-Method Service

Before:

Chain<String, String> step1 = input -> chatModel.generate("Step 1: " + input);
Chain<String, String> step2 = input -> chatModel.generate("Step 2: " + input);
Chain<String, String> step3 = input -> chatModel.generate("Step 3: " + input);

String result = step3.execute(step2.execute(step1.execute(originalInput)));

After:

interface Pipeline {
    @UserMessage("Step 1: {{input}}")
    String step1(@V("input") String input);

    @UserMessage("Step 2: {{input}}")
    String step2(@V("input") String input);

    @UserMessage("Step 3: {{input}}")
    String step3(@V("input") String input);
}

Pipeline pipeline = AiServices.create(Pipeline.class, chatModel);
String result = pipeline.step3(pipeline.step2(pipeline.step1(originalInput)));

Chain with State to Service with Memory

Before:

class StatefulChain implements Chain<String, String> {
    private final List<String> history = new ArrayList<>();
    private final ChatModel chatModel;

    @Override
    public String execute(String input) {
        history.add(input);
        String context = String.join("\n", history);
        return chatModel.generate("Context: " + context + "\nQuery: " + input);
    }
}

After:

interface Assistant {
    String chat(String message);
}

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .chatMemory(MessageWindowChatMemory.withMaxMessages(100))
    .build();

// Memory handled automatically!
String response = assistant.chat(message);

Why Chains are Deprecated

Chains were the original API for building LLM-powered applications in LangChain4j. However, AiServices provides a superior approach:

Technical Limitations of Chains

  1. Type Rigidity: Limited to specific input/output types
  2. No Streaming: Cannot support token-by-token responses
  3. Manual Wiring: Requires explicit composition of chain steps
  4. State Management: No built-in support for per-user state
  5. Testing Complexity: Harder to mock and test
  6. Limited Observability: No access to intermediate results or metadata
  7. No Tool Integration: Cannot use function calling
  8. Prompt Management: String concatenation is error-prone
  9. Framework Integration: Difficult to integrate with Spring/Quarkus/etc.
  10. Scalability: Poor support for multi-user scenarios

Advantages of AiServices

  • More Flexible: Define your own interfaces instead of implementing Chain
  • Better Abstraction: Focus on what you want to do, not how to chain operations
  • Rich Features: Built-in support for tools, RAG, streaming, moderation, etc.
  • Type Safety: Return any type you need with automatic parsing
  • Less Boilerplate: No need to manually wire together chain steps
  • Modern Patterns: Leverages Java interfaces and annotations effectively
  • Framework Ready: Easy integration with Spring Boot, Quarkus, Micronaut
  • Production Grade: Better error handling, observability, and debugging
  • Future Proof: Active development and new features

Migration Timeline

  • Now: Start migrating to AiServices for all new features
  • Short Term: Plan migration of existing chain code
  • Medium Term: Complete migration to AiServices
  • Long Term: Chains will be removed in future major version

Testing Patterns

Testing Deprecated Chain Code (Before Migration)

@Test
void testConversationalChain() {
    // Use test double for ChatModel
    ChatModel mockModel = mock(ChatModel.class);
    when(mockModel.generate(anyString())).thenReturn("Mocked response");

    ConversationalChain chain = ConversationalChain.builder()
        .chatModel(mockModel)
        .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
        .build();

    String response = chain.execute("Hello");

    assertEquals("Mocked response", response);
    verify(mockModel, times(1)).generate(anyString());
}

Testing with AiServices (Recommended)

@Test
void testAiService() {
    ChatModel mockModel = mock(ChatModel.class);
    when(mockModel.generate(any())).thenReturn(Response.from(
        AiMessage.from("Mocked response")
    ));

    interface Assistant {
        String chat(String message);
    }

    Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(mockModel)
        .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
        .build();

    String response = assistant.chat("Hello");

    assertEquals("Mocked response", response);
    verify(mockModel, times(1)).generate(any());
}

Testing Benefits of AiServices:

  • Easier to mock interface methods
  • Better assertion capabilities with Result<T>
  • Can verify tool executions
  • Access to token usage for cost testing
  • Can test streaming responses separately

Performance Comparison

Chain Performance Characteristics

// Each execute() call:
// 1. String concatenation for prompt
// 2. Memory lookup (if ConversationalChain)
// 3. Retrieval (if ConversationalRetrievalChain)
// 4. ChatModel API call
// 5. String return

ConversationalChain chain = ConversationalChain.builder()
    .chatModel(chatModel)
    .build();

long start = System.currentTimeMillis();
String response = chain.execute("Query");
long duration = System.currentTimeMillis() - start;
// Duration depends primarily on ChatModel latency

AiServices Performance Characteristics

// Similar performance to chains but with benefits:
// - Type parsing adds minimal overhead
// - Streaming can improve perceived performance
// - Caching opportunities for prompts
// - Better connection pooling in some implementations

interface Assistant {
    String chat(String message);
}

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)
    .build();

long start = System.currentTimeMillis();
String response = assistant.chat("Query");
long duration = System.currentTimeMillis() - start;
// Similar duration to chain, but with more features

Performance Verdict:

  • Raw performance is comparable
  • AiServices add minimal overhead (typically <5ms)
  • Streaming support in AiServices can significantly improve UX
  • Better resource management in AiServices implementations
  • Type parsing overhead is negligible for most use cases

Error Handling Comparison

Chain Error Handling

try {
    ConversationalChain chain = ConversationalChain.builder()
        .chatModel(chatModel)
        .build();

    String response = chain.execute("Query");
} catch (RuntimeException e) {
    // Must catch generic RuntimeException
    // No way to distinguish error types
    // No access to partial results
    // Memory state may be corrupted
}

AiServices Error Handling

try {
    interface Assistant {
        String chat(String message);
    }

    Assistant assistant = AiServices.builder(Assistant.class)
        .chatModel(chatModel)
        .build();

    String response = assistant.chat("Query");
} catch (Exception e) {
    // Can catch specific exceptions:
    // - ToolExecutionException
    // - ModerationException
    // - etc.
    // Better error context available
}

Related APIs

For Migration

  • AiServices - Primary replacement for all chain functionality
  • ChatMemory - Conversation state management (compatible with both)
  • ChatModel - Underlying model interface (compatible with both)
  • ContentRetriever - RAG retrieval (compatible with both)
  • RetrievalAugmentor - Advanced RAG (compatible with both)

New Capabilities in AiServices

  • @SystemMessage - System prompt annotation
  • @UserMessage - User message template annotation
  • @Tool - Function calling integration
  • TokenStream - Streaming response support
  • Result<T> - Rich result with metadata
  • @MemoryId - Per-user memory management
  • @Moderate - Content moderation

Migration Support

If you need help migrating from Chains to AiServices:

  1. Review Examples: See migration examples above
  2. Check Documentation: Refer to AiServices documentation
  3. Community Support: Ask in GitHub Discussions
  4. Professional Services: Contact Anthropic for migration assistance

For all new development, use AiServices instead of Chains. The migration effort is minimal and the benefits are substantial.

Install with Tessl CLI

npx tessl i tessl/maven-dev-langchain4j--langchain4j

docs

ai-services.md

chains.md

classification.md

data-types.md

document-processing.md

embedding-store.md

guardrails.md

index.md

memory.md

messages.md

models.md

output-parsing.md

prompts.md

rag.md

request-response.md

spi.md

tools.md

README.md

tile.json