CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-langchain4j--langchain4j-github-models

This package provides a deprecated integration module that enables Java applications to interact with GitHub Models through the LangChain4j framework. It offers chat models (both synchronous and streaming), embedding models, and support for AI services with tool integration, JSON schema responses, and responsible AI features. The module wraps Azure AI Inference SDK to provide a unified API for accessing various language models hosted on GitHub Models, including chat completion capabilities, embeddings generation, and content filtering management. As of version 1.10.0, this module has been marked for deprecation and future removal, with users recommended to migrate to the langchain4j-openai-official module for enhanced functionality and better integration. The library is designed for reusability as a foundational component in LLM-powered Java applications that need to leverage GitHub-hosted AI models, offering builder patterns for configuration, support for proxy options, custom timeouts, and comprehensive model service versioning capabilities.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

spi-factories-guide.mddocs/guides/

SPI Factories Usage Guide

Practical guide for implementing custom builder factories using Java Service Provider Interface.

What are SPI Factories?

SPI factories allow you to customize how model builders are created without modifying library code. When you call Model.builder(), the library uses Java's ServiceLoader to find your custom factory implementation.

Use Cases

  • Dependency Injection: Integrate with Spring, Guice, CDI
  • Configuration Management: Centralize configuration across application
  • Testing: Provide test-specific configurations or mocks
  • Environment-Specific Setup: Different configs for dev/staging/production

Basic Implementation

Step 1: Create Factory Class

package com.example.config;

import dev.langchain4j.model.github.GitHubModelsChatModel;
import dev.langchain4j.model.github.spi.GitHubModelsChatModelBuilderFactory;

public class CustomChatModelBuilderFactory implements GitHubModelsChatModelBuilderFactory {

    @Override
    public GitHubModelsChatModel.Builder get() {
        return GitHubModelsChatModel.builder()
            .gitHubToken(System.getenv("GITHUB_TOKEN"))
            .timeout(Duration.ofSeconds(60))
            .maxRetries(3);
    }
}

Step 2: Register via SPI

Create file: src/main/resources/META-INF/services/dev.langchain4j.model.github.spi.GitHubModelsChatModelBuilderFactory

Content:

com.example.config.CustomChatModelBuilderFactory

Step 3: Use in Application

// Factory automatically discovered and used
GitHubModelsChatModel model = GitHubModelsChatModel.builder()
    .modelName("gpt-4o")  // Additional configuration
    .temperature(0.7)
    .build();

// The builder already has token, timeout, and retries pre-configured

Spring Integration

Spring Factory Implementation

package com.example.spring;

import dev.langchain4j.model.github.GitHubModelsChatModel;
import dev.langchain4j.model.github.spi.GitHubModelsChatModelBuilderFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class SpringChatModelBuilderFactory implements GitHubModelsChatModelBuilderFactory {

    @Value("${github.models.token}")
    private String token;

    @Value("${github.models.endpoint:https://models.inference.ai.azure.com}")
    private String endpoint;

    @Value("${github.models.timeout:60}")
    private int timeoutSeconds;

    @Value("${github.models.max-retries:3}")
    private int maxRetries;

    @Value("${github.models.log-requests:false}")
    private boolean logRequests;

    @Override
    public GitHubModelsChatModel.Builder get() {
        return GitHubModelsChatModel.builder()
            .gitHubToken(token)
            .endpoint(endpoint)
            .timeout(Duration.ofSeconds(timeoutSeconds))
            .maxRetries(maxRetries)
            .logRequestsAndResponses(logRequests);
    }
}

application.properties

github.models.token=${GITHUB_TOKEN}
github.models.endpoint=https://models.inference.ai.azure.com
github.models.timeout=60
github.models.max-retries=3
github.models.log-requests=false

Usage in Spring Application

@Service
public class ChatService {

    public String chat(String message) {
        // Factory provides pre-configured builder from Spring properties
        GitHubModelsChatModel model = GitHubModelsChatModel.builder()
            .modelName("gpt-4o")
            .build();

        ChatResponse response = model.chat(ChatRequest.builder()
            .messages(UserMessage.from(message))
            .build());

        return response.aiMessage().text();
    }
}

Environment-Specific Configuration

Multi-Environment Factory

package com.example.env;

import dev.langchain4j.model.github.GitHubModelsChatModel;
import dev.langchain4j.model.github.spi.GitHubModelsChatModelBuilderFactory;
import java.time.Duration;

public class EnvironmentAwareChatModelBuilderFactory implements GitHubModelsChatModelBuilderFactory {

    private enum Environment {
        DEV, STAGING, PRODUCTION
    }

    private static final Environment ENV = detectEnvironment();

    @Override
    public GitHubModelsChatModel.Builder get() {
        String token = getTokenForEnvironment();

        GitHubModelsChatModel.Builder builder = GitHubModelsChatModel.builder()
            .gitHubToken(token);

        switch (ENV) {
            case DEV:
                return builder
                    .logRequestsAndResponses(true)
                    .timeout(Duration.ofMinutes(5));  // Longer for debugging

            case STAGING:
                return builder
                    .maxRetries(3)
                    .timeout(Duration.ofSeconds(60))
                    .logRequestsAndResponses(false);

            case PRODUCTION:
                return builder
                    .maxRetries(5)
                    .timeout(Duration.ofSeconds(30))
                    .logRequestsAndResponses(false);

            default:
                throw new IllegalStateException("Unknown environment");
        }
    }

    private static Environment detectEnvironment() {
        String env = System.getenv("APP_ENV");
        if (env == null) {
            return Environment.DEV;
        }
        try {
            return Environment.valueOf(env.toUpperCase());
        } catch (IllegalArgumentException e) {
            return Environment.DEV;
        }
    }

    private static String getTokenForEnvironment() {
        String envToken = System.getenv("GITHUB_TOKEN_" + ENV.name());
        if (envToken != null) {
            return envToken;
        }
        return System.getenv("GITHUB_TOKEN");
    }
}

Testing with SPI Factories

Test Factory with Mock Client

package com.example.test;

import dev.langchain4j.model.github.GitHubModelsChatModel;
import dev.langchain4j.model.github.spi.GitHubModelsChatModelBuilderFactory;
import com.azure.ai.inference.ChatCompletionsClient;

public class TestChatModelBuilderFactory implements GitHubModelsChatModelBuilderFactory {

    private static ChatCompletionsClient mockClient;

    public static void setMockClient(ChatCompletionsClient client) {
        mockClient = client;
    }

    public static void clearMockClient() {
        mockClient = null;
    }

    @Override
    public GitHubModelsChatModel.Builder get() {
        if (mockClient != null) {
            return GitHubModelsChatModel.builder()
                .chatCompletionsClient(mockClient)
                .modelName("test-model");
        }

        // Fallback to test token
        return GitHubModelsChatModel.builder()
            .gitHubToken("test-token-12345")
            .modelName("test-model")
            .timeout(Duration.ofSeconds(5));
    }
}

Usage in Tests

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;

public class ChatServiceTest {

    private ChatCompletionsClient mockClient;

    @BeforeEach
    public void setup() {
        mockClient = mock(ChatCompletionsClient.class);
        TestChatModelBuilderFactory.setMockClient(mockClient);
    }

    @AfterEach
    public void teardown() {
        TestChatModelBuilderFactory.clearMockClient();
    }

    @Test
    public void testChat() {
        // Model will use mock client through SPI
        GitHubModelsChatModel model = GitHubModelsChatModel.builder().build();

        // Configure mock behavior
        when(mockClient.complete(any())).thenReturn(/* mock response */);

        // Test your code
        ChatResponse response = model.chat(request);

        // Verify
        verify(mockClient).complete(any());
    }
}

All Three Factory Types

Complete Implementation

package com.example.factories;

import dev.langchain4j.model.github.*;
import dev.langchain4j.model.github.spi.*;
import java.time.Duration;

// Chat Model Factory
public class CustomChatModelBuilderFactory implements GitHubModelsChatModelBuilderFactory {
    @Override
    public GitHubModelsChatModel.Builder get() {
        return GitHubModelsChatModel.builder()
            .gitHubToken(Config.getToken())
            .timeout(Duration.ofSeconds(60))
            .maxRetries(3);
    }
}

// Streaming Chat Model Factory
public class CustomStreamingChatModelBuilderFactory implements GitHubModelsStreamingChatModelBuilderFactory {
    @Override
    public GitHubModelsStreamingChatModel.Builder get() {
        return GitHubModelsStreamingChatModel.builder()
            .gitHubToken(Config.getToken())
            .timeout(Duration.ofSeconds(90))
            .maxRetries(3);
    }
}

// Embedding Model Factory
public class CustomEmbeddingModelBuilderFactory implements GitHubModelsEmbeddingModelBuilderFactory {
    @Override
    public GitHubModelsEmbeddingModel.Builder get() {
        return GitHubModelsEmbeddingModel.builder()
            .gitHubToken(Config.getToken())
            .timeout(Duration.ofSeconds(30))
            .maxRetries(5);
    }
}

Register All Three

Create three SPI files:

META-INF/services/dev.langchain4j.model.github.spi.GitHubModelsChatModelBuilderFactory:

com.example.factories.CustomChatModelBuilderFactory

META-INF/services/dev.langchain4j.model.github.spi.GitHubModelsStreamingChatModelBuilderFactory:

com.example.factories.CustomStreamingChatModelBuilderFactory

META-INF/services/dev.langchain4j.model.github.spi.GitHubModelsEmbeddingModelBuilderFactory:

com.example.factories.CustomEmbeddingModelBuilderFactory

Advanced Patterns

Configuration from External Source

public class VaultConfiguredBuilderFactory implements GitHubModelsChatModelBuilderFactory {

    private static final SecretVault vault = initializeVault();

    @Override
    public GitHubModelsChatModel.Builder get() {
        String token = vault.getSecret("github-token");
        String endpoint = vault.getSecret("github-endpoint");

        return GitHubModelsChatModel.builder()
            .gitHubToken(token)
            .endpoint(endpoint)
            .timeout(Duration.ofSeconds(60));
    }

    private static SecretVault initializeVault() {
        // Initialize connection to HashiCorp Vault, AWS Secrets Manager, etc.
        return new SecretVault();
    }
}

Monitoring and Observability

public class ObservableBuilderFactory implements GitHubModelsChatModelBuilderFactory {

    private static final MetricsRegistry metrics = new MetricsRegistry();
    private static final LoggingListener loggingListener = new LoggingListener();

    @Override
    public GitHubModelsChatModel.Builder get() {
        return GitHubModelsChatModel.builder()
            .gitHubToken(getToken())
            .listeners(Arrays.asList(
                new MetricsCollectingListener(metrics),
                loggingListener,
                new TracingListener()
            ))
            .userAgentSuffix("my-app/1.0");
    }
}

Conditional Configuration

public class ConditionalBuilderFactory implements GitHubModelsChatModelBuilderFactory {

    @Override
    public GitHubModelsChatModel.Builder get() {
        GitHubModelsChatModel.Builder builder = GitHubModelsChatModel.builder()
            .gitHubToken(getToken());

        // Enable logging in development
        if (isDevelopment()) {
            builder.logRequestsAndResponses(true);
        }

        // Use proxy in corporate network
        if (isCorporateNetwork()) {
            builder.proxyOptions(getCorporateProxy());
        }

        // Add custom headers for tracking
        if (hasTrackingEnabled()) {
            builder.customHeaders(getTrackingHeaders());
        }

        return builder;
    }

    private boolean isDevelopment() {
        return "dev".equals(System.getenv("APP_ENV"));
    }

    private boolean isCorporateNetwork() {
        // Detect corporate network
        return System.getProperty("http.proxyHost") != null;
    }

    private ProxyOptions getCorporateProxy() {
        String host = System.getProperty("http.proxyHost");
        int port = Integer.parseInt(System.getProperty("http.proxyPort", "8080"));
        return new ProxyOptions(ProxyOptions.Type.HTTP,
            new InetSocketAddress(host, port));
    }
}

Best Practices

Keep Factories Stateless

// ✅ Good - stateless
public class GoodFactory implements GitHubModelsChatModelBuilderFactory {
    @Override
    public GitHubModelsChatModel.Builder get() {
        return GitHubModelsChatModel.builder()
            .gitHubToken(loadToken());  // Load each time
    }
}

// ❌ Bad - stateful (not thread-safe)
public class BadFactory implements GitHubModelsChatModelBuilderFactory {
    private String token;  // Don't store state

    @Override
    public GitHubModelsChatModel.Builder get() {
        return GitHubModelsChatModel.builder()
            .gitHubToken(token);
    }
}

Validate Configuration

public class ValidatingFactory implements GitHubModelsChatModelBuilderFactory {
    @Override
    public GitHubModelsChatModel.Builder get() {
        String token = System.getenv("GITHUB_TOKEN");

        if (token == null || token.trim().isEmpty()) {
            throw new IllegalStateException(
                "GITHUB_TOKEN environment variable is required");
        }

        return GitHubModelsChatModel.builder()
            .gitHubToken(token);
    }
}

Provide Clear Error Messages

public class HelpfulFactory implements GitHubModelsChatModelBuilderFactory {
    @Override
    public GitHubModelsChatModel.Builder get() {
        String token = System.getenv("GITHUB_TOKEN");

        if (token == null) {
            throw new IllegalStateException(
                "Missing GITHUB_TOKEN environment variable. " +
                "Set it with: export GITHUB_TOKEN=your-token-here"
            );
        }

        return GitHubModelsChatModel.builder()
            .gitHubToken(token);
    }
}

Document Factory Behavior

/**
 * Custom factory that pre-configures ChatModel builders with:
 * <ul>
 *   <li>GitHub token from GITHUB_TOKEN environment variable</li>
 *   <li>60-second timeout</li>
 *   <li>3 retry attempts</li>
 *   <li>Request/response logging in development mode</li>
 * </ul>
 *
 * <p>Environment detection:
 * <ul>
 *   <li>Development: APP_ENV=dev or unset</li>
 *   <li>Production: APP_ENV=prod</li>
 * </ul>
 *
 * @throws IllegalStateException if GITHUB_TOKEN is not set
 */
public class DocumentedFactory implements GitHubModelsChatModelBuilderFactory {
    @Override
    public GitHubModelsChatModel.Builder get() {
        // Implementation
    }
}

Allow Configuration Override

// Factory provides sensible defaults
public class OverridableFactory implements GitHubModelsChatModelBuilderFactory {
    @Override
    public GitHubModelsChatModel.Builder get() {
        return GitHubModelsChatModel.builder()
            .gitHubToken(Config.getToken())
            .timeout(Duration.ofSeconds(60))
            .maxRetries(3);
    }
}

// Users can still override individual settings
GitHubModelsChatModel model = GitHubModelsChatModel.builder()
    .timeout(Duration.ofSeconds(120))  // Override factory default
    .modelName("gpt-4o")
    .build();

Troubleshooting

Factory Not Being Used

  1. Check SPI file path: META-INF/services/dev.langchain4j.model.github.spi.*
  2. Verify file contains correct fully-qualified class name
  3. Ensure factory class is on classpath
  4. Check factory class is public and has public no-arg constructor
  5. Verify factory package is not excluded from build

Multiple Factories

If multiple implementations exist, ServiceLoader returns the first found. Control order by:

  • Having only one implementation on classpath
  • Using module system to control visibility
  • Loading specific factory programmatically

Debugging

public class DebuggingFactory implements GitHubModelsChatModelBuilderFactory {
    @Override
    public GitHubModelsChatModel.Builder get() {
        System.err.println("DebuggingFactory.get() called");
        System.err.println("Token available: " + (System.getenv("GITHUB_TOKEN") != null));

        return GitHubModelsChatModel.builder()
            .gitHubToken(System.getenv("GITHUB_TOKEN"));
    }
}

See Also

  • Chat Model API
  • Streaming Chat Model API
  • Embedding Model API
  • Configuration Guide

Install with Tessl CLI

npx tessl i tessl/maven-dev-langchain4j--langchain4j-github-models@1.11.0

docs

index.md

quick-reference.md

tile.json