Class: Parse::Embeddings::OpenAI
- Defined in:
- lib/parse/embeddings/openai.rb
Overview
OpenAI embeddings provider. Wraps POST /v1/embeddings and the
text-embedding-3-small, text-embedding-3-large, and legacy
text-embedding-ada-002 models.
Security
- The Faraday connection refuses
ssl: { verify: false }on the production HTTPS base URL and refusesproxy:unless the caller opts in viaallow_faraday_proxy: true. Env-proxy autodiscovery (HTTPS_PROXYetc.) is suppressed by default — same model asParse::Client. #inspect(inherited from Provider) never surfaces@api_key.Authorization,OpenAI-Organization, andOpenAI-Projectheaders are added to Middleware::BodyBuilder::REDACTED_HEADERS so Faraday logging cannot leak them.
Errors
All errors inherit from Error:
- AuthenticationError — 401 from OpenAI.
- RateLimitError — 429 from OpenAI (retried up to
max_retries). - BadRequestError — 400/404 (not retried).
- TransientError — 5xx or network/timeout (retried).
- InvalidResponseError — response shape violates the contract.
Defined Under Namespace
Classes: AuthenticationError, BadRequestError, RateLimitError, TransientError
Constant Summary collapse
- DEFAULT_BASE_URL =
"https://api.openai.com/v1"- DEFAULT_MODEL =
"text-embedding-3-small"- DEFAULT_TIMEOUT =
30- DEFAULT_OPEN_TIMEOUT =
5- DEFAULT_MAX_RETRIES =
3- DEFAULT_BATCH_SIZE =
100- MAX_RESPONSE_BYTES =
Hard ceiling on the response body we'll parse. A legitimate OpenAI embeddings response for the worst-case configuration (100 inputs × text-embedding-3-large, 3072 floats × ~12 chars per encoded float) is ~3.6 MB. We allow 16 MB to leave generous headroom for usage telemetry and future fields, while still bounding the buffer an adversarial / misconfigured base_url could ship at us before the 30s timeout fires.
16 * 1024 * 1024
- MODEL_DEFAULT_DIMENSIONS =
Native vector widths for each supported model.
text-embedding-3-*also accept adimensions:parameter that truncates the output (Matryoshka-style) — when set, it overrides the native width. { "text-embedding-3-small" => 1536, "text-embedding-3-large" => 3072, "text-embedding-ada-002" => 1536, }.freeze
- MODEL_MAX_INPUT_TOKENS =
Max input tokens per item for the supported models. Provided as a chunker hint via #max_input_tokens.
{ "text-embedding-3-small" => 8191, "text-embedding-3-large" => 8191, "text-embedding-ada-002" => 8191, }.freeze
Constants inherited from Provider
Provider::AS_NOTIFICATION_NAME
Instance Method Summary collapse
- #dimensions ⇒ Object
- #embed_batch_size ⇒ Object
-
#embed_text(strings, input_type: :search_document) ⇒ Array<Array<Float>>
Vectors aligned 1:1 with
strings. -
#initialize(api_key:, model: DEFAULT_MODEL, dimensions: nil, base_url: DEFAULT_BASE_URL, organization: nil, project: nil, timeout: DEFAULT_TIMEOUT, open_timeout: DEFAULT_OPEN_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES, embed_batch_size: DEFAULT_BATCH_SIZE, allow_faraday_proxy: false, allow_insecure_base_url: false, connection: nil) ⇒ OpenAI
constructor
A new instance of OpenAI.
-
#inspect_attrs ⇒ Object
Override the Provider's safe inspect to add OpenAI-specific non-sensitive attrs.
- #max_input_tokens ⇒ Object
- #model_name ⇒ Object
- #normalize? ⇒ Boolean
- #supports_input_type? ⇒ Boolean
Methods inherited from Provider
#embed_image, #embed_text_batched, #inspect, #instrument_embed, #modalities, #validate_response!
Constructor Details
#initialize(api_key:, model: DEFAULT_MODEL, dimensions: nil, base_url: DEFAULT_BASE_URL, organization: nil, project: nil, timeout: DEFAULT_TIMEOUT, open_timeout: DEFAULT_OPEN_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES, embed_batch_size: DEFAULT_BATCH_SIZE, allow_faraday_proxy: false, allow_insecure_base_url: false, connection: nil) ⇒ OpenAI
Returns a new instance of OpenAI.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/parse/embeddings/openai.rb', line 103 def initialize( api_key:, model: DEFAULT_MODEL, dimensions: nil, base_url: DEFAULT_BASE_URL, organization: nil, project: nil, timeout: DEFAULT_TIMEOUT, open_timeout: DEFAULT_OPEN_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES, embed_batch_size: DEFAULT_BATCH_SIZE, allow_faraday_proxy: false, allow_insecure_base_url: false, connection: nil ) validate_api_key!(api_key) validate_model!(model) validate_dimensions!(model, dimensions) sanitized_base_url = validate_base_url!(base_url, allow_insecure_base_url) validate_positive_integer!(:timeout, timeout) validate_positive_integer!(:open_timeout, open_timeout) validate_non_negative_integer!(:max_retries, max_retries) validate_positive_integer!(:embed_batch_size, ) @api_key = api_key @model = model @dimensions = dimensions || MODEL_DEFAULT_DIMENSIONS.fetch(model) @base_url = sanitized_base_url @organization = organization @project = project @timeout = timeout @open_timeout = open_timeout @max_retries = max_retries @embed_batch_size = @allow_faraday_proxy = allow_faraday_proxy @connection = connection || build_connection end |
Instance Method Details
#dimensions ⇒ Object
141 142 143 |
# File 'lib/parse/embeddings/openai.rb', line 141 def dimensions @dimensions end |
#embed_batch_size ⇒ Object
149 150 151 |
# File 'lib/parse/embeddings/openai.rb', line 149 def @embed_batch_size end |
#embed_text(strings, input_type: :search_document) ⇒ Array<Array<Float>>
Returns vectors aligned 1:1 with strings.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/parse/embeddings/openai.rb', line 176 def (strings, input_type: :search_document) unless strings.is_a?(Array) raise ArgumentError, "Parse::Embeddings::OpenAI#embed_text expects Array<String> (got #{strings.class})." end return [] if strings.empty? strings.each_with_index do |s, i| unless s.is_a?(String) raise ArgumentError, "Parse::Embeddings::OpenAI#embed_text strings[#{i}] is not a String (#{s.class})." end if s.empty? raise ArgumentError, "Parse::Embeddings::OpenAI#embed_text strings[#{i}] is empty; OpenAI rejects empty inputs." end end body = { input: strings, model: @model } # `dimensions:` is only valid for text-embedding-3-*. Sending it # to ada-002 yields a 400. When the caller specified an override # we always forward it; when the model is 3-series and we're # using the default, we still forward to make the contract # explicit (and to assert the server returns what we expect). body[:dimensions] = @dimensions if @model.start_with?("text-embedding-3-") (strings.length, input_type) do |emit_payload| payload = (body) # OpenAI's response envelope carries `usage: { prompt_tokens, # total_tokens }`. Forward total_tokens (the operator-facing # cost number) into the AS::N payload so cost subscribers can # budget embedding spend on the same footing as # `parse.agent.tool_call` token cost. Defensive on shape — a # mock / proxy that strips the usage block must not crash the # request path. if payload.is_a?(Hash) && payload["usage"].is_a?(Hash) tt = payload["usage"]["total_tokens"] emit_payload[:total_tokens] = tt if tt.is_a?(Integer) && tt >= 0 end vectors = extract_vectors!(payload, strings.length) validate_response!(strings.length, vectors) end end |
#inspect_attrs ⇒ Object
Override the Provider's safe inspect to add OpenAI-specific
non-sensitive attrs. @base_url is redacted to host-only
because operators may point this provider at an Azure / Ollama
endpoint they consider sensitive — the same policy
post_embeddings applies when raising on transient errors.
224 225 226 |
# File 'lib/parse/embeddings/openai.rb', line 224 def inspect_attrs super.merge(base: safe_base_host, retries: @max_retries) end |
#max_input_tokens ⇒ Object
153 154 155 |
# File 'lib/parse/embeddings/openai.rb', line 153 def max_input_tokens MODEL_MAX_INPUT_TOKENS[@model] end |
#model_name ⇒ Object
145 146 147 |
# File 'lib/parse/embeddings/openai.rb', line 145 def model_name @model end |
#normalize? ⇒ Boolean
157 158 159 160 161 |
# File 'lib/parse/embeddings/openai.rb', line 157 def normalize? # OpenAI's text-embedding-3-* and ada-002 all return # unit-normalized vectors. Documented in the API reference. true end |
#supports_input_type? ⇒ Boolean
163 164 165 166 167 168 |
# File 'lib/parse/embeddings/openai.rb', line 163 def supports_input_type? # OpenAI does NOT distinguish search_query vs search_document. # We accept the kwarg (for cache-key stability across providers) # but it does not affect the request payload. See {#embed_text}. false end |