diff --git a/docs/_core_features/embeddings.md b/docs/_core_features/embeddings.md index 085fb5b00..f794a8937 100644 --- a/docs/_core_features/embeddings.md +++ b/docs/_core_features/embeddings.md @@ -83,8 +83,25 @@ embedding_custom = RubyLLM.embed( provider: :openai, assume_model_exists: true ) + +# TwelveLabs Marengo multimodal embeddings (512 dimensions) +embedding_twelvelabs = RubyLLM.embed( + "a cat playing piano", + model: "marengo3.0", + provider: :twelvelabs +) ``` +TwelveLabs is an opt-in provider for multimodal (video) embeddings. Configure it with your API key: + +```ruby +RubyLLM.configure do |config| + config.twelvelabs_api_key = ENV["TWELVELABS_API_KEY"] +end +``` + +Marengo returns a single 512-dimensional float vector and accepts one text input per call. Grab a free API key at [twelvelabs.io](https://twelvelabs.io). + You can configure the default embedding model globally: ```ruby diff --git a/docs/_getting_started/configuration-reference.md b/docs/_getting_started/configuration-reference.md index 19acf77f1..421030eb1 100644 --- a/docs/_getting_started/configuration-reference.md +++ b/docs/_getting_started/configuration-reference.md @@ -84,6 +84,10 @@ RubyLLM.configure do |config| config.perplexity_api_key = String config.perplexity_api_base = String # v1.16+ + # TwelveLabs (multimodal/video embeddings via Marengo) + config.twelvelabs_api_key = String + config.twelvelabs_api_base = String + # Vertex AI config.vertexai_project_id = String # GCP project ID config.vertexai_location = String # e.g., 'us-central1' diff --git a/lib/ruby_llm.rb b/lib/ruby_llm.rb index 06d22287e..c9e7f94df 100644 --- a/lib/ruby_llm.rb +++ b/lib/ruby_llm.rb @@ -29,6 +29,7 @@ 'pdf' => 'PDF', 'perplexity' => 'Perplexity', 'ruby_llm' => 'RubyLLM', + 'twelvelabs' => 'TwelveLabs', 'vertexai' => 'VertexAI', 'xai' => 'XAI' ) @@ -132,6 +133,7 @@ def logger RubyLLM::Provider.register :openai, RubyLLM::Providers::OpenAI RubyLLM::Provider.register :openrouter, RubyLLM::Providers::OpenRouter RubyLLM::Provider.register :perplexity, RubyLLM::Providers::Perplexity +RubyLLM::Provider.register :twelvelabs, RubyLLM::Providers::TwelveLabs RubyLLM::Provider.register :vertexai, RubyLLM::Providers::VertexAI RubyLLM::Provider.register :xai, RubyLLM::Providers::XAI diff --git a/lib/ruby_llm/providers/twelvelabs.rb b/lib/ruby_llm/providers/twelvelabs.rb new file mode 100644 index 000000000..dba315e77 --- /dev/null +++ b/lib/ruby_llm/providers/twelvelabs.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module RubyLLM + module Providers + # TwelveLabs API integration. + # + # TwelveLabs provides multimodal video understanding (Pegasus) and + # multimodal embeddings (Marengo). This provider wires up Marengo text + # embeddings, which return a 512-dimensional float vector and fit RubyLLM's + # synchronous embedding interface. + class TwelveLabs < Provider + protocol :twelvelabs, API + + def api_base + @config.twelvelabs_api_base || 'https://api.twelvelabs.io/v1.3' + end + + def headers + { + 'x-api-key' => @config.twelvelabs_api_key + } + end + + class << self + def capabilities + TwelveLabs::Capabilities + end + + def configuration_options + %i[twelvelabs_api_key twelvelabs_api_base] + end + + def configuration_requirements + %i[twelvelabs_api_key] + end + + # TwelveLabs models are not in the models.dev registry. + def assume_models_exist? + true + end + end + end + end +end diff --git a/lib/ruby_llm/providers/twelvelabs/api.rb b/lib/ruby_llm/providers/twelvelabs/api.rb new file mode 100644 index 000000000..ebd74db31 --- /dev/null +++ b/lib/ruby_llm/providers/twelvelabs/api.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module RubyLLM + module Providers + class TwelveLabs + # TwelveLabs' embedding API protocol (Marengo). + # + # Only the embedding surface is implemented; chat/completion is not part + # of the TwelveLabs API. Models are assumed to exist (see the provider's + # `assume_models_exist?`), so `list_models` returns an empty set rather + # than calling a non-existent listing endpoint. + class API < Protocol + include TwelveLabs::Embeddings + + def list_models + [] + end + end + end + end +end diff --git a/lib/ruby_llm/providers/twelvelabs/capabilities.rb b/lib/ruby_llm/providers/twelvelabs/capabilities.rb new file mode 100644 index 000000000..ed50e5f61 --- /dev/null +++ b/lib/ruby_llm/providers/twelvelabs/capabilities.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module RubyLLM + module Providers + class TwelveLabs + # Provider-level capability checks for TwelveLabs models. + # + # TwelveLabs models are not listed in the models.dev registry, so the + # provider assumes models exist and falls back to these defaults. + module Capabilities + module_function + + # Marengo returns a fixed 512-dimensional embedding vector. + EMBEDDING_DIMENSIONS = 512 + + def context_window_for(_model_id) + nil + end + + def max_tokens_for(_model_id) + nil + end + + def supports_vision?(_model_id) + true + end + + def pricing_for(_model_id) + {} + end + end + end + end +end diff --git a/lib/ruby_llm/providers/twelvelabs/embeddings.rb b/lib/ruby_llm/providers/twelvelabs/embeddings.rb new file mode 100644 index 000000000..abc78c395 --- /dev/null +++ b/lib/ruby_llm/providers/twelvelabs/embeddings.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module RubyLLM + module Providers + class TwelveLabs + # Embeddings methods for the TwelveLabs (Marengo) API. + # + # TwelveLabs exposes a single multimodal embedding model family (Marengo). + # The text path is synchronous and returns a 512-dimensional float vector + # per input. The endpoint expects `multipart/form-data` rather than JSON, + # so payloads are built from `Faraday::Multipart::ParamPart`s to engage + # Faraday's multipart middleware. + module Embeddings + module_function + + def embedding_url(...) + 'embed' + end + + def render_embedding_payload(text, model:, dimensions:) # rubocop:disable Lint/UnusedMethodArgument + inputs = text.is_a?(Array) ? text : [text] + raise Error.new(nil, 'TwelveLabs embeddings accept a single text input') if inputs.size > 1 + + { + model_name: param_part(model), + text: param_part(inputs.first.to_s) + } + end + + def parse_embedding_response(response, model:, text:) + segments = response.body.dig('text_embedding', 'segments') || [] + vectors = segments.first&.fetch('float', nil) + vectors = [vectors] if text.is_a?(Array) + + Embedding.new(vectors:, model: response.body['model_name'] || model, input_tokens: 0) + end + + def param_part(value) + require 'faraday/multipart' + Faraday::Multipart::ParamPart.new(value.to_s, 'text/plain') + end + end + end + end +end diff --git a/spec/fixtures/vcr_cassettes/providers_twelvelabs_embeddings_marengo_text_embeddings_returns_a_single_512-dimensional_float_vector.yml b/spec/fixtures/vcr_cassettes/providers_twelvelabs_embeddings_marengo_text_embeddings_returns_a_single_512-dimensional_float_vector.yml new file mode 100644 index 000000000..9e752171d --- /dev/null +++ b/spec/fixtures/vcr_cassettes/providers_twelvelabs_embeddings_marengo_text_embeddings_returns_a_single_512-dimensional_float_vector.yml @@ -0,0 +1,63 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.twelvelabs.io/v1.3/embed + body: + encoding: UTF-8 + string: "-------------RubyMultipartPost-068b13b0aee335056049262d0940bfb7\r\nContent-Disposition: + form-data; name=\"model_name\"\r\nContent-Type: text/plain\r\n\r\nmarengo3.0\r\n-------------RubyMultipartPost-068b13b0aee335056049262d0940bfb7\r\nContent-Disposition: + form-data; name=\"text\"\r\nContent-Type: text/plain\r\n\r\na cat\r\n-------------RubyMultipartPost-068b13b0aee335056049262d0940bfb7--\r\n" + headers: + User-Agent: + - Faraday v2.14.3 + X-Api-Key: + - "" + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-068b13b0aee335056049262d0940bfb7 + Content-Length: + - '368' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Wed, 24 Jun 2026 04:19:17 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Tl-Report: + - backend + Vary: + - Accept-Encoding + X-Ratelimit-Dimensions: + - Duration,Request + X-Ratelimit-Duration-Limit: + - '144000' + X-Ratelimit-Duration-Remaining: + - '143999' + X-Ratelimit-Duration-Reset: + - '1782277433' + X-Ratelimit-Request-Limit: + - '2400' + X-Ratelimit-Request-Remaining: + - '2399' + X-Ratelimit-Request-Reset: + - '1782274817' + X-Trace-Id: + - 4cd31f85c7606b28938fb6bd6f23b2c1 + body: + encoding: ASCII-8BIT + string: '{"model_name":"marengo3.0","text_embedding":{"segments":[{"float":[-0.06982422,-0.06347656,0.008300781,-0.026733398,-0.046875,-0.020751953,-0.08154297,-0.08203125,-0.015258789,0.002532959,0.048339844,0.08300781,0.024291992,0.04736328,0.03491211,-0.029541016,-0.0025024414,-0.052001953,-0.008117676,-0.056884766,-0.016479492,0.083984375,-0.028686523,0.01928711,-0.04248047,-0.008300781,-0.048339844,0.01586914,-0.010620117,0.08203125,-0.07714844,-0.049804688,-0.014587402,-0.0032806396,-0.0031738281,-0.020874023,-0.040527344,0.07763672,0.03515625,0.017333984,0.049804688,-0.037353516,0.071777344,-0.04345703,-0.036621094,0.048339844,-0.055419922,-0.044677734,0.014892578,0.020263672,0.10107422,-0.037597656,-0.0048828125,0.0025634766,0.045410156,0.03466797,-0.008911133,-0.007446289,0.091796875,0.016845703,0.15136719,-0.038330078,-0.013305664,0.03955078,0.024169922,-0.018676758,-0.029907227,-0.049072266,-0.045654297,0.07470703,-0.00022983551,-0.024291992,0.048583984,-0.0234375,0.068847656,0.05810547,-0.033691406,-0.046142578,0.05883789,-0.040527344,0.00793457,0.06982422,0.0546875,0.020263672,0.05883789,0.047607422,0.051757812,0.055664062,-0.010986328,0.059570312,-0.046142578,-0.010925293,0.01977539,-0.072753906,0.04321289,-0.030517578,-0.052001953,0.017578125,0.029174805,-0.06640625,-0.056884766,-0.016967773,-0.027832031,-0.009460449,0.0064086914,-0.037597656,-0.0026245117,0.041259766,-0.02331543,-0.12207031,-0.011352539,0.092285156,-0.063964844,0.0029449463,-0.038330078,0.11621094,-0.004760742,0.030273438,-0.030639648,0.06298828,0.0075683594,-0.064941406,-0.0052490234,0.0007324219,0.0057678223,0.033203125,-0.044433594,0.044433594,0.05126953,-0.025024414,-0.083496094,-0.0019836426,-0.041259766,-0.010498047,-0.022338867,0.05493164,0.037597656,0.009094238,-0.010131836,-0.033691406,0.060546875,0.012084961,0.0033721924,-0.022338867,-0.03491211,0.03515625,0.044189453,-0.017822266,0.061035156,-0.01940918,-0.10498047,-0.029174805,0.07128906,0.008422852,-0.016113281,-0.010070801,-0.017822266,-0.03930664,-0.07763672,-0.017089844,0.0026245117,-0.032958984,0.04321289,-0.0016403198,0.003829956,-0.075683594,-0.025878906,0.013549805,-0.049316406,0.0078125,0.08105469,0.05834961,0.056152344,0.022827148,0.026733398,-0.038085938,0.05810547,-0.005584717,-0.03881836,-0.021728516,0.0063171387,0.050048828,0.044189453,0.049072266,-0.0010070801,-0.06347656,0.0066833496,-0.043701172,-0.008850098,-0.0234375,-0.080566406,-0.016113281,0.016845703,0.0027160645,-0.032470703,-0.032226562,-0.017822266,-0.033935547,0.046875,-0.07861328,-0.032470703,-0.0033416748,-0.033447266,0.095703125,-0.020263672,0.049072266,0.05883789,-0.047607422,-0.095214844,0.005554199,-0.013000488,-0.07861328,-0.003479004,0.03955078,-0.011657715,0.026611328,-0.064941406,-0.08691406,-0.020385742,0.006652832,0.041015625,0.07470703,-0.002029419,0.061279297,0.003967285,-0.020141602,-0.040527344,-0.01940918,-0.011413574,-0.010437012,-0.023803711,0.00982666,0.020507812,0.057373047,-0.024902344,-0.051757812,-0.024291992,-0.07128906,-0.063964844,0.024169922,0.01373291,0.01574707,-0.040283203,0.02709961,0.072753906,-0.02709961,0.025024414,0.032470703,-0.072753906,-0.06640625,0.026611328,-0.015991211,0.0077209473,-0.007659912,-0.07128906,0.016967773,-0.03100586,-0.015258789,-0.033935547,-0.024291992,0.0013580322,-0.0075683594,-0.026245117,-0.03149414,0.015625,0.04296875,-0.013366699,0.03149414,0.025878906,0.024169922,0.03540039,0.0134887695,-0.049316406,-0.007659912,0.013916016,-0.03564453,-0.02734375,0.037597656,-0.08105469,0.010437012,-0.072753906,-0.029052734,0.029907227,-0.072753906,0.029785156,-0.041259766,0.067871094,-0.00390625,-0.0390625,0.029174805,0.021484375,0.10839844,0.034179688,-0.00023651123,0.020385742,-0.053222656,0.05493164,0.013183594,0.010131836,0.044189453,-0.05126953,0.032470703,-0.03125,-0.068359375,-0.017700195,0.006958008,0.03149414,0.010559082,-0.038330078,0.01928711,0.00015640259,0.006866455,0.044189453,0.012084961,-0.00793457,0.05834961,0.059570312,-0.012451172,-0.067871094,0.041748047,0.0099487305,0.0859375,-0.06640625,-0.0007286072,0.026367188,-0.06347656,-0.0035095215,-0.017333984,-0.0020446777,0.04345703,-0.02331543,-0.0075683594,-0.037597656,0.08300781,-0.11816406,-0.044677734,-0.044677734,0.010314941,0.010498047,0.063964844,-0.047607422,0.084472656,0.002822876,-0.017333984,-0.034423828,0.05810547,-0.00046539307,-0.017700195,0.025878906,-0.087890625,0.006591797,0.07470703,0.07373047,-0.095214844,0.08154297,0.01953125,0.003326416,0.0023345947,-0.017089844,0.040527344,0.13574219,-0.036621094,0.029174805,0.010986328,-0.0008773804,0.052001953,-0.041015625,-0.013916016,-0.0012817383,-0.024291992,0.056884766,0.016845703,-0.03515625,0.03173828,0.0625,-0.044677734,0.020996094,-0.018188477,0.00982666,-0.013366699,0.045654297,0.00062179565,-0.044921875,-0.030883789,-0.029418945,-0.015319824,0.021484375,0.037353516,0.026733398,-0.08300781,-0.07373047,-0.004638672,-0.036376953,-0.009216309,-0.002456665,0.024291992,0.028198242,0.06591797,-0.030273438,0.10644531,-0.033203125,0.026611328,0.04711914,-0.045654297,-0.08105469,0.03955078,0.047607422,-0.025634766,-0.0546875,0.008728027,0.036621094,-0.016235352,-0.045410156,-0.044433594,0.017578125,0.07080078,0.08105469,0.000062942505,0.048339844,-0.10107422,0.008544922,0.02734375,-0.05053711,-0.01940918,0.03930664,0.008911133,0.024169922,-0.019165039,-0.013305664,0.040527344,0.040771484,-0.07470703,0.052001953,-0.029785156,-0.029418945,0.032470703,-0.016601562,0.018310547,0.029785156,-0.00793457,-0.03955078,0.018798828,-0.05078125,0.06982422,-0.032470703,0.032958984,-0.020141602,-0.019042969,-0.025634766,0.044677734,-0.018432617,0.009460449,-0.051757812,-0.014709473,0.000029206276,-0.0134887695,-0.017944336,0.009094238,-0.012145996,-0.026000977,-0.08642578,0.010314941,0.020874023,-0.011962891,0.044921875,0.022827148,0.008911133,-0.06689453,0.029541016,0.024291992,0.07910156,-0.049072266,-0.023071289,0.064941406,-0.014953613,0.02734375,0.0032348633,-0.06591797,-0.0034332275,-0.0079956055,-0.013183594,0.03515625,0.055664062,0.029785156,0.05908203,-0.040283203,0.03100586,0.014038086,0.0066833496,0.020263672,-0.007537842,-0.009338379,0.01977539,0.015075684,-0.0072021484,0.0076293945,0.05810547,0.012817383,-0.107910156,0.0064086914,-0.003768921,-0.061767578,0.088378906,-0.057617188,0.104003906,-0.03564453,-0.026611328,-0.020629883,0.03491211,-0.021362305,0.114746094,-0.010620117]}]}} + + ' + recorded_at: Wed, 24 Jun 2026 04:19:17 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/fixtures/vcr_cassettes/providers_twelvelabs_embeddings_marengo_text_embeddings_wraps_a_single-string_array_in_one_nested_vector.yml b/spec/fixtures/vcr_cassettes/providers_twelvelabs_embeddings_marengo_text_embeddings_wraps_a_single-string_array_in_one_nested_vector.yml new file mode 100644 index 000000000..ceb585fae --- /dev/null +++ b/spec/fixtures/vcr_cassettes/providers_twelvelabs_embeddings_marengo_text_embeddings_wraps_a_single-string_array_in_one_nested_vector.yml @@ -0,0 +1,63 @@ +--- +http_interactions: +- request: + method: post + uri: https://api.twelvelabs.io/v1.3/embed + body: + encoding: UTF-8 + string: "-------------RubyMultipartPost-62d26724bdc66437a1b62b21353c774e\r\nContent-Disposition: + form-data; name=\"model_name\"\r\nContent-Type: text/plain\r\n\r\nmarengo3.0\r\n-------------RubyMultipartPost-62d26724bdc66437a1b62b21353c774e\r\nContent-Disposition: + form-data; name=\"text\"\r\nContent-Type: text/plain\r\n\r\na cat\r\n-------------RubyMultipartPost-62d26724bdc66437a1b62b21353c774e--\r\n" + headers: + User-Agent: + - Faraday v2.14.3 + X-Api-Key: + - "" + Content-Type: + - multipart/form-data; boundary=-----------RubyMultipartPost-62d26724bdc66437a1b62b21353c774e + Content-Length: + - '368' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Wed, 24 Jun 2026 04:19:17 GMT + Content-Type: + - application/json + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Tl-Report: + - backend + Vary: + - Accept-Encoding + X-Ratelimit-Dimensions: + - Duration,Request + X-Ratelimit-Duration-Limit: + - '144000' + X-Ratelimit-Duration-Remaining: + - '143999' + X-Ratelimit-Duration-Reset: + - '1782277433' + X-Ratelimit-Request-Limit: + - '2400' + X-Ratelimit-Request-Remaining: + - '2398' + X-Ratelimit-Request-Reset: + - '1782274817' + X-Trace-Id: + - 3a4b29fb30006d18846ca9f0ea5e6868 + body: + encoding: ASCII-8BIT + string: '{"model_name":"marengo3.0","text_embedding":{"segments":[{"float":[-0.06982422,-0.06347656,0.008300781,-0.026733398,-0.046875,-0.020751953,-0.08154297,-0.08203125,-0.015258789,0.002532959,0.048339844,0.08300781,0.024291992,0.04736328,0.03491211,-0.029541016,-0.0025024414,-0.052001953,-0.008117676,-0.056884766,-0.016479492,0.083984375,-0.028686523,0.01928711,-0.04248047,-0.008300781,-0.048339844,0.01586914,-0.010620117,0.08203125,-0.07714844,-0.049804688,-0.014587402,-0.0032806396,-0.0031738281,-0.020874023,-0.040527344,0.07763672,0.03515625,0.017333984,0.049804688,-0.037353516,0.071777344,-0.04345703,-0.036621094,0.048339844,-0.055419922,-0.044677734,0.014892578,0.020263672,0.10107422,-0.037597656,-0.0048828125,0.0025634766,0.045410156,0.03466797,-0.008911133,-0.007446289,0.091796875,0.016845703,0.15136719,-0.038330078,-0.013305664,0.03955078,0.024169922,-0.018676758,-0.029907227,-0.049072266,-0.045654297,0.07470703,-0.00022983551,-0.024291992,0.048583984,-0.0234375,0.068847656,0.05810547,-0.033691406,-0.046142578,0.05883789,-0.040527344,0.00793457,0.06982422,0.0546875,0.020263672,0.05883789,0.047607422,0.051757812,0.055664062,-0.010986328,0.059570312,-0.046142578,-0.010925293,0.01977539,-0.072753906,0.04321289,-0.030517578,-0.052001953,0.017578125,0.029174805,-0.06640625,-0.056884766,-0.016967773,-0.027832031,-0.009460449,0.0064086914,-0.037597656,-0.0026245117,0.041259766,-0.02331543,-0.12207031,-0.011352539,0.092285156,-0.063964844,0.0029449463,-0.038330078,0.11621094,-0.004760742,0.030273438,-0.030639648,0.06298828,0.0075683594,-0.064941406,-0.0052490234,0.0007324219,0.0057678223,0.033203125,-0.044433594,0.044433594,0.05126953,-0.025024414,-0.083496094,-0.0019836426,-0.041259766,-0.010498047,-0.022338867,0.05493164,0.037597656,0.009094238,-0.010131836,-0.033691406,0.060546875,0.012084961,0.0033721924,-0.022338867,-0.03491211,0.03515625,0.044189453,-0.017822266,0.061035156,-0.01940918,-0.10498047,-0.029174805,0.07128906,0.008422852,-0.016113281,-0.010070801,-0.017822266,-0.03930664,-0.07763672,-0.017089844,0.0026245117,-0.032958984,0.04321289,-0.0016403198,0.003829956,-0.075683594,-0.025878906,0.013549805,-0.049316406,0.0078125,0.08105469,0.05834961,0.056152344,0.022827148,0.026733398,-0.038085938,0.05810547,-0.005584717,-0.03881836,-0.021728516,0.0063171387,0.050048828,0.044189453,0.049072266,-0.0010070801,-0.06347656,0.0066833496,-0.043701172,-0.008850098,-0.0234375,-0.080566406,-0.016113281,0.016845703,0.0027160645,-0.032470703,-0.032226562,-0.017822266,-0.033935547,0.046875,-0.07861328,-0.032470703,-0.0033416748,-0.033447266,0.095703125,-0.020263672,0.049072266,0.05883789,-0.047607422,-0.095214844,0.005554199,-0.013000488,-0.07861328,-0.003479004,0.03955078,-0.011657715,0.026611328,-0.064941406,-0.08691406,-0.020385742,0.006652832,0.041015625,0.07470703,-0.002029419,0.061279297,0.003967285,-0.020141602,-0.040527344,-0.01940918,-0.011413574,-0.010437012,-0.023803711,0.00982666,0.020507812,0.057373047,-0.024902344,-0.051757812,-0.024291992,-0.07128906,-0.063964844,0.024169922,0.01373291,0.01574707,-0.040283203,0.02709961,0.072753906,-0.02709961,0.025024414,0.032470703,-0.072753906,-0.06640625,0.026611328,-0.015991211,0.0077209473,-0.007659912,-0.07128906,0.016967773,-0.03100586,-0.015258789,-0.033935547,-0.024291992,0.0013580322,-0.0075683594,-0.026245117,-0.03149414,0.015625,0.04296875,-0.013366699,0.03149414,0.025878906,0.024169922,0.03540039,0.0134887695,-0.049316406,-0.007659912,0.013916016,-0.03564453,-0.02734375,0.037597656,-0.08105469,0.010437012,-0.072753906,-0.029052734,0.029907227,-0.072753906,0.029785156,-0.041259766,0.067871094,-0.00390625,-0.0390625,0.029174805,0.021484375,0.10839844,0.034179688,-0.00023651123,0.020385742,-0.053222656,0.05493164,0.013183594,0.010131836,0.044189453,-0.05126953,0.032470703,-0.03125,-0.068359375,-0.017700195,0.006958008,0.03149414,0.010559082,-0.038330078,0.01928711,0.00015640259,0.006866455,0.044189453,0.012084961,-0.00793457,0.05834961,0.059570312,-0.012451172,-0.067871094,0.041748047,0.0099487305,0.0859375,-0.06640625,-0.0007286072,0.026367188,-0.06347656,-0.0035095215,-0.017333984,-0.0020446777,0.04345703,-0.02331543,-0.0075683594,-0.037597656,0.08300781,-0.11816406,-0.044677734,-0.044677734,0.010314941,0.010498047,0.063964844,-0.047607422,0.084472656,0.002822876,-0.017333984,-0.034423828,0.05810547,-0.00046539307,-0.017700195,0.025878906,-0.087890625,0.006591797,0.07470703,0.07373047,-0.095214844,0.08154297,0.01953125,0.003326416,0.0023345947,-0.017089844,0.040527344,0.13574219,-0.036621094,0.029174805,0.010986328,-0.0008773804,0.052001953,-0.041015625,-0.013916016,-0.0012817383,-0.024291992,0.056884766,0.016845703,-0.03515625,0.03173828,0.0625,-0.044677734,0.020996094,-0.018188477,0.00982666,-0.013366699,0.045654297,0.00062179565,-0.044921875,-0.030883789,-0.029418945,-0.015319824,0.021484375,0.037353516,0.026733398,-0.08300781,-0.07373047,-0.004638672,-0.036376953,-0.009216309,-0.002456665,0.024291992,0.028198242,0.06591797,-0.030273438,0.10644531,-0.033203125,0.026611328,0.04711914,-0.045654297,-0.08105469,0.03955078,0.047607422,-0.025634766,-0.0546875,0.008728027,0.036621094,-0.016235352,-0.045410156,-0.044433594,0.017578125,0.07080078,0.08105469,0.000062942505,0.048339844,-0.10107422,0.008544922,0.02734375,-0.05053711,-0.01940918,0.03930664,0.008911133,0.024169922,-0.019165039,-0.013305664,0.040527344,0.040771484,-0.07470703,0.052001953,-0.029785156,-0.029418945,0.032470703,-0.016601562,0.018310547,0.029785156,-0.00793457,-0.03955078,0.018798828,-0.05078125,0.06982422,-0.032470703,0.032958984,-0.020141602,-0.019042969,-0.025634766,0.044677734,-0.018432617,0.009460449,-0.051757812,-0.014709473,0.000029206276,-0.0134887695,-0.017944336,0.009094238,-0.012145996,-0.026000977,-0.08642578,0.010314941,0.020874023,-0.011962891,0.044921875,0.022827148,0.008911133,-0.06689453,0.029541016,0.024291992,0.07910156,-0.049072266,-0.023071289,0.064941406,-0.014953613,0.02734375,0.0032348633,-0.06591797,-0.0034332275,-0.0079956055,-0.013183594,0.03515625,0.055664062,0.029785156,0.05908203,-0.040283203,0.03100586,0.014038086,0.0066833496,0.020263672,-0.007537842,-0.009338379,0.01977539,0.015075684,-0.0072021484,0.0076293945,0.05810547,0.012817383,-0.107910156,0.0064086914,-0.003768921,-0.061767578,0.088378906,-0.057617188,0.104003906,-0.03564453,-0.026611328,-0.020629883,0.03491211,-0.021362305,0.114746094,-0.010620117]}]}} + + ' + recorded_at: Wed, 24 Jun 2026 04:19:17 GMT +recorded_with: VCR 6.4.0 diff --git a/spec/ruby_llm/provider_spec.rb b/spec/ruby_llm/provider_spec.rb index 898a7dca2..208c9e4a1 100644 --- a/spec/ruby_llm/provider_spec.rb +++ b/spec/ruby_llm/provider_spec.rb @@ -68,6 +68,12 @@ def api_base_cases custom: 'https://perplexity-proxy.example.com', default: 'https://api.perplexity.ai' }, + twelvelabs: { + provider: RubyLLM::Providers::TwelveLabs, + key: :twelvelabs_api_base, + custom: 'https://twelvelabs-proxy.example.com/v1.3', + default: 'https://api.twelvelabs.io/v1.3' + }, vertexai: { provider: RubyLLM::Providers::VertexAI, key: :vertexai_api_base, @@ -113,6 +119,8 @@ def config_for(slug) config.openrouter_api_key = 'openrouter-key' when :perplexity config.perplexity_api_key = 'perplexity-key' + when :twelvelabs + config.twelvelabs_api_key = 'twelvelabs-key' when :vertexai config.vertexai_project_id = 'vertex-project' config.vertexai_location = 'us-east1' diff --git a/spec/ruby_llm/providers/twelve_labs/embeddings_spec.rb b/spec/ruby_llm/providers/twelve_labs/embeddings_spec.rb new file mode 100644 index 000000000..6da9bfda3 --- /dev/null +++ b/spec/ruby_llm/providers/twelve_labs/embeddings_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe RubyLLM::Providers::TwelveLabs::Embeddings do + include_context 'with configured RubyLLM' + + let(:model) { 'marengo3.0' } + + describe 'Marengo text embeddings' do + it 'returns a single 512-dimensional float vector' do + embedding = RubyLLM.embed('a cat', model: model, provider: :twelvelabs) + + expect(embedding.vectors).to be_an(Array) + expect(embedding.vectors.length).to eq(512) + expect(embedding.vectors.first).to be_a(Float) + expect(embedding.model).to eq(model) + end + + it 'wraps a single-string array in one nested vector' do + embedding = RubyLLM.embed(['a cat'], model: model, provider: :twelvelabs) + + expect(embedding.vectors).to be_an(Array) + expect(embedding.vectors.size).to eq(1) + expect(embedding.vectors.first).to be_an(Array) + expect(embedding.vectors.first.length).to eq(512) + end + + it 'rejects multiple inputs, which the embed endpoint does not support' do + expect do + RubyLLM.embed(%w[a b], model: model, provider: :twelvelabs) + end.to raise_error(RubyLLM::Error, /single text input/) + end + end +end diff --git a/spec/support/rubyllm_configuration.rb b/spec/support/rubyllm_configuration.rb index b31b4aa52..580bfa821 100644 --- a/spec/support/rubyllm_configuration.rb +++ b/spec/support/rubyllm_configuration.rb @@ -38,6 +38,7 @@ config.retry_backoff_factor = 0 config.retry_interval = 0 config.retry_interval_randomness = 0 + config.twelvelabs_api_key = ENV.fetch('TWELVELABS_API_KEY', 'test') config.vertexai_location = ENV.fetch('GOOGLE_CLOUD_LOCATION', 'global') config.vertexai_batch_gcs_uri = ENV.fetch('VERTEXAI_BATCH_GCS_URI', 'gs://ruby-llm-test/batches') config.vertexai_project_id = ENV.fetch('GOOGLE_CLOUD_PROJECT', 'test-project') diff --git a/spec/support/vcr_configuration.rb b/spec/support/vcr_configuration.rb index 52772be4f..ccd19d4ed 100644 --- a/spec/support/vcr_configuration.rb +++ b/spec/support/vcr_configuration.rb @@ -37,6 +37,7 @@ config.filter_sensitive_data('') { ENV.fetch('OPENROUTER_API_KEY', nil) } config.filter_sensitive_data('') { ENV.fetch('PERPLEXITY_API_KEY', nil) } config.filter_sensitive_data('') { ENV.fetch('XAI_API_KEY', nil) } + config.filter_sensitive_data('') { ENV.fetch('TWELVELABS_API_KEY', nil) } # Filter Google OAuth tokens and credentials config.filter_sensitive_data('') do |interaction|