nitro_intelligence 2.4.0 → 2.6.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/docs/README.md +76 -2
- data/lib/nitro_intelligence/assistant.rb +76 -0
- data/lib/nitro_intelligence/assistant_registry.rb +66 -0
- 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
- data/lib/nitro_intelligence.rb +9 -1
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 92e09bec20a682ff36923a40ad1dc9d3804c3bea3da199374859d5779034d524
|
|
4
|
+
data.tar.gz: 303cfc371638d3c14e41ad2610ca2de68944c5f0877db5ab77921ec77b192dd2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d7b784fb71aa635ec0f6dc6388b6925312200e4cb757a6598b498ea0c8632bfd1413cbe6dd2a192244bd206d83f335a8865f3387d11d038092beeac65ae5397
|
|
7
|
+
data.tar.gz: 6225968bb8d513bffe785dbf91aba771bfdf0306571f8d3150489a839e8780c60b1ab7e15608f310ac56305c8dfd643b7b00a312485db3c3ee716d7573f895b7
|
data/docs/README.md
CHANGED
|
@@ -32,7 +32,13 @@ NitroIntelligence.configure do |config|
|
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
# Nitro Intelligence Assistants settings (optional)
|
|
35
|
-
config.assistants_config = {
|
|
35
|
+
config.assistants_config = { # See "Assistants" below
|
|
36
|
+
"base_url" => "https://nip-assistants.example.com",
|
|
37
|
+
"user_id" => "my-app",
|
|
38
|
+
"definitions" => {
|
|
39
|
+
"candidate-concierge" => {},
|
|
40
|
+
}
|
|
41
|
+
}
|
|
36
42
|
|
|
37
43
|
# Model configuration
|
|
38
44
|
config.model_config = {
|
|
@@ -80,7 +86,7 @@ end
|
|
|
80
86
|
| `inference_base_url` | `String` | `""` | Base URL for the LLM inference service |
|
|
81
87
|
| `observability_base_url` | `String` | `""` | Base URL for the Langfuse observability service |
|
|
82
88
|
| `observability_projects` | `Array<Hash>` | `[]` | Langfuse project credentials (slug, id, public_key, secret_key) |
|
|
83
|
-
| `assistants_config` | `Hash` | `{}` |
|
|
89
|
+
| `assistants_config` | `Hash` | `{}` | Assistants to make addressable by key. `base_url` (String) and `user_id` (String, default: `"default-user"`) are shared by every entry; `definitions` (Hash) holds one entry per assistant, keyed by what it is looked up with, each able to override a shared value. Without `definitions` the hash is read as credentials for a single `Assistants.new` — see [Assistants](#assistants) |
|
|
84
90
|
| `model_config` | `Hash` | `{}` | Model defaults and per-model settings. Top-level keys: `default_text_model`, `default_audio_transcription_model`, `default_image_model`, `default_text_to_speech_model`, and `models` (array of per-model hashes keyed by `name` and `type`, with type-specific options like `aspect_ratios`/`resolutions` for images or `voices`/`response_formats` for TTS) |
|
|
85
91
|
|
|
86
92
|
## Basic Usage
|
|
@@ -494,3 +500,71 @@ client.chat(
|
|
|
494
500
|
`NitroIntelligence::Assistants` is Nitro Intelligence's lightweight SDK for working with hosted agent threads, runs, and human review flows. It is mainly used to initialize conversation threads, trigger agent runs, inspect agent tool calls pending human approval, and resume interrupted threads after human reviews.
|
|
495
501
|
|
|
496
502
|
For the full guide, see [ASSISTANTS.md](ASSISTANTS.md). For the service this SDK talks to, see the [Nitro Intelligence Assistants documentation](https://portal.powerapp.cloud/docs/default/system/nip-assistants).
|
|
503
|
+
|
|
504
|
+
## Assistants
|
|
505
|
+
|
|
506
|
+
An assistant is addressed by the key it is configured under:
|
|
507
|
+
|
|
508
|
+
```ruby
|
|
509
|
+
assistant = NitroIntelligence.assistants["candidate-concierge"]
|
|
510
|
+
|
|
511
|
+
assistant.await_run(thread_id: thread.id, messages: messages)
|
|
512
|
+
assistant.thread_state(thread_id: thread.id)
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
`await_run` and `review_tool_calls` supply the assistant's own id, so a caller never passes
|
|
516
|
+
it -- passing `assistant_id:` to either raises `ArgumentError` rather than quietly sending the
|
|
517
|
+
call to a different assistant, which is what a migrated caller carrying its old argument would
|
|
518
|
+
otherwise do. Everything else is thread-scoped and delegates to the client untouched. Reach
|
|
519
|
+
the client directly with `assistant.client` if you need it.
|
|
520
|
+
|
|
521
|
+
### Configuration
|
|
522
|
+
|
|
523
|
+
Connection settings shared by every assistant sit at the top level; `definitions` holds one
|
|
524
|
+
entry per assistant, keyed by what you look it up with:
|
|
525
|
+
|
|
526
|
+
```ruby
|
|
527
|
+
config.assistants_config = {
|
|
528
|
+
"base_url" => "https://nip-assistants.example.com",
|
|
529
|
+
"user_id" => "my-app",
|
|
530
|
+
"definitions" => {
|
|
531
|
+
"candidate-concierge" => {},
|
|
532
|
+
"home-studio" => {
|
|
533
|
+
# Overrides the shared value for this assistant only.
|
|
534
|
+
"base_url" => "https://pr306.nip-assistants.example.com"
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
An entry may carry keys this gem has no use for — a graph id, or the Cerebro project an
|
|
541
|
+
assistant reports to — and they are ignored, so one structure can serve both a deployment and
|
|
542
|
+
the application reading it. That includes a `name` of its own: an entry's `name` is a
|
|
543
|
+
human-readable label for the assistant's record, distinct from the key it is filed under.
|
|
544
|
+
`Assistant#key` returns the latter.
|
|
545
|
+
|
|
546
|
+
### Credentials
|
|
547
|
+
|
|
548
|
+
Each entry supplies its own `api_key` and `assistant_id`:
|
|
549
|
+
|
|
550
|
+
```ruby
|
|
551
|
+
"definitions" => {
|
|
552
|
+
"candidate-concierge" => {
|
|
553
|
+
"api_key" => "...",
|
|
554
|
+
"assistant_id" => "..."
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Where a host gets them is its own business — an environment variable its deployment mounts, a
|
|
560
|
+
secrets store, a literal for local work. This gem reads no environment and assumes no naming
|
|
561
|
+
convention, so nothing here is tied to one deployment's wiring.
|
|
562
|
+
|
|
563
|
+
A key that resolves without them raises `Assistant::ConfigurationError`, naming every field it
|
|
564
|
+
is missing at once so a host resolving them from elsewhere can see which lookup failed.
|
|
565
|
+
|
|
566
|
+
### Without `definitions`
|
|
567
|
+
|
|
568
|
+
`assistants_config` lacking `definitions` is read as keyword arguments for a single
|
|
569
|
+
`Assistants` client, and `NitroIntelligence.assistants` returns that client rather than a
|
|
570
|
+
registry. This is the shape that predates lookup by key; a host still on it is left alone.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module NitroIntelligence
|
|
2
|
+
# One assistant resolved by name: the client for its deployment, plus the id every run has to
|
|
3
|
+
# carry.
|
|
4
|
+
#
|
|
5
|
+
# Credentials are supplied by the host. Where they come from is the host's business -- an
|
|
6
|
+
# environment variable its deployment mounts, a secrets store, a literal in configuration --
|
|
7
|
+
# so nothing here encodes one deployment's wiring.
|
|
8
|
+
class Assistant
|
|
9
|
+
class ConfigurationError < StandardError; end
|
|
10
|
+
|
|
11
|
+
DEFAULT_USER_ID = "default-user".freeze
|
|
12
|
+
REQUIRED = %w[base_url api_key assistant_id].freeze
|
|
13
|
+
|
|
14
|
+
# The key an assistant is filed and looked up under. Not its name: an entry usually carries
|
|
15
|
+
# a `name` of its own, a human-readable label for the assistant's record, and the two are
|
|
16
|
+
# different things.
|
|
17
|
+
attr_reader :key
|
|
18
|
+
|
|
19
|
+
attr_reader :assistant_id, :base_url, :user_id
|
|
20
|
+
|
|
21
|
+
# The key is positional so that splatting an entry cannot overwrite it, whatever the entry
|
|
22
|
+
# happens to carry.
|
|
23
|
+
#
|
|
24
|
+
# Extra keys are accepted and ignored for the same reason: an entry carries fields the
|
|
25
|
+
# application has no use for, such as the graph or the observability project it reports to.
|
|
26
|
+
def initialize(key, base_url: nil, api_key: nil, assistant_id: nil, user_id: nil, **_kwargs)
|
|
27
|
+
@key = key.to_s
|
|
28
|
+
@base_url = base_url.presence
|
|
29
|
+
@api_key = api_key.presence
|
|
30
|
+
@assistant_id = assistant_id.presence
|
|
31
|
+
@user_id = user_id.presence || DEFAULT_USER_ID
|
|
32
|
+
|
|
33
|
+
validate!
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def client
|
|
37
|
+
@client ||= Assistants.new(base_url: @base_url, api_key: @api_key, user_id: @user_id)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The two calls that identify an assistant take it from here rather than from the caller.
|
|
41
|
+
# Everything else is thread-scoped and delegates untouched.
|
|
42
|
+
def await_run(thread_id:, messages:, **kwargs)
|
|
43
|
+
reject_assistant_id!(kwargs)
|
|
44
|
+
client.await_run(thread_id:, assistant_id:, messages:, **kwargs)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def review_tool_calls(thread_id:, reviewer_id:, tool_calls:, **kwargs)
|
|
48
|
+
reject_assistant_id!(kwargs)
|
|
49
|
+
client.review_tool_calls(thread_id:, assistant_id:, reviewer_id:, tool_calls:, **kwargs)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
delegate :thread_state, :thread_messages, :tool_calls_pending_review, to: :client
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# An assistant supplies its own id, so a caller has no business passing one. It would land
|
|
57
|
+
# in the trailing splat, which Ruby applies last, and quietly replace the configured id --
|
|
58
|
+
# routing the call to a different assistant with nothing to show for it. Refuse instead.
|
|
59
|
+
def reject_assistant_id!(kwargs)
|
|
60
|
+
return unless kwargs.key?(:assistant_id)
|
|
61
|
+
|
|
62
|
+
raise ArgumentError,
|
|
63
|
+
"assistant #{@key.inspect} supplies its own assistant_id; remove it from the call"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Reported together and named, since a host resolving these from somewhere else needs to
|
|
67
|
+
# know which one it failed to supply.
|
|
68
|
+
def validate!
|
|
69
|
+
values = { "base_url" => @base_url, "api_key" => @api_key, "assistant_id" => @assistant_id }
|
|
70
|
+
missing = REQUIRED.select { |field| values[field].blank? }
|
|
71
|
+
return if missing.empty?
|
|
72
|
+
|
|
73
|
+
raise ConfigurationError, "assistant #{@key.inspect} is missing #{missing.join(', ')}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require "nitro_intelligence/assistant"
|
|
2
|
+
|
|
3
|
+
module NitroIntelligence
|
|
4
|
+
# Assistants addressable by name.
|
|
5
|
+
#
|
|
6
|
+
# NitroIntelligence.assistants["candidate-concierge"].await_run(...)
|
|
7
|
+
#
|
|
8
|
+
# Built from `assistants_config`: connection settings shared by every assistant sit at the
|
|
9
|
+
# top level, and each entry under `definitions` overrides them where it needs to. The key an
|
|
10
|
+
# entry is filed under is what it is looked up by, distinct from any `name` it carries.
|
|
11
|
+
#
|
|
12
|
+
# {
|
|
13
|
+
# "base_url" => "https://nip-assistants.example.com",
|
|
14
|
+
# "user_id" => "nitro-web",
|
|
15
|
+
# "definitions" => {
|
|
16
|
+
# "candidate-concierge" => { "graph_id" => "react-agent" },
|
|
17
|
+
# },
|
|
18
|
+
# }
|
|
19
|
+
class AssistantRegistry
|
|
20
|
+
class UnknownAssistantError < StandardError; end
|
|
21
|
+
|
|
22
|
+
DEFINITIONS_KEY = "definitions".freeze
|
|
23
|
+
SHARED_KEYS = %w[base_url user_id].freeze
|
|
24
|
+
|
|
25
|
+
def initialize(config = {})
|
|
26
|
+
@config = config.to_h.deep_stringify_keys
|
|
27
|
+
@assistants = {}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def [](key)
|
|
31
|
+
fetch(key)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def fetch(key)
|
|
35
|
+
key = key.to_s
|
|
36
|
+
@assistants[key] ||= build(key)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def key?(key)
|
|
40
|
+
definitions.key?(key.to_s)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def keys
|
|
44
|
+
definitions.keys
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def definitions
|
|
50
|
+
@config[DEFINITIONS_KEY] || {}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def build(key)
|
|
54
|
+
definition = definitions[key]
|
|
55
|
+
|
|
56
|
+
unless definition
|
|
57
|
+
raise UnknownAssistantError,
|
|
58
|
+
"No assistant configured for #{key.inspect}. " \
|
|
59
|
+
"Configured: #{keys.sort.join(', ').presence || '(none)'}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
attributes = @config.slice(*SHARED_KEYS).merge(definition.to_h.deep_stringify_keys)
|
|
63
|
+
Assistant.new(key, **attributes.symbolize_keys)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -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}")
|
data/lib/nitro_intelligence.rb
CHANGED
|
@@ -7,6 +7,7 @@ require "openai"
|
|
|
7
7
|
|
|
8
8
|
require "nitro_intelligence/version"
|
|
9
9
|
require "nitro_intelligence/agent_server"
|
|
10
|
+
require "nitro_intelligence/assistant_registry"
|
|
10
11
|
require "nitro_intelligence/assistants"
|
|
11
12
|
require "nitro_intelligence/client/base"
|
|
12
13
|
require "nitro_intelligence/client/client"
|
|
@@ -23,8 +24,15 @@ module NitroIntelligence
|
|
|
23
24
|
class << self
|
|
24
25
|
delegate :configure, :config, :logger, :environment, to: :configuration
|
|
25
26
|
|
|
27
|
+
# A registry addressable by name when `assistants_config` carries `definitions`, and the
|
|
28
|
+
# single pre-registry client otherwise. The two shapes are mutually exclusive, so the
|
|
29
|
+
# configuration decides which one a host gets: one that has not reshaped its config keeps
|
|
30
|
+
# the client it already had.
|
|
26
31
|
def assistants
|
|
27
|
-
|
|
32
|
+
current = assistants_config.to_h.deep_stringify_keys
|
|
33
|
+
return AssistantRegistry.new(current) if current.key?(AssistantRegistry::DEFINITIONS_KEY)
|
|
34
|
+
|
|
35
|
+
Assistants.new(**current.symbolize_keys)
|
|
28
36
|
end
|
|
29
37
|
|
|
30
38
|
# Deprecated: use `NitroIntelligence.assistants`.
|
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.6.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
|
|
@@ -76,6 +76,8 @@ files:
|
|
|
76
76
|
- docs/README.md
|
|
77
77
|
- lib/nitro_intelligence.rb
|
|
78
78
|
- lib/nitro_intelligence/agent_server.rb
|
|
79
|
+
- lib/nitro_intelligence/assistant.rb
|
|
80
|
+
- lib/nitro_intelligence/assistant_registry.rb
|
|
79
81
|
- lib/nitro_intelligence/assistants.rb
|
|
80
82
|
- lib/nitro_intelligence/client/base.rb
|
|
81
83
|
- lib/nitro_intelligence/client/client.rb
|