Files
sure/app/models/provider/llm_concept.rb
Guillem Arias c1dbb51553 feat(ai): add Anthropic provider with chat parity (1/5)
Introduces Provider::Anthropic alongside Provider::Openai, implementing
the LlmConcept chat_response contract over the official anthropic Ruby
SDK. Batch ops, PDF, and RAG land in follow-up PRs.

- Provider::Anthropic uses Messages API for sync and streaming responses
- ChatConfig builds requests with ephemeral prompt-cache markers on the
  system prompt and the last tool definition
- MessageFormatter reconstructs multi-turn history (text + tool_use +
  tool_result blocks) from raw Message records, including the paired
  user-role tool_result turn Anthropic requires after every tool_use
- ChatParser maps Anthropic Message into the shared ChatResponse Data
- Registry, Setting, User, Chat default model wired for ANTHROPIC_*
  envs and Setting.anthropic_*; LLM_PROVIDER selects between providers
- Responder forwards raw conversation_history (Array<Message>) so
  providers without hosted conversation state can rebuild context
- OpenAI provider accepts and ignores the new kwarg (no behavior change)

Tests cover provider init, model gating, MessageFormatter for all turn
shapes, ChatConfig request building (max_tokens, system cache, tool
conversion), ChatParser for text / tool_use / mixed blocks, Registry
discovery, and mocked chat_response success / error / function_request
paths. Live VCR cassettes recorded in a follow-up with a real key.

Stacked PRs: 2/5 batch ops + cost ledger, 3/5 PDF, 4/5 pgvector RAG,
5/5 settings UI + disclosure.
2026-05-25 16:29:53 +02:00

53 lines
1.5 KiB
Ruby

module Provider::LlmConcept
extend ActiveSupport::Concern
AutoCategorization = Data.define(:transaction_id, :category_name)
def auto_categorize(transactions)
raise NotImplementedError, "Subclasses must implement #auto_categorize"
end
AutoDetectedMerchant = Data.define(:transaction_id, :business_name, :business_url)
def auto_detect_merchants(transactions)
raise NotImplementedError, "Subclasses must implement #auto_detect_merchants"
end
EnhancedMerchant = Data.define(:merchant_id, :business_url)
def enhance_provider_merchants(merchants)
raise NotImplementedError, "Subclasses must implement #enhance_provider_merchants"
end
PdfProcessingResult = Data.define(:summary, :document_type, :extracted_data)
def supports_pdf_processing?
false
end
def process_pdf(pdf_content:, family: nil)
raise NotImplementedError, "Provider does not support PDF processing"
end
ChatMessage = Data.define(:id, :output_text)
ChatStreamChunk = Data.define(:type, :data, :usage)
ChatResponse = Data.define(:id, :model, :messages, :function_requests)
ChatFunctionRequest = Data.define(:id, :call_id, :function_name, :function_args)
def chat_response(
prompt,
model:,
instructions: nil,
functions: [],
function_results: [],
messages: nil,
conversation_history: [],
streamer: nil,
previous_response_id: nil,
session_id: nil,
user_identifier: nil
)
raise NotImplementedError, "Subclasses must implement #chat_response"
end
end