nitro_intelligence 2.4.0 → 2.5.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 +4 -4
- data/lib/nitro_intelligence/client/handlers/base_handler.rb +61 -0
- data/lib/nitro_intelligence/client/handlers/observed/audio_transcription_handler.rb +1 -0
- data/lib/nitro_intelligence/client/handlers/observed/chat_handler.rb +1 -0
- data/lib/nitro_intelligence/client/handlers/observed/image_handler.rb +1 -0
- data/lib/nitro_intelligence/client/observers/langfuse_observer.rb +22 -11
- data/lib/nitro_intelligence/reporter.rb +13 -1
- data/lib/nitro_intelligence/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0bc1642f0475d8292a1e9611003d79e31983fb9afe2fd5e35d5ddf821a191e53
|
|
4
|
+
data.tar.gz: f61c77b0a3cd8231ba52bd7df317ca715423dc08eb72639e028febf508d52472
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7603cb32f91bda923f1728c5318db1a1b0e7fb59c1a46bca3a4ac3c01567de9d850b578626dd76f05d17b3348e1cdb5495fa6e98f28a6e9c871323147f02db24
|
|
7
|
+
data.tar.gz: 251976a344210f499e8b7b0c7c05bec38461fd4d941cd852fe1d5b52bc4d69cc052f992b22fe83a0241845b8d8212773f77e2e5a0ea5addcc7c459d4d65bde2f
|
|
@@ -16,12 +16,73 @@ module NitroIntelligence
|
|
|
16
16
|
# supplied, so cap it rather than turning a large hash into a failed request.
|
|
17
17
|
MAX_SPEND_LOGS_METADATA_BYTES = 4096
|
|
18
18
|
|
|
19
|
+
# Cost the inference gateway calculated for a response.
|
|
20
|
+
# See https://docs.litellm.ai/docs/proxy/response_headers
|
|
21
|
+
#
|
|
22
|
+
# The gateway is the only component that knows which deployment actually
|
|
23
|
+
# served a request, and the same model group can be served by internal
|
|
24
|
+
# capacity or by any of several third-party providers at different rates.
|
|
25
|
+
# Recomputing cost downstream from token counts therefore means maintaining
|
|
26
|
+
# a second price table that silently drifts from the one doing the billing.
|
|
27
|
+
RESPONSE_COST_HEADER = "x-litellm-response-cost".freeze
|
|
28
|
+
RESPONSE_COST_INPUT_HEADER = "x-litellm-response-cost-input".freeze
|
|
29
|
+
RESPONSE_COST_OUTPUT_HEADER = "x-litellm-response-cost-output".freeze
|
|
30
|
+
|
|
19
31
|
def initialize(client:)
|
|
20
32
|
@client = client
|
|
21
33
|
end
|
|
22
34
|
|
|
35
|
+
# Cost breakdown for an observation, or nil when the gateway did not price
|
|
36
|
+
# the request.
|
|
37
|
+
#
|
|
38
|
+
# A deployment the gateway has no price for sends no cost header at all
|
|
39
|
+
# rather than a zero one, so absence has to mean "unknown" here. Recording
|
|
40
|
+
# nil leaves the generation without a cost; recording 0.0 would assert the
|
|
41
|
+
# request was free and quietly understate spend for every model still
|
|
42
|
+
# awaiting a price.
|
|
43
|
+
#
|
|
44
|
+
# Only the total is guaranteed. Where the cost comes from the upstream
|
|
45
|
+
# provider rather than the gateway's own calculation - OpenRouter reports a
|
|
46
|
+
# real per-request cost of its own, which the gateway passes through - there
|
|
47
|
+
# is no component breakdown, so input and output appear only when sent.
|
|
48
|
+
def cost_details(response)
|
|
49
|
+
headers = response_headers(response)
|
|
50
|
+
return nil if headers.nil?
|
|
51
|
+
|
|
52
|
+
total = header_amount(headers, RESPONSE_COST_HEADER)
|
|
53
|
+
return nil if total.nil?
|
|
54
|
+
|
|
55
|
+
{
|
|
56
|
+
total:,
|
|
57
|
+
input: header_amount(headers, RESPONSE_COST_INPUT_HEADER),
|
|
58
|
+
output: header_amount(headers, RESPONSE_COST_OUTPUT_HEADER),
|
|
59
|
+
}.compact
|
|
60
|
+
end
|
|
61
|
+
|
|
23
62
|
private
|
|
24
63
|
|
|
64
|
+
# `last_response` carries the HTTP metadata of the response a typed model was
|
|
65
|
+
# built from. The client leaves it unset on nested and locally constructed
|
|
66
|
+
# models, and on endpoints returning raw or binary payloads, so both the
|
|
67
|
+
# method and its value are optional.
|
|
68
|
+
def response_headers(response)
|
|
69
|
+
return nil unless response.respond_to?(:last_response)
|
|
70
|
+
|
|
71
|
+
response.last_response&.headers
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Header values arrive as strings. A malformed one is worth ignoring rather
|
|
75
|
+
# than raising: a trace missing its cost is a far smaller problem than an
|
|
76
|
+
# inference call failing because the gateway sent something unexpected.
|
|
77
|
+
def header_amount(headers, name)
|
|
78
|
+
value = headers[name]
|
|
79
|
+
return nil if value.blank?
|
|
80
|
+
|
|
81
|
+
Float(value)
|
|
82
|
+
rescue ArgumentError, TypeError
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
25
86
|
def add_request_headers(parameters, headers)
|
|
26
87
|
request_options = (parameters[:request_options] ||= {})
|
|
27
88
|
(request_options[:extra_headers] ||= {}).merge!(headers.compact)
|
|
@@ -79,6 +79,7 @@ module NitroIntelligence
|
|
|
79
79
|
output_tokens: audio_transcription.usage.output_tokens,
|
|
80
80
|
total_tokens: audio_transcription.usage.total_tokens,
|
|
81
81
|
},
|
|
82
|
+
cost_details: @base_handler.cost_details(audio_transcription),
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
[audio_transcription, trace_attributes]
|
|
@@ -42,17 +42,7 @@ module NitroIntelligence
|
|
|
42
42
|
record_input(generation, input)
|
|
43
43
|
|
|
44
44
|
result, trace_attributes = observe_failures(generation) { yield(generation) }
|
|
45
|
-
|
|
46
|
-
if trace_attributes
|
|
47
|
-
handle_truncation(trace_attributes[:input], trace_attributes[:output], trace_attributes[:model])
|
|
48
|
-
|
|
49
|
-
generation.model = trace_attributes[:model] if trace_attributes[:model]
|
|
50
|
-
generation.usage_details = trace_attributes[:usage_details] if trace_attributes[:usage_details]
|
|
51
|
-
generation.input = trace_attributes[:input] if trace_attributes[:input]
|
|
52
|
-
generation.output = trace_attributes[:output] if trace_attributes[:output]
|
|
53
|
-
|
|
54
|
-
generation.update_trace(input: trace_attributes[:input], output: trace_attributes[:output])
|
|
55
|
-
end
|
|
45
|
+
record_result(generation, trace_attributes)
|
|
56
46
|
|
|
57
47
|
result
|
|
58
48
|
end
|
|
@@ -61,6 +51,27 @@ module NitroIntelligence
|
|
|
61
51
|
|
|
62
52
|
private
|
|
63
53
|
|
|
54
|
+
# Applied once the response is in hand, so the observation reflects what came
|
|
55
|
+
# back rather than what was asked for. Each attribute is set only when the
|
|
56
|
+
# handler supplied it: an observation that records nothing is better than one
|
|
57
|
+
# asserting a value the response never carried. Cost is the clearest case -
|
|
58
|
+
# the gateway omits the header entirely for a deployment it has no price for,
|
|
59
|
+
# and writing a zero there would read as a free request rather than an
|
|
60
|
+
# unpriced one.
|
|
61
|
+
def record_result(generation, trace_attributes)
|
|
62
|
+
return unless trace_attributes
|
|
63
|
+
|
|
64
|
+
handle_truncation(trace_attributes[:input], trace_attributes[:output], trace_attributes[:model])
|
|
65
|
+
|
|
66
|
+
generation.model = trace_attributes[:model] if trace_attributes[:model]
|
|
67
|
+
generation.usage_details = trace_attributes[:usage_details] if trace_attributes[:usage_details]
|
|
68
|
+
generation.cost_details = trace_attributes[:cost_details] if trace_attributes[:cost_details]
|
|
69
|
+
generation.input = trace_attributes[:input] if trace_attributes[:input]
|
|
70
|
+
generation.output = trace_attributes[:output] if trace_attributes[:output]
|
|
71
|
+
|
|
72
|
+
generation.update_trace(input: trace_attributes[:input], output: trace_attributes[:output])
|
|
73
|
+
end
|
|
74
|
+
|
|
64
75
|
# Recorded before the request is made so that a request which raises still
|
|
65
76
|
# shows what was sent. Handlers whose input is not safe to record twice
|
|
66
77
|
# (image generation sends base64 payloads that are replaced with media
|
|
@@ -4,6 +4,8 @@ require "uri"
|
|
|
4
4
|
|
|
5
5
|
module NitroIntelligence
|
|
6
6
|
class Reporter
|
|
7
|
+
class DatasetItemError < StandardError; end
|
|
8
|
+
|
|
7
9
|
def initialize(observability_project_slug:)
|
|
8
10
|
@observability_project_slug = observability_project_slug
|
|
9
11
|
@project_client = fetch_project_client
|
|
@@ -20,7 +22,17 @@ module NitroIntelligence
|
|
|
20
22
|
request["Authorization"] = "Basic #{@project_client.project.auth_token}"
|
|
21
23
|
request.body = attributes.to_json
|
|
22
24
|
|
|
23
|
-
http.request(request)
|
|
25
|
+
response = http.request(request)
|
|
26
|
+
|
|
27
|
+
# Every other request in this gem raises on an unsuccessful response. Without this a
|
|
28
|
+
# rejected write - bad credentials, a malformed item, a dataset that does not exist -
|
|
29
|
+
# is indistinguishable from a successful one, and a caller building a dataset ends up
|
|
30
|
+
# with a run against items that were never stored.
|
|
31
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
32
|
+
raise DatasetItemError, "#{response.code} creating dataset item: #{response.body}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
response
|
|
24
36
|
end
|
|
25
37
|
|
|
26
38
|
def score(trace_id:, name:, value:, id: "#{trace_id}-#{name}")
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nitro_intelligence
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Igor Artemenko
|
|
@@ -57,14 +57,14 @@ dependencies:
|
|
|
57
57
|
requirements:
|
|
58
58
|
- - "~>"
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.
|
|
60
|
+
version: '0.79'
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - "~>"
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '0.
|
|
67
|
+
version: '0.79'
|
|
68
68
|
description: The Ruby client for Nitro Intelligence
|
|
69
69
|
email:
|
|
70
70
|
- igor.artemenko@powerhrg.com
|