Skip to content

Commit f369df1

Browse files
authored
Describe tool calling for Assistants
1 parent 7b4ff4c commit f369df1

File tree

1 file changed

+122
-4
lines changed

1 file changed

+122
-4
lines changed

README.md

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -526,21 +526,139 @@ message = client.messages.retrieve(thread_id: thread_id, id: message_id)
526526
# Review all messages on the thread
527527
messages = client.messages.list(thread_id: thread_id)
528528
```
529+
530+
To clean up after a thread is no longer needed:
531+
532+
```
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+
```
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+
```
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+
```
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+
529616
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):
530617

531618
```
532619
client.runs.list(thread_id: thread_id)
533620
```
534621

535-
To clean up after a thread is no longer needed:
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:
536625

537626
```
538-
# To delete the thread (and all associated messages):
539-
client.threads.delete(id: thread_id)
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
540635
541-
client.messages.retrieve(thread_id: thread_id, id: message_id) # -> Fails after thread is deleted
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
542658
```
543659

660+
Note that you have 10 minutes to submit your tool output before the run expires.
661+
544662
### Image Generation
545663

546664
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)