verica-observability 0.1.7 → 0.1.8

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: 816c57a94a8a82efc43e464165b84611981c76dc6a456eb6bd359804621beddc
4
- data.tar.gz: 929106f12d8e32c03de0bd9427b9babe4cf5639c5159e31d6a7f34d3d1a32d89
3
+ metadata.gz: 9b2286c95c84642a8961ca9521858ce47f8cdff3d764e7269a278acd6a78284c
4
+ data.tar.gz: bb471935256948e92255edf0b4c92ef97f245544890fe4de09b9862b92d2c092
5
5
  SHA512:
6
- metadata.gz: e7242e9480405617939a9d4ab4109f5f1eb104046372009d100c458627b7f846987a34f59b0439a766d5b297244d4a11b46400062faf3cf72ea09b0f571e9f71
7
- data.tar.gz: 832738007de7cab2d6ea750369609e0d94e08c5578477038018b7086df8cddcf93de574a0f7cabdff00f13d26d736855af5a61678f3bdbc7d9655c75eb16363b
6
+ metadata.gz: e2765905d27ac2b2d5b94f59d79ebda8fab9b42aeb9e677cd97eeff9e1fb9021567b68b249d58a65ccc423f76997fcf6274320b205b220f18d60c8c9b73acdfb
7
+ data.tar.gz: d1d66aa9cf309ac254c4bcfde0dadbc4ecfdbad75aa85da8f98b88ccb143f3058fea5d2586946168bdfacc7f33fc61867cb400da690b18a069fad22011851f95
data/README.md CHANGED
@@ -81,6 +81,32 @@ end
81
81
  body with it and keep the return unchanged. Each thread carries its own override,
82
82
  so concurrent requests never leak conversation ids into one another.
83
83
 
84
+ **Resend history exactly as sent.** Turns are stored as deltas: at ingest,
85
+ Verica matches the history you resend against what previous turns already
86
+ stored, and that match is exact (byte-identical text). If your app mutates
87
+ prior messages between requests (for example, appending "Respond in JSON" to
88
+ the last user message and stripping it from earlier turns when rebuilding the
89
+ history), no prefix ever matches and every turn falls back to storing, and
90
+ showing, the full conversation again. Keep injected instructions in the system
91
+ prompt, or resend them exactly as originally sent.
92
+
93
+ ## Tags
94
+
95
+ Tags land on each trace (`traces.tags`): filter the workbench by them, and bind
96
+ criteria to them so evaluation preselects the right criteria per tag.
97
+
98
+ ```ruby
99
+ Verica.init(token: ENV.fetch("VERICA_TOKEN"), tags: ["mtn-campus", "prod"])
100
+
101
+ Verica.with_tags(["mentor-chat"]) do
102
+ client.chat(parameters: { ... })
103
+ end
104
+ ```
105
+
106
+ Per-request tags UNION with the globals (dedup, order preserved); nested blocks
107
+ accumulate; the scope is thread-local and restored even if the block raises.
108
+ Values are coerced with `to_s`; the server caps at 20 tags x 120 chars.
109
+
84
110
  ## Streaming (`wrap_openai_compatible`)
85
111
 
86
112
  Streaming calls (a `stream:` proc in `parameters`) are fully captured: the
@@ -127,14 +153,15 @@ span batch is exported.
127
153
 
128
154
  ## Options
129
155
 
130
- | Option / env var | Default | Notes |
131
- | --------------------------------------------- | ---------- | ---------------------------------- |
132
- | `token:` / `VERICA_TOKEN` | (required) | ingest-scoped API token |
133
- | `capture_content:` / `VERICA_CAPTURE_CONTENT` | `true` | send prompt/response content |
134
- | `conversation_id:` | (none) | stamps `gen_ai.conversation.id` |
135
- | `service_name:` / `OTEL_SERVICE_NAME` | `app` | resource service.name |
136
- | `stream_usage:` / `VERICA_STREAM_USAGE` | `false` | inject `stream_options` for tokens |
137
- | `debug:` / `VERICA_DEBUG` | `false` | log export errors |
156
+ | Option / env var | Default | Notes |
157
+ | --------------------------------------------- | ---------- | -------------------------------------------- |
158
+ | `token:` / `VERICA_TOKEN` | (required) | ingest-scoped API token |
159
+ | `capture_content:` / `VERICA_CAPTURE_CONTENT` | `true` | send prompt/response content |
160
+ | `conversation_id:` | (none) | stamps `gen_ai.conversation.id` |
161
+ | `tags:` | (none) | global tags; per-request: `Verica.with_tags` |
162
+ | `service_name:` / `OTEL_SERVICE_NAME` | `app` | resource service.name |
163
+ | `stream_usage:` / `VERICA_STREAM_USAGE` | `false` | inject `stream_options` for tokens |
164
+ | `debug:` / `VERICA_DEBUG` | `false` | log export errors |
138
165
 
139
166
  Fail-open by design: if Verica is unreachable or the token is invalid, spans are
140
167
  dropped and your app is never affected. Export errors are silent unless `debug`
data/lib/verica/config.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Verica
4
4
  # Config resolution: options > env vars > defaults. Pure.
5
- Config = Struct.new(:token, :endpoint, :capture_content, :conversation_id, :service_name, :debug,
5
+ Config = Struct.new(:token, :endpoint, :capture_content, :conversation_id, :tags, :service_name, :debug,
6
6
  :stream_usage, keyword_init: true) do
7
7
  # Verica's hosted ingest endpoint. Override via the `endpoint:` option or the
8
8
  # VERICA_ENDPOINT env var for self-host or local dev. Only `token` is required.
@@ -21,6 +21,7 @@ module Verica
21
21
  endpoint: raw_endpoint.sub(%r{/+\z}, ''),
22
22
  capture_content: capture,
23
23
  conversation_id: options[:conversation_id],
24
+ tags: Array(options[:tags]).map(&:to_s).reject(&:empty?).uniq,
24
25
  service_name: options[:service_name] || env['OTEL_SERVICE_NAME'] || 'app',
25
26
  debug: options[:debug].nil? ? %w[1 true].include?(env['VERICA_DEBUG']) : options[:debug],
26
27
  # Opt-in: append `stream_options: { include_usage: true }` to streaming
@@ -78,6 +78,8 @@ module Verica
78
78
  span.set_attribute('gen_ai.request.model', params[:model].to_s) if params[:model]
79
79
  conversation_id = Verica.conversation_id
80
80
  span.set_attribute('gen_ai.conversation.id', conversation_id) if conversation_id
81
+ tags = Verica.current_tags
82
+ span.set_attribute('verica.tags', JSON.generate(tags)) unless tags.empty?
81
83
 
82
84
  model = response.respond_to?(:model) ? response.model : nil
83
85
  span.set_attribute('gen_ai.response.model', model) if model
@@ -136,6 +136,8 @@ module Verica
136
136
  span.set_attribute('gen_ai.request.model', model.to_s) if model
137
137
  conversation_id = Verica.conversation_id
138
138
  span.set_attribute('gen_ai.conversation.id', conversation_id) if conversation_id
139
+ tags = Verica.current_tags
140
+ span.set_attribute('verica.tags', JSON.generate(tags)) unless tags.empty?
139
141
  span.set_attribute('gen_ai.input.messages', JSON.generate(messages)) if cfg&.capture_content && messages
140
142
 
141
143
  # Streaming: annotate the response side from the drained accumulator.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Verica
4
- VERSION = '0.1.7'
4
+ VERSION = '0.1.8'
5
5
  end
data/lib/verica.rb CHANGED
@@ -13,6 +13,10 @@ module Verica
13
13
  # String (including '') means an override IS active for this fiber/thread.
14
14
  CONVERSATION_KEY = :verica_conversation_override
15
15
 
16
+ # Fiber-local key holding the per-request tag scope (Array<String>); nil means
17
+ # no scope is active. Scopes UNION with the global config tags.
18
+ TAGS_KEY = :verica_tags_scope
19
+
16
20
  class << self
17
21
  attr_reader :config
18
22
 
@@ -43,6 +47,27 @@ module Verica
43
47
  value.empty? ? nil : value
44
48
  end
45
49
 
50
+ # Adds tags to every span emitted inside the block, UNIONED with the global
51
+ # `tags:` from `init` (global first, dedup, `to_s` coercion). Thread-safe
52
+ # (fiber-local), nestable (nested scopes accumulate), restored on the way
53
+ # out even if the block raises. Returns the block's value.
54
+ #
55
+ # Verica.with_tags(["mentor-chat"]) do
56
+ # client.chat(parameters: { ... })
57
+ # end
58
+ def with_tags(tags)
59
+ previous = Thread.current[TAGS_KEY]
60
+ Thread.current[TAGS_KEY] = merge_tags(previous || [], tags)
61
+ yield
62
+ ensure
63
+ Thread.current[TAGS_KEY] = previous
64
+ end
65
+
66
+ # Global config tags ∪ the active thread-local scope, deduped in order.
67
+ def current_tags
68
+ merge_tags(@config&.tags || [], Thread.current[TAGS_KEY] || [])
69
+ end
70
+
46
71
  def init(**options)
47
72
  if @initialized
48
73
  warn '[verica] init called twice; ignoring the second call.'
@@ -113,5 +138,16 @@ module Verica
113
138
  @initialized = false
114
139
  @config = nil
115
140
  end
141
+
142
+ private
143
+
144
+ def merge_tags(base, extra)
145
+ out = base.dup
146
+ Array(extra).each do |t|
147
+ s = t.to_s
148
+ out << s unless s.empty? || out.include?(s)
149
+ end
150
+ out
151
+ end
116
152
  end
117
153
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verica-observability
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Verica