Ruby SDK
Official Ruby SDK for Kai the AI
Overview
The official Ruby SDK for Kai the AI provides an elegant, idiomatic Ruby interface for integrating intelligent teaching assistance into your Ruby and Rails applications. Built with Ruby 2.7+ features, the SDK embraces Ruby conventions with blocks, symbols, and expressive DSLs while supporting both synchronous and asynchronous operations.
Installation
Add the SDK to your project using Bundler:
# Gemfile
gem 'kai-sdk', '~> 1.0'Then install:
bundle installgem install kai-sdkFor Rails applications, add to your Gemfile and create an initializer:
# Gemfile
gem 'kai-sdk', '~> 1.0'
# config/initializers/kai.rb
Kai.configure do |config|
config.api_key = ENV['KAI_API_KEY']
config.timeout = 30
config.max_retries = 3
endRequirements
- Ruby 2.7 or higher
- Active Kai the AI API key
- Internet connection for API requests
Quick Start
Here’s a minimal example to get you started:
require 'kai-sdk'
# Initialize the client
client = Kai::Client.new(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'
)
puts "Score: #{result.score}/100"
puts "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"require 'kai-sdk'
# Automatically reads from KAI_API_KEY environment variable
client = Kai::Client.newrequire 'kai-sdk'
client = Kai::Client.new(api_key: 'your_api_key_here')require 'kai-sdk'
Kai.configure do |config|
config.api_key = 'your_api_key_here'
config.base_url = 'https://chi2api.com/v1'
config.timeout = 30
end
client = Kai::Client.new# config/initializers/kai.rb
Kai.configure do |config|
config.api_key = Rails.application.credentials.dig(:kai, :api_key)
endNever hardcode API keys in your source code or commit them to version control. Use environment variables, Rails credentials, or secure secret management systems.
Core Classes
Kai::Client
The main client class for interacting with Kai’s API.
module Kai
class Client
# Create a new client instance
#
# @param api_key [String] Your API key (defaults to ENV['KAI_API_KEY'])
# @param base_url [String] API base URL
# @param timeout [Integer] Request timeout in seconds
# @param max_retries [Integer] Maximum retry attempts
def initialize(api_key: nil, base_url: nil, timeout: 30, max_retries: 3)
# ...
end
# Access assignment grading operations
# @return [Kai::Assignments]
def assignments
@assignments ||= Kai::Assignments.new(self)
end
# Access content generation operations
# @return [Kai::Content]
def content
@content ||= Kai::Content.new(self)
end
# Access student analytics operations
# @return [Kai::Analytics]
def analytics
@analytics ||= Kai::Analytics.new(self)
end
# Access quiz generation operations
# @return [Kai::Quizzes]
def quizzes
@quizzes ||= Kai::Quizzes.new(self)
end
end
endKai::Assignments
Handle assignment grading and feedback operations.
module Kai
class Assignments
# Grade a student submission
#
# @param assignment_id [String] Assignment identifier
# @param student_submission [String] Student's work
# @param rubric_id [String, nil] Optional rubric to apply
# @param grading_style [String] "detailed", "concise", or "points_only"
# @return [Kai::GradeResult]
# @raise [Kai::ValidationError] Invalid parameters
# @raise [Kai::RateLimitError] Rate limit exceeded
# @raise [Kai::APIError] API request failed
def grade(assignment_id:, student_submission:, rubric_id: nil, grading_style: 'detailed')
# ...
end
# Grade with a block for additional configuration
#
# @yield [request] Configuration block
# @return [Kai::GradeResult]
def grade_with(&block)
request = GradeRequest.new
block.call(request)
execute_grade(request)
end
# Retrieve a previously generated grade
#
# @param grade_id [String] Grade identifier
# @return [Kai::GradeResult]
def get_grade(grade_id)
# ...
end
# Grade multiple submissions in batch
#
# @param submissions [Array<Hash>] Array of submission data
# @return [Array<Kai::GradeResult>]
def batch_grade(submissions)
# ...
end
end
endKai::Quizzes
Generate and manage quiz content.
module Kai
class Quizzes
# Generate quiz questions
#
# @param topic [String] Subject matter for quiz
# @param difficulty [String] "easy", "medium", or "hard"
# @param question_count [Integer] Number of questions (1-50)
# @param question_types [Array<String>] Types of questions to include
# @return [Kai::QuizResult]
def generate(topic:, difficulty: 'medium', question_count: 10, question_types: nil)
# ...
end
# Generate quiz with configuration block
#
# @yield [request] Configuration block
# @return [Kai::QuizResult]
def generate_with(&block)
request = QuizRequest.new
block.call(request)
execute_generate(request)
end
# Retrieve a previously generated quiz
#
# @param quiz_id [String] Quiz identifier
# @return [Kai::QuizResult]
def get_quiz(quiz_id)
# ...
end
end
endKai::Analytics
Access student performance data and analytics.
module Kai
class Analytics
# Retrieve student performance metrics
#
# @param student_id [String] Student identifier
# @param start_date [String, Date, Time] Start date filter (ISO 8601)
# @param end_date [String, Date, Time] End date filter (ISO 8601)
# @param course_id [String, nil] Optional course filter
# @return [Kai::PerformanceData]
def student_performance(student_id:, start_date: nil, end_date: nil, course_id: nil)
# ...
end
# Retrieve class-level analytics
#
# @param course_id [String] Course identifier
# @param metric_types [Array<String>, nil] Specific metrics to retrieve
# @return [Kai::ClassAnalytics]
def class_analytics(course_id:, metric_types: nil)
# ...
end
end
endResponse Models
Kai::GradeResult
module Kai
class GradeResult
attr_reader :grade_id, :score, :feedback, :suggestions,
:timestamp, :rubric_breakdown
# @param attributes [Hash] Grade result attributes
def initialize(attributes = {})
@grade_id = attributes[:grade_id]
@score = attributes[:score]
@feedback = attributes[:feedback]
@suggestions = attributes[:suggestions] || []
@timestamp = parse_time(attributes[:timestamp])
@rubric_breakdown = attributes[:rubric_breakdown]
end
# Check if grade meets passing threshold (>= 70)
# @return [Boolean]
def passed?
score >= 70
end
# Get letter grade based on score
# @return [String]
def letter_grade
case score
when 90..100 then 'A'
when 80..89 then 'B'
when 70..79 then 'C'
when 60..69 then 'D'
else 'F'
end
end
def to_h
{
grade_id: grade_id,
score: score,
feedback: feedback,
suggestions: suggestions,
timestamp: timestamp,
rubric_breakdown: rubric_breakdown
}
end
end
endKai::QuizResult
module Kai
class QuizQuestion
attr_reader :question_id, :question_text, :question_type,
:options, :correct_answer, :explanation
def initialize(attributes = {})
@question_id = attributes[:question_id]
@question_text = attributes[:question_text]
@question_type = attributes[:question_type]
@options = attributes[:options]
@correct_answer = attributes[:correct_answer]
@explanation = attributes[:explanation]
end
def multiple_choice?
question_type == 'multiple_choice'
end
def true_false?
question_type == 'true_false'
end
end
class QuizResult
attr_reader :quiz_id, :topic, :difficulty, :questions,
:estimated_time_minutes
def initialize(attributes = {})
@quiz_id = attributes[:quiz_id]
@topic = attributes[:topic]
@difficulty = attributes[:difficulty]
@questions = parse_questions(attributes[:questions])
@estimated_time_minutes = attributes[:estimated_time_minutes]
end
# Get total number of questions
# @return [Integer]
def question_count
questions.size
end
# Iterate over questions
# @yield [question] Each question
def each_question(&block)
questions.each(&block)
end
private
def parse_questions(questions_data)
(questions_data || []).map { |q| QuizQuestion.new(q) }
end
end
endKai::PerformanceData
module Kai
class PerformanceData
attr_reader :student_id, :average_score, :assignments_completed,
:assignments_total, :strength_areas, :improvement_areas,
:trend, :last_updated
def initialize(attributes = {})
@student_id = attributes[:student_id]
@average_score = attributes[:average_score].to_f
@assignments_completed = attributes[:assignments_completed]
@assignments_total = attributes[:assignments_total]
@strength_areas = attributes[:strength_areas] || []
@improvement_areas = attributes[:improvement_areas] || []
@trend = attributes[:trend]
@last_updated = parse_time(attributes[:last_updated])
end
# Calculate completion rate as percentage
# @return [Float] Completion rate (0-100)
def completion_rate
return 0.0 if assignments_total.zero?
(assignments_completed.to_f / assignments_total * 100).round(1)
end
# Check if student is improving
# @return [Boolean]
def improving?
trend == 'improving'
end
# Check if student is declining
# @return [Boolean]
def declining?
trend == 'declining'
end
end
endComplete API Methods
Assignment Grading
require 'kai-sdk'
client = Kai::Client.new
# 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'
)
puts "Grade ID: #{result.grade_id}"
puts "Score: #{result.score}/100"
puts "Letter Grade: #{result.letter_grade}"
puts "Feedback: #{result.feedback}"
if result.rubric_breakdown
puts "\nRubric Breakdown:"
result.rubric_breakdown.each do |criterion, points|
puts " #{criterion}: #{points}"
end
end
puts "\nSuggestions:"
result.suggestions.each do |suggestion|
puts " - #{suggestion}"
endrequire 'kai-sdk'
client = Kai::Client.new
# Use block for cleaner configuration
result = client.assignments.grade_with do |req|
req.assignment_id = 'CS101-HW1'
req.student_submission = 'My algorithm implementation...'
req.rubric_id = 'standard_coding_rubric'
req.grading_style = 'detailed'
end
puts "Score: #{result.score}/100"
puts "Passed: #{result.passed? ? 'Yes' : 'No'}"require 'kai-sdk'
client = Kai::Client.new
begin
result = client.assignments.grade(
assignment_id: 'CS101-HW1',
student_submission: 'Student work...',
grading_style: 'detailed'
)
puts "Score: #{result.score}/100"
rescue Kai::ValidationError => e
puts "Invalid parameters: #{e.message}"
puts "Details: #{e.details}"
rescue Kai::AuthenticationError => e
puts "Authentication failed: #{e.message}"
puts "Please check your API key"
rescue Kai::RateLimitError => e
puts "Rate limit exceeded: #{e.message}"
puts "Retry after: #{e.retry_after} seconds"
rescue Kai::TimeoutError => e
puts "Request timed out: #{e.message}"
rescue Kai::APIError => e
puts "API error: #{e.message}"
puts "Status code: #{e.status_code}"
rescue Kai::Error => e
puts "SDK error: #{e.message}"
endBatch Grading
Grade multiple submissions efficiently:
require 'kai-sdk'
client = Kai::Client.new
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..."
},
{
assignment_id: 'CS101-HW1',
student_id: 'student_003',
student_submission: "Third student's work..."
}
]
results = client.assignments.batch_grade(submissions)
results.each do |result|
puts "Student: Score #{result.score}/100"
end
# Calculate class statistics
average_score = results.sum(&:score) / results.size.to_f
puts "Class average: #{average_score.round(1)}/100"
# Filter passing students
passing = results.select(&:passed?)
puts "Passing students: #{passing.size}/#{results.size}"Quiz Generation
require 'kai-sdk'
client = Kai::Client.new
quiz = client.quizzes.generate(
topic: 'Ruby Fundamentals',
difficulty: 'medium',
question_count: 10,
question_types: ['multiple_choice', 'true_false']
)
puts "Quiz ID: #{quiz.quiz_id}"
puts "Topic: #{quiz.topic}"
puts "Estimated time: #{quiz.estimated_time_minutes} minutes"
puts "Questions: #{quiz.question_count}"
quiz.each_question.with_index(1) do |question, i|
puts "\nQuestion #{i}: #{question.question_text}"
if question.options
question.options.each do |opt|
puts " #{opt}"
end
end
endrequire 'kai-sdk'
client = Kai::Client.new
quiz = client.quizzes.generate_with do |req|
req.topic = 'Ruby on Rails'
req.difficulty = 'hard'
req.question_count = 15
req.question_types = ['multiple_choice', 'short_answer']
end
# Export quiz to hash
quiz_data = {
id: quiz.quiz_id,
topic: quiz.topic,
difficulty: quiz.difficulty,
time: quiz.estimated_time_minutes,
questions: quiz.questions.map(&:to_h)
}
# Save as JSON
File.write('quiz.json', JSON.pretty_generate(quiz_data))
puts "Quiz saved to quiz.json"require 'kai-sdk'
client = Kai::Client.new
topics = [
{ topic: 'Ruby Basics', difficulty: 'easy', count: 15 },
{ topic: 'Object-Oriented Programming', difficulty: 'medium', count: 20 },
{ topic: 'Metaprogramming', difficulty: 'hard', count: 10 }
]
quizzes = topics.map do |config|
client.quizzes.generate(
topic: config[:topic],
difficulty: config[:difficulty],
question_count: config[:count]
)
end
quizzes.each do |quiz|
puts "Generated: #{quiz.topic} (#{quiz.question_count} questions)"
endStudent Analytics
require 'kai-sdk'
require 'date'
client = Kai::Client.new
# Get performance for the last 30 days
end_date = Date.today
start_date = end_date - 30
performance = client.analytics.student_performance(
student_id: 'student_12345',
start_date: start_date.iso8601,
end_date: end_date.iso8601,
course_id: 'CS101'
)
puts "Average Score: #{performance.average_score.round(1)}"
puts "Completion Rate: #{performance.assignments_completed}/#{performance.assignments_total} (#{performance.completion_rate}%)"
puts "Trend: #{performance.trend}"
puts "\nStrengths:"
performance.strength_areas.each do |area|
puts " ✓ #{area}"
end
puts "\nAreas for Improvement:"
performance.improvement_areas.each do |area|
puts " ! #{area}"
end
# Check status
if performance.declining?
puts "\n⚠️ Student performance is declining"
elsif performance.improving?
puts "\n✅ Student performance is improving"
endError Handling
The SDK provides specific exception types for different error scenarios:
require 'kai-sdk'
client = Kai::Client.new
begin
result = client.assignments.grade(
assignment_id: 'CS101-HW1',
student_submission: 'Student work...',
grading_style: 'detailed'
)
puts "Score: #{result.score}"
rescue Kai::ValidationError => e
# Handle invalid parameters
puts "Invalid input: #{e.message}"
puts "Details: #{e.details.inspect}"
rescue Kai::AuthenticationError => e
# Handle authentication failures
puts "Authentication failed: #{e.message}"
puts "Please check your API key"
rescue Kai::RateLimitError => e
# Handle rate limiting
puts "Rate limit exceeded: #{e.message}"
puts "Retry after: #{e.retry_after} seconds"
rescue Kai::TimeoutError => e
# Handle timeouts
puts "Request timed out: #{e.message}"
puts "Try increasing the timeout parameter"
rescue Kai::APIError => e
# Handle general API errors
puts "API error: #{e.message}"
puts "Status code: #{e.status_code}"
puts "Error code: #{e.error_code}"
rescue Kai::Error => e
# Catch-all for SDK errors
puts "SDK error: #{e.message}"
endException Hierarchy
Kai::Error (base)
├── Kai::ValidationError # Invalid parameters
├── Kai::AuthenticationError # Invalid API key
├── Kai::RateLimitError # Rate limit exceeded
├── Kai::TimeoutError # Request timeout
└── Kai::APIError # General API errorImplement exponential backoff for rate limit errors:
def grade_with_retry(client, assignment_id, submission, max_retries: 3)
retry_delay = 1
max_retries.times do |attempt|
begin
return client.assignments.grade(
assignment_id: assignment_id,
student_submission: submission
)
rescue Kai::RateLimitError => e
if attempt < max_retries - 1
wait_time = retry_delay * (2 ** attempt)
puts "Rate limited, waiting #{wait_time}s..."
sleep(wait_time)
else
raise
end
end
end
endRails Integration
Configuration
# config/initializers/kai.rb
Kai.configure do |config|
config.api_key = Rails.application.credentials.dig(:kai, :api_key)
config.timeout = 30
config.max_retries = 3
config.logger = Rails.logger
endModel Integration
# app/models/assignment.rb
class Assignment < ApplicationRecord
has_many :submissions
def grade_submission(submission)
client = Kai::Client.new
result = client.assignments.grade(
assignment_id: id.to_s,
student_submission: submission.content,
rubric_id: rubric_id,
grading_style: 'detailed'
)
submission.update!(
score: result.score,
feedback: result.feedback,
graded_at: Time.current
)
result
rescue Kai::Error => e
Rails.logger.error("Grading failed: #{e.message}")
nil
end
endService Object Pattern
# app/services/grading_service.rb
class GradingService
def initialize(client: nil)
@client = client || Kai::Client.new
end
def grade_submission(assignment, submission)
result = @client.assignments.grade(
assignment_id: assignment.id.to_s,
student_submission: submission.content,
grading_style: 'detailed'
)
update_submission(submission, result)
notify_student(submission)
result
end
def batch_grade(assignment, submissions)
submission_data = submissions.map do |sub|
{
assignment_id: assignment.id.to_s,
student_id: sub.student_id.to_s,
student_submission: sub.content
}
end
results = @client.assignments.batch_grade(submission_data)
submissions.zip(results).each do |submission, result|
update_submission(submission, result)
end
results
end
private
def update_submission(submission, result)
submission.update!(
score: result.score,
feedback: result.feedback,
suggestions: result.suggestions,
graded_at: Time.current
)
end
def notify_student(submission)
StudentMailer.grade_notification(submission).deliver_later
end
endBackground Job
# app/jobs/grade_submission_job.rb
class GradeSubmissionJob < ApplicationJob
queue_as :default
retry_on Kai::RateLimitError, wait: :exponentially_longer, attempts: 5
def perform(submission_id)
submission = Submission.find(submission_id)
assignment = submission.assignment
client = Kai::Client.new
result = client.assignments.grade(
assignment_id: assignment.id.to_s,
student_submission: submission.content,
grading_style: 'detailed'
)
submission.update!(
score: result.score,
feedback: result.feedback,
graded_at: Time.current
)
StudentMailer.grade_notification(submission).deliver_now
rescue Kai::Error => e
Rails.logger.error("Grading failed for submission #{submission_id}: #{e.message}")
submission.update!(grading_error: e.message)
raise
end
end
# Usage in controller
class SubmissionsController < ApplicationController
def create
@submission = current_student.submissions.build(submission_params)
if @submission.save
GradeSubmissionJob.perform_later(@submission.id)
redirect_to @submission, notice: 'Submission received. Grading in progress.'
else
render :new
end
end
endController Integration
# app/controllers/api/v1/grading_controller.rb
module Api
module V1
class GradingController < ApplicationController
before_action :authenticate_user!
def create
service = GradingService.new
result = service.grade_submission(
assignment,
submission
)
if result
render json: {
grade_id: result.grade_id,
score: result.score,
feedback: result.feedback,
suggestions: result.suggestions
}, status: :created
else
render json: { error: 'Grading failed' }, status: :unprocessable_entity
end
end
def batch_create
service = GradingService.new
results = service.batch_grade(assignment, submissions)
render json: results.map { |r|
{
grade_id: r.grade_id,
score: r.score,
feedback: r.feedback
}
}, status: :created
end
private
def assignment
@assignment ||= Assignment.find(params[:assignment_id])
end
def submission
@submission ||= assignment.submissions.find(params[:submission_id])
end
def submissions
@submissions ||= assignment.submissions.where(id: params[:submission_ids])
end
end
end
endComplete Working Examples
Example 1: Automated Grading Pipeline
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'kai-sdk'
require 'csv'
# Automated grading pipeline for processing student submissions from CSV
class AutomatedGradingPipeline
def initialize(client)
@client = client
end
def load_submissions(csv_path)
submissions = []
CSV.foreach(csv_path, headers: true) do |row|
submissions << {
student_id: row['student_id'],
assignment_id: row['assignment_id'],
submission_text: row['submission_text']
}
end
submissions
end
def grade_submissions(submissions)
results = []
submissions.each do |submission|
begin
result = @client.assignments.grade(
assignment_id: submission[:assignment_id],
student_submission: submission[:submission_text],
grading_style: 'detailed'
)
results << {
student_id: submission[:student_id],
score: result.score,
feedback: result.feedback,
suggestions: result.suggestions.join(', ')
}
puts "✓ Graded #{submission[:student_id]}: #{result.score}/100"
rescue Kai::Error => e
puts "✗ Failed to grade #{submission[:student_id]}: #{e.message}"
results << {
student_id: submission[:student_id],
score: nil,
feedback: "Error: #{e.message}",
suggestions: ''
}
end
end
results
end
def save_results(results, output_path)
CSV.open(output_path, 'w') do |csv|
csv << %w[student_id score feedback suggestions]
results.each do |result|
csv << [
result[:student_id],
result[:score],
result[:feedback],
result[:suggestions]
]
end
end
puts "\n✓ Results saved to #{output_path}"
end
def print_summary(results)
successful = results.count { |r| r[:score] }
scores = results.map { |r| r[:score] }.compact
avg_score = scores.sum / scores.size.to_f
puts "\n=== Summary ==="
puts "Total submissions: #{results.size}"
puts "Successfully graded: #{successful}"
puts "Average score: #{avg_score.round(1)}/100"
end
def run(input_path, output_path)
submissions = load_submissions(input_path)
puts "Loaded #{submissions.size} submissions"
results = grade_submissions(submissions)
save_results(results, output_path)
print_summary(results)
end
end
# Run the pipeline
if __FILE__ == $PROGRAM_NAME
client = Kai::Client.new
pipeline = AutomatedGradingPipeline.new(client)
pipeline.run('submissions.csv', 'grading_results.csv')
endExample 2: Quiz Generator with Export
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'kai-sdk'
require 'json'
# Generate quizzes for multiple topics and export to various formats
class QuizGenerator
def initialize(client)
@client = client
end
def generate_quiz(topic, difficulty: 'medium', count: 10)
quiz = @client.quizzes.generate(
topic: topic,
difficulty: difficulty,
question_count: count,
question_types: ['multiple_choice', 'true_false', 'short_answer']
)
puts "\n✓ Generated quiz: #{topic}"
puts " ID: #{quiz.quiz_id}"
puts " Questions: #{quiz.question_count}"
puts " Time: ~#{quiz.estimated_time_minutes} minutes"
quiz
end
def export_to_json(quiz, filename)
quiz_data = {
quiz_id: quiz.quiz_id,
topic: quiz.topic,
difficulty: quiz.difficulty,
estimated_time_minutes: quiz.estimated_time_minutes,
questions: quiz.questions.map do |q|
{
id: q.question_id,
text: q.question_text,
type: q.question_type,
options: q.options,
correct_answer: q.correct_answer,
explanation: q.explanation
}
end
}
File.write(filename, JSON.pretty_generate(quiz_data))
puts "✓ Exported to #{filename}"
end
def export_to_markdown(quiz, filename)
File.open(filename, 'w') do |f|
f.puts "# #{quiz.topic} Quiz\n\n"
f.puts "**Difficulty:** #{quiz.difficulty}\n"
f.puts "**Estimated Time:** #{quiz.estimated_time_minutes} minutes\n\n"
f.puts "---\n\n"
quiz.each_question.with_index(1) do |question, i|
f.puts "## Question #{i}\n\n"
f.puts "#{question.question_text}\n\n"
if question.options
question.options.each do |opt|
f.puts "- #{opt}\n"
end
f.puts "\n"
end
if question.explanation
f.puts "**Explanation:** #{question.explanation}\n\n"
end
f.puts "---\n\n"
end
end
puts "✓ Exported to #{filename}"
end
def generate_all(topics)
topics.each do |config|
quiz = generate_quiz(
config[:topic],
difficulty: config[:difficulty],
count: config[:count]
)
base_name = config[:topic].downcase.gsub(' ', '_')
export_to_json(quiz, "#{base_name}_quiz.json")
export_to_markdown(quiz, "#{base_name}_quiz.md")
end
puts "\n✓ All quizzes generated and exported successfully!"
end
end
# Run the generator
if __FILE__ == $PROGRAM_NAME
client = Kai::Client.new
generator = QuizGenerator.new(client)
topics = [
{ topic: 'Ruby Fundamentals', difficulty: 'easy', count: 15 },
{ topic: 'Rails Development', difficulty: 'medium', count: 20 },
{ topic: 'Ruby Metaprogramming', difficulty: 'hard', count: 10 }
]
generator.generate_all(topics)
endExample 3: Student Analytics Dashboard
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'kai-sdk'
require 'csv'
require 'date'
# Generate comprehensive student analytics reports
class StudentAnalyticsDashboard
def initialize(client)
@client = client
end
def load_roster(csv_path)
student_ids = []
CSV.foreach(csv_path, headers: true) do |row|
student_ids << row['student_id']
end
student_ids
end
def analyze_student(student_id, course_id)
end_date = Date.today
start_date = end_date - 90 # Last 90 days
performance = @client.analytics.student_performance(
student_id: student_id,
start_date: start_date.iso8601,
end_date: end_date.iso8601,
course_id: course_id
)
{
student_id: student_id,
average_score: performance.average_score,
completion_rate: performance.completion_rate,
trend: performance.trend,
strengths: performance.strength_areas,
improvements: performance.improvement_areas
}
end
def analyze_class(student_ids, course_id)
analytics_data = []
student_ids.each do |student_id|
begin
data = analyze_student(student_id, course_id)
analytics_data << data
puts "✓ Analyzed #{student_id}"
rescue Kai::Error => e
puts "✗ Failed to analyze #{student_id}: #{e.message}"
end
end
analytics_data
end
def generate_report(analytics_data, output_dir)
Dir.mkdir(output_dir) unless Dir.exist?(output_dir)
# Save detailed CSV
csv_path = File.join(output_dir, 'class_analytics.csv')
CSV.open(csv_path, 'w') do |csv|
csv << %w[student_id average_score completion_rate trend strengths improvements]
analytics_data.each do |data|
csv << [
data[:student_id],
data[:average_score].round(1),
data[:completion_rate].round(1),
data[:trend],
data[:strengths].join('; '),
data[:improvements].join('; ')
]
end
end
# Generate summary report
summary_path = File.join(output_dir, 'class_summary.txt')
File.open(summary_path, 'w') do |f|
write_summary(f, analytics_data)
end
puts "\n✓ Reports saved to #{output_dir}/"
puts " - #{csv_path}"
puts " - #{summary_path}"
end
def write_summary(file, analytics_data)
scores = analytics_data.map { |d| d[:average_score] }
completion_rates = analytics_data.map { |d| d[:completion_rate] }
file.puts "CLASS ANALYTICS REPORT"
file.puts "=" * 50
file.puts
file.puts "Total Students: #{analytics_data.size}"
file.puts "Class Average: #{(scores.sum / scores.size).round(1)}"
file.puts "Average Completion Rate: #{(completion_rates.sum / completion_rates.size).round(1)}%"
file.puts
# Students by trend
improving = analytics_data.count { |d| d[:trend] == 'improving' }
stable = analytics_data.count { |d| d[:trend] == 'stable' }
declining = analytics_data.count { |d| d[:trend] == 'declining' }
file.puts "Student Trends:"
file.puts " Improving: #{improving} (#{(improving.to_f / analytics_data.size * 100).round(1)}%)"
file.puts " Stable: #{stable} (#{(stable.to_f / analytics_data.size * 100).round(1)}%)"
file.puts " Declining: #{declining} (#{(declining.to_f / analytics_data.size * 100).round(1)}%)"
file.puts
# Students needing attention
file.puts "Students Needing Attention:"
analytics_data.each do |data|
if data[:average_score] < 70 || data[:trend] == 'declining'
file.puts " - #{data[:student_id]}: Score #{data[:average_score].round(1)}, #{data[:trend]}"
end
end
end
def run(roster_path, course_id, output_dir)
student_ids = load_roster(roster_path)
puts "Analyzing #{student_ids.size} students..."
analytics_data = analyze_class(student_ids, course_id)
generate_report(analytics_data, output_dir)
puts "\n✓ Analytics complete!"
end
end
# Run the dashboard
if __FILE__ == $PROGRAM_NAME
client = Kai::Client.new
dashboard = StudentAnalyticsDashboard.new(client)
dashboard.run('roster.csv', 'CS101', 'analytics_report')
endRake Tasks
Create custom rake tasks for common operations:
# lib/tasks/kai.rake
namespace :kai do
desc 'Grade all pending submissions'
task grade_pending: :environment do
client = Kai::Client.new
Submission.pending.find_each do |submission|
begin
result = client.assignments.grade(
assignment_id: submission.assignment_id.to_s,
student_submission: submission.content
)
submission.update!(
score: result.score,
feedback: result.feedback,
graded_at: Time.current
)
puts "✓ Graded submission #{submission.id}"
rescue Kai::Error => e
puts "✗ Failed to grade submission #{submission.id}: #{e.message}"
end
end
end
desc 'Generate weekly analytics report'
task weekly_report: :environment do
client = Kai::Client.new
Course.find_each do |course|
analytics = client.analytics.class_analytics(course_id: course.id.to_s)
WeeklyReport.create!(
course: course,
data: analytics.to_h,
generated_at: Time.current
)
puts "✓ Generated report for #{course.name}"
end
end
endBest Practices
Use Symbols for Keys: Follow Ruby conventions
# Good: Using symbols client.assignments.grade( assignment_id: 'CS101', student_submission: 'Work...' ) # Avoid: Using strings client.assignments.grade( 'assignment_id' => 'CS101', 'student_submission' => 'Work...' )Leverage Blocks: Use Ruby’s block syntax for configuration
# Good: Block-based configuration Kai.configure do |config| config.api_key = ENV['KAI_API_KEY'] config.timeout = 30 end # Also good: Chaining with blocks quiz = client.quizzes.generate_with do |req| req.topic = 'Ruby' req.difficulty = 'medium' req.question_count = 10 endUse Safe Navigation: Handle nil values gracefully
# Good: Safe navigation score = result&.score || 0 feedback = result&.feedback&.truncate(100) # Use presence for nil/empty checks api_key = ENV['KAI_API_KEY'].presence || 'default_key'Follow Rails Patterns: Use service objects and jobs
# Good: Service object class GradingService def call(submission) # Grading logic end end # Good: Background job GradeSubmissionJob.perform_later(submission.id)Handle Errors Idiomatically: Use rescue with specific exceptions
# Good: Specific exception handling begin result = client.assignments.grade(...) rescue Kai::RateLimitError => e sleep(e.retry_after) retry rescue Kai::Error => e Rails.logger.error("Grading failed: #{e.message}") nil endUse Enumerable Methods: Leverage Ruby’s powerful enumerables
# Good: Functional style passing = results.select(&:passed?) scores = results.map(&:score) average = scores.sum / scores.size.to_f # Chain operations top_performers = results .select { |r| r.score >= 90 } .sort_by(&:score) .reverse .first(5)
Testing
RSpec Examples
# spec/services/grading_service_spec.rb
require 'rails_helper'
RSpec.describe GradingService do
let(:client) { instance_double(Kai::Client) }
let(:service) { described_class.new(client: client) }
describe '#grade_submission' do
let(:assignment) { create(:assignment) }
let(:submission) { create(:submission, assignment: assignment) }
let(:kai_result) do
Kai::GradeResult.new(
grade_id: 'grade_123',
score: 85,
feedback: 'Good work',
suggestions: ['Add more comments']
)
end
before do
allow(client.assignments).to receive(:grade).and_return(kai_result)
end
it 'grades the submission successfully' do
result = service.grade_submission(assignment, submission)
expect(result.score).to eq(85)
expect(submission.reload.score).to eq(85)
expect(submission.feedback).to eq('Good work')
end
context 'when grading fails' do
before do
allow(client.assignments).to receive(:grade)
.and_raise(Kai::APIError.new('API error'))
end
it 'handles the error gracefully' do
expect {
service.grade_submission(assignment, submission)
}.not_to raise_error
expect(submission.reload.graded_at).to be_nil
end
end
end
endLinks and Resources
SDK Resources
- GitHub Repository - Source code and issues
- RubyGems - Package distribution
- Documentation - API documentation
- Changelog - Version history
- Examples - More code examples
Support
- API Status: status.kaitheai.com
- Developer Forum: forum.kaitheai.com
- Email: developers@chi2labs.com
Star the GitHub repository to receive notifications about new releases and features.