Class: NitroIntelligence::Assistants
- Inherits:
-
Object
- Object
- NitroIntelligence::Assistants
- Defined in:
- lib/nitro_intelligence/assistants.rb
Defined Under Namespace
Classes: ConfigurationError, RunError, ThreadInitializationError, ThreadResumptionError, ThreadStateError
Constant Summary collapse
- THREAD_CONFLICT_CODE =
Assistants answers with a conflict when
ifExists: "raise"is sent for a thread that already exists. 409- DEFAULT_BASE_URL =
Every assistant this gem is built for is served by the same deployment, so a client told nothing about where to reach one gets that deployment. A host talking to a different one -- a review environment, a local server -- still says so.
"https://assistants.powerhome.ai".freeze
- DEFAULT_USER_ID =
"default-user".freeze
Instance Attribute Summary collapse
-
#base_url ⇒ Object
readonly
Returns the value of attribute base_url.
-
#user_id ⇒ Object
readonly
Returns the value of attribute user_id.
Instance Method Summary collapse
- #await_run(thread_id:, assistant_id:, messages:, context: {}) ⇒ Object
-
#initialize(api_key:, base_url: nil, user_id: DEFAULT_USER_ID) ⇒ Assistants
constructor
A new instance of Assistants.
-
#review_tool_calls(thread_id:, assistant_id:, tool_calls:, context: {}) ⇒ Object
Resumes an interrupted thread with one review per tool call the interrupt is holding.
-
#thread_messages(thread_id:) ⇒ Object
The thread's messages as Assistants reports them, unformatted, oldest first.
-
#thread_state(thread_id:) ⇒ Object
The thread's state as Assistants reports it, unformatted.
- #tool_calls_pending_review(thread_id:) ⇒ Object
-
#tool_calls_under_review(thread_id:) ⇒ Object
The tool calls the thread's interrupt is holding, in the order the platform wants decisions for them, each with the
allowed_decisionsa reviewer may take on it.
Constructor Details
#initialize(api_key:, base_url: nil, user_id: DEFAULT_USER_ID) ⇒ Assistants
Returns a new instance of Assistants.
27 28 29 30 31 32 33 34 35 36 |
# File 'lib/nitro_intelligence/assistants.rb', line 27 def initialize(api_key:, base_url: nil, user_id: DEFAULT_USER_ID) raise ConfigurationError, "api_key is required" if api_key.blank? raise ConfigurationError, "user_id is required" if user_id.blank? @base_url = base_url.presence || DEFAULT_BASE_URL @api_key = api_key @user_id = user_id @tool_call_review_validator = ToolCallReviewValidator.new @graph_ids = {} end |
Instance Attribute Details
#base_url ⇒ Object (readonly)
Returns the value of attribute base_url.
25 26 27 |
# File 'lib/nitro_intelligence/assistants.rb', line 25 def base_url @base_url end |
#user_id ⇒ Object (readonly)
Returns the value of attribute user_id.
25 26 27 |
# File 'lib/nitro_intelligence/assistants.rb', line 25 def user_id @user_id end |
Instance Method Details
#await_run(thread_id:, assistant_id:, messages:, context: {}) ⇒ Object
38 39 40 41 42 43 44 45 46 |
# File 'lib/nitro_intelligence/assistants.rb', line 38 def await_run(thread_id:, assistant_id:, messages:, context: {}) raise RunError, "messages cannot be empty" if .blank? initial_state = [0..-2] = .last initialize_thread_if_needed(thread_id:, assistant_id:, initial_state:) trigger_run(thread_id:, assistant_id:, context:, last_message:) end |
#review_tool_calls(thread_id:, assistant_id:, tool_calls:, context: {}) ⇒ Object
Resumes an interrupted thread with one review per tool call the interrupt is holding. Each
review is keyed by tool call id and names an action -- approve, edit, reject or
respond -- from the decisions the interrupt allows for that tool. edit carries args,
merged over the arguments the model asked for; respond carries the message returned to the
model as the tool's result; reject may carry a message explaining the refusal.
Assistants records nothing about who reviewed a tool call, and the resume payload it accepts has nowhere to carry it, so there is no reviewer argument to pass.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/nitro_intelligence/assistants.rb', line 98 def review_tool_calls(thread_id:, assistant_id:, tool_calls:, context: {}) thread = get_thread(thread_id:) raise ThreadResumptionError, "Thread #{thread_id} is not in the interrupted state" unless interrupted?(thread) interrupt = ToolCallReviewInterrupt.new(get_thread_state(thread_id:)) tool_calls_under_review = interrupt.tool_calls if tool_calls_under_review.empty? raise ThreadResumptionError, "Thread #{thread_id} has no tool calls awaiting review" end @tool_call_review_validator.validate!(tool_calls:, tool_calls_under_review:) resume_run( thread_id:, assistant_id:, resume: { decisions: interrupt.decisions(tool_calls) }, context: ) nil end |
#thread_messages(thread_id:) ⇒ Object
The thread's messages as Assistants reports them, unformatted, oldest first. Each message
carries its own type ("human", "ai", "tool", ...), which callers map to their own roles.
56 57 58 |
# File 'lib/nitro_intelligence/assistants.rb', line 56 def (thread_id:) (thread_state(thread_id:)) end |
#thread_state(thread_id:) ⇒ Object
The thread's state as Assistants reports it, unformatted. Callers that only want the conversation should reach for #thread_messages instead.
50 51 52 |
# File 'lib/nitro_intelligence/assistants.rb', line 50 def thread_state(thread_id:) get_thread_state(thread_id:, error: ThreadStateError) end |
#tool_calls_pending_review(thread_id:) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/nitro_intelligence/assistants.rb', line 60 def tool_calls_pending_review(thread_id:) thread_state = get_thread_state(thread_id:) = (thread_state) reviewed_tool_call_ids = ().map { || ["tool_call_id"] } .each_with_index.flat_map do |, index| next [] unless ["type"] == "ai" pending_tool_calls(, reviewed_tool_call_ids).map do |tool_call| { "previous_message_id" => index.zero? ? nil : [index - 1]&.dig("id"), "id" => tool_call["id"], "name" => tool_call["name"], "args" => tool_call["args"] || {}, } end end end |
#tool_calls_under_review(thread_id:) ⇒ Object
The tool calls the thread's interrupt is holding, in the order the platform wants decisions
for them, each with the allowed_decisions a reviewer may take on it. Empty when the thread
is not waiting on a review.
A tool the assistant is not configured to interrupt on runs without review, so an AI message can mix calls under review with calls that are only waiting to be executed. This reports the former; #tool_calls_pending_review reports both.
86 87 88 |
# File 'lib/nitro_intelligence/assistants.rb', line 86 def tool_calls_under_review(thread_id:) ToolCallReviewInterrupt.new(get_thread_state(thread_id:)).tool_calls end |