Claude API 루비 가이드: Rails 앱에 Claude 통합하기
Claude API를 Ruby on Rails에서 사용하려면 anthropic-sdk-ruby gem을 Gemfile에 추가하고, ANTHROPIC_API_KEY 환경변수를 설정한 뒤, Anthropic::Client를 통해 메시지를 전송하면 됩니다. 서비스 객체 패턴으로 Claude 호출을 캡슐화하면 컨트롤러를 깔끔하게 유지할 수 있습니다. ActiveJob과 연동하면 긴 응답을 백그라운드에서 처리하여 요청 타임아웃을 방지할 수 있습니다. 이 가이드에서는 gem 설치부터 프로덕션 Rails API 엔드포인트 구현, 에러 처리, 재시도 전략까지 단계별로 설명합니다.
gem 설치
Gemfile에 Claude Ruby SDK를 추가합니다:
# Gemfile
gem "anthropic-sdk-ruby", "~> 0.3"
gem "dotenv-rails", groups: [:development, :test]
bundle install
환경변수 설정:
# .env (개발 환경)
ANTHROPIC_API_KEY=sk-ant-...
Rails credentials를 사용하는 경우:
rails credentials:edit
# config/credentials.yml.enc
anthropic:
api_key: sk-ant-...
기본 메시지 생성
Claude 클라이언트를 이니셜라이저로 설정하고 첫 번째 메시지를 전송합니다:
# config/initializers/anthropic.rb
require "anthropic"
ANTHROPIC_CLIENT = Anthropic::Client.new(
api_key: ENV.fetch("ANTHROPIC_API_KEY") {
Rails.application.credentials.dig(:anthropic, :api_key)
}
)
# 기본 메시지 전송 예시 (Rails 콘솔 또는 서비스에서 사용)
response = ANTHROPIC_CLIENT.messages(
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [
{ role: "user", content: "Rails 앱의 N+1 쿼리 문제를 해결하는 방법을 알려주세요." }
]
)
puts response.content.first.text
puts "사용 토큰 — 입력: #{response.usage.input_tokens}, 출력: #{response.usage.output_tokens}"
시스템 프롬프트를 추가하면 Claude의 역할을 명확히 지정할 수 있습니다:
response = ANTHROPIC_CLIENT.messages(
model: "claude-sonnet-4-5",
max_tokens: 2048,
system: "당신은 Ruby on Rails 전문 개발자입니다. 간결한 코드 예제와 함께 답변해 주세요.",
messages: [
{ role: "user", content: "ActiveRecord에서 복잡한 조인 쿼리를 최적화하는 패턴을 알려주세요." }
]
)
스트리밍 처리
긴 응답을 실시간으로 스트리밍하면 사용자 경험을 크게 향상시킬 수 있습니다:
# app/services/claude_streaming_service.rb
class ClaudeStreamingService
def initialize
@client = ANTHROPIC_CLIENT
end
def stream(prompt, &block)
@client.messages(
model: "claude-sonnet-4-5",
max_tokens: 2048,
messages: [{ role: "user", content: prompt }],
stream: true
) do |event|
case event.type
when "content_block_delta"
block.call(event.delta.text) if event.delta.type == "text_delta"
when "message_stop"
block.call(nil) # 스트리밍 완료 신호
end
end
end
end
Server-Sent Events(SSE)로 프론트엔드에 스트리밍을 전달합니다:
# app/controllers/api/chat_controller.rb
class Api::ChatController < ApplicationController
include ActionController::Live
def stream
response.headers["Content-Type"] = "text/event-stream"
response.headers["Cache-Control"] = "no-cache"
response.headers["X-Accel-Buffering"] = "no"
service = ClaudeStreamingService.new
service.stream(params[:message]) do |chunk|
if chunk
response.stream.write("data: #{chunk.to_json}\n\n")
else
response.stream.write("data: [DONE]\n\n")
end
end
rescue ActionController::Live::ClientDisconnected
# 클라이언트 연결 종료 — 정상 처리
ensure
response.stream.close
end
end
Ruby, Python, Node.js로 Claude API를 활용하는 30개 이상의 프로덕션 레시피
Agent SDK Cookbook ($49 / ₩64,000)은 스트리밍 파이프라인, Rails 통합 패턴, 멀티에이전트 조율, 에러 처리, 비용 최적화 예제를 모두 포함합니다.
ActiveJob 백그라운드 처리
시간이 걸리는 Claude 호출은 ActiveJob으로 백그라운드 처리하여 웹 요청 타임아웃을 방지합니다:
# app/jobs/claude_analysis_job.rb
class ClaudeAnalysisJob < ApplicationJob
queue_as :default
# 재시도 전략 설정
retry_on Anthropic::RateLimitError, wait: :exponentially_longer, attempts: 5
retry_on Anthropic::APIConnectionError, wait: 3.seconds, attempts: 3
discard_on Anthropic::AuthenticationError
def perform(document_id, prompt)
document = Document.find(document_id)
response = ANTHROPIC_CLIENT.messages(
model: "claude-sonnet-4-5",
max_tokens: 4096,
system: "문서 분석 전문가입니다. 한국어로 핵심 내용을 요약해 주세요.",
messages: [
{
role: "user",
content: "다음 문서를 분석해 주세요:\n\n#{document.content}\n\n#{prompt}"
}
]
)
document.update!(
analysis: response.content.first.text,
analyzed_at: Time.current,
token_count: response.usage.input_tokens + response.usage.output_tokens
)
# 분석 완료 알림 (선택)
AnalysisMailer.completed(document).deliver_later
end
end
컨트롤러에서 Job 큐에 등록합니다:
# app/controllers/documents_controller.rb
def analyze
@document = Document.find(params[:id])
ClaudeAnalysisJob.perform_later(
@document.id,
"이 문서의 핵심 인사이트 3가지를 추출해 주세요."
)
render json: { status: "queued", document_id: @document.id }
end
Rails API 엔드포인트
프로덕션 수준의 Claude API 엔드포인트를 구성합니다:
# app/services/claude_service.rb
class ClaudeService
MODEL = "claude-sonnet-4-5"
MAX_TOKENS = 2048
def initialize
@client = ANTHROPIC_CLIENT
end
def chat(messages, system: nil)
params = {
model: MODEL,
max_tokens: MAX_TOKENS,
messages: messages
}
params[:system] = system if system.present?
response = @client.messages(**params)
{
text: response.content.first.text,
usage: {
input_tokens: response.usage.input_tokens,
output_tokens: response.usage.output_tokens
}
}
end
end
# app/controllers/api/v1/messages_controller.rb
class Api::V1::MessagesController < ApplicationController
before_action :authenticate_api_key!
def create
messages = build_messages(params[:messages])
system_prompt = params[:system]
result = ClaudeService.new.chat(messages, system: system_prompt)
render json: {
content: result[:text],
usage: result[:usage]
}, status: :ok
rescue Anthropic::RateLimitError => e
render json: { error: "요청 한도 초과. 잠시 후 다시 시도해 주세요.", code: "rate_limit" },
status: :too_many_requests
rescue Anthropic::AuthenticationError
render json: { error: "API 인증 오류.", code: "auth_error" },
status: :unauthorized
rescue Anthropic::APIError => e
Rails.logger.error("Claude API Error: #{e.message}")
render json: { error: "AI 서비스 오류가 발생했습니다.", code: "api_error" },
status: :service_unavailable
end
private
def build_messages(raw_messages)
raw_messages.map do |msg|
{ role: msg[:role], content: msg[:content] }
end
end
def authenticate_api_key!
token = request.headers["Authorization"]&.split(" ")&.last
render json: { error: "인증 필요" }, status: :unauthorized unless valid_token?(token)
end
def valid_token?(token)
ApiKey.active.exists?(token: token)
end
end
라우트를 설정합니다:
# config/routes.rb
namespace :api do
namespace :v1 do
resources :messages, only: [:create]
end
namespace :chat do
post :stream, to: "chat#stream"
end
end
에러 처리 및 재시도
견고한 에러 처리와 재시도 로직을 구현합니다:
# app/services/claude_with_retry_service.rb
class ClaudeWithRetryService
MAX_RETRIES = 3
BASE_DELAY = 1.0 # 초
def call(messages, model: "claude-haiku-4-5", max_tokens: 1024)
attempt = 0
begin
attempt += 1
ANTHROPIC_CLIENT.messages(
model: model,
max_tokens: max_tokens,
messages: messages
)
rescue Anthropic::RateLimitError => e
raise if attempt >= MAX_RETRIES
# 지수 백오프: 1초, 2초, 4초
delay = BASE_DELAY * (2 ** (attempt - 1))
Rails.logger.warn("Rate limit hit. Retry #{attempt}/#{MAX_RETRIES} in #{delay}s")
sleep(delay)
retry
rescue Anthropic::APIConnectionError => e
raise if attempt >= MAX_RETRIES
Rails.logger.warn("Connection error. Retry #{attempt}/#{MAX_RETRIES}")
sleep(BASE_DELAY)
retry
rescue Anthropic::BadRequestError => e
# 재시도 불가 에러 — 즉시 실패
Rails.logger.error("Bad request: #{e.message}")
raise
end
end
end
에러 코드별 대응 전략은 Claude API 에러 코드 레퍼런스 가이드를 참고하세요.
모델 선택에 따라 비용과 성능이 크게 달라집니다. 단순 분류 작업에는 Haiku, 복잡한 코드 분석에는 Sonnet을 사용하는 전략은 Claude Haiku vs Sonnet vs Opus: 어떤 모델을 선택해야 할까 가이드에서 자세히 확인할 수 있습니다.
Rails 프로덕션 환경에서 Claude API를 다루는 실전 예제 모음
Agent SDK Cookbook ($49 / ₩64,000)에는 ActiveJob 통합, 스트리밍 SSE 패턴, 비용 모니터링, 프롬프트 캐싱 적용, 멀티에이전트 파이프라인 레시피가 포함되어 있습니다.
Frequently Asked Questions
Claude API Ruby gem을 어떻게 설치하나요?
Gemfile에 gem "anthropic-sdk-ruby" 를 추가하고 bundle install을 실행합니다. 그런 다음 ANTHROPIC_API_KEY 환경변수를 설정합니다. Rails에서는 config/initializers/anthropic.rb 파일에 Anthropic::Client.new로 전역 클라이언트 인스턴스를 만들어 사용하는 것이 일반적입니다.
Rails에서 Claude API 호출이 타임아웃되는 경우 어떻게 처리하나요?
Claude API 응답은 수 초에서 수십 초가 걸릴 수 있어 웹 요청 타임아웃(일반적으로 30초)에 걸릴 수 있습니다. 긴 응답이 필요한 경우 ActiveJob을 사용해 백그라운드에서 처리하거나, 스트리밍과 SSE를 조합해 응답을 청크 단위로 전달하는 방식을 사용하세요. Heroku 같은 플랫폼은 30초 타임아웃이 있으므로 스트리밍이 필수입니다.
Rate Limit 에러(429)를 어떻게 처리해야 하나요?
지수 백오프(exponential backoff)를 구현하거나, ActiveJob의 retry_on Anthropic::RateLimitError, wait: :exponentially_longer 옵션을 사용하세요. 동시 요청이 많은 경우 Sidekiq의 concurrency 설정을 낮추거나, Redis 기반의 토큰 버킷 알고리즘으로 요청 속도를 제한하세요. 자세한 에러 코드 대응 방법은 Claude API 에러 코드 레퍼런스를 참고하세요.
Rails에서 Claude API 비용을 어떻게 추적하나요?
각 API 응답의 response.usage.input_tokens와 response.usage.output_tokens를 데이터베이스에 기록하세요. UsageLog 모델을 만들어 사용자, 엔드포인트, 토큰 수, 비용을 저장하면 월별 비용 분석이 가능합니다. 모델별 단가는 Haiku가 가장 저렴하므로, 단순 작업은 Haiku로 라우팅하면 비용을 크게 줄일 수 있습니다.
멀티턴 대화를 Rails에서 어떻게 구현하나요?
대화 히스토리를 세션이나 데이터베이스에 저장하고, 매 요청 시 전체 메시지 배열을 Claude API에 전달하면 됩니다. Conversation 모델과 Message 모델을 만들어 has_many :messages 관계로 관리하는 것이 일반적입니다. Claude는 자체 메모리가 없으므로 클라이언트에서 컨텍스트를 전달해야 합니다. 대화가 길어지면 오래된 메시지를 요약하여 토큰을 절약하세요.