openlayer 0.7.1 → 0.8.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 +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +1 -1
- data/lib/openlayer/integrations/google_conversational_search_tracer.rb +340 -25
- data/lib/openlayer/version.rb +1 -1
- data/rbi/openlayer/integrations.rbi +6 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2012084e7121e1610abd934759b02dea258adbf82bb08fed4975e793793b0bf
|
|
4
|
+
data.tar.gz: 2326a997d8b2a24a01e80007e002f5141a6b0ce5b06e769350172689f68197ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d39d0ebbd0068ff855e3071021c6d8ab30d01c0c76c12ae61cdd3419eef0400b33f68249aeb682768e96b7f9c5a0bd72dfb6607cff34830d2c616c04ac83be5
|
|
7
|
+
data.tar.gz: 2a766e20dcbf8f279658818a25a2c5fcd6739ca208ef9a91313e90943434e4256326f00bb699adfa8e21269c36f363a6709bfac2bff79d7d5f7a8e10a5555c1e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.1 (2026-01-06)
|
|
4
|
+
|
|
5
|
+
Full Changelog: [v0.8.0...v0.8.1](https://github.com/openlayer-ai/openlayer-ruby/compare/v0.8.0...v0.8.1)
|
|
6
|
+
|
|
7
|
+
### Chores
|
|
8
|
+
|
|
9
|
+
* **closes OPEN-8602:** parse document chunks from ConversationalSearchService response ([afb7d2e](https://github.com/openlayer-ai/openlayer-ruby/commit/afb7d2e7ea00f809c27e382bc410bc4ea1571c7f))
|
|
10
|
+
* **closes OPEN-8603:** represent nested steps for ConversationalSearchService traces ([e5026c7](https://github.com/openlayer-ai/openlayer-ruby/commit/e5026c72738e77610da528eff1fa826a67ffa5fe))
|
|
11
|
+
|
|
12
|
+
## 0.8.0 (2026-01-05)
|
|
13
|
+
|
|
14
|
+
Full Changelog: [v0.7.1...v0.8.0](https://github.com/openlayer-ai/openlayer-ruby/compare/v0.7.1...v0.8.0)
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
* **closes OPEN-8574:** session and user id support for the ConversationalSearchService tracer ([9157c52](https://github.com/openlayer-ai/openlayer-ruby/commit/9157c528195ae1a3c56ba652b8b598a2c2e53eab))
|
|
19
|
+
|
|
3
20
|
## 0.7.1 (2025-12-19)
|
|
4
21
|
|
|
5
22
|
Full Changelog: [v0.7.0...v0.7.1](https://github.com/openlayer-ai/openlayer-ruby/compare/v0.7.0...v0.7.1)
|
data/README.md
CHANGED
|
@@ -38,8 +38,12 @@ module Openlayer
|
|
|
38
38
|
# The Openlayer client instance for sending traces
|
|
39
39
|
# @param inference_pipeline_id [String]
|
|
40
40
|
# The Openlayer inference pipeline ID to send traces to
|
|
41
|
+
# @param session_id [String, nil]
|
|
42
|
+
# Optional session ID to use for all traces. Takes precedence over auto-extracted sessions.
|
|
43
|
+
# @param user_id [String, nil]
|
|
44
|
+
# Optional user ID to use for all traces.
|
|
41
45
|
# @return [void]
|
|
42
|
-
def self.trace_client(client, openlayer_client:, inference_pipeline_id:)
|
|
46
|
+
def self.trace_client(client, openlayer_client:, inference_pipeline_id:, session_id: nil, user_id: nil)
|
|
43
47
|
# Store original method reference
|
|
44
48
|
original_answer_query = client.method(:answer_query)
|
|
45
49
|
|
|
@@ -63,7 +67,9 @@ module Openlayer
|
|
|
63
67
|
start_time: start_time,
|
|
64
68
|
end_time: end_time,
|
|
65
69
|
openlayer_client: openlayer_client,
|
|
66
|
-
inference_pipeline_id: inference_pipeline_id
|
|
70
|
+
inference_pipeline_id: inference_pipeline_id,
|
|
71
|
+
session_id: session_id,
|
|
72
|
+
user_id: user_id
|
|
67
73
|
)
|
|
68
74
|
rescue StandardError => e
|
|
69
75
|
# Never break the user's application due to tracing errors
|
|
@@ -87,8 +93,10 @@ module Openlayer
|
|
|
87
93
|
# @param end_time [Time] Request end time
|
|
88
94
|
# @param openlayer_client [Openlayer::Client] Openlayer client instance
|
|
89
95
|
# @param inference_pipeline_id [String] Pipeline ID
|
|
96
|
+
# @param session_id [String, nil] Optional session ID (takes precedence over auto-extracted)
|
|
97
|
+
# @param user_id [String, nil] Optional user ID
|
|
90
98
|
# @return [void]
|
|
91
|
-
def self.send_trace(args:, kwargs:, response:, start_time:, end_time:, openlayer_client:, inference_pipeline_id:)
|
|
99
|
+
def self.send_trace(args:, kwargs:, response:, start_time:, end_time:, openlayer_client:, inference_pipeline_id:, session_id: nil, user_id: nil)
|
|
92
100
|
# Calculate latency
|
|
93
101
|
latency_ms = ((end_time - start_time) * 1000).round(2)
|
|
94
102
|
|
|
@@ -105,6 +113,51 @@ module Openlayer
|
|
|
105
113
|
prompt_tokens = (query_text.length / 4.0).ceil
|
|
106
114
|
completion_tokens = (answer_data[:answer_text].length / 4.0).ceil
|
|
107
115
|
|
|
116
|
+
# Extract grounding information from metadata for step root level
|
|
117
|
+
citations = metadata.delete(:citations)
|
|
118
|
+
references = metadata.delete(:references)
|
|
119
|
+
related_questions = metadata.delete(:relatedQuestions)
|
|
120
|
+
|
|
121
|
+
# Extract context from references (array of content strings)
|
|
122
|
+
context = if references && references.is_a?(Array)
|
|
123
|
+
references.map { |ref| ref[:content] }.compact
|
|
124
|
+
else
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Extract nested steps (Google's execution steps)
|
|
129
|
+
answer = response.respond_to?(:answer) ? response.answer : nil
|
|
130
|
+
nested_steps = answer ? extract_steps(answer, query_text) : nil
|
|
131
|
+
|
|
132
|
+
# Build step object
|
|
133
|
+
step = {
|
|
134
|
+
name: "Conversational Search answer_query",
|
|
135
|
+
type: "chat_completion",
|
|
136
|
+
provider: "Google",
|
|
137
|
+
startTime: start_time.to_i,
|
|
138
|
+
endTime: end_time.to_i,
|
|
139
|
+
latency: latency_ms,
|
|
140
|
+
metadata: metadata,
|
|
141
|
+
inputs: {
|
|
142
|
+
prompt: [
|
|
143
|
+
{role: "user", content: query_text}
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
output: answer_data[:answer_text],
|
|
147
|
+
promptTokens: prompt_tokens,
|
|
148
|
+
completionTokens: completion_tokens,
|
|
149
|
+
tokens: prompt_tokens + completion_tokens,
|
|
150
|
+
model: "google-discovery-engine"
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Add grounding information at step root level
|
|
154
|
+
step[:citations] = citations if citations
|
|
155
|
+
step[:references] = references if references
|
|
156
|
+
step[:relatedQuestions] = related_questions if related_questions
|
|
157
|
+
|
|
158
|
+
# Add nested steps (Google's execution steps as RetrieverSteps)
|
|
159
|
+
step[:steps] = nested_steps if nested_steps && !nested_steps.empty?
|
|
160
|
+
|
|
108
161
|
# Build trace data in Openlayer format
|
|
109
162
|
trace_data = {
|
|
110
163
|
config: {
|
|
@@ -120,31 +173,32 @@ module Openlayer
|
|
|
120
173
|
latency_ms: latency_ms,
|
|
121
174
|
timestamp: start_time.to_i,
|
|
122
175
|
metadata: metadata,
|
|
123
|
-
steps: [
|
|
124
|
-
{
|
|
125
|
-
name: "Conversational Search answer_query",
|
|
126
|
-
type: "chat_completion",
|
|
127
|
-
provider: "Google",
|
|
128
|
-
startTime: start_time.to_i,
|
|
129
|
-
endTime: end_time.to_i,
|
|
130
|
-
latency: latency_ms,
|
|
131
|
-
metadata: metadata,
|
|
132
|
-
inputs: {
|
|
133
|
-
prompt: [
|
|
134
|
-
{role: "user", content: query_text}
|
|
135
|
-
]
|
|
136
|
-
},
|
|
137
|
-
output: answer_data[:answer_text],
|
|
138
|
-
promptTokens: prompt_tokens,
|
|
139
|
-
completionTokens: completion_tokens,
|
|
140
|
-
tokens: prompt_tokens + completion_tokens,
|
|
141
|
-
model: "google-discovery-engine"
|
|
142
|
-
}
|
|
143
|
-
]
|
|
176
|
+
steps: [step]
|
|
144
177
|
}
|
|
145
178
|
]
|
|
146
179
|
}
|
|
147
180
|
|
|
181
|
+
# Add context column if available
|
|
182
|
+
if context && !context.empty?
|
|
183
|
+
trace_data[:rows][0][:context] = context
|
|
184
|
+
trace_data[:config][:contextColumnName] = "context"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Determine which session to use (kwarg takes precedence over auto-extracted)
|
|
188
|
+
final_session = session_id || metadata[:session]
|
|
189
|
+
if final_session
|
|
190
|
+
trace_data[:rows][0][:session_id] = final_session
|
|
191
|
+
trace_data[:config][:sessionIdColumnName] = "session_id"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Determine which user_id to use (kwarg takes precedence over auto-extracted)
|
|
195
|
+
user_pseudo_id = extract_user_pseudo_id(response)
|
|
196
|
+
final_user_id = user_id || user_pseudo_id
|
|
197
|
+
if final_user_id
|
|
198
|
+
trace_data[:rows][0][:user_id] = final_user_id
|
|
199
|
+
trace_data[:config][:userIdColumnName] = "user_id"
|
|
200
|
+
end
|
|
201
|
+
|
|
148
202
|
# Send to Openlayer
|
|
149
203
|
openlayer_client
|
|
150
204
|
.inference_pipelines
|
|
@@ -202,20 +256,208 @@ module Openlayer
|
|
|
202
256
|
answer = response.answer
|
|
203
257
|
return {answer_text: nil} if answer.nil?
|
|
204
258
|
|
|
259
|
+
# Extract answer_skipped_reasons if present
|
|
260
|
+
answer_skipped_reasons = safe_extract(answer, :answer_skipped_reasons)
|
|
261
|
+
answer_skipped_reasons = if answer_skipped_reasons && answer_skipped_reasons.respond_to?(:to_a)
|
|
262
|
+
answer_skipped_reasons.to_a.map(&:to_s).compact
|
|
263
|
+
end
|
|
264
|
+
|
|
205
265
|
{
|
|
206
266
|
answer_text: safe_extract(answer, :answer_text),
|
|
267
|
+
answer_name: safe_extract(answer, :name),
|
|
207
268
|
state: safe_extract(answer, :state)&.to_s,
|
|
208
269
|
grounding_score: safe_extract(answer, :grounding_score),
|
|
209
270
|
create_time: extract_timestamp(answer, :create_time),
|
|
210
271
|
complete_time: extract_timestamp(answer, :complete_time),
|
|
211
272
|
citations_count: safe_count(answer, :citations),
|
|
212
|
-
references_count: safe_count(answer, :references)
|
|
273
|
+
references_count: safe_count(answer, :references),
|
|
274
|
+
answer_skipped_reasons: answer_skipped_reasons
|
|
213
275
|
}
|
|
214
276
|
rescue StandardError => e
|
|
215
277
|
warn_if_debug("[Openlayer] Failed to extract answer data: #{e.message}")
|
|
216
278
|
{answer_text: nil}
|
|
217
279
|
end
|
|
218
280
|
|
|
281
|
+
# Extract citations from answer
|
|
282
|
+
#
|
|
283
|
+
# @param answer [Object] Answer object from response
|
|
284
|
+
# @return [Array<Hash>, nil] Array of citation hashes or nil
|
|
285
|
+
def self.extract_citations(answer)
|
|
286
|
+
return nil unless answer && answer.respond_to?(:citations)
|
|
287
|
+
|
|
288
|
+
citations = safe_extract(answer, :citations)
|
|
289
|
+
return nil if citations.nil? || !citations.respond_to?(:map)
|
|
290
|
+
|
|
291
|
+
citations.map do |citation|
|
|
292
|
+
{
|
|
293
|
+
start_index: safe_extract(citation, :start_index)&.to_i,
|
|
294
|
+
end_index: safe_extract(citation, :end_index)&.to_i,
|
|
295
|
+
sources: extract_citation_sources(citation)
|
|
296
|
+
}.compact
|
|
297
|
+
end
|
|
298
|
+
rescue StandardError => e
|
|
299
|
+
warn_if_debug("[Openlayer] Failed to extract citations: #{e.message}")
|
|
300
|
+
nil
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Extract sources from a citation
|
|
304
|
+
#
|
|
305
|
+
# @param citation [Object] Citation object
|
|
306
|
+
# @return [Array<Hash>, nil] Array of source hashes or nil
|
|
307
|
+
def self.extract_citation_sources(citation)
|
|
308
|
+
sources = safe_extract(citation, :sources)
|
|
309
|
+
return nil if sources.nil? || !sources.respond_to?(:map)
|
|
310
|
+
|
|
311
|
+
sources.map do |source|
|
|
312
|
+
{reference_id: safe_extract(source, :reference_id)}.compact
|
|
313
|
+
end
|
|
314
|
+
rescue StandardError
|
|
315
|
+
nil
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Extract references from answer
|
|
319
|
+
#
|
|
320
|
+
# @param answer [Object] Answer object from response
|
|
321
|
+
# @return [Array<Hash>, nil] Array of reference hashes or nil
|
|
322
|
+
def self.extract_references(answer)
|
|
323
|
+
return nil unless answer && answer.respond_to?(:references)
|
|
324
|
+
|
|
325
|
+
references = safe_extract(answer, :references)
|
|
326
|
+
return nil if references.nil? || !references.respond_to?(:each_with_index)
|
|
327
|
+
|
|
328
|
+
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
|
|
342
|
+
end.compact
|
|
343
|
+
rescue StandardError => e
|
|
344
|
+
warn_if_debug("[Openlayer] Failed to extract references: #{e.message}")
|
|
345
|
+
nil
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Extract related questions from answer
|
|
349
|
+
#
|
|
350
|
+
# @param answer [Object] Answer object from response
|
|
351
|
+
# @return [Array<String>, nil] Array of related questions or nil
|
|
352
|
+
def self.extract_related_questions(answer)
|
|
353
|
+
return nil unless answer && answer.respond_to?(:related_questions)
|
|
354
|
+
|
|
355
|
+
questions = safe_extract(answer, :related_questions)
|
|
356
|
+
return nil if questions.nil? || !questions.respond_to?(:to_a)
|
|
357
|
+
|
|
358
|
+
questions.to_a.map(&:to_s).compact
|
|
359
|
+
rescue StandardError => e
|
|
360
|
+
warn_if_debug("[Openlayer] Failed to extract related questions: #{e.message}")
|
|
361
|
+
nil
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Extract execution steps from answer
|
|
365
|
+
#
|
|
366
|
+
# @param answer [Object] Answer object from response
|
|
367
|
+
# @param original_query [String, nil] Original user query for comparison
|
|
368
|
+
# @return [Array<Hash>, nil] Array of step hashes or nil
|
|
369
|
+
def self.extract_steps(answer, original_query = nil)
|
|
370
|
+
return nil unless answer && answer.respond_to?(:steps)
|
|
371
|
+
|
|
372
|
+
steps = safe_extract(answer, :steps)
|
|
373
|
+
return nil if steps.nil? || !steps.respond_to?(:map)
|
|
374
|
+
|
|
375
|
+
steps.map do |step|
|
|
376
|
+
extract_step_data(step, original_query)
|
|
377
|
+
end.compact
|
|
378
|
+
rescue StandardError => e
|
|
379
|
+
warn_if_debug("[Openlayer] Failed to extract steps: #{e.message}")
|
|
380
|
+
nil
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Extract data from a single step
|
|
384
|
+
#
|
|
385
|
+
# @param step [Object] Step object
|
|
386
|
+
# @param original_query [String, nil] Original user query for comparison
|
|
387
|
+
# @return [Hash, nil] Step hash or nil
|
|
388
|
+
def self.extract_step_data(step, original_query = nil)
|
|
389
|
+
return nil if step.nil?
|
|
390
|
+
|
|
391
|
+
# Extract basic step info
|
|
392
|
+
description = safe_extract(step, :description)
|
|
393
|
+
state = safe_extract(step, :state)&.to_s
|
|
394
|
+
actions = safe_extract(step, :actions)
|
|
395
|
+
|
|
396
|
+
# Extract search action and results from first action
|
|
397
|
+
search_query = nil
|
|
398
|
+
search_results = nil
|
|
399
|
+
|
|
400
|
+
if actions && actions.respond_to?(:first)
|
|
401
|
+
first_action = actions.first
|
|
402
|
+
if first_action
|
|
403
|
+
search_action = safe_extract(first_action, :search_action)
|
|
404
|
+
observation = safe_extract(first_action, :observation)
|
|
405
|
+
|
|
406
|
+
search_query = safe_extract(search_action, :query) if search_action
|
|
407
|
+
search_results = safe_extract(observation, :search_results) if observation
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Build inputs showing both original and rephrased queries
|
|
412
|
+
inputs = if search_query
|
|
413
|
+
result = {rephrased_query: search_query}
|
|
414
|
+
result[:original_query] = original_query if original_query && original_query != search_query
|
|
415
|
+
result
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# Map to OpenLayer RetrieverStep format
|
|
419
|
+
{
|
|
420
|
+
name: (description ? description.gsub(/\.\z/, "") : "Search and retrieve documents"),
|
|
421
|
+
type: "retriever",
|
|
422
|
+
inputs: inputs,
|
|
423
|
+
output: search_results ? {num_results: search_results.length} : nil,
|
|
424
|
+
documents: extract_search_results(search_results),
|
|
425
|
+
metadata: {
|
|
426
|
+
state: state,
|
|
427
|
+
action_type: "searchAction"
|
|
428
|
+
}.compact
|
|
429
|
+
}.compact
|
|
430
|
+
rescue StandardError => e
|
|
431
|
+
warn_if_debug("[Openlayer] Failed to extract step data: #{e.message}")
|
|
432
|
+
nil
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# Extract search results as documents
|
|
436
|
+
#
|
|
437
|
+
# @param search_results [Array] Array of search result objects
|
|
438
|
+
# @return [Array<Hash>, nil] Array of document hashes or nil
|
|
439
|
+
def self.extract_search_results(search_results)
|
|
440
|
+
return nil if search_results.nil? || !search_results.respond_to?(:map)
|
|
441
|
+
|
|
442
|
+
search_results.map do |result|
|
|
443
|
+
snippet_info = safe_extract(result, :snippet_info)
|
|
444
|
+
snippet = if snippet_info && snippet_info.respond_to?(:first)
|
|
445
|
+
first_snippet = snippet_info.first
|
|
446
|
+
safe_extract(first_snippet, :snippet) if first_snippet
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
{
|
|
450
|
+
id: safe_extract(result, :document),
|
|
451
|
+
uri: safe_extract(result, :uri),
|
|
452
|
+
title: safe_extract(result, :title),
|
|
453
|
+
snippet: snippet
|
|
454
|
+
}.compact
|
|
455
|
+
end.compact
|
|
456
|
+
rescue StandardError => e
|
|
457
|
+
warn_if_debug("[Openlayer] Failed to extract search results: #{e.message}")
|
|
458
|
+
nil
|
|
459
|
+
end
|
|
460
|
+
|
|
219
461
|
# Extract metadata from request and response
|
|
220
462
|
#
|
|
221
463
|
# @param args [Array] Positional arguments
|
|
@@ -225,6 +467,7 @@ module Openlayer
|
|
|
225
467
|
# @return [Hash] Metadata hash
|
|
226
468
|
def self.extract_metadata(args, kwargs, response, latency_ms)
|
|
227
469
|
answer_data = extract_answer_data(response)
|
|
470
|
+
answer = response.respond_to?(:answer) ? response.answer : nil
|
|
228
471
|
|
|
229
472
|
metadata = {
|
|
230
473
|
provider: "google",
|
|
@@ -233,15 +476,36 @@ module Openlayer
|
|
|
233
476
|
}
|
|
234
477
|
|
|
235
478
|
# Add answer metadata
|
|
479
|
+
metadata[:answer_name] = answer_data[:answer_name] if answer_data[:answer_name]
|
|
236
480
|
metadata[:grounding_score] = answer_data[:grounding_score] if answer_data[:grounding_score]
|
|
237
481
|
metadata[:state] = answer_data[:state] if answer_data[:state]
|
|
238
482
|
metadata[:citations_count] = answer_data[:citations_count] if answer_data[:citations_count]
|
|
239
483
|
metadata[:references_count] = answer_data[:references_count] if answer_data[:references_count]
|
|
484
|
+
metadata[:answer_skipped_reasons] = answer_data[:answer_skipped_reasons] if answer_data[:answer_skipped_reasons] && !answer_data[:answer_skipped_reasons].empty?
|
|
485
|
+
|
|
486
|
+
# Add grounding information (citations, references, related questions)
|
|
487
|
+
if answer
|
|
488
|
+
citations = extract_citations(answer)
|
|
489
|
+
references = extract_references(answer)
|
|
490
|
+
related_questions = extract_related_questions(answer)
|
|
491
|
+
|
|
492
|
+
metadata[:citations] = citations if citations && !citations.empty?
|
|
493
|
+
metadata[:references] = references if references && !references.empty?
|
|
494
|
+
metadata[:relatedQuestions] = related_questions if related_questions && !related_questions.empty?
|
|
495
|
+
|
|
496
|
+
# Add query understanding info
|
|
497
|
+
query_understanding_info = extract_query_understanding_info(answer)
|
|
498
|
+
metadata[:query_understanding_info] = query_understanding_info if query_understanding_info
|
|
499
|
+
end
|
|
240
500
|
|
|
241
501
|
# Add request metadata
|
|
242
502
|
metadata[:serving_config] = extract_serving_config(args, kwargs)
|
|
243
503
|
metadata[:session] = extract_session(args, kwargs)
|
|
244
504
|
|
|
505
|
+
# Add answer query token
|
|
506
|
+
answer_query_token = safe_extract(response, :answer_query_token)
|
|
507
|
+
metadata[:answer_query_token] = answer_query_token if answer_query_token
|
|
508
|
+
|
|
245
509
|
# Add timing metadata
|
|
246
510
|
if answer_data[:create_time] && answer_data[:complete_time]
|
|
247
511
|
generation_time_ms = ((answer_data[:complete_time] - answer_data[:create_time]) * 1000).round(2)
|
|
@@ -288,6 +552,48 @@ module Openlayer
|
|
|
288
552
|
nil
|
|
289
553
|
end
|
|
290
554
|
|
|
555
|
+
# Extract user pseudo ID from response session
|
|
556
|
+
#
|
|
557
|
+
# @param response [Object] Response object
|
|
558
|
+
# @return [String, nil] User pseudo ID or nil
|
|
559
|
+
def self.extract_user_pseudo_id(response)
|
|
560
|
+
return nil unless response && response.respond_to?(:session)
|
|
561
|
+
|
|
562
|
+
session = safe_extract(response, :session)
|
|
563
|
+
return nil unless session
|
|
564
|
+
|
|
565
|
+
safe_extract(session, :user_pseudo_id)
|
|
566
|
+
rescue StandardError
|
|
567
|
+
nil
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
# Extract query understanding info from answer
|
|
571
|
+
#
|
|
572
|
+
# @param answer [Object] Answer object
|
|
573
|
+
# @return [Hash, nil] Query understanding info or nil
|
|
574
|
+
def self.extract_query_understanding_info(answer)
|
|
575
|
+
return nil unless answer && answer.respond_to?(:query_understanding_info)
|
|
576
|
+
|
|
577
|
+
query_understanding_info = safe_extract(answer, :query_understanding_info)
|
|
578
|
+
return nil unless query_understanding_info
|
|
579
|
+
|
|
580
|
+
result = {}
|
|
581
|
+
|
|
582
|
+
# Extract query classification info
|
|
583
|
+
classification_info = safe_extract(query_understanding_info, :query_classification_info)
|
|
584
|
+
if classification_info && classification_info.respond_to?(:map)
|
|
585
|
+
result[:query_classification_info] = classification_info.map do |info|
|
|
586
|
+
type = safe_extract(info, :type)
|
|
587
|
+
type ? {type: type.to_s} : nil
|
|
588
|
+
end.compact
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
result.empty? ? nil : result
|
|
592
|
+
rescue StandardError => e
|
|
593
|
+
warn_if_debug("[Openlayer] Failed to extract query understanding info: #{e.message}")
|
|
594
|
+
nil
|
|
595
|
+
end
|
|
596
|
+
|
|
291
597
|
# Safely extract a field from an object
|
|
292
598
|
#
|
|
293
599
|
# @param obj [Object] Object to extract from
|
|
@@ -341,9 +647,18 @@ module Openlayer
|
|
|
341
647
|
# from the singleton method context
|
|
342
648
|
private_class_method :extract_query,
|
|
343
649
|
:extract_answer_data,
|
|
650
|
+
:extract_citations,
|
|
651
|
+
:extract_citation_sources,
|
|
652
|
+
:extract_references,
|
|
653
|
+
:extract_related_questions,
|
|
654
|
+
:extract_steps,
|
|
655
|
+
:extract_step_data,
|
|
656
|
+
:extract_search_results,
|
|
344
657
|
:extract_metadata,
|
|
345
658
|
:extract_serving_config,
|
|
346
659
|
:extract_session,
|
|
660
|
+
:extract_user_pseudo_id,
|
|
661
|
+
:extract_query_understanding_info,
|
|
347
662
|
:safe_extract,
|
|
348
663
|
:safe_count,
|
|
349
664
|
:extract_timestamp
|
data/lib/openlayer/version.rb
CHANGED
|
@@ -8,13 +8,17 @@ module Openlayer
|
|
|
8
8
|
params(
|
|
9
9
|
client: T.untyped,
|
|
10
10
|
openlayer_client: Openlayer::Client,
|
|
11
|
-
inference_pipeline_id: String
|
|
11
|
+
inference_pipeline_id: String,
|
|
12
|
+
session_id: T.nilable(String),
|
|
13
|
+
user_id: T.nilable(String)
|
|
12
14
|
).void
|
|
13
15
|
end
|
|
14
16
|
def self.trace_client(
|
|
15
17
|
client,
|
|
16
18
|
openlayer_client:,
|
|
17
|
-
inference_pipeline_id
|
|
19
|
+
inference_pipeline_id:,
|
|
20
|
+
session_id: nil,
|
|
21
|
+
user_id: nil
|
|
18
22
|
)
|
|
19
23
|
end
|
|
20
24
|
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.
|
|
4
|
+
version: 0.8.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-01-
|
|
11
|
+
date: 2026-01-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: connection_pool
|