Skip to content

Commit f9046a3

Browse files
authored
Merge pull request alexrudall#404 from coezbek/coezbek-readme-assistant
Completed README additions for Assistants API
2 parents 6b11361 + 5a04542 commit f9046a3

File tree

1 file changed

+161
-4
lines changed

1 file changed

+161
-4
lines changed

README.md

Lines changed: 161 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ Assistants can call models to interact with threads and use tools to perform tas
456456

457457
To create a new assistant (see [API documentation](https://platform.openai.com/docs/api-reference/assistants/createAssistant)):
458458

459-
```
459+
```ruby
460460
response = client.assistants.create(
461461
parameters: {
462462
model: "gpt-3.5-turbo-1106", # Retrieve via client.models.list. Assistants need 'gpt-3.5-turbo-1106' or later.
@@ -475,19 +475,19 @@ assistant_id = response["id"]
475475

476476
Given an `assistant_id` you can `retrieve` the current field values:
477477

478-
```
478+
```ruby
479479
client.assistants.retrieve(id: assistant_id)
480480
```
481481

482482
You can get a `list` of all assistants currently available under the organization:
483483

484-
```
484+
```ruby
485485
client.assistants.list
486486
```
487487

488488
You can modify an existing assistant using the assistant's id (see [API documentation](https://platform.openai.com/docs/api-reference/assistants/modifyAssistant)):
489489

490-
```
490+
```ruby
491491
response = client.assistants.modify(
492492
id: assistant_id,
493493
parameters: {
@@ -502,6 +502,163 @@ You can delete assistants:
502502
client.assistants.delete(id: assistant_id)
503503
```
504504

505+
### Threads and Messages
506+
507+
Once you have created an assistant as described above, you need to prepare a `Thread` of `Messages` for the assistant to work on (see [introduction on Assistants](https://platform.openai.com/docs/assistants/how-it-works)). For example, as an initial setup you could do:
508+
509+
```ruby
510+
# Create thread
511+
response = client.threads.create # Note: Once you create a thread, there is no way to list it
512+
# or recover it currently (as of 2023-12-10). So hold onto the `id`
513+
thread_id = response["id"]
514+
515+
# Add initial message from user (see https://platform.openai.com/docs/api-reference/messages/createMessage)
516+
message_id = client.messages.create(
517+
thread_id: thread_id,
518+
parameters: {
519+
role: "user", # Required for manually created messages
520+
content: "Can you help me write an API library to interact with the OpenAI API please?"
521+
})["id"]
522+
523+
# Retrieve individual message
524+
message = client.messages.retrieve(thread_id: thread_id, id: message_id)
525+
526+
# Review all messages on the thread
527+
messages = client.messages.list(thread_id: thread_id)
528+
```
529+
530+
To clean up after a thread is no longer needed:
531+
532+
```ruby
533+
# To delete the thread (and all associated messages):
534+
client.threads.delete(id: thread_id)
535+
536+
client.messages.retrieve(thread_id: thread_id, id: message_id) # -> Fails after thread is deleted
537+
```
538+
539+
540+
### Runs
541+
542+
To submit a thread to be evaluated with the model of an assistant, create a `Run` as follows (Note: This is one place where OpenAI will take your money):
543+
544+
```ruby
545+
# Create run (will use instruction/model/tools from Assistant's definition)
546+
response = client.runs.create(thread_id: thread_id,
547+
parameters: {
548+
assistant_id: assistant_id
549+
})
550+
run_id = response['id']
551+
552+
# Retrieve/poll Run to observe status
553+
response = client.runs.retrieve(id: run_id, thread_id: thread_id)
554+
status = response['status']
555+
```
556+
557+
The `status` response can include the following strings `queued`, `in_progress`, `requires_action`, `cancelling`, `cancelled`, `failed`, `completed`, or `expired` which you can handle as follows:
558+
559+
```ruby
560+
while true do
561+
562+
response = client.runs.retrieve(id: run_id, thread_id: thread_id)
563+
status = response['status']
564+
565+
case status
566+
when 'queued', 'in_progress', 'cancelling'
567+
puts 'Sleeping'
568+
sleep 1 # Wait one second and poll again
569+
when 'completed'
570+
break # Exit loop and report result to user
571+
when 'requires_action'
572+
# Handle tool calls (see below)
573+
when 'cancelled', 'failed', 'expired'
574+
puts response['last_error'].inspect
575+
break # or `exit`
576+
else
577+
puts "Unknown status response: #{status}"
578+
end
579+
end
580+
```
581+
582+
If the `status` response indicates that the `run` is `completed`, the associated `thread` will have one or more new `messages` attached:
583+
584+
```ruby
585+
# Either retrieve all messages in bulk again, or...
586+
messages = client.messages.list(thread_id: thread_id) # Note: as of 2023-12-11 adding limit or order options isn't working, yet
587+
588+
# Alternatively retrieve the `run steps` for the run which link to the messages:
589+
run_steps = client.run_steps.list(thread_id: thread_id, run_id: run_id)
590+
new_message_ids = run_steps['data'].filter_map { |step|
591+
if step['type'] == 'message_creation'
592+
step.dig('step_details', "message_creation", "message_id")
593+
end # Ignore tool calls, because they don't create new messages.
594+
}
595+
596+
# Retrieve the individual messages
597+
new_messages = new_message_ids.map { |msg_id|
598+
client.messages.retrieve(id: msg_id, thread_id: thread_id)
599+
}
600+
601+
# Find the actual response text in the content array of the messages
602+
new_messages.each { |msg|
603+
msg['content'].each { |content_item|
604+
case content_item['type']
605+
when 'text'
606+
puts content_item.dig('text', 'value')
607+
# Also handle annotations
608+
when 'image_file'
609+
# Use File endpoint to retrieve file contents via id
610+
id = content_item.dig('image_file', 'file_id')
611+
end
612+
}
613+
}
614+
```
615+
616+
At any time you can list all runs which have been performed on a particular thread or are currently running (in descending/newest first order):
617+
618+
```ruby
619+
client.runs.list(thread_id: thread_id)
620+
```
621+
622+
#### Runs involving function tools
623+
624+
In case you are allowing the assistant to access `function` tools (they are defined in the same way as functions during chat completion), you might get a status code of `requires_action` when the assistant wants you to evaluate one or more function tools:
625+
626+
```ruby
627+
def get_current_weather(location:, unit: "celsius")
628+
# Your function code goes here
629+
if location =~ /San Francisco/i
630+
return unit == "celsius" ? "The weather is nice 🌞 at 27°C" : "The weather is nice 🌞 at 80°F"
631+
else
632+
return unit == "celsius" ? "The weather is icy 🥶 at -5°C" : "The weather is icy 🥶 at 23°F"
633+
end
634+
end
635+
636+
if status == 'requires_action'
637+
638+
tools_to_call = response.dig('required_action', 'submit_tool_outputs', 'tool_calls')
639+
640+
my_tool_outputs = tools_to_call.map { |tool|
641+
# Call the functions based on the tool's name
642+
function_name = tool.dig('function', 'name')
643+
arguments = JSON.parse(
644+
tool.dig("function", "arguments"),
645+
{ symbolize_names: true },
646+
)
647+
648+
tool_output = case function_name
649+
when "get_current_weather"
650+
get_current_weather(**arguments)
651+
end
652+
653+
{ tool_call_id: tool['id'], output: tool_output }
654+
}
655+
656+
client.runs.submit_tool_outputs(thread_id: thread_id, run_id: run_id, parameters: { tool_outputs: my_tool_outputs })
657+
end
658+
```
659+
660+
Note that you have 10 minutes to submit your tool output before the run expires.
661+
505662
### Image Generation
506663

507664
Generate an image using DALL·E! The size of any generated images must be one of `256x256`, `512x512` or `1024x1024` -

0 commit comments

Comments
 (0)