Java SDK

Official Java SDK for Kai the AI

Published

November 20, 2025

Overview

The official Java SDK for Kai the AI provides a robust, enterprise-ready interface for integrating intelligent teaching assistance into your Java applications. Built with Java 11+ features, the SDK supports both synchronous and asynchronous operations, comprehensive error handling, thread safety, and follows modern Java best practices.

Installation

Add the SDK to your project using Maven or Gradle:

<dependency>
    <groupId>com.chi2labs</groupId>
    <artifactId>kai-sdk</artifactId>
    <version>1.0.0</version>
</dependency>
dependencies {
    implementation 'com.chi2labs:kai-sdk:1.0.0'
}
dependencies {
    implementation("com.chi2labs:kai-sdk:1.0.0")
}

Requirements

  • Java 11 or higher
  • Active Kai the AI API key
  • Internet connection for API requests

Quick Start

Here’s a minimal example to get you started:

import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.GradeResult;

public class QuickStart {
    public static void main(String[] args) {
        // Initialize the client
        KaiClient client = new KaiClient("your_api_key_here");

        // Grade a student submission
        GradeResult result = client.assignments().grade()
            .assignmentId("CS101-HW1")
            .studentSubmission("The sorting algorithm works by...")
            .gradingStyle("detailed")
            .execute();

        System.out.printf("Score: %d/100%n", result.getScore());
        System.out.printf("Feedback: %s%n", result.getFeedback());
    }
}

Authentication

API Key Setup

The SDK requires an API key for authentication. You can provide the key in several ways:

export KAI_API_KEY="your_api_key_here"
import com.chi2labs.kai.KaiClient;

// Automatically reads from KAI_API_KEY environment variable
KaiClient client = new KaiClient();
import com.chi2labs.kai.KaiClient;

KaiClient client = new KaiClient("your_api_key_here");
import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.config.KaiConfig;

KaiConfig config = KaiConfig.builder()
    .apiKey("your_api_key_here")
    .baseUrl("https://chi2api.com/v1")
    .timeout(30000)
    .build();

KaiClient client = new KaiClient(config);
# kai.properties
kai.api.key=your_api_key_here
kai.api.baseUrl=https://chi2api.com/v1
kai.api.timeout=30000
import com.chi2labs.kai.KaiClient;
import java.util.Properties;
import java.io.FileInputStream;

Properties props = new Properties();
props.load(new FileInputStream("kai.properties"));

KaiClient client = new KaiClient(props.getProperty("kai.api.key"));
WarningSecurity Best Practice

Never hardcode API keys in your source code or commit them to version control. Use environment variables or secure configuration management systems like AWS Secrets Manager or HashiCorp Vault.

Core Classes

KaiClient

The main client class for interacting with Kai’s API.

public class KaiClient implements AutoCloseable {
    /**
     * Create a new KaiClient with the specified API key.
     *
     * @param apiKey Your API key (or null to read from KAI_API_KEY env variable)
     */
    public KaiClient(String apiKey) { }

    /**
     * Create a new KaiClient with custom configuration.
     *
     * @param config Configuration object
     */
    public KaiClient(KaiConfig config) { }

    /**
     * Access assignment grading operations.
     *
     * @return AssignmentOperations instance
     */
    public AssignmentOperations assignments() { }

    /**
     * Access content generation operations.
     *
     * @return ContentOperations instance
     */
    public ContentOperations content() { }

    /**
     * Access student analytics operations.
     *
     * @return AnalyticsOperations instance
     */
    public AnalyticsOperations analytics() { }

    /**
     * Access quiz generation operations.
     *
     * @return QuizOperations instance
     */
    public QuizOperations quizzes() { }

    @Override
    public void close() { }
}

Usage with try-with-resources:

try (KaiClient client = new KaiClient()) {
    GradeResult result = client.assignments().grade()
        .assignmentId("CS101-HW1")
        .studentSubmission("Student work...")
        .execute();

    System.out.println("Score: " + result.getScore());
}

AssignmentOperations

Handle assignment grading and feedback operations.

public interface AssignmentOperations {
    /**
     * Create a new grade request builder.
     *
     * @return GradeRequestBuilder for fluent API
     */
    GradeRequestBuilder grade();

    /**
     * Retrieve a previously generated grade.
     *
     * @param gradeId The grade identifier
     * @return GradeResult object
     * @throws KaiException if grade not found or API error
     */
    GradeResult getGrade(String gradeId) throws KaiException;

    /**
     * Grade multiple submissions in a single request.
     *
     * @param submissions List of submission data
     * @return List of GradeResult objects
     * @throws KaiException if batch grading fails
     */
    List<GradeResult> batchGrade(List<SubmissionData> submissions)
        throws KaiException;
}

GradeRequestBuilder

Fluent API for building grade requests:

public interface GradeRequestBuilder {
    GradeRequestBuilder assignmentId(String assignmentId);
    GradeRequestBuilder studentSubmission(String submission);
    GradeRequestBuilder rubricId(String rubricId);
    GradeRequestBuilder gradingStyle(String style);
    GradeResult execute() throws KaiException;
    CompletableFuture<GradeResult> executeAsync();
}

QuizOperations

Generate and manage quiz content.

public interface QuizOperations {
    /**
     * Create a new quiz generation request builder.
     *
     * @return QuizRequestBuilder for fluent API
     */
    QuizRequestBuilder generate();

    /**
     * Retrieve a previously generated quiz.
     *
     * @param quizId The quiz identifier
     * @return QuizResult object
     * @throws KaiException if quiz not found or API error
     */
    QuizResult getQuiz(String quizId) throws KaiException;
}

public interface QuizRequestBuilder {
    QuizRequestBuilder topic(String topic);
    QuizRequestBuilder difficulty(String difficulty);
    QuizRequestBuilder questionCount(int count);
    QuizRequestBuilder questionTypes(String... types);
    QuizResult execute() throws KaiException;
    CompletableFuture<QuizResult> executeAsync();
}

AnalyticsOperations

Access student performance data and analytics.

public interface AnalyticsOperations {
    /**
     * Retrieve student performance metrics.
     *
     * @param studentId Student identifier
     * @return PerformanceData with metrics and trends
     * @throws KaiException if analytics retrieval fails
     */
    PerformanceData getStudentPerformance(String studentId)
        throws KaiException;

    /**
     * Retrieve student performance metrics with filters.
     *
     * @param request Performance request with filters
     * @return PerformanceData with metrics and trends
     * @throws KaiException if analytics retrieval fails
     */
    PerformanceData getStudentPerformance(PerformanceRequest request)
        throws KaiException;

    /**
     * Retrieve aggregate class-level analytics.
     *
     * @param courseId Course identifier
     * @return ClassAnalytics object
     * @throws KaiException if analytics retrieval fails
     */
    ClassAnalytics getClassAnalytics(String courseId)
        throws KaiException;
}

Response Models

GradeResult

public class GradeResult {
    private final String gradeId;
    private final int score;
    private final String feedback;
    private final List<String> suggestions;
    private final Instant timestamp;
    private final Map<String, Integer> rubricBreakdown;

    // Getters
    public String getGradeId() { return gradeId; }
    public int getScore() { return score; }
    public String getFeedback() { return feedback; }
    public List<String> getSuggestions() { return suggestions; }
    public Instant getTimestamp() { return timestamp; }
    public Map<String, Integer> getRubricBreakdown() { return rubricBreakdown; }

    /**
     * Check if grade meets passing threshold (>= 70).
     *
     * @return true if passing, false otherwise
     */
    public boolean isPassed() {
        return score >= 70;
    }

    @Override
    public String toString() {
        return String.format("GradeResult{gradeId='%s', score=%d, passed=%s}",
            gradeId, score, isPassed());
    }
}

QuizResult

public class QuizQuestion {
    private final String questionId;
    private final String questionText;
    private final String questionType;
    private final List<String> options;
    private final String correctAnswer;
    private final String explanation;

    // Getters omitted for brevity
}

public class QuizResult {
    private final String quizId;
    private final String topic;
    private final String difficulty;
    private final List<QuizQuestion> questions;
    private final int estimatedTimeMinutes;

    // Getters
    public String getQuizId() { return quizId; }
    public String getTopic() { return topic; }
    public String getDifficulty() { return difficulty; }
    public List<QuizQuestion> getQuestions() { return questions; }
    public int getEstimatedTimeMinutes() { return estimatedTimeMinutes; }

    /**
     * Get the total number of questions in the quiz.
     *
     * @return number of questions
     */
    public int getQuestionCount() {
        return questions.size();
    }
}

PerformanceData

public class PerformanceData {
    private final String studentId;
    private final double averageScore;
    private final int assignmentsCompleted;
    private final int assignmentsTotal;
    private final List<String> strengthAreas;
    private final List<String> improvementAreas;
    private final String trend;
    private final Instant lastUpdated;

    // Getters
    public String getStudentId() { return studentId; }
    public double getAverageScore() { return averageScore; }
    public int getAssignmentsCompleted() { return assignmentsCompleted; }
    public int getAssignmentsTotal() { return assignmentsTotal; }
    public List<String> getStrengthAreas() { return strengthAreas; }
    public List<String> getImprovementAreas() { return improvementAreas; }
    public String getTrend() { return trend; }
    public Instant getLastUpdated() { return lastUpdated; }

    /**
     * Calculate completion rate as a percentage.
     *
     * @return completion rate (0-100)
     */
    public double getCompletionRate() {
        return (double) assignmentsCompleted / assignmentsTotal * 100;
    }
}

Complete API Methods

Assignment Grading

import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.GradeResult;
import com.chi2labs.kai.exceptions.*;

public class GradingExample {
    public static void main(String[] args) {
        try (KaiClient client = new KaiClient()) {
            // Grade a single submission
            GradeResult result = client.assignments().grade()
                .assignmentId("CS101-HW1")
                .studentSubmission("My algorithm implementation...")
                .rubricId("standard_coding_rubric")
                .gradingStyle("detailed")
                .execute();

            System.out.printf("Grade ID: %s%n", result.getGradeId());
            System.out.printf("Score: %d/100%n", result.getScore());
            System.out.printf("Feedback: %s%n", result.getFeedback());

            if (result.getRubricBreakdown() != null) {
                System.out.println("\nRubric Breakdown:");
                result.getRubricBreakdown().forEach((criterion, points) ->
                    System.out.printf("  %s: %d%n", criterion, points)
                );
            }

            System.out.println("\nSuggestions:");
            result.getSuggestions().forEach(suggestion ->
                System.out.println("  - " + suggestion)
            );

        } catch (ValidationException e) {
            System.err.println("Invalid parameters: " + e.getMessage());
        } catch (RateLimitException e) {
            System.err.println("Rate limit exceeded: " + e.getMessage());
        } catch (KaiException e) {
            System.err.println("API error: " + e.getMessage());
        }
    }
}
import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.GradeResult;
import java.util.concurrent.CompletableFuture;

public class AsyncGradingExample {
    public static void main(String[] args) {
        try (KaiClient client = new KaiClient()) {
            CompletableFuture<GradeResult> future = client.assignments().grade()
                .assignmentId("CS101-HW1")
                .studentSubmission("My algorithm implementation...")
                .gradingStyle("detailed")
                .executeAsync();

            // Handle result asynchronously
            future.thenAccept(result -> {
                System.out.printf("Score: %d/100%n", result.getScore());
                System.out.println("Feedback: " + result.getFeedback());
            }).exceptionally(ex -> {
                System.err.println("Grading failed: " + ex.getMessage());
                return null;
            });

            // Wait for completion (in real app, do other work)
            future.join();
        }
    }
}
import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.GradeResult;
import java.util.concurrent.CompletableFuture;
import java.util.List;

public class ComposedAsyncExample {
    public static void main(String[] args) {
        try (KaiClient client = new KaiClient()) {
            List<String> submissions = List.of(
                "First submission...",
                "Second submission...",
                "Third submission..."
            );

            // Grade all submissions concurrently
            CompletableFuture<?>[] futures = submissions.stream()
                .map(submission -> client.assignments().grade()
                    .assignmentId("CS101-HW1")
                    .studentSubmission(submission)
                    .executeAsync()
                    .thenAccept(result ->
                        System.out.printf("Score: %d/100%n", result.getScore())
                    ))
                .toArray(CompletableFuture[]::new);

            // Wait for all to complete
            CompletableFuture.allOf(futures).join();
            System.out.println("All grading completed!");
        }
    }
}

Batch Grading

Grade multiple submissions efficiently:

import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.GradeResult;
import com.chi2labs.kai.models.SubmissionData;
import java.util.List;

public class BatchGradingExample {
    public static void main(String[] args) {
        try (KaiClient client = new KaiClient()) {
            List<SubmissionData> submissions = List.of(
                new SubmissionData("CS101-HW1", "student_001", "First student's work..."),
                new SubmissionData("CS101-HW1", "student_002", "Second student's work..."),
                new SubmissionData("CS101-HW1", "student_003", "Third student's work...")
            );

            List<GradeResult> results = client.assignments().batchGrade(submissions);

            results.forEach(result ->
                System.out.printf("Student: Score %d/100%n", result.getScore())
            );

            // Calculate class statistics
            double averageScore = results.stream()
                .mapToInt(GradeResult::getScore)
                .average()
                .orElse(0.0);

            System.out.printf("Class average: %.1f/100%n", averageScore);

        } catch (Exception e) {
            System.err.println("Batch grading failed: " + e.getMessage());
        }
    }
}

Quiz Generation

import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.QuizResult;
import com.chi2labs.kai.models.QuizQuestion;

public class QuizExample {
    public static void main(String[] args) {
        try (KaiClient client = new KaiClient()) {
            QuizResult quiz = client.quizzes().generate()
                .topic("Java Data Structures")
                .difficulty("medium")
                .questionCount(10)
                .questionTypes("multiple_choice", "true_false")
                .execute();

            System.out.printf("Quiz ID: %s%n", quiz.getQuizId());
            System.out.printf("Estimated time: %d minutes%n",
                quiz.getEstimatedTimeMinutes());
            System.out.printf("Questions: %d%n", quiz.getQuestionCount());

            int questionNum = 1;
            for (QuizQuestion question : quiz.getQuestions()) {
                System.out.printf("%nQuestion %d: %s%n",
                    questionNum++, question.getQuestionText());

                if (question.getOptions() != null) {
                    question.getOptions().forEach(opt ->
                        System.out.println("  " + opt)
                    );
                }
            }

        } catch (Exception e) {
            System.err.println("Quiz generation failed: " + e.getMessage());
        }
    }
}
import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.QuizResult;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class MultipleQuizzesExample {
    public static void main(String[] args) {
        try (KaiClient client = new KaiClient()) {
            List<String> topics = List.of(
                "Java Basics",
                "Object-Oriented Programming",
                "Data Structures"
            );

            // Generate multiple quizzes concurrently
            CompletableFuture<?>[] futures = topics.stream()
                .map(topic -> client.quizzes().generate()
                    .topic(topic)
                    .difficulty("medium")
                    .questionCount(5)
                    .executeAsync()
                    .thenAccept(quiz ->
                        System.out.printf("Generated quiz: %s (%d questions)%n",
                            quiz.getTopic(), quiz.getQuestionCount())
                    ))
                .toArray(CompletableFuture[]::new);

            CompletableFuture.allOf(futures).join();
            System.out.printf("Generated %d quizzes%n", topics.size());

        } catch (Exception e) {
            System.err.println("Quiz generation failed: " + e.getMessage());
        }
    }
}

Student Analytics

import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.PerformanceData;
import com.chi2labs.kai.models.PerformanceRequest;
import java.time.Instant;
import java.time.temporal.ChronoUnit;

public class AnalyticsExample {
    public static void main(String[] args) {
        try (KaiClient client = new KaiClient()) {
            // Get performance for the last 30 days
            Instant endDate = Instant.now();
            Instant startDate = endDate.minus(30, ChronoUnit.DAYS);

            PerformanceRequest request = new PerformanceRequest.Builder()
                .studentId("student_12345")
                .startDate(startDate)
                .endDate(endDate)
                .courseId("CS101")
                .build();

            PerformanceData performance = client.analytics()
                .getStudentPerformance(request);

            System.out.printf("Average Score: %.1f%n",
                performance.getAverageScore());
            System.out.printf("Completion Rate: %d/%d (%.1f%%)%n",
                performance.getAssignmentsCompleted(),
                performance.getAssignmentsTotal(),
                performance.getCompletionRate());
            System.out.printf("Trend: %s%n", performance.getTrend());

            System.out.println("\nStrengths:");
            performance.getStrengthAreas().forEach(area ->
                System.out.println("  ✓ " + area)
            );

            System.out.println("\nAreas for Improvement:");
            performance.getImprovementAreas().forEach(area ->
                System.out.println("  ! " + area)
            );

        } catch (Exception e) {
            System.err.println("Analytics retrieval failed: " + e.getMessage());
        }
    }
}

Error Handling

The SDK provides specific exception types for different error scenarios:

import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.GradeResult;
import com.chi2labs.kai.exceptions.*;

public class ErrorHandlingExample {
    public static void main(String[] args) {
        try (KaiClient client = new KaiClient()) {
            GradeResult result = client.assignments().grade()
                .assignmentId("CS101-HW1")
                .studentSubmission("Student work...")
                .gradingStyle("detailed")
                .execute();

            System.out.println("Score: " + result.getScore());

        } catch (ValidationException e) {
            // Handle invalid parameters
            System.err.println("Invalid input: " + e.getMessage());
            System.err.println("Details: " + e.getDetails());

        } catch (AuthenticationException e) {
            // Handle authentication failures
            System.err.println("Authentication failed: " + e.getMessage());
            System.err.println("Please check your API key");

        } catch (RateLimitException e) {
            // Handle rate limiting
            System.err.println("Rate limit exceeded: " + e.getMessage());
            System.err.printf("Retry after: %d seconds%n",
                e.getRetryAfterSeconds());

        } catch (TimeoutException e) {
            // Handle timeouts
            System.err.println("Request timed out: " + e.getMessage());
            System.err.println("Try increasing the timeout parameter");

        } catch (ApiException e) {
            // Handle general API errors
            System.err.println("API error: " + e.getMessage());
            System.err.println("Status code: " + e.getStatusCode());
            System.err.println("Error code: " + e.getErrorCode());

        } catch (KaiException e) {
            // Catch-all for SDK errors
            System.err.println("SDK error: " + e.getMessage());
        }
    }
}

Exception Hierarchy

KaiException (base)
├── ValidationException        // Invalid parameters
├── AuthenticationException    // Invalid API key
├── RateLimitException        // Rate limit exceeded
├── TimeoutException          // Request timeout
└── ApiException              // General API error
TipError Recovery Pattern

Implement exponential backoff for rate limit errors:

import com.chi2labs.kai.exceptions.RateLimitException;

public GradeResult gradeWithRetry(KaiClient client, String assignmentId,
                                   String submission) throws KaiException {
    int maxRetries = 3;
    int retryDelay = 1000; // milliseconds

    for (int attempt = 0; attempt < maxRetries; attempt++) {
        try {
            return client.assignments().grade()
                .assignmentId(assignmentId)
                .studentSubmission(submission)
                .execute();

        } catch (RateLimitException e) {
            if (attempt < maxRetries - 1) {
                int waitTime = retryDelay * (int) Math.pow(2, attempt);
                System.out.printf("Rate limited, waiting %dms...%n", waitTime);
                Thread.sleep(waitTime);
            } else {
                throw e;
            }
        }
    }
    throw new KaiException("Max retries exceeded");
}

Thread Safety

The KaiClient is thread-safe and can be shared across multiple threads:

import com.chi2labs.kai.KaiClient;
import java.util.concurrent.*;

public class ThreadSafetyExample {
    private static final KaiClient client = new KaiClient();

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(10);

        // Submit multiple grading tasks concurrently
        for (int i = 0; i < 100; i++) {
            final int index = i;
            executor.submit(() -> {
                try {
                    var result = client.assignments().grade()
                        .assignmentId("CS101-HW" + index)
                        .studentSubmission("Submission " + index)
                        .execute();

                    System.out.printf("Thread %s: Score %d%n",
                        Thread.currentThread().getName(),
                        result.getScore());

                } catch (Exception e) {
                    System.err.println("Error in thread: " + e.getMessage());
                }
            });
        }

        executor.shutdown();
        executor.awaitTermination(5, TimeUnit.MINUTES);
        client.close();
    }
}
NoteConnection Pooling

The SDK uses connection pooling by default for efficient resource usage. You can configure pool settings:

KaiConfig config = KaiConfig.builder()
    .apiKey("your_api_key")
    .maxConnections(50)
    .connectionTimeout(30000)
    .build();

KaiClient client = new KaiClient(config);

Configuration Options

Client Configuration

import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.config.KaiConfig;
import java.time.Duration;

// Method 1: Builder pattern
KaiConfig config = KaiConfig.builder()
    .apiKey("your_api_key")
    .baseUrl("https://chi2api.com/v1")
    .timeout(Duration.ofSeconds(30))
    .maxRetries(3)
    .maxConnections(20)
    .verifySSL(true)
    .userAgent("MyApp/1.0")
    .build();

KaiClient client = new KaiClient(config);

// Method 2: From properties file
KaiClient client = KaiClient.fromProperties("kai.properties");

// Method 3: From environment variables
KaiClient client = KaiClient.fromEnvironment();

Logging Configuration

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.chi2labs.kai.KaiClient;

public class LoggingExample {
    private static final Logger logger = LoggerFactory.getLogger(LoggingExample.class);

    public static void main(String[] args) {
        // The SDK uses SLF4J for logging
        // Configure your logging framework (Logback, Log4j2, etc.)

        try (KaiClient client = new KaiClient()) {
            logger.info("Initializing Kai client");

            var result = client.assignments().grade()
                .assignmentId("CS101-HW1")
                .studentSubmission("Student work...")
                .execute();

            logger.info("Grading completed: score={}", result.getScore());
        }
    }
}

Logback configuration (logback.xml):

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- Set SDK logging level -->
    <logger name="com.chi2labs.kai" level="DEBUG"/>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

Spring Framework Integration

Spring Boot Configuration

import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.config.KaiConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class KaiConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "kai")
    public KaiConfig kaiConfig() {
        return KaiConfig.builder().build();
    }

    @Bean
    public KaiClient kaiClient(KaiConfig config) {
        return new KaiClient(config);
    }
}

application.yml:

kai:
  api-key: ${KAI_API_KEY}
  base-url: https://chi2api.com/v1
  timeout: 30000
  max-retries: 3

Spring Service Example

import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.GradeResult;
import com.chi2labs.kai.exceptions.KaiException;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class GradingService {

    private final KaiClient kaiClient;

    @Autowired
    public GradingService(KaiClient kaiClient) {
        this.kaiClient = kaiClient;
    }

    public GradeResult gradeAssignment(String assignmentId, String submission) {
        try {
            return kaiClient.assignments().grade()
                .assignmentId(assignmentId)
                .studentSubmission(submission)
                .gradingStyle("detailed")
                .execute();
        } catch (KaiException e) {
            throw new RuntimeException("Grading failed", e);
        }
    }

    public CompletableFuture<GradeResult> gradeAssignmentAsync(
            String assignmentId, String submission) {
        return kaiClient.assignments().grade()
            .assignmentId(assignmentId)
            .studentSubmission(submission)
            .executeAsync();
    }
}

Spring REST Controller

import com.chi2labs.kai.models.GradeResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;

@RestController
@RequestMapping("/api/grading")
public class GradingController {

    private final GradingService gradingService;

    @Autowired
    public GradingController(GradingService gradingService) {
        this.gradingService = gradingService;
    }

    @PostMapping("/grade")
    public ResponseEntity<GradeResult> gradeSubmission(
            @RequestBody SubmissionRequest request) {
        GradeResult result = gradingService.gradeAssignment(
            request.getAssignmentId(),
            request.getSubmission()
        );
        return ResponseEntity.ok(result);
    }

    @PostMapping("/grade/async")
    public CompletableFuture<ResponseEntity<GradeResult>> gradeSubmissionAsync(
            @RequestBody SubmissionRequest request) {
        return gradingService.gradeAssignmentAsync(
            request.getAssignmentId(),
            request.getSubmission()
        ).thenApply(ResponseEntity::ok);
    }
}

Complete Working Examples

Example 1: Automated Grading Pipeline

import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.GradeResult;
import com.chi2labs.kai.models.SubmissionData;
import com.chi2labs.kai.exceptions.KaiException;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Automated grading pipeline for processing student submissions from CSV.
 */
public class AutomatedGradingPipeline {

    private final KaiClient client;

    public AutomatedGradingPipeline(KaiClient client) {
        this.client = client;
    }

    public static class Submission {
        private final String studentId;
        private final String assignmentId;
        private final String submissionText;

        public Submission(String studentId, String assignmentId, String submissionText) {
            this.studentId = studentId;
            this.assignmentId = assignmentId;
            this.submissionText = submissionText;
        }

        // Getters
        public String getStudentId() { return studentId; }
        public String getAssignmentId() { return assignmentId; }
        public String getSubmissionText() { return submissionText; }
    }

    public static class GradingRecord {
        private final String studentId;
        private final Integer score;
        private final String feedback;
        private final String suggestions;

        public GradingRecord(String studentId, Integer score,
                            String feedback, String suggestions) {
            this.studentId = studentId;
            this.score = score;
            this.feedback = feedback;
            this.suggestions = suggestions;
        }

        // Getters omitted for brevity
    }

    public List<Submission> loadSubmissions(Path csvPath) throws IOException {
        List<Submission> submissions = new ArrayList<>();

        try (BufferedReader reader = Files.newBufferedReader(csvPath)) {
            String line = reader.readLine(); // Skip header
            while ((line = reader.readLine()) != null) {
                String[] parts = line.split(",", 3);
                if (parts.length == 3) {
                    submissions.add(new Submission(
                        parts[0].trim(),
                        parts[1].trim(),
                        parts[2].trim()
                    ));
                }
            }
        }

        return submissions;
    }

    public List<GradingRecord> gradeSubmissions(List<Submission> submissions) {
        List<GradingRecord> records = new ArrayList<>();

        for (Submission submission : submissions) {
            try {
                GradeResult result = client.assignments().grade()
                    .assignmentId(submission.getAssignmentId())
                    .studentSubmission(submission.getSubmissionText())
                    .gradingStyle("detailed")
                    .execute();

                records.add(new GradingRecord(
                    submission.getStudentId(),
                    result.getScore(),
                    result.getFeedback(),
                    String.join(", ", result.getSuggestions())
                ));

                System.out.printf("✓ Graded %s: %d/100%n",
                    submission.getStudentId(), result.getScore());

            } catch (KaiException e) {
                System.err.printf("✗ Failed to grade %s: %s%n",
                    submission.getStudentId(), e.getMessage());

                records.add(new GradingRecord(
                    submission.getStudentId(),
                    null,
                    "Error: " + e.getMessage(),
                    ""
                ));
            }
        }

        return records;
    }

    public void saveResults(List<GradingRecord> records, Path outputPath)
            throws IOException {
        try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(outputPath))) {
            writer.println("student_id,score,feedback,suggestions");

            for (GradingRecord record : records) {
                writer.printf("%s,%s,\"%s\",\"%s\"%n",
                    record.studentId,
                    record.score != null ? record.score : "",
                    record.feedback.replace("\"", "\"\""),
                    record.suggestions.replace("\"", "\"\"")
                );
            }
        }

        System.out.println("\n✓ Results saved to " + outputPath);
    }

    public static void main(String[] args) {
        try (KaiClient client = new KaiClient()) {
            AutomatedGradingPipeline pipeline = new AutomatedGradingPipeline(client);

            // Load submissions
            Path inputPath = Paths.get("submissions.csv");
            List<Submission> submissions = pipeline.loadSubmissions(inputPath);
            System.out.printf("Loaded %d submissions%n", submissions.size());

            // Grade all submissions
            List<GradingRecord> results = pipeline.gradeSubmissions(submissions);

            // Save results
            Path outputPath = Paths.get("grading_results.csv");
            pipeline.saveResults(results, outputPath);

            // Print summary
            long successful = results.stream()
                .filter(r -> r.score != null)
                .count();

            double avgScore = results.stream()
                .filter(r -> r.score != null)
                .mapToInt(r -> r.score)
                .average()
                .orElse(0.0);

            System.out.println("\n=== Summary ===");
            System.out.printf("Total submissions: %d%n", submissions.size());
            System.out.printf("Successfully graded: %d%n", successful);
            System.out.printf("Average score: %.1f/100%n", avgScore);

        } catch (Exception e) {
            System.err.println("Pipeline failed: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Example 2: Quiz Generator with Export

import com.chi2labs.kai.KaiClient;
import com.chi2labs.kai.models.QuizResult;
import com.chi2labs.kai.models.QuizQuestion;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.*;
import java.nio.file.*;
import java.util.*;

/**
 * Generate quizzes for multiple topics and export to various formats.
 */
public class QuizGenerator {

    private final KaiClient client;
    private final Gson gson;

    public QuizGenerator(KaiClient client) {
        this.client = client;
        this.gson = new GsonBuilder().setPrettyPrinting().create();
    }

    public QuizResult generateQuiz(String topic, String difficulty, int count)
            throws Exception {
        QuizResult quiz = client.quizzes().generate()
            .topic(topic)
            .difficulty(difficulty)
            .questionCount(count)
            .questionTypes("multiple_choice", "true_false", "short_answer")
            .execute();

        System.out.printf("%n✓ Generated quiz: %s%n", topic);
        System.out.printf("  ID: %s%n", quiz.getQuizId());
        System.out.printf("  Questions: %d%n", quiz.getQuestionCount());
        System.out.printf("  Time: ~%d minutes%n", quiz.getEstimatedTimeMinutes());

        return quiz;
    }

    public void exportToJson(QuizResult quiz, String filename) throws IOException {
        Map<String, Object> quizData = new HashMap<>();
        quizData.put("quiz_id", quiz.getQuizId());
        quizData.put("topic", quiz.getTopic());
        quizData.put("difficulty", quiz.getDifficulty());
        quizData.put("estimated_time_minutes", quiz.getEstimatedTimeMinutes());

        List<Map<String, Object>> questions = new ArrayList<>();
        for (QuizQuestion q : quiz.getQuestions()) {
            Map<String, Object> questionData = new HashMap<>();
            questionData.put("id", q.getQuestionId());
            questionData.put("text", q.getQuestionText());
            questionData.put("type", q.getQuestionType());
            questionData.put("options", q.getOptions());
            questionData.put("correct_answer", q.getCorrectAnswer());
            questionData.put("explanation", q.getExplanation());
            questions.add(questionData);
        }
        quizData.put("questions", questions);

        String json = gson.toJson(quizData);
        Files.writeString(Paths.get(filename), json);
        System.out.println("✓ Exported to " + filename);
    }

    public void exportToMarkdown(QuizResult quiz, String filename) throws IOException {
        StringBuilder md = new StringBuilder();
        md.append("# ").append(quiz.getTopic()).append(" Quiz\n\n");
        md.append("**Difficulty:** ").append(quiz.getDifficulty()).append("\n");
        md.append("**Estimated Time:** ").append(quiz.getEstimatedTimeMinutes())
          .append(" minutes\n\n");
        md.append("---\n\n");

        int i = 1;
        for (QuizQuestion q : quiz.getQuestions()) {
            md.append("## Question ").append(i++).append("\n\n");
            md.append(q.getQuestionText()).append("\n\n");

            if (q.getOptions() != null) {
                for (String opt : q.getOptions()) {
                    md.append("- ").append(opt).append("\n");
                }
                md.append("\n");
            }

            if (q.getExplanation() != null) {
                md.append("**Explanation:** ").append(q.getExplanation()).append("\n\n");
            }

            md.append("---\n\n");
        }

        Files.writeString(Paths.get(filename), md.toString());
        System.out.println("✓ Exported to " + filename);
    }

    public static void main(String[] args) {
        try (KaiClient client = new KaiClient()) {
            QuizGenerator generator = new QuizGenerator(client);

            // Topics to generate quizzes for
            List<TopicConfig> topics = List.of(
                new TopicConfig("Java Fundamentals", "easy", 15),
                new TopicConfig("Data Structures", "medium", 20),
                new TopicConfig("Algorithm Design", "hard", 10)
            );

            for (TopicConfig config : topics) {
                // Generate quiz
                QuizResult quiz = generator.generateQuiz(
                    config.topic,
                    config.difficulty,
                    config.count
                );

                // Export to different formats
                String baseName = config.topic.toLowerCase().replace(" ", "_");
                generator.exportToJson(quiz, baseName + "_quiz.json");
                generator.exportToMarkdown(quiz, baseName + "_quiz.md");
            }

            System.out.println("\n✓ All quizzes generated and exported successfully!");

        } catch (Exception e) {
            System.err.println("Quiz generation failed: " + e.getMessage());
            e.printStackTrace();
        }
    }

    private static class TopicConfig {
        final String topic;
        final String difficulty;
        final int count;

        TopicConfig(String topic, String difficulty, int count) {
            this.topic = topic;
            this.difficulty = difficulty;
            this.count = count;
        }
    }
}

Best Practices

NotePerformance Optimization Tips
  1. Reuse KaiClient Instance: Create once and share across your application

    // Good: Single instance
    private static final KaiClient client = new KaiClient();
    
    // Bad: Creating new instances
    new KaiClient().assignments().grade()...
  2. Use Batch Operations: Grade multiple submissions in one request

    List<GradeResult> results = client.assignments().batchGrade(submissions);
  3. Enable Async for Concurrent Operations: Use executeAsync() for parallel requests

    CompletableFuture<?>[] futures = submissions.stream()
        .map(s -> client.assignments().grade()
            .assignmentId(s.getId())
            .studentSubmission(s.getText())
            .executeAsync())
        .toArray(CompletableFuture[]::new);
    
    CompletableFuture.allOf(futures).join();
  4. Implement Caching: Cache frequently accessed data

    private final LoadingCache<String, RubricData> rubricCache =
        CacheBuilder.newBuilder()
            .maximumSize(100)
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build(new CacheLoader<String, RubricData>() {
                public RubricData load(String rubricId) {
                    return client.rubrics().get(rubricId);
                }
            });
  5. Monitor Rate Limits: Check response headers

    GradeResult result = client.assignments().grade()...execute();
    int remaining = client.getLastResponse()
        .header("X-RateLimit-Remaining")
        .map(Integer::parseInt)
        .orElse(0);
  6. Use Try-With-Resources: Ensure proper cleanup

    try (KaiClient client = new KaiClient()) {
        // Use client
    } // Automatically closed