CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-langchain4j--langchain4j-hugging-face

LangChain4j integration library for Hugging Face inference capabilities including chat, language, and embedding models

Overview
Eval results
Files

spi-extensions.mddocs/

SPI Extensions

Service Provider Interface (SPI) for customizing model builders and client implementations.

Overview

SPI provides extension points through Java's ServiceLoader mechanism, allowing custom implementations of:

  • Model builders (with custom defaults or logic)
  • HTTP clients (for testing, custom auth, logging, etc.)

Package: dev.langchain4j.model.huggingface.spi

SPI Interfaces

HuggingFaceEmbeddingModelBuilderFactory

package dev.langchain4j.model.huggingface.spi;

public interface HuggingFaceEmbeddingModelBuilderFactory
    extends java.util.function.Supplier<HuggingFaceEmbeddingModel.HuggingFaceEmbeddingModelBuilder> {

    HuggingFaceEmbeddingModel.HuggingFaceEmbeddingModelBuilder get();
}

Purpose: Provide custom embedding model builders with pre-configured defaults.

HuggingFaceChatModelBuilderFactory (Deprecated)

@Deprecated(forRemoval = true, since = "1.7.0-beta13")
public interface HuggingFaceChatModelBuilderFactory
    extends java.util.function.Supplier<HuggingFaceChatModel.Builder> {

    HuggingFaceChatModel.Builder get();
}

HuggingFaceLanguageModelBuilderFactory (Deprecated)

@Deprecated(forRemoval = true, since = "1.7.0-beta13")
public interface HuggingFaceLanguageModelBuilderFactory
    extends java.util.function.Supplier<HuggingFaceLanguageModel.Builder> {

    HuggingFaceLanguageModel.Builder get();
}

HuggingFaceClientFactory

public interface HuggingFaceClientFactory {
    HuggingFaceClient create(Input input);

    interface Input {
        default String baseUrl() { return null; }
        String apiKey();
        String modelId();
        java.time.Duration timeout();
    }
}

Purpose: Provide custom HTTP client implementations.

Use Cases

1. Custom Builder Defaults

package com.example.custom;

import dev.langchain4j.model.huggingface.HuggingFaceEmbeddingModel;
import dev.langchain4j.model.huggingface.spi.HuggingFaceEmbeddingModelBuilderFactory;
import java.time.Duration;

public class CustomEmbeddingBuilderFactory
    implements HuggingFaceEmbeddingModelBuilderFactory {

    @Override
    public HuggingFaceEmbeddingModel.HuggingFaceEmbeddingModelBuilder get() {
        return new CustomBuilder();
    }

    static class CustomBuilder
        extends HuggingFaceEmbeddingModel.HuggingFaceEmbeddingModelBuilder {

        public CustomBuilder() {
            super();
            // Set custom defaults
            this.timeout(Duration.ofSeconds(60));
            this.waitForModel(true);
            this.modelId("sentence-transformers/all-MiniLM-L6-v2");
        }
    }
}

2. Mock Client for Testing

package com.example.test;

import dev.langchain4j.model.huggingface.client.*;
import dev.langchain4j.model.huggingface.spi.HuggingFaceClientFactory;
import java.util.List;
import java.util.stream.Collectors;

public class MockClientFactory implements HuggingFaceClientFactory {

    @Override
    public HuggingFaceClient create(Input input) {
        return new MockClient();
    }

    static class MockClient implements HuggingFaceClient {

        @Override
        public TextGenerationResponse chat(TextGenerationRequest request) {
            return new TextGenerationResponse("Mock chat response");
        }

        @Override
        public TextGenerationResponse generate(TextGenerationRequest request) {
            return new TextGenerationResponse("Mock generated text");
        }

        @Override
        public List<float[]> embed(EmbeddingRequest request) {
            return request.getInputs().stream()
                .map(text -> new float[384]) // Mock 384-dim vectors
                .collect(Collectors.toList());
        }
    }
}

3. Logging Client

package com.example.logging;

import dev.langchain4j.model.huggingface.client.*;
import dev.langchain4j.model.huggingface.spi.HuggingFaceClientFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggingClientFactory implements HuggingFaceClientFactory {

    @Override
    public HuggingFaceClient create(Input input) {
        HuggingFaceClient delegate = createDefaultClient(input);
        return new LoggingClient(delegate);
    }

    static class LoggingClient implements HuggingFaceClient {
        private static final Logger log = LoggerFactory.getLogger(LoggingClient.class);
        private final HuggingFaceClient delegate;

        LoggingClient(HuggingFaceClient delegate) {
            this.delegate = delegate;
        }

        @Override
        public TextGenerationResponse chat(TextGenerationRequest request) {
            log.info("Chat request: {}", request.getInputs());
            try {
                TextGenerationResponse response = delegate.chat(request);
                log.info("Chat response: {}", response.getGeneratedText());
                return response;
            } catch (Exception e) {
                log.error("Chat failed", e);
                throw e;
            }
        }

        @Override
        public TextGenerationResponse generate(TextGenerationRequest request) {
            log.info("Generate request: {}", request.getInputs());
            try {
                TextGenerationResponse response = delegate.generate(request);
                log.info("Generated: {}", response.getGeneratedText());
                return response;
            } catch (Exception e) {
                log.error("Generate failed", e);
                throw e;
            }
        }

        @Override
        public List<float[]> embed(EmbeddingRequest request) {
            log.info("Embed request: {} texts", request.getInputs().size());
            try {
                List<float[]> embeddings = delegate.embed(request);
                log.info("Generated {} embeddings", embeddings.size());
                return embeddings;
            } catch (Exception e) {
                log.error("Embed failed", e);
                throw e;
            }
        }
    }

    private static HuggingFaceClient createDefaultClient(Input input) {
        // Create default client implementation
        return new DefaultHuggingFaceClient(/* ... */);
    }
}

4. Retry Client

package com.example.retry;

import dev.langchain4j.model.huggingface.client.*;
import dev.langchain4j.model.huggingface.spi.HuggingFaceClientFactory;

public class RetryClientFactory implements HuggingFaceClientFactory {

    @Override
    public HuggingFaceClient create(Input input) {
        HuggingFaceClient delegate = createDefaultClient(input);
        return new RetryClient(delegate, 3);
    }

    static class RetryClient implements HuggingFaceClient {
        private final HuggingFaceClient delegate;
        private final int maxRetries;

        RetryClient(HuggingFaceClient delegate, int maxRetries) {
            this.delegate = delegate;
            this.maxRetries = maxRetries;
        }

        @Override
        public List<float[]> embed(EmbeddingRequest request) {
            int attempt = 0;
            while (attempt < maxRetries) {
                try {
                    return delegate.embed(request);
                } catch (RuntimeException e) {
                    attempt++;
                    if (attempt >= maxRetries ||
                        !isRetryable(e)) {
                        throw e;
                    }
                    sleep((long) Math.pow(2, attempt) * 1000);
                }
            }
            throw new RuntimeException("Max retries exceeded");
        }

        // Similar implementations for chat() and generate()

        private boolean isRetryable(RuntimeException e) {
            String msg = e.getMessage();
            return msg.contains("429") ||
                   msg.contains("503") ||
                   msg.toLowerCase().contains("timeout");
        }

        private void sleep(long ms) {
            try {
                Thread.sleep(ms);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

ServiceLoader Registration

To register SPI implementations, create files in META-INF/services/:

Directory Structure

src/main/resources/
└── META-INF/
    └── services/
        ├── dev.langchain4j.model.huggingface.spi.HuggingFaceClientFactory
        ├── dev.langchain4j.model.huggingface.spi.HuggingFaceEmbeddingModelBuilderFactory
        ├── dev.langchain4j.model.huggingface.spi.HuggingFaceChatModelBuilderFactory
        └── dev.langchain4j.model.huggingface.spi.HuggingFaceLanguageModelBuilderFactory

File Contents

Example: HuggingFaceClientFactory

com.example.custom.CustomClientFactory

Example: HuggingFaceEmbeddingModelBuilderFactory

com.example.custom.CustomEmbeddingBuilderFactory

Maven/Gradle

Ensure files are included in your JAR:

  • Maven: Files in src/main/resources/ are automatically included
  • Gradle: Same as Maven

Discovery Mechanism

When you call builder(), the library searches for SPI implementations:

// From HuggingFaceEmbeddingModel
public static HuggingFaceEmbeddingModelBuilder builder() {
    for (HuggingFaceEmbeddingModelBuilderFactory factory :
            loadFactories(HuggingFaceEmbeddingModelBuilderFactory.class)) {
        return factory.get(); // Use first found
    }
    return new HuggingFaceEmbeddingModelBuilder(); // Default fallback
}

Priority: First implementation found is used.

Testing SPI

Unit Test

@Test
public void testCustomBuilder() {
    // Assumes CustomBuilderFactory is registered
    HuggingFaceEmbeddingModel model = HuggingFaceEmbeddingModel.builder()
        .accessToken("test-key")
        .build();

    // Custom defaults should be applied
    assertNotNull(model);
}

Integration Test

@Test
public void testMockClient() {
    // Assumes MockClientFactory is registered
    HuggingFaceEmbeddingModel model = HuggingFaceEmbeddingModel.builder()
        .accessToken("test-key")
        .build();

    // Should use mock client
    Embedding emb = model.embed("test").content();
    assertNotNull(emb);
    assertEquals(384, emb.dimension());
}

Best Practices

  1. Single Responsibility: Each factory should have one clear purpose
  2. Documentation: Document custom behavior clearly
  3. Error Handling: Provide clear error messages
  4. Thread Safety: Ensure implementations are thread-safe
  5. Resource Management: Clean up resources (connections, threads)
  6. Testing: Thoroughly test custom implementations
  7. Versioning: Consider compatibility with library updates

Limitations

  • Only one implementation per interface is used (first discovered)
  • Discovery happens at runtime via ServiceLoader
  • No ordering guarantees without module system
  • Deprecated factories (Chat, Language) will be removed

Debugging

Verify Registration

import java.util.ServiceLoader;
import dev.langchain4j.model.huggingface.spi.HuggingFaceClientFactory;

ServiceLoader<HuggingFaceClientFactory> loader =
    ServiceLoader.load(HuggingFaceClientFactory.class);

for (HuggingFaceClientFactory factory : loader) {
    System.out.println("Found factory: " + factory.getClass().getName());
}

Check Classpath

jar tf your-library.jar | grep META-INF/services

Related Documentation

  • Embedding Model API - Model API
  • Client API - Client interfaces
  • Configuration Guide - Configuration options
  • Common Tasks - Usage examples

Install with Tessl CLI

npx tessl i tessl/maven-dev-langchain4j--langchain4j-hugging-face@1.11.0

docs

chat-model.md

client-api.md

common-tasks.md

configuration.md

embedding-model.md

error-handling.md

index.md

language-model.md

migration-guide.md

model-names.md

quick-start.md

spi-extensions.md

tile.json