openlayer 0.13.1 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0ebd82d46fc27b599d3a133cdfd24180321ecdb473eb723f518988030fbeadd
4
- data.tar.gz: 7eb9e3c362170b6d425fb4a3566920d8a6bc574c33865c834f39143d03dba3b0
3
+ metadata.gz: 697bde0fdcf61182dd6235f06ab2ab9439b245251284f66100a53c44a6bfc146
4
+ data.tar.gz: ea5d247e85a34a14143e82e2aa49e39e44c322d7329cbba01967afb15fb7fc5e
5
5
  SHA512:
6
- metadata.gz: 164b9d01d9d76a03df6f7336010460fa6a0811c1e80f509becb9b7989891d82763726ea554327a34a754534442919dd2840a954b5c6a68d165c7b6a5dafc2117
7
- data.tar.gz: 1409de06917f3f86d231d576941e1f2fb94a492fbc96eefd24e8b6b9e7c9bc2acc4c37426cab8dca5f54069df7e0b9ec95b2f75eb9938f56c0cf523b74ed79ad
6
+ metadata.gz: a22c1acd44798f116733781f5648b8cf74697811c09a8ed76ac73a217b803467dd3213a7efe19a1b57c13b3ff93dddd7ca0b4eb5496ea05228f559a29c830dc5
7
+ data.tar.gz: c2b731b11958e0adc38bd37b30045494a832225daf1d8c79a310fb78b8cb72e49a7765d45700c3fcf01e982a5a830faacc7bf8127b0e84806d4a79dccbfc4f8f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.1 (2026-07-06)
4
+
5
+ Full Changelog: [v0.14.0...v0.14.1](https://github.com/openlayer-ai/openlayer-ruby/compare/v0.14.0...v0.14.1)
6
+
7
+ ## 0.14.0 (2026-07-06)
8
+
9
+ Full Changelog: [v0.13.1...v0.14.0](https://github.com/openlayer-ai/openlayer-ruby/compare/v0.13.1...v0.14.0)
10
+
11
+ ### Features
12
+
13
+ * **closes OPEN-11569:** support additional columns in ConversationalSearchService tracer ([e5cd8df](https://github.com/openlayer-ai/openlayer-ruby/commit/e5cd8df9335795f7296b394ea007caea3b090475))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * **client:** send content-type header for requests with an omitted optional body ([1778b7d](https://github.com/openlayer-ai/openlayer-ruby/commit/1778b7d71ddf2c244be71648141b84666f81addd))
19
+
20
+
21
+ ### Chores
22
+
23
+ * **internal:** bound formatter parallelism to CPU count ([f8781d0](https://github.com/openlayer-ai/openlayer-ruby/commit/f8781d074e80fc95497513b400c601e246998729))
24
+
3
25
  ## 0.13.1 (2026-05-14)
4
26
 
5
27
  Full Changelog: [v0.13.0...v0.13.1](https://github.com/openlayer-ai/openlayer-ruby/compare/v0.13.0...v0.13.1)
data/README.md CHANGED
@@ -17,7 +17,7 @@ To use this gem, install via Bundler by adding the following to your application
17
17
  <!-- x-release-please-start-version -->
18
18
 
19
19
  ```ruby
20
- gem "openlayer", "~> 0.13.1"
20
+ gem "openlayer", "~> 0.14.1"
21
21
  ```
22
22
 
23
23
  <!-- x-release-please-end -->
@@ -21,15 +21,25 @@ module Openlayer
21
21
  # Openlayer::Integrations::GoogleConversationalSearchTracer.trace_client(
22
22
  # google_client,
23
23
  # openlayer_client: openlayer,
24
- # inference_pipeline_id: 'your-pipeline-id'
24
+ # inference_pipeline_id: 'your-pipeline-id',
25
+ # additional_columns: { environment: 'production' }
25
26
  # )
26
27
  #
27
- # # Now all answer_query calls are automatically traced
28
+ # # Now all answer_query calls are automatically traced! Pass
29
+ # # additional_columns on an individual call to attach data (like your
30
+ # # own trace ID) to just that row; it takes precedence over the
31
+ # # static defaults above on a key conflict.
28
32
  # response = google_client.answer_query(
29
33
  # serving_config: "projects/.../servingConfigs/default",
30
- # query: { text: "What is the meaning of life?" }
34
+ # query: { text: "What is the meaning of life?" },
35
+ # additional_columns: { trace_id: "abc-123" }
31
36
  # )
32
37
  class GoogleConversationalSearchTracer
38
+ # Row keys computed by this tracer. Any key in a caller-supplied
39
+ # additional_columns hash matching one of these is dropped, so custom
40
+ # data can never overwrite core trace fields.
41
+ RESERVED_ROW_KEYS = [:query, :answer, :latency_ms, :timestamp, :metadata, :steps, :context, :session_id, :user_id].freeze
42
+
33
43
  # Enable tracing on a Google ConversationalSearchService client
34
44
  #
35
45
  # @param client [Google::Cloud::DiscoveryEngine::V1::ConversationalSearchService::Client]
@@ -42,8 +52,12 @@ module Openlayer
42
52
  # Optional session ID to use for all traces. Takes precedence over auto-extracted sessions.
43
53
  # @param user_id [String, nil]
44
54
  # Optional user ID to use for all traces.
55
+ # @param additional_columns [Hash, nil]
56
+ # Optional static column values merged into every trace sent through this client (e.g. `{ environment: 'production' }`).
57
+ # A value passed to an individual answer_query call takes precedence over these on a key conflict. Keys colliding
58
+ # with a reserved row column (query, answer, latency_ms, timestamp, metadata, steps, context, session_id, user_id) are dropped.
45
59
  # @return [void]
46
- def self.trace_client(client, openlayer_client:, inference_pipeline_id:, session_id: nil, user_id: nil)
60
+ def self.trace_client(client, openlayer_client:, inference_pipeline_id:, session_id: nil, user_id: nil, additional_columns: {})
47
61
  # Store original method reference
48
62
  original_answer_query = client.method(:answer_query)
49
63
 
@@ -52,6 +66,10 @@ module Openlayer
52
66
  # Capture start time
53
67
  start_time = Time.now
54
68
 
69
+ # Extract per-call additional columns before forwarding to the
70
+ # real client; Google's client never sees this key
71
+ call_additional_columns = kwargs.delete(:additional_columns)
72
+
55
73
  # Execute the original method
56
74
  response = original_answer_query.call(*args, **kwargs, &block)
57
75
 
@@ -69,7 +87,9 @@ module Openlayer
69
87
  openlayer_client: openlayer_client,
70
88
  inference_pipeline_id: inference_pipeline_id,
71
89
  session_id: session_id,
72
- user_id: user_id
90
+ user_id: user_id,
91
+ additional_columns: additional_columns,
92
+ call_additional_columns: call_additional_columns
73
93
  )
74
94
  rescue StandardError => e
75
95
  # Never break the user's application due to tracing errors
@@ -95,8 +115,10 @@ module Openlayer
95
115
  # @param inference_pipeline_id [String] Pipeline ID
96
116
  # @param session_id [String, nil] Optional session ID (takes precedence over auto-extracted)
97
117
  # @param user_id [String, nil] Optional user ID
118
+ # @param additional_columns [Hash, nil] Optional static column values (see {.trace_client})
119
+ # @param call_additional_columns [Hash, nil] Optional per-call column values; takes precedence over additional_columns
98
120
  # @return [void]
99
- def self.send_trace(args:, kwargs:, response:, start_time:, end_time:, openlayer_client:, inference_pipeline_id:, session_id: nil, user_id: nil)
121
+ def self.send_trace(args:, kwargs:, response:, start_time:, end_time:, openlayer_client:, inference_pipeline_id:, session_id: nil, user_id: nil, additional_columns: {}, call_additional_columns: {})
100
122
  # Calculate latency
101
123
  latency_ms = ((end_time - start_time) * 1000).round(2)
102
124
 
@@ -199,6 +221,12 @@ module Openlayer
199
221
  trace_data[:config][:userIdColumnName] = "user_id"
200
222
  end
201
223
 
224
+ # Merge additional columns (per-call values take precedence over
225
+ # static defaults; keys colliding with reserved row columns are
226
+ # dropped so custom data can never corrupt core trace fields)
227
+ extra_columns = resolve_additional_columns(additional_columns, call_additional_columns)
228
+ trace_data[:rows][0].merge!(extra_columns) unless extra_columns.empty?
229
+
202
230
  # Send to Openlayer
203
231
  openlayer_client
204
232
  .inference_pipelines
@@ -317,6 +345,9 @@ module Openlayer
317
345
 
318
346
  # Extract references from answer
319
347
  #
348
+ # Each reference's `content` is a oneof: exactly one of chunk_info,
349
+ # structured_document_info, or unstructured_document_info is present.
350
+ #
320
351
  # @param answer [Object] Answer object from response
321
352
  # @return [Array<Hash>, nil] Array of reference hashes or nil
322
353
  def self.extract_references(answer)
@@ -326,25 +357,85 @@ module Openlayer
326
357
  return nil if references.nil? || !references.respond_to?(:each_with_index)
327
358
 
328
359
  references.each_with_index.map do |reference, index|
329
- chunk_info = safe_extract(reference, :chunk_info)
330
- next nil if chunk_info.nil?
331
-
332
- doc_metadata = safe_extract(chunk_info, :document_metadata)
333
-
334
- {
335
- reference_id: index.to_s,
336
- content: safe_extract(chunk_info, :content),
337
- relevance_score: safe_extract(chunk_info, :relevance_score)&.to_f,
338
- document_id: doc_metadata ? safe_extract(doc_metadata, :document) : nil,
339
- uri: doc_metadata ? safe_extract(doc_metadata, :uri) : nil,
340
- title: doc_metadata ? safe_extract(doc_metadata, :title) : nil
341
- }.compact
360
+ extract_reference(reference, index)
342
361
  end.compact
343
362
  rescue StandardError => e
344
363
  warn_if_debug("[Openlayer] Failed to extract references: #{e.message}")
345
364
  nil
346
365
  end
347
366
 
367
+ # Extract a single reference hash from whichever `content` oneof
368
+ # variant is present on it
369
+ #
370
+ # @param reference [Object] Reference object
371
+ # @param index [Integer] Reference's index in the references array
372
+ # @return [Hash, nil] Reference hash, or nil if no known variant is set
373
+ def self.extract_reference(reference, index)
374
+ if (chunk_info = safe_extract(reference, :chunk_info))
375
+ extract_chunk_info_reference(chunk_info, index)
376
+ elsif (structured_document_info = safe_extract(reference, :structured_document_info))
377
+ extract_structured_document_info_reference(structured_document_info, index)
378
+ elsif (unstructured_document_info = safe_extract(reference, :unstructured_document_info))
379
+ extract_unstructured_document_info_reference(unstructured_document_info, index)
380
+ end
381
+ rescue StandardError => e
382
+ warn_if_debug("[Openlayer] Failed to extract reference: #{e.message}")
383
+ nil
384
+ end
385
+
386
+ # Build a reference hash from a ChunkInfo variant
387
+ #
388
+ # @param chunk_info [Object] ChunkInfo object
389
+ # @param index [Integer] Reference's index in the references array
390
+ # @return [Hash] Reference hash
391
+ def self.extract_chunk_info_reference(chunk_info, index)
392
+ doc_metadata = safe_extract(chunk_info, :document_metadata)
393
+
394
+ {
395
+ reference_id: index.to_s,
396
+ content: safe_extract(chunk_info, :content),
397
+ relevance_score: safe_extract(chunk_info, :relevance_score)&.to_f,
398
+ document_id: doc_metadata ? safe_extract(doc_metadata, :document) : nil,
399
+ uri: doc_metadata ? safe_extract(doc_metadata, :uri) : nil,
400
+ title: doc_metadata ? safe_extract(doc_metadata, :title) : nil
401
+ }.compact
402
+ end
403
+
404
+ # Build a reference hash from a StructuredDocumentInfo variant
405
+ #
406
+ # @param structured_document_info [Object] StructuredDocumentInfo object
407
+ # @param index [Integer] Reference's index in the references array
408
+ # @return [Hash] Reference hash
409
+ def self.extract_structured_document_info_reference(structured_document_info, index)
410
+ {
411
+ reference_id: index.to_s,
412
+ document_id: safe_extract(structured_document_info, :document),
413
+ uri: safe_extract(structured_document_info, :uri),
414
+ title: safe_extract(structured_document_info, :title),
415
+ struct_data: safe_extract(structured_document_info, :struct_data)
416
+ }.compact
417
+ end
418
+
419
+ # Build a reference hash from an UnstructuredDocumentInfo variant
420
+ #
421
+ # @param unstructured_document_info [Object] UnstructuredDocumentInfo object
422
+ # @param index [Integer] Reference's index in the references array
423
+ # @return [Hash] Reference hash
424
+ def self.extract_unstructured_document_info_reference(unstructured_document_info, index)
425
+ chunk_contents = safe_extract(unstructured_document_info, :chunk_contents)
426
+ content = if chunk_contents.respond_to?(:map)
427
+ chunk_contents.map { |chunk| safe_extract(chunk, :content) }.compact.join("\n")
428
+ end
429
+
430
+ {
431
+ reference_id: index.to_s,
432
+ content: (content unless content.nil? || content.empty?),
433
+ document_id: safe_extract(unstructured_document_info, :document),
434
+ uri: safe_extract(unstructured_document_info, :uri),
435
+ title: safe_extract(unstructured_document_info, :title)
436
+ }.compact
437
+ end
438
+
348
439
  # Extract related questions from answer
349
440
  #
350
441
  # @param answer [Object] Answer object from response
@@ -594,6 +685,45 @@ module Openlayer
594
685
  nil
595
686
  end
596
687
 
688
+ # Merge static and per-call additional columns into a single Hash of
689
+ # extra row columns. Call-level values take precedence over static
690
+ # ones on key conflict, and any key colliding with a reserved row
691
+ # column is dropped.
692
+ #
693
+ # @param static_columns [Object] Value passed to trace_client (expected Hash)
694
+ # @param call_columns [Object] Value passed to an individual answer_query call (expected Hash)
695
+ # @return [Hash] Extra columns safe to merge onto a trace row
696
+ def self.resolve_additional_columns(static_columns, call_columns)
697
+ merged = normalize_additional_columns(static_columns).merge(normalize_additional_columns(call_columns))
698
+
699
+ merged.each_with_object({}) do |(key, value), result|
700
+ if RESERVED_ROW_KEYS.include?(key)
701
+ warn_if_debug("[Openlayer] additional_columns key :#{key} collides with a reserved column and was ignored")
702
+ else
703
+ result[key] = value
704
+ end
705
+ end
706
+ end
707
+
708
+ # Normalize an additional_columns value into a Hash with Symbol keys.
709
+ # Non-Hash input (or a key that can't be a Symbol) is dropped rather
710
+ # than raising, so a caller mistake can never break tracing.
711
+ #
712
+ # @param columns [Object] Expected to be a Hash of column name => value
713
+ # @return [Hash]
714
+ def self.normalize_additional_columns(columns)
715
+ return {} unless columns.is_a?(Hash)
716
+
717
+ columns.each_with_object({}) do |(key, value), result|
718
+ next unless key.respond_to?(:to_sym)
719
+
720
+ result[key.to_sym] = value
721
+ end
722
+ rescue StandardError => e
723
+ warn_if_debug("[Openlayer] Failed to normalize additional columns: #{e.message}")
724
+ {}
725
+ end
726
+
597
727
  # Safely extract a field from an object
598
728
  #
599
729
  # @param obj [Object] Object to extract from
@@ -650,6 +780,10 @@ module Openlayer
650
780
  :extract_citations,
651
781
  :extract_citation_sources,
652
782
  :extract_references,
783
+ :extract_reference,
784
+ :extract_chunk_info_reference,
785
+ :extract_structured_document_info_reference,
786
+ :extract_unstructured_document_info_reference,
653
787
  :extract_related_questions,
654
788
  :extract_steps,
655
789
  :extract_step_data,
@@ -659,6 +793,8 @@ module Openlayer
659
793
  :extract_session,
660
794
  :extract_user_pseudo_id,
661
795
  :extract_query_understanding_info,
796
+ :resolve_additional_columns,
797
+ :normalize_additional_columns,
662
798
  :safe_extract,
663
799
  :safe_count,
664
800
  :extract_timestamp
@@ -306,7 +306,10 @@ module Openlayer
306
306
  Openlayer::Internal::Util.deep_merge(*[req[:body], opts[:extra_body]].compact)
307
307
  end
308
308
 
309
- headers.delete("content-type") if body.nil?
309
+ # Generated methods always pass `req[:body]` for operations that define a
310
+ # request body, so only elide the content-type header when the operation
311
+ # has no body at all, not when an optional body param was omitted.
312
+ headers.delete("content-type") if body.nil? && !req.key?(:body)
310
313
 
311
314
  url = Openlayer::Internal::Util.join_parsed_uri(
312
315
  @base_url_components,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Openlayer
4
- VERSION = "0.13.1"
4
+ VERSION = "0.14.1"
5
5
  end
@@ -10,7 +10,8 @@ module Openlayer
10
10
  openlayer_client: Openlayer::Client,
11
11
  inference_pipeline_id: String,
12
12
  session_id: T.nilable(String),
13
- user_id: T.nilable(String)
13
+ user_id: T.nilable(String),
14
+ additional_columns: T::Hash[Symbol, T.untyped]
14
15
  ).void
15
16
  end
16
17
  def self.trace_client(
@@ -18,7 +19,8 @@ module Openlayer
18
19
  openlayer_client:,
19
20
  inference_pipeline_id:,
20
21
  session_id: nil,
21
- user_id: nil
22
+ user_id: nil,
23
+ additional_columns: {}
22
24
  )
23
25
  end
24
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openlayer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.1
4
+ version: 0.14.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Openlayer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-15 00:00:00.000000000 Z
11
+ date: 2026-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cgi