Python SDK

Official Python SDK for Kai the AI

Published

November 20, 2025

Overview

The official Python SDK for Kai the AI provides a modern, type-safe interface for integrating intelligent teaching assistance into your Python applications. Built with Python 3.8+ features, the SDK supports both synchronous and asynchronous operations, comprehensive error handling, and follows PEP 8 style guidelines.

Installation

Install the SDK using pip:

pip install kai-sdk

For development installations with optional dependencies:

pip install kai-sdk[dev]  # Includes testing and development tools
pip install kai-sdk[async]  # Includes async HTTP client dependencies

Requirements

  • Python 3.8 or higher
  • Active Kai the AI API key
  • Internet connection for API requests

Quick Start

Here’s a minimal example to get you started:

from kai_sdk import KaiClient

# Initialize the client
client = KaiClient(api_key="your_api_key_here")

# Grade a student submission
result = client.assignments.grade(
    assignment_id="CS101-HW1",
    student_submission="The sorting algorithm works by...",
    grading_style="detailed"
)

print(f"Score: {result.score}/100")
print(f"Feedback: {result.feedback}")

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"
from kai_sdk import KaiClient

# Automatically reads from KAI_API_KEY environment variable
client = KaiClient()
from kai_sdk import KaiClient

client = KaiClient(api_key="your_api_key_here")
from kai_sdk import KaiClient
import os

# Load from configuration file
with open("config/kai_config.txt") as f:
    api_key = f.read().strip()

client = KaiClient(api_key=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.

Core Classes

KaiClient

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

class KaiClient:
    """
    Main client for Kai the AI API.

    Args:
        api_key: Your API key (defaults to KAI_API_KEY env variable)
        base_url: API base URL (default: https://chi2api.com/v1)
        timeout: Request timeout in seconds (default: 30)
        max_retries: Maximum number of retry attempts (default: 3)
    """

    def __init__(
        self,
        api_key: str | None = None,
        base_url: str = "https://chi2api.com/v1",
        timeout: int = 30,
        max_retries: int = 3
    ) -> None:
        ...

Attributes:

  • assignments: Assignment grading operations
  • content: Content generation operations
  • analytics: Student analytics and performance metrics
  • quizzes: Quiz generation and management

Assignment

Handle assignment grading and feedback operations.

class Assignment:
    """Assignment grading operations."""

    def grade(
        self,
        assignment_id: str,
        student_submission: str,
        rubric_id: str | None = None,
        grading_style: str = "detailed"
    ) -> GradeResult:
        """
        Grade a student submission.

        Args:
            assignment_id: Unique identifier for the assignment
            student_submission: Student's submitted work
            rubric_id: Optional rubric to apply
            grading_style: "detailed", "concise", or "points_only"

        Returns:
            GradeResult object containing score and feedback

        Raises:
            ValidationError: Invalid parameters
            RateLimitError: Rate limit exceeded
            APIError: API request failed
        """
        ...

    def get_grade(self, grade_id: str) -> GradeResult:
        """Retrieve a previously generated grade."""
        ...

    def batch_grade(
        self,
        submissions: list[dict]
    ) -> list[GradeResult]:
        """Grade multiple submissions in a single request."""
        ...

Quiz

Generate and manage quiz content.

class Quiz:
    """Quiz generation and management operations."""

    def generate(
        self,
        topic: str,
        difficulty: str = "medium",
        question_count: int = 10,
        question_types: list[str] | None = None
    ) -> QuizResult:
        """
        Generate quiz questions on a topic.

        Args:
            topic: Subject matter for quiz questions
            difficulty: "easy", "medium", or "hard"
            question_count: Number of questions (1-50)
            question_types: List of question types to include

        Returns:
            QuizResult with generated questions
        """
        ...

Analytics

Access student performance data and analytics.

class Analytics:
    """Student analytics and performance metrics."""

    def get_student_performance(
        self,
        student_id: str,
        start_date: str | None = None,
        end_date: str | None = None,
        course_id: str | None = None
    ) -> PerformanceData:
        """
        Retrieve student performance metrics.

        Args:
            student_id: Student identifier
            start_date: ISO 8601 date string (optional)
            end_date: ISO 8601 date string (optional)
            course_id: Filter by specific course (optional)

        Returns:
            PerformanceData with metrics and trends
        """
        ...

    def get_class_analytics(
        self,
        course_id: str,
        metric_types: list[str] | None = None
    ) -> ClassAnalytics:
        """Retrieve aggregate class-level analytics."""
        ...

Response Models

GradeResult

from dataclasses import dataclass
from datetime import datetime

@dataclass
class GradeResult:
    """Result from grading operation."""

    grade_id: str
    score: int  # 0-100
    feedback: str
    suggestions: list[str]
    timestamp: datetime
    rubric_breakdown: dict[str, int] | None = None

    @property
    def passed(self) -> bool:
        """Check if grade meets passing threshold (>= 70)."""
        return self.score >= 70

QuizResult

@dataclass
class QuizQuestion:
    """Individual quiz question."""

    question_id: str
    question_text: str
    question_type: str
    options: list[str] | None = None
    correct_answer: str | None = None
    explanation: str | None = None

@dataclass
class QuizResult:
    """Result from quiz generation."""

    quiz_id: str
    topic: str
    difficulty: str
    questions: list[QuizQuestion]
    estimated_time_minutes: int

PerformanceData

@dataclass
class PerformanceData:
    """Student performance metrics."""

    student_id: str
    average_score: float
    assignments_completed: int
    assignments_total: int
    strength_areas: list[str]
    improvement_areas: list[str]
    trend: str  # "improving", "stable", "declining"
    last_updated: datetime

Complete API Methods

Assignment Grading

from kai_sdk import KaiClient
from kai_sdk.exceptions import ValidationError, RateLimitError

client = KaiClient(api_key="your_api_key")

try:
    # Grade a single submission
    result = client.assignments.grade(
        assignment_id="CS101-HW1",
        student_submission="My algorithm implementation...",
        rubric_id="standard_coding_rubric",
        grading_style="detailed"
    )

    print(f"Grade ID: {result.grade_id}")
    print(f"Score: {result.score}/100")
    print(f"Feedback: {result.feedback}")

    if result.rubric_breakdown:
        print("\nRubric Breakdown:")
        for criterion, points in result.rubric_breakdown.items():
            print(f"  {criterion}: {points}")

    print("\nSuggestions:")
    for suggestion in result.suggestions:
        print(f"  - {suggestion}")

except ValidationError as e:
    print(f"Invalid parameters: {e}")
except RateLimitError as e:
    print(f"Rate limit exceeded: {e}")
import asyncio
from kai_sdk import AsyncKaiClient

async def grade_submission():
    async with AsyncKaiClient(api_key="your_api_key") as client:
        result = await client.assignments.grade(
            assignment_id="CS101-HW1",
            student_submission="My algorithm implementation...",
            grading_style="detailed"
        )

        print(f"Score: {result.score}/100")
        return result

# Run async function
result = asyncio.run(grade_submission())

Batch Grading

Grade multiple submissions efficiently:

from kai_sdk import KaiClient

client = KaiClient()

submissions = [
    {
        "assignment_id": "CS101-HW1",
        "student_id": "student_001",
        "student_submission": "First student's work..."
    },
    {
        "assignment_id": "CS101-HW1",
        "student_id": "student_002",
        "student_submission": "Second student's work..."
    },
    # ... more submissions
]

results = client.assignments.batch_grade(submissions)

for result in results:
    print(f"Student {result.student_id}: {result.score}/100")

Quiz Generation

from kai_sdk import KaiClient

client = KaiClient()

quiz = client.quizzes.generate(
    topic="Python Data Structures",
    difficulty="medium",
    question_count=10,
    question_types=["multiple_choice", "true_false"]
)

print(f"Quiz ID: {quiz.quiz_id}")
print(f"Estimated time: {quiz.estimated_time_minutes} minutes")
print(f"Questions: {len(quiz.questions)}")

for i, question in enumerate(quiz.questions, 1):
    print(f"\nQuestion {i}: {question.question_text}")
    if question.options:
        for opt in question.options:
            print(f"  {opt}")
import asyncio
from kai_sdk import AsyncKaiClient

async def generate_multiple_quizzes():
    async with AsyncKaiClient() as client:
        # Generate multiple quizzes concurrently
        topics = [
            "Python Basics",
            "Object-Oriented Programming",
            "Data Structures"
        ]

        tasks = [
            client.quizzes.generate(
                topic=topic,
                difficulty="medium",
                question_count=5
            )
            for topic in topics
        ]

        quizzes = await asyncio.gather(*tasks)
        return quizzes

quizzes = asyncio.run(generate_multiple_quizzes())
print(f"Generated {len(quizzes)} quizzes")

Student Analytics

from kai_sdk import KaiClient
from datetime import datetime, timedelta

client = KaiClient()

# Get performance for the last 30 days
end_date = datetime.now()
start_date = end_date - timedelta(days=30)

performance = client.analytics.get_student_performance(
    student_id="student_12345",
    start_date=start_date.isoformat(),
    end_date=end_date.isoformat(),
    course_id="CS101"
)

print(f"Average Score: {performance.average_score:.1f}")
print(f"Completion Rate: {performance.assignments_completed}/{performance.assignments_total}")
print(f"Trend: {performance.trend}")

print("\nStrengths:")
for area in performance.strength_areas:
    print(f"  ✓ {area}")

print("\nAreas for Improvement:")
for area in performance.improvement_areas:
    print(f"  ! {area}")

Error Handling

The SDK provides specific exception types for different error scenarios:

from kai_sdk import KaiClient
from kai_sdk.exceptions import (
    KaiSDKError,           # Base exception
    ValidationError,       # Invalid parameters
    AuthenticationError,   # Invalid API key
    RateLimitError,       # Rate limit exceeded
    APIError,             # General API error
    TimeoutError,         # Request timeout
)

client = KaiClient()

try:
    result = client.assignments.grade(
        assignment_id="CS101-HW1",
        student_submission="Student work...",
        grading_style="detailed"
    )

except ValidationError as e:
    # Handle invalid parameters
    print(f"Invalid input: {e}")
    print(f"Details: {e.details}")

except AuthenticationError as e:
    # Handle authentication failures
    print(f"Authentication failed: {e}")
    print("Please check your API key")

except RateLimitError as e:
    # Handle rate limiting
    print(f"Rate limit exceeded: {e}")
    print(f"Retry after: {e.retry_after} seconds")

except TimeoutError as e:
    # Handle timeouts
    print(f"Request timed out: {e}")
    print("Try increasing the timeout parameter")

except APIError as e:
    # Handle general API errors
    print(f"API error: {e}")
    print(f"Status code: {e.status_code}")
    print(f"Error code: {e.error_code}")

except KaiSDKError as e:
    # Catch-all for SDK errors
    print(f"SDK error: {e}")
TipError Recovery Pattern

Implement exponential backoff for rate limit errors:

import time
from kai_sdk.exceptions import RateLimitError

max_retries = 3
retry_delay = 1

for attempt in range(max_retries):
    try:
        result = client.assignments.grade(...)
        break
    except RateLimitError as e:
        if attempt < max_retries - 1:
            wait_time = retry_delay * (2 ** attempt)
            print(f"Rate limited, waiting {wait_time}s...")
            time.sleep(wait_time)
        else:
            raise

Async/Await Support

The SDK provides full async support through the AsyncKaiClient:

import asyncio
from kai_sdk import AsyncKaiClient

async def main():
    async with AsyncKaiClient(api_key="your_api_key") as client:
        # All methods are awaitable
        result = await client.assignments.grade(
            assignment_id="CS101-HW1",
            student_submission="Student work..."
        )
        print(f"Score: {result.score}")

asyncio.run(main())
import asyncio
from kai_sdk import AsyncKaiClient

async def process_multiple_submissions():
    async with AsyncKaiClient() as client:
        # Grade multiple submissions concurrently
        submissions = [
            ("assignment_1", "submission text 1"),
            ("assignment_2", "submission text 2"),
            ("assignment_3", "submission text 3"),
        ]

        tasks = [
            client.assignments.grade(
                assignment_id=aid,
                student_submission=text
            )
            for aid, text in submissions
        ]

        # Execute all grading requests concurrently
        results = await asyncio.gather(*tasks)

        for i, result in enumerate(results, 1):
            print(f"Submission {i}: {result.score}/100")

asyncio.run(process_multiple_submissions())
import asyncio
from kai_sdk import AsyncKaiClient
from kai_sdk.exceptions import RateLimitError, APIError

async def safe_grade(client, assignment_id, submission):
    try:
        result = await client.assignments.grade(
            assignment_id=assignment_id,
            student_submission=submission
        )
        return result
    except RateLimitError:
        # Wait and retry
        await asyncio.sleep(5)
        return await safe_grade(client, assignment_id, submission)
    except APIError as e:
        print(f"Failed to grade: {e}")
        return None

async def main():
    async with AsyncKaiClient() as client:
        result = await safe_grade(
            client,
            "CS101-HW1",
            "Student work..."
        )
        if result:
            print(f"Score: {result.score}")

asyncio.run(main())

Rate Limiting and Best Practices

Understanding Rate Limits

Kai enforces the following rate limits based on your plan:

Plan Requests/Minute Requests/Day
Free 10 1,000
Educator 60 10,000
Institution 300 100,000

Handling Rate Limits

The SDK automatically handles rate limiting with exponential backoff:

from kai_sdk import KaiClient

# Configure retry behavior
client = KaiClient(
    api_key="your_api_key",
    max_retries=5,  # Retry up to 5 times
    timeout=60      # 60 second timeout
)

# The client will automatically retry on rate limit errors
result = client.assignments.grade(...)

Best Practices

NotePerformance Optimization Tips
  1. Use Batch Operations: Grade multiple submissions in one request

    results = client.assignments.batch_grade(submissions)
  2. Enable Async for Concurrent Operations: Use AsyncKaiClient for parallel requests

    async with AsyncKaiClient() as client:
        results = await asyncio.gather(*tasks)
  3. Implement Caching: Cache frequently accessed data like rubrics

    from functools import lru_cache
    
    @lru_cache(maxsize=100)
    def get_rubric(rubric_id):
        return client.rubrics.get(rubric_id)
  4. Monitor Rate Limits: Check response headers for rate limit status

    result = client.assignments.grade(...)
    remaining = client.last_response.headers.get('X-RateLimit-Remaining')
    print(f"Requests remaining: {remaining}")
  5. Use Webhooks for Long Operations: For batch processing, use webhooks instead of polling

Configuration Options

Client Configuration

from kai_sdk import KaiClient
from kai_sdk.config import Config

# Method 1: Direct configuration
client = KaiClient(
    api_key="your_api_key",
    base_url="https://chi2api.com/v1",
    timeout=30,
    max_retries=3,
    verify_ssl=True,
    user_agent="MyApp/1.0"
)

# Method 2: Using Config object
config = Config(
    api_key="your_api_key",
    timeout=60,
    max_retries=5
)
client = KaiClient(config=config)

# Method 3: Environment-based configuration
# Reads from KAI_API_KEY, KAI_BASE_URL, etc.
client = KaiClient.from_env()

Logging Configuration

import logging
from kai_sdk import KaiClient

# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
kai_logger = logging.getLogger('kai_sdk')
kai_logger.setLevel(logging.DEBUG)

client = KaiClient()

# Now all SDK operations will log debug information
result = client.assignments.grade(...)

Custom HTTP Session

from kai_sdk import KaiClient
import requests

# Create custom session with specific settings
session = requests.Session()
session.headers.update({'X-Custom-Header': 'value'})
session.proxies = {'https': 'http://proxy.example.com:8080'}

client = KaiClient(
    api_key="your_api_key",
    session=session
)

Complete Working Examples

Example 1: Automated Grading Pipeline

#!/usr/bin/env python3
"""
Automated grading pipeline for processing student submissions.
"""
from pathlib import Path
from typing import List
from kai_sdk import KaiClient
from kai_sdk.exceptions import KaiSDKError
import csv

def load_submissions(csv_path: Path) -> List[dict]:
    """Load student submissions from CSV file."""
    submissions = []
    with open(csv_path) as f:
        reader = csv.DictReader(f)
        for row in reader:
            submissions.append({
                'student_id': row['student_id'],
                'assignment_id': row['assignment_id'],
                'submission_text': row['submission_text']
            })
    return submissions

def grade_submissions(client: KaiClient, submissions: List[dict]) -> List[dict]:
    """Grade all submissions and return results."""
    results = []

    for submission in submissions:
        try:
            result = client.assignments.grade(
                assignment_id=submission['assignment_id'],
                student_submission=submission['submission_text'],
                grading_style='detailed'
            )

            results.append({
                'student_id': submission['student_id'],
                'score': result.score,
                'feedback': result.feedback,
                'suggestions': ', '.join(result.suggestions)
            })

            print(f"✓ Graded {submission['student_id']}: {result.score}/100")

        except KaiSDKError as e:
            print(f"✗ Failed to grade {submission['student_id']}: {e}")
            results.append({
                'student_id': submission['student_id'],
                'score': None,
                'feedback': f"Error: {e}",
                'suggestions': ''
            })

    return results

def save_results(results: List[dict], output_path: Path):
    """Save grading results to CSV."""
    with open(output_path, 'w', newline='') as f:
        writer = csv.DictWriter(f, fieldnames=['student_id', 'score', 'feedback', 'suggestions'])
        writer.writeheader()
        writer.writerows(results)

    print(f"\n✓ Results saved to {output_path}")

def main():
    # Initialize client
    client = KaiClient()

    # Load submissions
    submissions = load_submissions(Path('submissions.csv'))
    print(f"Loaded {len(submissions)} submissions")

    # Grade all submissions
    results = grade_submissions(client, submissions)

    # Save results
    save_results(results, Path('grading_results.csv'))

    # Print summary
    successful = sum(1 for r in results if r['score'] is not None)
    avg_score = sum(r['score'] for r in results if r['score']) / successful

    print(f"\n=== Summary ===")
    print(f"Total submissions: {len(submissions)}")
    print(f"Successfully graded: {successful}")
    print(f"Average score: {avg_score:.1f}/100")

if __name__ == '__main__':
    main()

Example 2: Quiz Generator with Export

#!/usr/bin/env python3
"""
Generate quizzes for multiple topics and export to various formats.
"""
from kai_sdk import KaiClient
from typing import List
import json

def generate_quiz(
    client: KaiClient,
    topic: str,
    difficulty: str = "medium",
    count: int = 10
):
    """Generate a quiz for a specific topic."""
    quiz = client.quizzes.generate(
        topic=topic,
        difficulty=difficulty,
        question_count=count,
        question_types=["multiple_choice", "true_false", "short_answer"]
    )

    print(f"\n✓ Generated quiz: {topic}")
    print(f"  ID: {quiz.quiz_id}")
    print(f"  Questions: {len(quiz.questions)}")
    print(f"  Time: ~{quiz.estimated_time_minutes} minutes")

    return quiz

def export_quiz_to_json(quiz, filename: str):
    """Export quiz to JSON format."""
    quiz_data = {
        'quiz_id': quiz.quiz_id,
        'topic': quiz.topic,
        'difficulty': quiz.difficulty,
        'estimated_time_minutes': quiz.estimated_time_minutes,
        'questions': [
            {
                'id': q.question_id,
                'text': q.question_text,
                'type': q.question_type,
                'options': q.options,
                'correct_answer': q.correct_answer,
                'explanation': q.explanation
            }
            for q in quiz.questions
        ]
    }

    with open(filename, 'w') as f:
        json.dump(quiz_data, f, indent=2)

    print(f"✓ Exported to {filename}")

def export_quiz_to_markdown(quiz, filename: str):
    """Export quiz to Markdown format."""
    with open(filename, 'w') as f:
        f.write(f"# {quiz.topic} Quiz\n\n")
        f.write(f"**Difficulty:** {quiz.difficulty}\n")
        f.write(f"**Estimated Time:** {quiz.estimated_time_minutes} minutes\n\n")
        f.write("---\n\n")

        for i, q in enumerate(quiz.questions, 1):
            f.write(f"## Question {i}\n\n")
            f.write(f"{q.question_text}\n\n")

            if q.options:
                for opt in q.options:
                    f.write(f"- {opt}\n")
                f.write("\n")

            if q.explanation:
                f.write(f"**Explanation:** {q.explanation}\n\n")

            f.write("---\n\n")

    print(f"✓ Exported to {filename}")

def main():
    client = KaiClient()

    # Topics to generate quizzes for
    topics = [
        ("Python Fundamentals", "easy", 15),
        ("Data Structures", "medium", 20),
        ("Algorithm Design", "hard", 10)
    ]

    for topic, difficulty, count in topics:
        # Generate quiz
        quiz = generate_quiz(client, topic, difficulty, count)

        # Export to different formats
        base_name = topic.lower().replace(' ', '_')
        export_quiz_to_json(quiz, f"{base_name}_quiz.json")
        export_quiz_to_markdown(quiz, f"{base_name}_quiz.md")

    print("\n✓ All quizzes generated and exported successfully!")

if __name__ == '__main__':
    main()

Example 3: Student Analytics Dashboard

#!/usr/bin/env python3
"""
Generate comprehensive student analytics reports.
"""
from kai_sdk import KaiClient
from datetime import datetime, timedelta
from typing import List
import matplotlib.pyplot as plt
from pathlib import Path

def get_class_roster(csv_path: Path) -> List[str]:
    """Load student IDs from roster."""
    import csv
    with open(csv_path) as f:
        reader = csv.DictReader(f)
        return [row['student_id'] for row in reader]

def analyze_student(client: KaiClient, student_id: str, course_id: str):
    """Get comprehensive analytics for a student."""
    end_date = datetime.now()
    start_date = end_date - timedelta(days=90)  # Last 90 days

    performance = client.analytics.get_student_performance(
        student_id=student_id,
        start_date=start_date.isoformat(),
        end_date=end_date.isoformat(),
        course_id=course_id
    )

    return {
        'student_id': student_id,
        'average_score': performance.average_score,
        'completion_rate': performance.assignments_completed / performance.assignments_total,
        'trend': performance.trend,
        'strengths': performance.strength_areas,
        'improvements': performance.improvement_areas
    }

def generate_class_report(analytics_data: List[dict], output_dir: Path):
    """Generate visual report for the class."""
    output_dir.mkdir(exist_ok=True)

    # Extract data for visualization
    scores = [data['average_score'] for data in analytics_data]
    completion_rates = [data['completion_rate'] * 100 for data in analytics_data]

    # Create visualizations
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

    # Score distribution
    ax1.hist(scores, bins=10, edgecolor='black')
    ax1.set_xlabel('Average Score')
    ax1.set_ylabel('Number of Students')
    ax1.set_title('Score Distribution')
    ax1.axvline(sum(scores)/len(scores), color='red', linestyle='--', label='Class Average')
    ax1.legend()

    # Completion rate distribution
    ax2.hist(completion_rates, bins=10, edgecolor='black')
    ax2.set_xlabel('Completion Rate (%)')
    ax2.set_ylabel('Number of Students')
    ax2.set_title('Assignment Completion Distribution')

    plt.tight_layout()
    plt.savefig(output_dir / 'class_analytics.png', dpi=300, bbox_inches='tight')
    print(f"✓ Saved visualization to {output_dir / 'class_analytics.png'}")

    # Generate text report
    with open(output_dir / 'class_report.txt', 'w') as f:
        f.write("CLASS ANALYTICS REPORT\n")
        f.write("=" * 50 + "\n\n")

        f.write(f"Total Students: {len(analytics_data)}\n")
        f.write(f"Class Average: {sum(scores)/len(scores):.1f}\n")
        f.write(f"Average Completion Rate: {sum(completion_rates)/len(completion_rates):.1f}%\n\n")

        # Students by trend
        improving = sum(1 for d in analytics_data if d['trend'] == 'improving')
        stable = sum(1 for d in analytics_data if d['trend'] == 'stable')
        declining = sum(1 for d in analytics_data if d['trend'] == 'declining')

        f.write("Student Trends:\n")
        f.write(f"  Improving: {improving} ({improving/len(analytics_data)*100:.1f}%)\n")
        f.write(f"  Stable: {stable} ({stable/len(analytics_data)*100:.1f}%)\n")
        f.write(f"  Declining: {declining} ({declining/len(analytics_data)*100:.1f}%)\n\n")

        # Students needing attention
        f.write("Students Needing Attention:\n")
        for data in analytics_data:
            if data['average_score'] < 70 or data['trend'] == 'declining':
                f.write(f"  - {data['student_id']}: Score {data['average_score']:.1f}, {data['trend']}\n")

    print(f"✓ Saved report to {output_dir / 'class_report.txt'}")

def main():
    client = KaiClient()
    course_id = "CS101"

    # Load student roster
    student_ids = get_class_roster(Path('roster.csv'))
    print(f"Analyzing {len(student_ids)} students...")

    # Gather analytics for all students
    analytics_data = []
    for student_id in student_ids:
        try:
            data = analyze_student(client, student_id, course_id)
            analytics_data.append(data)
            print(f"✓ Analyzed {student_id}")
        except Exception as e:
            print(f"✗ Failed to analyze {student_id}: {e}")

    # Generate comprehensive report
    output_dir = Path('analytics_report')
    generate_class_report(analytics_data, output_dir)

    print(f"\n✓ Analytics complete! Check {output_dir} for results")

if __name__ == '__main__':
    main()

Type Hints and Modern Python

The SDK leverages modern Python features for better type safety:

from typing import Optional, Union, List, Dict, Any
from kai_sdk import KaiClient
from kai_sdk.models import GradeResult

def process_grade(
    client: KaiClient,
    assignment_id: str,
    submission: str,
    rubric_id: Optional[str] = None
) -> Union[GradeResult, None]:
    """
    Type-safe grading function with proper annotations.

    Args:
        client: Initialized KaiClient instance
        assignment_id: Assignment identifier
        submission: Student submission text
        rubric_id: Optional rubric identifier

    Returns:
        GradeResult if successful, None if failed
    """
    try:
        result = client.assignments.grade(
            assignment_id=assignment_id,
            student_submission=submission,
            rubric_id=rubric_id
        )
        return result
    except Exception as e:
        print(f"Grading failed: {e}")
        return None

# Type checkers (mypy, pyright) will validate all types
result: Optional[GradeResult] = process_grade(
    client=client,
    assignment_id="CS101-HW1",
    submission="Student work..."
)

if result is not None:
    score: int = result.score
    print(f"Score: {score}")