nitro_intelligence 2.7.0 → 3.0.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/assistant.rb +4 -3
- data/lib/nitro_intelligence/assistants.rb +49 -15
- data/lib/nitro_intelligence/client/handlers/base_handler.rb +3 -4
- data/lib/nitro_intelligence/client/handlers/observed/text_to_speech_handler.rb +5 -3
- data/lib/nitro_intelligence/configuration.rb +0 -4
- data/lib/nitro_intelligence/tool_call_review_interrupt.rb +135 -0
- data/lib/nitro_intelligence/tool_call_review_validator.rb +43 -12
- data/lib/nitro_intelligence/version.rb +1 -1
- data/lib/nitro_intelligence.rb +1 -24
- metadata +4 -5
- data/lib/nitro_intelligence/agent_server.rb +0 -12
- data/lib/nitro_intelligence/deprecation.rb +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6f2f9a233086c2c5b63e91124dd8ff9f6a838ba3eb1a7e08b382029efaf6c8a
|
|
4
|
+
data.tar.gz: d4b72f0693e8c10ad97d6ead9eebb469f69043d1323e49c097811530e17c5d90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: caf47b70030388d5a3aaaa31e4fa9aa217b401e740fdd41a166539c12bfae0e9c4342e57543510a83af4c8c9788e7c50b752324273ee4a7302c903f754a5b14e
|
|
7
|
+
data.tar.gz: 426c37a3551965cee7946631d27b00b9e84e79620640cf1575a60d827a15f5e3b2ae1f40cde95e2df39efa15ec6c2a730ee3f66fbf1dcb4e4c0c148c037e5be2
|
|
@@ -48,12 +48,13 @@ module NitroIntelligence
|
|
|
48
48
|
client.await_run(thread_id:, assistant_id:, messages:, **kwargs)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def review_tool_calls(thread_id:,
|
|
51
|
+
def review_tool_calls(thread_id:, tool_calls:, **kwargs)
|
|
52
52
|
reject_assistant_id!(kwargs)
|
|
53
|
-
client.review_tool_calls(thread_id:, assistant_id:,
|
|
53
|
+
client.review_tool_calls(thread_id:, assistant_id:, tool_calls:, **kwargs)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
delegate :thread_state, :thread_messages, :tool_calls_pending_review,
|
|
56
|
+
delegate :thread_state, :thread_messages, :tool_calls_pending_review, :tool_calls_under_review,
|
|
57
|
+
to: :client
|
|
57
58
|
|
|
58
59
|
private
|
|
59
60
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "net/http"
|
|
3
3
|
require "uri"
|
|
4
|
+
require "nitro_intelligence/tool_call_review_interrupt"
|
|
4
5
|
require "nitro_intelligence/tool_call_review_validator"
|
|
5
6
|
|
|
6
7
|
module NitroIntelligence
|
|
@@ -75,29 +76,53 @@ module NitroIntelligence
|
|
|
75
76
|
end
|
|
76
77
|
end
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
# The tool calls the thread's interrupt is holding, in the order the platform wants decisions
|
|
80
|
+
# for them, each with the `allowed_decisions` a reviewer may take on it. Empty when the thread
|
|
81
|
+
# is not waiting on a review.
|
|
82
|
+
#
|
|
83
|
+
# A tool the assistant is not configured to interrupt on runs without review, so an AI message
|
|
84
|
+
# can mix calls under review with calls that are only waiting to be executed. This reports the
|
|
85
|
+
# former; #tool_calls_pending_review reports both.
|
|
86
|
+
def tool_calls_under_review(thread_id:)
|
|
87
|
+
ToolCallReviewInterrupt.new(get_thread_state(thread_id:)).tool_calls
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Resumes an interrupted thread with one review per tool call the interrupt is holding. Each
|
|
91
|
+
# review is keyed by tool call id and names an `action` -- `approve`, `edit`, `reject` or
|
|
92
|
+
# `respond` -- from the decisions the interrupt allows for that tool. `edit` carries `args`,
|
|
93
|
+
# merged over the arguments the model asked for; `respond` carries the `message` returned to the
|
|
94
|
+
# model as the tool's result; `reject` may carry a `message` explaining the refusal.
|
|
95
|
+
#
|
|
96
|
+
# Assistants records nothing about who reviewed a tool call, and the resume payload it accepts
|
|
97
|
+
# has nowhere to carry it, so there is no reviewer argument to pass.
|
|
98
|
+
def review_tool_calls(thread_id:, assistant_id:, tool_calls:, context: {})
|
|
80
99
|
thread = get_thread(thread_id:)
|
|
81
100
|
raise ThreadResumptionError, "Thread #{thread_id} is not in the interrupted state" unless interrupted?(thread)
|
|
82
101
|
|
|
83
|
-
|
|
102
|
+
interrupt = ToolCallReviewInterrupt.new(get_thread_state(thread_id:))
|
|
103
|
+
tool_calls_under_review = interrupt.tool_calls
|
|
84
104
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
105
|
+
if tool_calls_under_review.empty?
|
|
106
|
+
raise ThreadResumptionError, "Thread #{thread_id} has no tool calls awaiting review"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@tool_call_review_validator.validate!(tool_calls:, tool_calls_under_review:)
|
|
90
110
|
|
|
91
111
|
resume_run(
|
|
92
112
|
thread_id:,
|
|
93
113
|
assistant_id:,
|
|
94
|
-
resume
|
|
95
|
-
context:
|
|
114
|
+
resume: { decisions: interrupt.decisions(tool_calls) },
|
|
115
|
+
context:
|
|
96
116
|
)
|
|
97
117
|
|
|
98
118
|
nil
|
|
99
119
|
end
|
|
100
120
|
|
|
121
|
+
# ContactCenter::VirtualConfirmationAgent::Client in nitro-web subclasses this to speak the
|
|
122
|
+
# VCA's own review protocol, and its override reaches `get_thread`, `interrupted?`,
|
|
123
|
+
# `get_thread_state` and `resume_run` below. Renaming any of the four breaks that override --
|
|
124
|
+
# loudly, in nitro-web's suite at bump time rather than in anything here. The subclass goes when
|
|
125
|
+
# the VCA moves onto this platform, and this note with it.
|
|
101
126
|
private
|
|
102
127
|
|
|
103
128
|
# Assistants accepts `initial_state` on thread creation but never applies it, so a brand new thread is
|
|
@@ -207,9 +232,19 @@ module NitroIntelligence
|
|
|
207
232
|
raise RunError, run_response.body if run_response.code.to_i != 200
|
|
208
233
|
|
|
209
234
|
run = JSON.parse(run_response.body)
|
|
235
|
+
raise_run_error!(run, RunError)
|
|
236
|
+
|
|
210
237
|
Array(run["messages"]).last&.dig("content")
|
|
211
238
|
end
|
|
212
239
|
|
|
240
|
+
def raise_run_error!(run, error)
|
|
241
|
+
failure = run["__error__"]
|
|
242
|
+
return if failure.blank?
|
|
243
|
+
|
|
244
|
+
detail = failure.is_a?(Hash) ? [failure["error"], failure["message"]].compact.join(": ") : failure.to_s
|
|
245
|
+
raise error, detail
|
|
246
|
+
end
|
|
247
|
+
|
|
213
248
|
def resume_run(thread_id:, assistant_id:, resume:, context:)
|
|
214
249
|
run_response = post(
|
|
215
250
|
path: "/threads/#{thread_id}/runs/wait",
|
|
@@ -224,17 +259,16 @@ module NitroIntelligence
|
|
|
224
259
|
|
|
225
260
|
raise ThreadResumptionError, run_response.body if run_response.code.to_i != 200
|
|
226
261
|
|
|
227
|
-
JSON.parse(run_response.body)
|
|
262
|
+
run = JSON.parse(run_response.body)
|
|
263
|
+
raise_run_error!(run, ThreadResumptionError)
|
|
264
|
+
|
|
265
|
+
run
|
|
228
266
|
end
|
|
229
267
|
|
|
230
268
|
def interrupted?(thread)
|
|
231
269
|
thread["status"] == "interrupted"
|
|
232
270
|
end
|
|
233
271
|
|
|
234
|
-
def interrupt_context(thread_state)
|
|
235
|
-
thread_state.dig("interrupts", 0, "value", "context") || {}
|
|
236
|
-
end
|
|
237
|
-
|
|
238
272
|
def messages_in(thread_state)
|
|
239
273
|
Array(thread_state.dig("values", "messages"))
|
|
240
274
|
end
|
|
@@ -61,10 +61,9 @@ module NitroIntelligence
|
|
|
61
61
|
|
|
62
62
|
private
|
|
63
63
|
|
|
64
|
-
# `last_response` carries the HTTP metadata of the response a
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
# method and its value are optional.
|
|
64
|
+
# `last_response` carries the HTTP metadata of the response a model was built
|
|
65
|
+
# from. The client leaves it unset on nested and locally constructed models,
|
|
66
|
+
# so both the method and its value are optional.
|
|
68
67
|
def response_headers(response)
|
|
69
68
|
return nil unless response.respond_to?(:last_response)
|
|
70
69
|
|
|
@@ -79,11 +79,13 @@ module NitroIntelligence
|
|
|
79
79
|
output = handle_text_to_speech_upload(tempfile, trace_id)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
82
|
+
# Usage details and the resolved model are read off a response body, and
|
|
83
|
+
# this endpoint's body is audio, so neither is available here. The
|
|
84
|
+
# requested model and the input are already on the observation from
|
|
85
|
+
# before the request ran.
|
|
85
86
|
trace_attributes = {
|
|
86
87
|
output:,
|
|
88
|
+
cost_details: @base_handler.cost_details(tts),
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
[tts, trace_attributes]
|
|
@@ -18,10 +18,6 @@ module NitroIntelligence
|
|
|
18
18
|
config_accessor :observability_projects, default: []
|
|
19
19
|
config_accessor :observability_user_id, default: ""
|
|
20
20
|
|
|
21
|
-
# Deprecated: configure `assistants_config` instead. Keeps its original `{}` default through the
|
|
22
|
-
# deprecation window, so a host building the hash up in place still has one to build on.
|
|
23
|
-
config_accessor :agent_server_config, default: {}
|
|
24
|
-
|
|
25
21
|
class << self
|
|
26
22
|
def configure
|
|
27
23
|
yield config
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require "active_support/core_ext/hash/indifferent_access"
|
|
2
|
+
|
|
3
|
+
module NitroIntelligence
|
|
4
|
+
# The tool calls one interrupt is holding, and the resume payload that answers them.
|
|
5
|
+
#
|
|
6
|
+
# LangChain's `HumanInTheLoopMiddleware` publishes `action_requests` -- a tool name, its arguments
|
|
7
|
+
# and a description -- alongside a `review_configs` entry per tool naming the decisions a reviewer
|
|
8
|
+
# may take. It resumes with `decisions`, one per action request, matched to them by position.
|
|
9
|
+
#
|
|
10
|
+
# Action requests carry no tool call id, and ids are what a review interface works in, so each is
|
|
11
|
+
# matched back onto the tool calls of the thread's last AI message -- the ones the middleware built
|
|
12
|
+
# them from -- to recover the id. The order of `#tool_calls` is the order `decisions` must be in.
|
|
13
|
+
class ToolCallReviewInterrupt
|
|
14
|
+
def initialize(thread_state)
|
|
15
|
+
@thread_state = thread_state
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def tool_calls
|
|
19
|
+
@tool_calls ||= build_tool_calls
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The `command.resume` payload for the reviews, ordered to match the action requests. Callers
|
|
23
|
+
# key their reviews by tool call id; the platform wants a positional list.
|
|
24
|
+
def decisions(reviews)
|
|
25
|
+
reviews = reviews.with_indifferent_access
|
|
26
|
+
|
|
27
|
+
tool_calls.map { |tool_call| decision_for(tool_call, reviews[tool_call["id"]]) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def build_tool_calls
|
|
33
|
+
unmatched = last_ai_tool_calls.dup
|
|
34
|
+
|
|
35
|
+
action_requests.map do |action_request|
|
|
36
|
+
tool_call = take_matching_tool_call(unmatched, action_request)
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
"previous_message_id" => previous_message_id,
|
|
40
|
+
"id" => tool_call["id"],
|
|
41
|
+
"name" => tool_call["name"],
|
|
42
|
+
"args" => tool_call["args"] || {},
|
|
43
|
+
"allowed_decisions" => allowed_decisions_by_tool_name.fetch(action_request["name"], []),
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Arguments distinguish two calls to the same tool, and a name-only match covers a platform that
|
|
49
|
+
# reformats them on the way into the interrupt.
|
|
50
|
+
def take_matching_tool_call(unmatched, action_request)
|
|
51
|
+
name = action_request["name"]
|
|
52
|
+
index = unmatched.index { |tool_call| tool_call["name"] == name && tool_call["args"] == action_request["args"] }
|
|
53
|
+
index ||= unmatched.index { |tool_call| tool_call["name"] == name }
|
|
54
|
+
|
|
55
|
+
unless index
|
|
56
|
+
raise Assistants::ThreadResumptionError,
|
|
57
|
+
"No tool call on the thread matches the interrupt's action request for `#{name}`"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
unmatched.delete_at(index)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# An action this does not recognise is refused rather than read as an approval. #review_tool_calls
|
|
64
|
+
# validates before it gets here, so this fires for a caller building a resume payload itself --
|
|
65
|
+
# or for a decision the platform gains and this does not, where running the tool for a reviewer
|
|
66
|
+
# who asked for something else is the one outcome human review exists to prevent.
|
|
67
|
+
def decision_for(tool_call, review)
|
|
68
|
+
review = (review || {}).with_indifferent_access
|
|
69
|
+
|
|
70
|
+
case review[:action].to_s
|
|
71
|
+
when "approve"
|
|
72
|
+
{ "type" => "approve" }
|
|
73
|
+
when "edit"
|
|
74
|
+
{ "type" => "edit", "edited_action" => edited_action(tool_call, review) }
|
|
75
|
+
when "reject"
|
|
76
|
+
review[:message].nil? ? { "type" => "reject" } : { "type" => "reject", "message" => review[:message] }
|
|
77
|
+
when "respond"
|
|
78
|
+
{ "type" => "respond", "message" => review[:message] }
|
|
79
|
+
else
|
|
80
|
+
raise Assistants::ThreadResumptionError,
|
|
81
|
+
"Tool call #{tool_call['id']} has no review naming `approve`, `edit`, `reject` or `respond`"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Edited arguments are merged over the call the model made, so a reviewer changing one of them
|
|
86
|
+
# does not have to restate the rest -- and cannot drop one by omitting it.
|
|
87
|
+
def edited_action(tool_call, review)
|
|
88
|
+
{
|
|
89
|
+
"name" => tool_call["name"],
|
|
90
|
+
"args" => tool_call["args"].merge(review[:args] || {}),
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def interrupt_value
|
|
95
|
+
@interrupt_value ||= @thread_state.dig("interrupts", 0, "value") || {}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def action_requests
|
|
99
|
+
Array(interrupt_value["action_requests"])
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def allowed_decisions_by_tool_name
|
|
103
|
+
@allowed_decisions_by_tool_name ||= Array(interrupt_value["review_configs"]).to_h do |review_config|
|
|
104
|
+
[review_config["action_name"], Array(review_config["allowed_decisions"]).map(&:to_s)]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def last_ai_tool_calls
|
|
109
|
+
Array(last_ai_message&.dig("tool_calls"))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# An interrupt only ever holds the tool calls of the message the model has just produced.
|
|
113
|
+
def last_ai_message_index
|
|
114
|
+
@last_ai_message_index ||= messages.rindex do |message|
|
|
115
|
+
message["type"] == "ai" && Array(message["tool_calls"]).any?
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def last_ai_message
|
|
120
|
+
last_ai_message_index && messages[last_ai_message_index]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# The message the reviewer needs to read to judge the call, as #tool_calls_pending_review
|
|
124
|
+
# reports it: the one immediately before the tool-call attempt.
|
|
125
|
+
def previous_message_id
|
|
126
|
+
return nil if last_ai_message_index.nil? || last_ai_message_index.zero?
|
|
127
|
+
|
|
128
|
+
messages[last_ai_message_index - 1]&.dig("id")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def messages
|
|
132
|
+
@messages ||= Array(@thread_state.dig("values", "messages"))
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
require "active_support/core_ext/hash/indifferent_access"
|
|
2
2
|
|
|
3
3
|
module NitroIntelligence
|
|
4
|
+
# The reviews a caller submitted, checked against the tool calls the interrupt is holding, before
|
|
5
|
+
# anything is sent. Raises `Assistants::ThreadResumptionError` on the first problem it finds.
|
|
6
|
+
#
|
|
7
|
+
# Internal to `Assistants#review_tool_calls`, which builds the only instance there is. What
|
|
8
|
+
# `#validate!` takes follows what that method needs and has changed with it before, so it carries
|
|
9
|
+
# no promise to anything calling it directly.
|
|
4
10
|
class ToolCallReviewValidator
|
|
5
|
-
def validate!(
|
|
11
|
+
def validate!(tool_calls:, tool_calls_under_review:)
|
|
6
12
|
tool_calls = normalize_tool_calls(tool_calls)
|
|
7
|
-
|
|
8
|
-
review_actions = Array(thread_state.dig("interrupts", 0, "value", "review_actions"))
|
|
13
|
+
tool_calls_under_review_by_id = Array(tool_calls_under_review).index_by { |tool_call| tool_call["id"] }
|
|
9
14
|
|
|
10
15
|
tool_calls.each do |tool_call_id, review|
|
|
11
|
-
|
|
12
|
-
raise_error!("Unknown tool call ids: #{tool_call_id}") unless
|
|
16
|
+
tool_call_under_review = tool_calls_under_review_by_id[tool_call_id]&.with_indifferent_access
|
|
17
|
+
raise_error!("Unknown tool call ids: #{tool_call_id}") unless tool_call_under_review
|
|
13
18
|
|
|
14
19
|
review = normalize_review(tool_call_id, review)
|
|
15
20
|
review_action = review[:action].to_s
|
|
16
21
|
|
|
17
|
-
unless
|
|
22
|
+
unless Array(tool_call_under_review[:allowed_decisions]).include?(review_action)
|
|
18
23
|
raise_error!("Invalid review action `#{review_action}` for tool call #{tool_call_id}")
|
|
19
24
|
end
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
validate_review_details!(tool_call_id:, review:, review_action:, tool_call_under_review:)
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
validate_completeness!(
|
|
25
30
|
submitted_tool_call_ids: tool_calls.keys,
|
|
26
|
-
|
|
31
|
+
tool_calls_under_review:
|
|
27
32
|
)
|
|
28
33
|
end
|
|
29
34
|
|
|
@@ -41,19 +46,45 @@ module NitroIntelligence
|
|
|
41
46
|
review.with_indifferent_access
|
|
42
47
|
end
|
|
43
48
|
|
|
44
|
-
def
|
|
49
|
+
def validate_review_details!(tool_call_id:, review:, review_action:, tool_call_under_review:)
|
|
50
|
+
case review_action
|
|
51
|
+
when "edit"
|
|
52
|
+
validate_edited_args!(tool_call_id:, review:, tool_call_under_review:)
|
|
53
|
+
when "reject"
|
|
54
|
+
# The middleware falls back to its own wording when a rejection carries no reason.
|
|
55
|
+
validate_message!(tool_call_id:, review:, required: false)
|
|
56
|
+
when "respond"
|
|
57
|
+
# The message is returned to the model as the tool's result, so there is nothing to send
|
|
58
|
+
# without it.
|
|
59
|
+
validate_message!(tool_call_id:, review:, required: true)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def validate_edited_args!(tool_call_id:, review:, tool_call_under_review:)
|
|
45
64
|
provided_args = review[:args]
|
|
46
65
|
raise_error!("Edited args for tool call #{tool_call_id} must be a hash") unless provided_args.is_a?(Hash)
|
|
47
66
|
|
|
48
|
-
valid_arg_names =
|
|
67
|
+
valid_arg_names = tool_call_under_review.fetch(:args, {}).keys.map(&:to_s)
|
|
49
68
|
invalid_arg_names = provided_args.keys.map(&:to_s) - valid_arg_names
|
|
50
69
|
return if invalid_arg_names.empty?
|
|
51
70
|
|
|
52
71
|
raise_error!("Invalid edited args for tool call #{tool_call_id}: #{invalid_arg_names.join(', ')}")
|
|
53
72
|
end
|
|
54
73
|
|
|
55
|
-
def
|
|
56
|
-
|
|
74
|
+
def validate_message!(tool_call_id:, review:, required:)
|
|
75
|
+
message = review[:message]
|
|
76
|
+
|
|
77
|
+
if message.nil?
|
|
78
|
+
raise_error!("Review for tool call #{tool_call_id} must include a message") if required
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
raise_error!("Message for tool call #{tool_call_id} must be a string") unless message.is_a?(String)
|
|
83
|
+
raise_error!("Review for tool call #{tool_call_id} must include a message") if required && message.blank?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def validate_completeness!(submitted_tool_call_ids:, tool_calls_under_review:)
|
|
87
|
+
missing_tool_call_ids = Array(tool_calls_under_review).filter_map do |tool_call|
|
|
57
88
|
tool_call_id = tool_call["id"].to_s
|
|
58
89
|
tool_call_id unless submitted_tool_call_ids.include?(tool_call_id)
|
|
59
90
|
end
|
data/lib/nitro_intelligence.rb
CHANGED
|
@@ -6,13 +6,11 @@ require "langfuse"
|
|
|
6
6
|
require "openai"
|
|
7
7
|
|
|
8
8
|
require "nitro_intelligence/version"
|
|
9
|
-
require "nitro_intelligence/agent_server"
|
|
10
9
|
require "nitro_intelligence/assistant_registry"
|
|
11
10
|
require "nitro_intelligence/assistants"
|
|
12
11
|
require "nitro_intelligence/client/base"
|
|
13
12
|
require "nitro_intelligence/client/client"
|
|
14
13
|
require "nitro_intelligence/configuration"
|
|
15
|
-
require "nitro_intelligence/deprecation"
|
|
16
14
|
require "nitro_intelligence/media/image_generation"
|
|
17
15
|
require "nitro_intelligence/models/model_catalog"
|
|
18
16
|
require "nitro_intelligence/observability/project_client_registry"
|
|
@@ -29,18 +27,12 @@ module NitroIntelligence
|
|
|
29
27
|
# configuration decides which one a host gets: one that has not reshaped its config keeps
|
|
30
28
|
# the client it already had.
|
|
31
29
|
def assistants
|
|
32
|
-
current = assistants_config.to_h.deep_stringify_keys
|
|
30
|
+
current = configuration.assistants_config.to_h.deep_stringify_keys
|
|
33
31
|
return AssistantRegistry.new(current) if current.key?(AssistantRegistry::DEFINITIONS_KEY)
|
|
34
32
|
|
|
35
33
|
Assistants.new(**current.symbolize_keys)
|
|
36
34
|
end
|
|
37
35
|
|
|
38
|
-
# Deprecated: use `NitroIntelligence.assistants`.
|
|
39
|
-
def agent_server
|
|
40
|
-
deprecator.warn("`NitroIntelligence.agent_server` is deprecated. Use `NitroIntelligence.assistants` instead.")
|
|
41
|
-
assistants
|
|
42
|
-
end
|
|
43
|
-
|
|
44
36
|
def cache
|
|
45
37
|
configuration.cache_provider
|
|
46
38
|
end
|
|
@@ -54,20 +46,5 @@ module NitroIntelligence
|
|
|
54
46
|
base_url: configuration.observability_base_url
|
|
55
47
|
)
|
|
56
48
|
end
|
|
57
|
-
|
|
58
|
-
private
|
|
59
|
-
|
|
60
|
-
# A host that has migrated is left alone, so a stale `agent_server_config` cannot override the
|
|
61
|
-
# configuration it was replaced by.
|
|
62
|
-
def assistants_config
|
|
63
|
-
current_config = configuration.assistants_config
|
|
64
|
-
legacy_config = configuration.agent_server_config
|
|
65
|
-
return current_config if current_config.present? || legacy_config.blank?
|
|
66
|
-
|
|
67
|
-
deprecator.warn(
|
|
68
|
-
"`agent_server_config` is deprecated. Configure `assistants_config` instead."
|
|
69
|
-
)
|
|
70
|
-
legacy_config
|
|
71
|
-
end
|
|
72
49
|
end
|
|
73
50
|
end
|
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:
|
|
4
|
+
version: 3.0.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.86'
|
|
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.86'
|
|
68
68
|
description: The Ruby client for Nitro Intelligence
|
|
69
69
|
email:
|
|
70
70
|
- igor.artemenko@powerhrg.com
|
|
@@ -75,7 +75,6 @@ files:
|
|
|
75
75
|
- Rakefile
|
|
76
76
|
- docs/README.md
|
|
77
77
|
- lib/nitro_intelligence.rb
|
|
78
|
-
- lib/nitro_intelligence/agent_server.rb
|
|
79
78
|
- lib/nitro_intelligence/assistant.rb
|
|
80
79
|
- lib/nitro_intelligence/assistant_registry.rb
|
|
81
80
|
- lib/nitro_intelligence/assistants.rb
|
|
@@ -94,7 +93,6 @@ files:
|
|
|
94
93
|
- lib/nitro_intelligence/client/observed.rb
|
|
95
94
|
- lib/nitro_intelligence/client/observers/langfuse_observer.rb
|
|
96
95
|
- lib/nitro_intelligence/configuration.rb
|
|
97
|
-
- lib/nitro_intelligence/deprecation.rb
|
|
98
96
|
- lib/nitro_intelligence/langfuse_extension.rb
|
|
99
97
|
- lib/nitro_intelligence/langfuse_tracer_provider.rb
|
|
100
98
|
- lib/nitro_intelligence/media/audio.rb
|
|
@@ -113,6 +111,7 @@ files:
|
|
|
113
111
|
- lib/nitro_intelligence/observability/prompt_store.rb
|
|
114
112
|
- lib/nitro_intelligence/observability/upload_handler.rb
|
|
115
113
|
- lib/nitro_intelligence/reporter.rb
|
|
114
|
+
- lib/nitro_intelligence/tool_call_review_interrupt.rb
|
|
116
115
|
- lib/nitro_intelligence/tool_call_review_validator.rb
|
|
117
116
|
- lib/nitro_intelligence/trace.rb
|
|
118
117
|
- lib/nitro_intelligence/version.rb
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
require "active_support/deprecation/constant_accessor"
|
|
2
|
-
require "nitro_intelligence/assistants"
|
|
3
|
-
require "nitro_intelligence/deprecation"
|
|
4
|
-
|
|
5
|
-
module NitroIntelligence
|
|
6
|
-
include ActiveSupport::Deprecation::DeprecatedConstantAccessor
|
|
7
|
-
|
|
8
|
-
# Deprecated: the agent server is now Nitro Intelligence Assistants. Resolved through
|
|
9
|
-
# `const_missing` so that the old name returns the real class rather than a stand-in for it,
|
|
10
|
-
# keeping `is_a?`, `===`, `rescue` and the nested error constants working on upgrade.
|
|
11
|
-
deprecate_constant :AgentServer, "NitroIntelligence::Assistants", deprecator:
|
|
12
|
-
end
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
require "active_support/deprecation"
|
|
2
|
-
|
|
3
|
-
module NitroIntelligence
|
|
4
|
-
class << self
|
|
5
|
-
# Deprecations introduced while the agent server was renamed to Nitro Intelligence Assistants.
|
|
6
|
-
# The names they cover are removed in 3.0.
|
|
7
|
-
def deprecator
|
|
8
|
-
@deprecator ||= ActiveSupport::Deprecation.new("3.0", "Nitro Intelligence")
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|