Module: Verica
- Defined in:
- lib/verica.rb,
lib/verica/config.rb,
lib/verica/version.rb,
lib/verica/providers.rb,
lib/verica/openai_wrapper.rb,
lib/verica/ruby_openai_wrapper.rb
Overview
Fail-open by contract: nothing in this module ever raises into the host app.
Defined Under Namespace
Modules: Providers Classes: Config, OpenAIWrapper, RubyOpenAIWrapper
Constant Summary collapse
- CONVERSATION_KEY =
Fiber-local key holding the per-request conversation override. A value of
nilmeans "no override active" (fall back to the global config); any String (including '') means an override IS active for this fiber/thread. :verica_conversation_override- TAGS_KEY =
Fiber-local key holding the per-request tag scope (Array
); nil means no scope is active. Scopes UNION with the global config tags. :verica_tags_scope- VERSION =
'0.1.8'
Class Attribute Summary collapse
-
.config ⇒ Object
readonly
Returns the value of attribute config.
Class Method Summary collapse
-
.conversation_id ⇒ Object
Resolves the conversation id both wrappers stamp on spans: the thread-local override (when a
with_conversationblock is active) wins over the global config; nil/empty resolves to nil so the attribute is omitted. -
.current_tags ⇒ Object
Global config tags ∪ the active thread-local scope, deduped in order.
- .flush ⇒ Object
- .init(**options) ⇒ Object
- .shutdown ⇒ Object
-
.with_conversation(id) ⇒ Object
Scopes
gen_ai.conversation.idto a single request/thread, overriding the globalconversation_id:frominit. -
.with_tags(tags) ⇒ Object
Adds tags to every span emitted inside the block, UNIONED with the global
tags:frominit(global first, dedup,to_scoercion). -
.wrap_openai(client) ⇒ Object
Wraps the official
openaigem client so chat completions emit traces. -
.wrap_openai_compatible(client) ⇒ Object
(also: wrap_ruby_openai)
Wraps a client that speaks the community
ruby-openaigem'schat(parameters:)API, so its calls emit traces.
Class Attribute Details
.config ⇒ Object (readonly)
Returns the value of attribute config.
21 22 23 |
# File 'lib/verica.rb', line 21 def config @config end |
Class Method Details
.conversation_id ⇒ Object
Resolves the conversation id both wrappers stamp on spans: the thread-local
override (when a with_conversation block is active) wins over the global
config; nil/empty resolves to nil so the attribute is omitted.
43 44 45 46 47 48 |
# File 'lib/verica.rb', line 43 def conversation_id override = Thread.current[CONVERSATION_KEY] raw = override.nil? ? @config&.conversation_id : override value = raw.to_s value.empty? ? nil : value end |
.current_tags ⇒ Object
Global config tags ∪ the active thread-local scope, deduped in order.
67 68 69 |
# File 'lib/verica.rb', line 67 def (@config&. || [], Thread.current[TAGS_KEY] || []) end |
.flush ⇒ Object
127 128 129 130 131 |
# File 'lib/verica.rb', line 127 def flush OpenTelemetry.tracer_provider.force_flush if @initialized rescue StandardError nil end |
.init(**options) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/verica.rb', line 71 def init(**) if @initialized warn '[verica] init called twice; ignoring the second call.' return true end cfg, missing = Config.resolve(, ENV) if cfg.nil? warn "[verica] missing #{missing.join(' and ')}; tracing is disabled." return false end begin require 'logger' require 'opentelemetry/sdk' require 'opentelemetry-exporter-otlp' # Spec §5: export errors (401, network) must not spam the host app's # logs; OTel Ruby reports them through OpenTelemetry.logger. OpenTelemetry.logger = Logger.new(IO::NULL) unless cfg.debug OpenTelemetry::SDK.configure do |c| c.service_name = cfg.service_name c.add_span_processor( OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new( OpenTelemetry::Exporter::OTLP::Exporter.new( endpoint: "#{cfg.endpoint}/v1/traces", headers: { 'Authorization' => "Bearer #{cfg.token}" } ) ) ) end @config = cfg @initialized = true true rescue StandardError => e warn "[verica] init failed; tracing is disabled.#{cfg.debug ? " #{e.}" : ''}" false end end |
.shutdown ⇒ Object
133 134 135 136 137 138 139 140 |
# File 'lib/verica.rb', line 133 def shutdown OpenTelemetry.tracer_provider.shutdown if @initialized rescue StandardError nil ensure @initialized = false @config = nil end |
.with_conversation(id) ⇒ Object
Scopes gen_ai.conversation.id to a single request/thread, overriding the
global conversation_id: from init. Thread-safe (fiber-local storage),
nestable, and restored on the way out even if the block raises. id is
coerced with to_s; nil/empty means "no conversation attribute" for spans
emitted inside the block. Returns the block's value.
Verica.with_conversation("chat-#{conversation.id}") do
client.chat(parameters: { ... })
end
32 33 34 35 36 37 38 |
# File 'lib/verica.rb', line 32 def with_conversation(id) previous = Thread.current[CONVERSATION_KEY] Thread.current[CONVERSATION_KEY] = id.to_s yield ensure Thread.current[CONVERSATION_KEY] = previous end |
.with_tags(tags) ⇒ Object
Adds tags to every span emitted inside the block, UNIONED with the global
tags: from init (global first, dedup, to_s coercion). Thread-safe
(fiber-local), nestable (nested scopes accumulate), restored on the way
out even if the block raises. Returns the block's value.
Verica.with_tags(["mentor-chat"]) do
client.chat(parameters: { ... })
end
58 59 60 61 62 63 64 |
# File 'lib/verica.rb', line 58 def () previous = Thread.current[TAGS_KEY] Thread.current[TAGS_KEY] = (previous || [], ) yield ensure Thread.current[TAGS_KEY] = previous end |
.wrap_openai(client) ⇒ Object
Wraps the official openai gem client so chat completions emit traces.
113 114 115 |
# File 'lib/verica.rb', line 113 def wrap_openai(client) OpenAIWrapper.new(client) end |
.wrap_openai_compatible(client) ⇒ Object Also known as: wrap_ruby_openai
Wraps a client that speaks the community ruby-openai gem's
chat(parameters:) API, so its calls emit traces. This is the path for
any OpenAI-compatible endpoint: OpenAI itself, plus Gemini and Anthropic
via their OpenAI-compatible endpoints (the provider is inferred from the
model). Preferred name; wrap_ruby_openai is a back-compat alias.
122 123 124 |
# File 'lib/verica.rb', line 122 def wrap_openai_compatible(client) RubyOpenAIWrapper.new(client) end |