verica-observability 0.1.0
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 +7 -0
- data/README.md +71 -0
- data/lib/verica/config.rb +28 -0
- data/lib/verica/openai_wrapper.rb +111 -0
- data/lib/verica/ruby_openai_wrapper.rb +88 -0
- data/lib/verica/version.rb +5 -0
- data/lib/verica.rb +81 -0
- metadata +76 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5a673e65acb78166ecd02e7e1f71e732b9b5a94266b13b5439847323a754d0fd
|
|
4
|
+
data.tar.gz: 6ad1429c62b84d071393c1c906852ec63d1d1c09e5bf9e003f74ae0efe0f779c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 314db6f26e34ed191181439756db2e2a05c42be85cb39061d7fe9c18d396c6def8cf1f1cf33e6253e1ac79e7aa4fc17df030dd386ccd8a3bb6907bfc5bfb5586
|
|
7
|
+
data.tar.gz: 5c093204aee3f513664aa1b8b7ad64d2c1f63308fd209bbed5c7c03e2eaee1f4bf571071ddb4d8f629a52b6993826e2558d87a18d51ded69a7716652859cbf08
|
data/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# verica-observability
|
|
2
|
+
|
|
3
|
+
Two-line LLM tracing for [Verica](https://verica.app). The official `openai`
|
|
4
|
+
gem has no auto-instrumentation anywhere: this gem ships it.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
gem install verica-observability
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Use (official `openai` gem)
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
require 'verica'
|
|
16
|
+
|
|
17
|
+
Verica.init(token: ENV['VERICA_TOKEN'], endpoint: 'https://<your-verica-host>')
|
|
18
|
+
client = Verica.wrap_openai(OpenAI::Client.new)
|
|
19
|
+
# use `client` exactly like the original; chat completions are traced.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Use (community `ruby-openai` gem, alexrudall)
|
|
23
|
+
|
|
24
|
+
The community [`ruby-openai`](https://github.com/alexrudall/ruby-openai) gem has
|
|
25
|
+
a different API than the official gem: `client.chat(parameters: { ... })`
|
|
26
|
+
returning a plain Hash. It gets its own wrapper.
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
require 'verica'
|
|
30
|
+
|
|
31
|
+
Verica.init(token: ENV['VERICA_TOKEN'], endpoint: 'https://<your-verica-host>')
|
|
32
|
+
client = Verica.wrap_ruby_openai(OpenAI::Client.new)
|
|
33
|
+
|
|
34
|
+
client.chat(parameters: {
|
|
35
|
+
model: 'gpt-4o-mini',
|
|
36
|
+
messages: [{ role: 'user', content: 'Hello!' }]
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Streaming calls (a `stream:` proc in `parameters`) pass through untouched and
|
|
41
|
+
are traced with request-side attributes only (the streamed response is not a
|
|
42
|
+
stable Hash to read the model or usage from).
|
|
43
|
+
|
|
44
|
+
Use `wrap_openai` for the official `openai` gem and `wrap_ruby_openai` for
|
|
45
|
+
`ruby-openai`. The two gems' APIs are not interchangeable, so neither are the
|
|
46
|
+
wrappers.
|
|
47
|
+
|
|
48
|
+
## Use (RubyLLM)
|
|
49
|
+
|
|
50
|
+
With RubyLLM plus its thoughtbot OpenTelemetry instrumentation, `Verica.init`
|
|
51
|
+
alone is enough: the spans it emits are exported to Verica.
|
|
52
|
+
|
|
53
|
+
## Serverless
|
|
54
|
+
|
|
55
|
+
Call `Verica.flush` (or `Verica.shutdown`) before the runtime freezes so the
|
|
56
|
+
span batch is exported.
|
|
57
|
+
|
|
58
|
+
## Options
|
|
59
|
+
|
|
60
|
+
| Option / env var | Default | Notes |
|
|
61
|
+
| --------------------------------------------- | ---------- | ------------------------------- |
|
|
62
|
+
| `token:` / `VERICA_TOKEN` | (required) | ingest-scoped API token |
|
|
63
|
+
| `endpoint:` / `VERICA_ENDPOINT` | (required) | your Verica origin |
|
|
64
|
+
| `capture_content:` / `VERICA_CAPTURE_CONTENT` | `true` | send prompt/response content |
|
|
65
|
+
| `conversation_id:` | (none) | stamps `gen_ai.conversation.id` |
|
|
66
|
+
| `service_name:` / `OTEL_SERVICE_NAME` | `app` | resource service.name |
|
|
67
|
+
| `debug:` / `VERICA_DEBUG` | `false` | log export errors |
|
|
68
|
+
|
|
69
|
+
Fail-open by design: if the endpoint is down or the token is invalid, spans are
|
|
70
|
+
dropped and your app is never affected. Export errors are silent unless `debug`
|
|
71
|
+
is on.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verica
|
|
4
|
+
# Config resolution: options > env vars > defaults. Pure.
|
|
5
|
+
Config = Struct.new(:token, :endpoint, :capture_content, :conversation_id, :service_name, :debug,
|
|
6
|
+
keyword_init: true) do
|
|
7
|
+
def self.resolve(options, env)
|
|
8
|
+
token = (options[:token] || env['VERICA_TOKEN'] || '').to_s
|
|
9
|
+
raw_endpoint = (options[:endpoint] || env['VERICA_ENDPOINT'] || '').to_s
|
|
10
|
+
missing = []
|
|
11
|
+
missing << 'token' if token.empty?
|
|
12
|
+
missing << 'endpoint' if raw_endpoint.empty?
|
|
13
|
+
return [nil, missing] unless missing.empty?
|
|
14
|
+
|
|
15
|
+
capture = options[:capture_content]
|
|
16
|
+
capture = env.key?('VERICA_CAPTURE_CONTENT') ? %w[1 true].include?(env['VERICA_CAPTURE_CONTENT']) : true if capture.nil?
|
|
17
|
+
|
|
18
|
+
[new(
|
|
19
|
+
token: token,
|
|
20
|
+
endpoint: raw_endpoint.sub(%r{/+\z}, ''),
|
|
21
|
+
capture_content: capture,
|
|
22
|
+
conversation_id: options[:conversation_id],
|
|
23
|
+
service_name: options[:service_name] || env['OTEL_SERVICE_NAME'] || 'app',
|
|
24
|
+
debug: options[:debug].nil? ? %w[1 true].include?(env['VERICA_DEBUG']) : options[:debug]
|
|
25
|
+
), []]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Verica
|
|
6
|
+
# The official `openai` gem has no auto-instrumentation, so Verica ships its
|
|
7
|
+
# own thin decorator: everything delegates to the real client; only
|
|
8
|
+
# chat.completions.create is intercepted to emit ONE gen_ai.* span (pinned
|
|
9
|
+
# semconv, the exact attributes the Verica normalizer accepts). Fail-open:
|
|
10
|
+
# instrumentation errors never reach the caller; provider errors always do.
|
|
11
|
+
class OpenAIWrapper
|
|
12
|
+
def initialize(client)
|
|
13
|
+
@client = client
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def chat
|
|
17
|
+
ChatProxy.new(@client.chat)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def method_missing(name, ...)
|
|
21
|
+
@client.public_send(name, ...)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def respond_to_missing?(name, include_private = false)
|
|
25
|
+
@client.respond_to?(name, include_private) || super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class ChatProxy
|
|
29
|
+
def initialize(chat) = @chat = chat
|
|
30
|
+
|
|
31
|
+
def completions
|
|
32
|
+
CompletionsProxy.new(@chat.completions)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def method_missing(name, ...)
|
|
36
|
+
@chat.public_send(name, ...)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def respond_to_missing?(name, include_private = false)
|
|
40
|
+
@chat.respond_to?(name, include_private) || super
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class CompletionsProxy
|
|
45
|
+
def initialize(completions) = @completions = completions
|
|
46
|
+
|
|
47
|
+
def create(**params)
|
|
48
|
+
span = safely do
|
|
49
|
+
tracer = OpenTelemetry.tracer_provider.tracer('verica-observability', Verica::VERSION)
|
|
50
|
+
tracer.start_span("chat #{params[:model]}", kind: :client)
|
|
51
|
+
end
|
|
52
|
+
begin
|
|
53
|
+
response = @completions.create(**params)
|
|
54
|
+
safely { annotate(span, params, response) } if span
|
|
55
|
+
response
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
safely { span&.status = OpenTelemetry::Trace::Status.error(e.message) }
|
|
58
|
+
raise
|
|
59
|
+
ensure
|
|
60
|
+
safely { span&.finish }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def method_missing(name, ...)
|
|
65
|
+
@completions.public_send(name, ...)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def respond_to_missing?(name, include_private = false)
|
|
69
|
+
@completions.respond_to?(name, include_private) || super
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def annotate(span, params, response)
|
|
75
|
+
cfg = Verica.config
|
|
76
|
+
span.set_attribute('gen_ai.operation.name', 'chat')
|
|
77
|
+
span.set_attribute('gen_ai.provider.name', 'openai')
|
|
78
|
+
span.set_attribute('gen_ai.request.model', params[:model].to_s) if params[:model]
|
|
79
|
+
span.set_attribute('gen_ai.conversation.id', cfg.conversation_id) if cfg&.conversation_id
|
|
80
|
+
|
|
81
|
+
model = response.respond_to?(:model) ? response.model : nil
|
|
82
|
+
span.set_attribute('gen_ai.response.model', model) if model
|
|
83
|
+
usage = response.respond_to?(:usage) ? response.usage : nil
|
|
84
|
+
if usage
|
|
85
|
+
input = usage.respond_to?(:prompt_tokens) ? usage.prompt_tokens : nil
|
|
86
|
+
output = usage.respond_to?(:completion_tokens) ? usage.completion_tokens : nil
|
|
87
|
+
span.set_attribute('gen_ai.usage.input_tokens', input) if input
|
|
88
|
+
span.set_attribute('gen_ai.usage.output_tokens', output) if output
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
return unless cfg&.capture_content
|
|
92
|
+
|
|
93
|
+
span.set_attribute('gen_ai.input.messages', JSON.generate(params[:messages])) if params[:messages]
|
|
94
|
+
choices = response.respond_to?(:choices) ? response.choices : nil
|
|
95
|
+
message = choices&.first.respond_to?(:message) ? choices.first.message : nil
|
|
96
|
+
return unless message
|
|
97
|
+
|
|
98
|
+
span.set_attribute(
|
|
99
|
+
'gen_ai.output.messages',
|
|
100
|
+
JSON.generate([{ role: message.role.to_s, content: message.content }])
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def safely
|
|
105
|
+
yield
|
|
106
|
+
rescue StandardError
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Verica
|
|
6
|
+
# The community `ruby-openai` gem (alexrudall) has a different API than the
|
|
7
|
+
# official `openai` gem: `client.chat(parameters: { model:, messages: })`
|
|
8
|
+
# returning a plain Hash (string keys) instead of typed objects. Both gems
|
|
9
|
+
# define OpenAI::Client, so an app uses one or the other; this thin decorator
|
|
10
|
+
# is the ruby-openai counterpart of OpenAIWrapper. Everything delegates to the
|
|
11
|
+
# real client; only `chat` is intercepted to emit ONE gen_ai.* span with the
|
|
12
|
+
# SAME pinned semconv the Verica normalizer accepts. Fail-open: instrumentation
|
|
13
|
+
# errors never reach the caller; provider errors always do.
|
|
14
|
+
class RubyOpenAIWrapper
|
|
15
|
+
def initialize(client)
|
|
16
|
+
@client = client
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def chat(parameters: {})
|
|
20
|
+
model = parameters[:model] || parameters['model']
|
|
21
|
+
span = safely do
|
|
22
|
+
tracer = OpenTelemetry.tracer_provider.tracer('verica-observability', Verica::VERSION)
|
|
23
|
+
tracer.start_span("chat #{model}", kind: :client)
|
|
24
|
+
end
|
|
25
|
+
begin
|
|
26
|
+
response = @client.chat(parameters: parameters)
|
|
27
|
+
safely { annotate(span, parameters, response) } if span
|
|
28
|
+
response
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
safely { span&.status = OpenTelemetry::Trace::Status.error(e.message) }
|
|
31
|
+
raise
|
|
32
|
+
ensure
|
|
33
|
+
safely { span&.finish }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def method_missing(name, ...)
|
|
38
|
+
@client.public_send(name, ...)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def respond_to_missing?(name, include_private = false)
|
|
42
|
+
@client.respond_to?(name, include_private) || super
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def annotate(span, parameters, response)
|
|
48
|
+
cfg = Verica.config
|
|
49
|
+
model = parameters[:model] || parameters['model']
|
|
50
|
+
messages = parameters[:messages] || parameters['messages']
|
|
51
|
+
streaming = parameters[:stream] || parameters['stream']
|
|
52
|
+
|
|
53
|
+
span.set_attribute('gen_ai.operation.name', 'chat')
|
|
54
|
+
span.set_attribute('gen_ai.provider.name', 'openai')
|
|
55
|
+
span.set_attribute('gen_ai.request.model', model.to_s) if model
|
|
56
|
+
span.set_attribute('gen_ai.conversation.id', cfg.conversation_id) if cfg&.conversation_id
|
|
57
|
+
span.set_attribute('gen_ai.input.messages', JSON.generate(messages)) if cfg&.capture_content && messages
|
|
58
|
+
|
|
59
|
+
# Streaming: chunks go to the caller's proc; the returned value is not a
|
|
60
|
+
# stable Hash, so skip ALL response-side annotation.
|
|
61
|
+
return if streaming
|
|
62
|
+
return unless response.is_a?(Hash)
|
|
63
|
+
|
|
64
|
+
response_model = response['model']
|
|
65
|
+
span.set_attribute('gen_ai.response.model', response_model) if response_model
|
|
66
|
+
input_tokens = response.dig('usage', 'prompt_tokens')
|
|
67
|
+
output_tokens = response.dig('usage', 'completion_tokens')
|
|
68
|
+
span.set_attribute('gen_ai.usage.input_tokens', input_tokens) if input_tokens
|
|
69
|
+
span.set_attribute('gen_ai.usage.output_tokens', output_tokens) if output_tokens
|
|
70
|
+
|
|
71
|
+
return unless cfg&.capture_content
|
|
72
|
+
|
|
73
|
+
message = response.dig('choices', 0, 'message')
|
|
74
|
+
return unless message.is_a?(Hash)
|
|
75
|
+
|
|
76
|
+
span.set_attribute(
|
|
77
|
+
'gen_ai.output.messages',
|
|
78
|
+
JSON.generate([{ role: message['role'].to_s, content: message['content'] }])
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def safely
|
|
83
|
+
yield
|
|
84
|
+
rescue StandardError
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/verica.rb
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'verica/version'
|
|
4
|
+
require 'verica/config'
|
|
5
|
+
require 'verica/openai_wrapper'
|
|
6
|
+
require 'verica/ruby_openai_wrapper'
|
|
7
|
+
|
|
8
|
+
# Fail-open by contract: nothing in this module ever raises into the host app.
|
|
9
|
+
module Verica
|
|
10
|
+
class << self
|
|
11
|
+
attr_reader :config
|
|
12
|
+
|
|
13
|
+
def init(**options)
|
|
14
|
+
if @initialized
|
|
15
|
+
warn '[verica] init called twice; ignoring the second call.'
|
|
16
|
+
return true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
cfg, missing = Config.resolve(options, ENV)
|
|
20
|
+
if cfg.nil?
|
|
21
|
+
warn "[verica] missing #{missing.join(' and ')}; tracing is disabled."
|
|
22
|
+
return false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
begin
|
|
26
|
+
require 'logger'
|
|
27
|
+
require 'opentelemetry/sdk'
|
|
28
|
+
require 'opentelemetry-exporter-otlp'
|
|
29
|
+
|
|
30
|
+
# Spec §5: export errors (401, network) must not spam the host app's
|
|
31
|
+
# logs; OTel Ruby reports them through OpenTelemetry.logger.
|
|
32
|
+
OpenTelemetry.logger = Logger.new(IO::NULL) unless cfg.debug
|
|
33
|
+
|
|
34
|
+
OpenTelemetry::SDK.configure do |c|
|
|
35
|
+
c.service_name = cfg.service_name
|
|
36
|
+
c.add_span_processor(
|
|
37
|
+
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
|
|
38
|
+
OpenTelemetry::Exporter::OTLP::Exporter.new(
|
|
39
|
+
endpoint: "#{cfg.endpoint}/v1/traces",
|
|
40
|
+
headers: { 'Authorization' => "Bearer #{cfg.token}" }
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
@config = cfg
|
|
46
|
+
@initialized = true
|
|
47
|
+
true
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
warn "[verica] init failed; tracing is disabled.#{cfg.debug ? " #{e.message}" : ''}"
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Wraps the official `openai` gem client so chat completions emit traces.
|
|
55
|
+
def wrap_openai(client)
|
|
56
|
+
OpenAIWrapper.new(client)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Wraps the community `ruby-openai` gem (alexrudall) client so its
|
|
60
|
+
# `chat(parameters:)` calls emit traces. Its API and Hash responses differ
|
|
61
|
+
# from the official gem, so it needs its own wrapper (not interchangeable).
|
|
62
|
+
def wrap_ruby_openai(client)
|
|
63
|
+
RubyOpenAIWrapper.new(client)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def flush
|
|
67
|
+
OpenTelemetry.tracer_provider.force_flush if @initialized
|
|
68
|
+
rescue StandardError
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def shutdown
|
|
73
|
+
OpenTelemetry.tracer_provider.shutdown if @initialized
|
|
74
|
+
rescue StandardError
|
|
75
|
+
nil
|
|
76
|
+
ensure
|
|
77
|
+
@initialized = false
|
|
78
|
+
@config = nil
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: verica-observability
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Verica
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-07-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: opentelemetry-exporter-otlp
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.29'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.29'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: opentelemetry-sdk
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.5'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.5'
|
|
41
|
+
description: init(token) and your OpenAI calls land as evaluable traces in Verica.
|
|
42
|
+
email:
|
|
43
|
+
executables: []
|
|
44
|
+
extensions: []
|
|
45
|
+
extra_rdoc_files: []
|
|
46
|
+
files:
|
|
47
|
+
- README.md
|
|
48
|
+
- lib/verica.rb
|
|
49
|
+
- lib/verica/config.rb
|
|
50
|
+
- lib/verica/openai_wrapper.rb
|
|
51
|
+
- lib/verica/ruby_openai_wrapper.rb
|
|
52
|
+
- lib/verica/version.rb
|
|
53
|
+
homepage: https://verica.app
|
|
54
|
+
licenses:
|
|
55
|
+
- MIT
|
|
56
|
+
metadata: {}
|
|
57
|
+
post_install_message:
|
|
58
|
+
rdoc_options: []
|
|
59
|
+
require_paths:
|
|
60
|
+
- lib
|
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - ">="
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '3.1'
|
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
|
+
requirements:
|
|
68
|
+
- - ">="
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: '0'
|
|
71
|
+
requirements: []
|
|
72
|
+
rubygems_version: 3.5.22
|
|
73
|
+
signing_key:
|
|
74
|
+
specification_version: 4
|
|
75
|
+
summary: Two-line LLM tracing for Verica.
|
|
76
|
+
test_files: []
|