Class: Tina4::Ai
- Inherits:
-
Object
- Object
- Tina4::Ai
- Defined in:
- lib/tina4/ai_client.rb
Overview
Zero-dependency app-facing AI client (ADR-0053).
Constant Summary collapse
- PROVIDERS =
%w[local openai anthropic].freeze
Class Method Summary collapse
- .chat(messages, model: nil, temperature: nil, max_tokens: nil, stream: false, timeout: nil, provider: nil) ⇒ Object
- .complete(prompt, **options) ⇒ Object
- .embed(text_or_texts, model: nil, timeout: nil, provider: nil) ⇒ Object
Class Method Details
.chat(messages, model: nil, temperature: nil, max_tokens: nil, stream: false, timeout: nil, provider: nil) ⇒ Object
29 30 31 32 33 34 35 36 |
# File 'lib/tina4/ai_client.rb', line 29 def chat(, model: nil, temperature: nil, max_tokens: nil, stream: false, timeout: nil, provider: nil) () config = resolve_config("chat", model, timeout, provider) body = chat_body(config, , temperature, max_tokens, stream) return stream_request(config, headers(config), body) if stream normalize_chat(config[:provider], request_json(config, headers(config), body)) end |
.complete(prompt, **options) ⇒ Object
38 39 40 41 42 43 |
# File 'lib/tina4/ai_client.rb', line 38 def complete(prompt, **) raise AiConfigError, "AI prompt must be a string" unless prompt.is_a?(String) .delete(:stream) chat([{ role: "user", content: prompt }], **, stream: false).text end |
.embed(text_or_texts, model: nil, timeout: nil, provider: nil) ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/tina4/ai_client.rb', line 45 def (text_or_texts, model: nil, timeout: nil, provider: nil) single = text_or_texts.is_a?(String) valid_batch = text_or_texts.is_a?(Array) && !text_or_texts.empty? && text_or_texts.all? { |item| item.is_a?(String) } raise AiConfigError, "AI embedding input must be a string or a non-empty list of strings" unless single || valid_batch config = resolve_config("embed", model, timeout, provider) raise AiConfigError, "Anthropic does not provide the embedding endpoint in this contract" if config[:provider] == "anthropic" raw = request_json(config, headers(config), { model: config[:model], input: text_or_texts }) begin data = raw.fetch("data").sort_by { |item| item.fetch("index", 0) } vectors = data.map { |item| item.fetch("embedding") } expected = single ? 1 : text_or_texts.length valid = vectors.length == expected && vectors.all? do |vector| vector.is_a?(Array) && !vector.empty? && vector.all? { |value| value.is_a?(Numeric) } end raise KeyError unless valid rescue KeyError, TypeError raise AiParseError, "AI provider returned a malformed embedding response" end single ? vectors.first : vectors end |