Class: Legion::Extensions::Llm::Vertex::Provider

Inherits:
Provider
  • Object
show all
Defined in:
lib/legion/extensions/llm/vertex/provider.rb

Overview

Google Cloud Vertex AI provider implementation for the Legion::Extensions::Llm 0.8.0 canonical contract.

The base funnel (chat/stream_chat -> complete) is the single completion path: it enforces Canonical::Message inputs centrally and returns Canonical::Response (sync) or yields Canonical::Chunk (stream). This class owns the Vertex wire dialect only — render_payload renders FROM canonical values; parse_completion_response and build_chunk parse TO canonical types (08 R1-R4). The provider-native offering read path is gone: discover_offerings serves the SSOT registry snapshot (07 C5) and the discovery actor's writer is the sole publication path.

Defined Under Namespace

Modules: Capabilities

Constant Summary collapse

STATIC_MODELS =
[
  { model: 'gemini-2.5-flash', alias: 'gemini-flash', publisher: 'google', model_family: :gemini },
  { model: 'gemini-2.5-pro', alias: 'gemini-pro', publisher: 'google', model_family: :gemini },
  { model: 'gemini-embedding-001', alias: 'gemini-embedding', publisher: 'google',
    model_family: :gemini, usage_type: :embedding },
  { model: 'text-embedding-005', alias: 'text-embedding', publisher: 'google',
    model_family: :gemini, usage_type: :embedding },
  { model: 'claude-sonnet-4-5', alias: 'claude-sonnet', publisher: 'anthropic',
    model_family: :anthropic, api: :raw_predict },
  { model: 'mistral-medium-3', alias: 'mistral-medium', publisher: 'mistralai',
    model_family: :mistral, api: :raw_predict },
  { model: 'llama-4-maverick', alias: 'llama-4-maverick', publisher: 'meta',
    model_family: :meta, api: :raw_predict }
].freeze
ALIASES =
STATIC_MODELS.to_h { |entry| [entry.fetch(:alias), entry.fetch(:model)] }.freeze
PUBLISHERS =
STATIC_MODELS.to_h { |entry| [entry.fetch(:model), entry.fetch(:publisher)] }.freeze
API_MODES =
STATIC_MODELS.to_h { |entry| [entry.fetch(:model), entry.fetch(:api, :generate_content)] }.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.capabilitiesObject



61
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 61

def capabilities = Capabilities

.configuration_optionsObject



48
49
50
51
52
53
54
55
56
57
58
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 48

def configuration_options
  %i[
    vertex_project
    vertex_location
    vertex_api_base
    vertex_access_token
    vertex_credentials
    vertex_model_aliases
    vertex_discovery_live
  ]
end

.configuration_requirementsObject



60
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 60

def configuration_requirements = []

.default_tierObject



46
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 46

def default_tier = :cloud

.default_transportObject



45
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 45

def default_transport = :http

.resolve_model_id(model_id, config: nil) ⇒ Object



63
64
65
66
67
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 63

def resolve_model_id(model_id, config: nil)
  configured_aliases = config&.vertex_model_aliases
  aliases = ALIASES.merge((configured_aliases || {}).transform_keys(&:to_s))
  aliases.fetch(model_id.to_s, model_id.to_s)
end

.slugObject



44
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 44

def slug = 'vertex'

Instance Method Details

#api_baseObject



87
88
89
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 87

def api_base
  config.vertex_api_base || "https://#{location}-aiplatform.googleapis.com/v1"
end

#completion_urlObject

The base funnel posts to these model-scoped endpoints. render_payload pins @model before the base sync/stream response path resolves them.

Raises:

  • (ArgumentError)


103
104
105
106
107
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 103

def completion_url
  raise ArgumentError, 'model is required for completion_url' if @model.nil?

  chat_url(@model, stream: false)
end

#count_tokens(messages:, model:, params: nil) ⇒ Object



163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 163

def count_tokens(messages:, model:, params: nil)
  _ = [params]
  model_id = model_id(model)
  unless generate_content_model?(model_id)
    raise NotImplementedError, "Vertex countTokens for #{model_id} is not standardized"
  end

  enforce_canonical_messages!(messages)
  payload = { contents: format_messages(messages) }
  response = connection.post(count_tokens_url(model: model_id), payload)
  response.body['totalTokens']
end

#count_tokens_url(model:) ⇒ Object



115
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 115

def count_tokens_url(model:) = "#{publisher_model_path(model)}:countTokens"

#default_publisherObject



98
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 98

def default_publisher = 'google'

#embed(text:, model:, dimensions: nil, task_type: nil, title: nil, params: nil, headers: {}) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 176

def embed(text:, model:, dimensions: nil, task_type: nil, title: nil, params: nil, headers: {})
  _ = [params]
  enforce_model_allowed!(model)
  model_id = model_id(model)
  unless Capabilities.embeddings?(model_id)
    raise NotImplementedError, "Vertex embedding payload for #{model_id} is not standardized"
  end

  instances = Array(text).map { |item| embedding_instance(item, task_type:, title:) }
  parameters = { outputDimensionality: dimensions }.compact
  payload = { instances: instances, parameters: parameters }
  response = connection.post(embedding_url(model: model_id), payload) do |req|
    req.headers = headers.merge(req.headers) unless headers.empty?
  end
  parse_embedding_response(response, model: model_id, text: text)
end

#embedding_url(model:) ⇒ Object



116
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 116

def embedding_url(model:) = "#{publisher_model_path(model)}:predict"

#generate_content_url(model:) ⇒ Object



118
119
120
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 118

def generate_content_url(model:)
  "#{publisher_model_path(model)}:generateContent"
end

#headersObject



91
92
93
94
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 91

def headers
  identity_headers.merge({ 'Authorization' => bearer_token,
                           'Content-Type' => 'application/json; charset=utf-8' }.compact)
end

#health(live: false) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 138

def health(live: false)
  log.info { "checking health live=#{live} project=#{project} location=#{location}" }
  baseline = {
    provider: :vertex,
    project: project,
    location: location,
    configured: configured?,
    ready: configured?,
    live: live,
    credentials: credential_source
  }
  return baseline.merge(checked: false) unless live

  connection.get(models_url)
  baseline.merge(checked: true)
rescue StandardError => e
  handle_exception(e, level: :warn, handled: true, operation: 'vertex.provider.health')
  baseline.merge(checked: true, ready: false, error: e.class.name, message: e.message)
end

#list_models(**_filters) ⇒ Object



131
132
133
134
135
136
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 131

def list_models(**_filters)
  log.info { 'listing available Vertex models from static catalog' }
  STATIC_MODELS.map { |entry| entry[:model] }.tap do |models|
    log.info { "listed #{models.size} Vertex model(s)" }
  end
end

#locationObject



97
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 97

def location = config.vertex_location

#models_urlObject



99
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 99

def models_url = publisher_parent

#projectObject



96
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 96

def project = config.vertex_project || ENV.fetch('GOOGLE_CLOUD_PROJECT', nil)

#raw_predict_url(model:, stream: false) ⇒ Object



126
127
128
129
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 126

def raw_predict_url(model:, stream: false)
  suffix = stream ? 'streamRawPredict' : 'rawPredict'
  "#{publisher_model_path(model)}:#{suffix}"
end

#readiness(live: false) ⇒ Object



158
159
160
161
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 158

def readiness(live: false)
  health(live:).merge(local: false, remote: true, api_base: api_base,
                      endpoints: endpoint_manifest)
end

#stream_generate_content_url(model:) ⇒ Object



122
123
124
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 122

def stream_generate_content_url(model:)
  "#{publisher_model_path(model)}:streamGenerateContent?alt=sse"
end

#stream_urlObject

Raises:

  • (ArgumentError)


109
110
111
112
113
# File 'lib/legion/extensions/llm/vertex/provider.rb', line 109

def stream_url
  raise ArgumentError, 'model is required for stream_url' if @model.nil?

  chat_url(@model, stream: true)
end