Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/_core_features/embeddings.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions docs/_getting_started/configuration-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions lib/ruby_llm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
'pdf' => 'PDF',
'perplexity' => 'Perplexity',
'ruby_llm' => 'RubyLLM',
'twelvelabs' => 'TwelveLabs',
'vertexai' => 'VertexAI',
'xai' => 'XAI'
)
Expand Down Expand Up @@ -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

Expand Down
44 changes: 44 additions & 0 deletions lib/ruby_llm/providers/twelvelabs.rb
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions lib/ruby_llm/providers/twelvelabs/api.rb
Original file line number Diff line number Diff line change
@@ -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
34 changes: 34 additions & 0 deletions lib/ruby_llm/providers/twelvelabs/capabilities.rb
Original file line number Diff line number Diff line change
@@ -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
45 changes: 45 additions & 0 deletions lib/ruby_llm/providers/twelvelabs/embeddings.rb
Original file line number Diff line number Diff line change
@@ -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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading