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

error-handling.mddocs/reference/

Error Handling Reference

Comprehensive guide to handling errors in langchain4j-github-models.

Error Types

HTTP Response Exceptions

import com.azure.core.exception.HttpResponseException;

try {
    ChatResponse response = model.chat(request);
} catch (HttpResponseException e) {
    int statusCode = e.getResponse().getStatusCode();
    String message = e.getMessage();

    System.err.println("HTTP " + statusCode + ": " + message);
}

Common HTTP Status Codes

CodeMeaningHandling
400Bad RequestCheck request format, parameters
401UnauthorizedVerify GitHub token is valid
403ForbiddenCheck token permissions
404Not FoundVerify model name, endpoint
429Rate LimitImplement backoff, retry
500Server ErrorRetry with backoff
503Service UnavailableRetry with backoff

Authentication Errors (401, 403)

try {
    ChatResponse response = model.chat(request);
} catch (HttpResponseException e) {
    if (e.getResponse().getStatusCode() == 401) {
        System.err.println("Authentication failed. Check your GitHub token.");
        // Token is invalid or expired
    } else if (e.getResponse().getStatusCode() == 403) {
        System.err.println("Access forbidden. Token may lack required permissions.");
        // Token lacks necessary permissions
    }
}

Rate Limiting (429)

import java.util.concurrent.TimeUnit;

int maxAttempts = 5;
int attempt = 0;

while (attempt < maxAttempts) {
    try {
        ChatResponse response = model.chat(request);
        // Success
        break;
    } catch (HttpResponseException e) {
        if (e.getResponse().getStatusCode() == 429) {
            attempt++;
            if (attempt < maxAttempts) {
                long backoffMs = (long) Math.pow(2, attempt) * 1000;
                System.out.println("Rate limited. Retrying in " + backoffMs + "ms...");
                TimeUnit.MILLISECONDS.sleep(backoffMs);
            } else {
                System.err.println("Rate limit exceeded after " + maxAttempts + " attempts");
                throw e;
            }
        } else {
            throw e;
        }
    }
}

Server Errors (500, 503)

try {
    ChatResponse response = model.chat(request);
} catch (HttpResponseException e) {
    int status = e.getResponse().getStatusCode();

    if (status >= 500 && status < 600) {
        System.err.println("Server error. The service may be experiencing issues.");
        // Retry with exponential backoff
        // The model's maxRetries setting handles this automatically
    }
}

Content Filtering

Detecting Content Filter

Content filter violations return FinishReason.CONTENT_FILTER instead of throwing exceptions.

ChatResponse response = model.chat(request);

if (response.metadata().finishReason() == FinishReason.CONTENT_FILTER) {
    String filterMessage = response.aiMessage().text();
    System.out.println("Content was filtered: " + filterMessage);
    // Handle filtered content gracefully
}

Streaming Content Filter

model.chat(request, new StreamingChatResponseHandler() {
    @Override
    public void onPartialResponse(String token) {
        System.out.print(token);
    }

    @Override
    public void onCompleteResponse(ChatResponse response) {
        if (response.metadata().finishReason() == FinishReason.CONTENT_FILTER) {
            System.out.println("\n[Content was filtered by responsible AI policies]");
        }
    }

    @Override
    public void onError(Throwable error) {
        System.err.println("Error: " + error.getMessage());
    }
});

Handling Filtered Content

ChatResponse response = model.chat(request);

switch (response.metadata().finishReason()) {
    case STOP:
        // Normal completion
        return response.aiMessage().text();

    case CONTENT_FILTER:
        // Content filtered
        System.out.println("Content filter triggered");
        return "Response unavailable due to content policy";

    case LENGTH:
        // Stopped due to max_tokens
        System.out.println("Response truncated");
        return response.aiMessage().text() + "...";

    case TOOL_EXECUTION:
        // Stopped to execute tool
        // Process tool execution
        return processToolExecution(response);

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

Unsupported Features

UnsupportedFeatureException

Thrown when attempting to use features not supported by the model or API.

import dev.langchain4j.model.UnsupportedFeatureException;

try {
    // Trying to use REQUIRED with multiple tools (not supported)
    ChatResponse response = model.chat(request);
} catch (UnsupportedFeatureException e) {
    System.err.println("Unsupported feature: " + e.getMessage());
    // Adjust request to use supported features
}

Known Limitations

ToolChoice.REQUIRED with multiple tools:

// ❌ This throws UnsupportedFeatureException
ChatRequest request = ChatRequest.builder()
    .messages(UserMessage.from("Help me"))
    .parameters(ChatRequestParameters.builder()
        .toolSpecifications(tool1, tool2, tool3)
        .toolChoice(ToolChoice.REQUIRED)  // Only supports single tool
        .build())
    .build();

// ✅ Use with single tool or AUTO
ChatRequest request = ChatRequest.builder()
    .messages(UserMessage.from("Help me"))
    .parameters(ChatRequestParameters.builder()
        .toolSpecifications(tool1)
        .toolChoice(ToolChoice.REQUIRED)  // OK with single tool
        .build())
    .build();

Timeout Errors

Detecting Timeouts

import java.net.SocketTimeoutException;
import java.util.concurrent.TimeoutException;

try {
    ChatResponse response = model.chat(request);
} catch (Exception e) {
    Throwable cause = e.getCause();

    if (cause instanceof SocketTimeoutException ||
        cause instanceof TimeoutException) {
        System.err.println("Request timed out");
        // Consider:
        // - Increasing timeout
        // - Using smaller model
        // - Reducing max_tokens
        // - Simplifying prompt
    }
}

Handling Timeouts

public ChatResponse chatWithTimeoutHandling(ChatRequest request) {
    try {
        return model.chat(request);
    } catch (Exception e) {
        if (isTimeoutError(e)) {
            System.err.println("Request timed out. Retrying with smaller max_tokens...");

            // Retry with more constrained parameters
            ChatRequest retryRequest = ChatRequest.builder()
                .messages(request.messages())
                .parameters(ChatRequestParameters.builder()
                    .maxTokens(500)  // Reduce from potentially larger value
                    .build())
                .build();

            return model.chat(retryRequest);
        }
        throw e;
    }
}

private boolean isTimeoutError(Exception e) {
    Throwable cause = e.getCause();
    return cause instanceof SocketTimeoutException ||
           cause instanceof TimeoutException;
}

Network Errors

Connection Errors

import java.net.ConnectException;
import java.net.UnknownHostException;

try {
    ChatResponse response = model.chat(request);
} catch (Exception e) {
    Throwable cause = e.getCause();

    if (cause instanceof ConnectException) {
        System.err.println("Connection failed. Check network connectivity.");
    } else if (cause instanceof UnknownHostException) {
        System.err.println("Host not found. Check endpoint configuration.");
    }
}

Proxy Errors

try {
    ChatResponse response = model.chat(request);
} catch (Exception e) {
    String message = e.getMessage();

    if (message != null && message.toLowerCase().contains("proxy")) {
        System.err.println("Proxy connection failed. Check proxy configuration.");
    }
}

Validation Errors

Missing Required Fields

try {
    GitHubModelsChatModel model = GitHubModelsChatModel.builder()
        // Missing gitHubToken
        .modelName("gpt-4o")
        .build();
} catch (IllegalStateException e) {
    System.err.println("Configuration error: " + e.getMessage());
    // Ensure all required fields are set
}

Invalid Configuration

try {
    GitHubModelsChatModel model = GitHubModelsChatModel.builder()
        .gitHubToken(token)
        .modelName("gpt-4o")
        .temperature(-1.0)  // Invalid: outside 0.0-2.0 range
        .build();
} catch (IllegalArgumentException e) {
    System.err.println("Invalid configuration: " + e.getMessage());
}

Embedding Model Errors

Empty Input

List<TextSegment> emptyList = new ArrayList<>();

Response<List<Embedding>> response = model.embedAll(emptyList);

System.out.println("Embeddings: " + response.content().size());  // 0
// Empty input returns empty result, not an error

Batch Processing Errors

List<TextSegment> segments = loadSegments();

try {
    Response<List<Embedding>> response = model.embedAll(segments);
} catch (HttpResponseException e) {
    System.err.println("Embedding failed: " + e.getMessage());

    // Retry with smaller batch if possible
    int halfSize = segments.size() / 2;
    List<TextSegment> firstHalf = segments.subList(0, halfSize);
    List<TextSegment> secondHalf = segments.subList(halfSize, segments.size());

    Response<List<Embedding>> response1 = model.embedAll(firstHalf);
    Response<List<Embedding>> response2 = model.embedAll(secondHalf);

    List<Embedding> allEmbeddings = new ArrayList<>();
    allEmbeddings.addAll(response1.content());
    allEmbeddings.addAll(response2.content());
}

Streaming Errors

Error Callback

model.chat(request, new StreamingChatResponseHandler() {
    @Override
    public void onPartialResponse(String token) {
        System.out.print(token);
    }

    @Override
    public void onCompleteResponse(ChatResponse response) {
        System.out.println("\nComplete");
    }

    @Override
    public void onError(Throwable error) {
        System.err.println("\nStreaming error: " + error.getMessage());

        if (error instanceof HttpResponseException) {
            HttpResponseException httpError = (HttpResponseException) error;
            System.err.println("Status: " + httpError.getResponse().getStatusCode());
        }

        // Handle error appropriately
        // Note: Error is delivered here, not thrown
    }
});

Graceful Degradation

AtomicReference<String> partialContent = new AtomicReference<>("");

model.chat(request, new StreamingChatResponseHandler() {
    @Override
    public void onPartialResponse(String token) {
        partialContent.updateAndGet(current -> current + token);
        System.out.print(token);
    }

    @Override
    public void onCompleteResponse(ChatResponse response) {
        System.out.println("\nComplete");
    }

    @Override
    public void onError(Throwable error) {
        String partial = partialContent.get();
        if (!partial.isEmpty()) {
            System.err.println("\nError occurred, but got partial response:");
            System.out.println(partial);
            // Use partial response if acceptable
        } else {
            System.err.println("\nError with no partial response");
            // Fallback strategy
        }
    }
});

Comprehensive Error Handler

Reusable Error Handler

public class ModelErrorHandler {

    public static void handleChatError(Exception e, ChatRequest request) {
        if (e instanceof HttpResponseException) {
            HttpResponseException httpError = (HttpResponseException) e;
            int status = httpError.getResponse().getStatusCode();

            switch (status) {
                case 400:
                    System.err.println("Bad request. Check request format.");
                    break;
                case 401:
                    System.err.println("Unauthorized. Check GitHub token.");
                    break;
                case 403:
                    System.err.println("Forbidden. Check token permissions.");
                    break;
                case 404:
                    System.err.println("Not found. Check model name.");
                    break;
                case 429:
                    System.err.println("Rate limited. Implement backoff.");
                    break;
                case 500:
                case 503:
                    System.err.println("Server error. Retry with backoff.");
                    break;
                default:
                    System.err.println("HTTP " + status + ": " + httpError.getMessage());
            }
        } else if (e instanceof UnsupportedFeatureException) {
            System.err.println("Unsupported feature: " + e.getMessage());
        } else if (isTimeoutError(e)) {
            System.err.println("Request timed out.");
        } else if (isNetworkError(e)) {
            System.err.println("Network error: " + e.getMessage());
        } else {
            System.err.println("Unexpected error: " + e.getMessage());
            e.printStackTrace();
        }
    }

    private static boolean isTimeoutError(Exception e) {
        Throwable cause = e.getCause();
        return cause instanceof SocketTimeoutException ||
               cause instanceof TimeoutException;
    }

    private static boolean isNetworkError(Exception e) {
        Throwable cause = e.getCause();
        return cause instanceof ConnectException ||
               cause instanceof UnknownHostException;
    }
}

// Usage
try {
    ChatResponse response = model.chat(request);
} catch (Exception e) {
    ModelErrorHandler.handleChatError(e, request);
}

Best Practices

Always Handle Errors

// ✅ Good - proper error handling
try {
    ChatResponse response = model.chat(request);
    return response.aiMessage().text();
} catch (HttpResponseException e) {
    System.err.println("API error: " + e.getMessage());
    return "Sorry, I encountered an error.";
}

// ❌ Bad - ignoring errors
ChatResponse response = model.chat(request);
return response.aiMessage().text();

Check Finish Reasons

// ✅ Good - check finish reason
ChatResponse response = model.chat(request);
if (response.metadata().finishReason() == FinishReason.LENGTH) {
    System.out.println("Warning: Response was truncated");
}
return response.aiMessage().text();

Log Errors Appropriately

// ✅ Good - structured logging
try {
    ChatResponse response = model.chat(request);
} catch (HttpResponseException e) {
    logger.error("Chat API error: status={}, message={}",
        e.getResponse().getStatusCode(),
        e.getMessage());
}

Implement Retry Logic

// ✅ Good - retry transient errors
int maxRetries = 3;
for (int i = 0; i < maxRetries; i++) {
    try {
        return model.chat(request);
    } catch (HttpResponseException e) {
        if (i == maxRetries - 1 || !isRetryable(e)) {
            throw e;
        }
        Thread.sleep(1000 * (i + 1));
    }
}

See Also

  • Builder Configuration
  • Network Configuration
  • Chat Model API
  • Best Practices

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