I have created a assistant using the openai ui and i have tested it in playground, and works perfect, its a assistant with a single function and a single input message that returns a json with some data transformed by chatgpt.
Im not clear where check in the documentation the steps to make it in python:
This are my python lines:
thread = openai.beta.threads.create()
message = openai.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="Use custom instructions and return JSON using this text: " + custom_text
)
run = openai.beta.threads.runs.create(
thread_id=thread.id,
assistant_id="my assistant key created in the openai webside")
while run.status != 'completed':
print(run.status)
run = openai.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id
)
try:
messages = openai.beta.threads.messages.list(
thread_id=thread.id
)
print(messages)
except:
continue
return messages
The thread run starts on queue, but the next state is required_action and never change, if i print the messsage i can see the custom_text added in the content.
requires_action
SyncCursorPage[ThreadMessage](data= info about the message content)
Y need execute that text using the assistant function, but i donāt know how.
Run changes to required_action state when assistant calls a function. Here is snippet from OpenAI docs.
When using the Function calling tool, the Run will move to a required_action state once the model determines the names and arguments of the functions to be called. You must then run those functions and submit the outputs before the run proceeds. If the outputs are not provided before the expires_at timestamp passes (roughly 10 mins past creation), the run will move to an expired status.
So you need to process the function and call submit_outputs for run to proceed. It will remain at required_action state till you process it or the run expires(roughly 10 mins after the state has been changed to required_action)
Also run can fail, cancel or expire. You need to exit the loop when that happens.
Hi. I have been struggling with similar issues. Can you help me by showing your complete code? I am very new to coding. I am trying to use the assistant api to loop through data with a single retrieval function and single input message at a time. To run every questions as input in an excel sequentially. Thank you!
Yes i finally have a code that is not the best but works
def myfuction():
thread = openai.beta.threads.create()
message = openai.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="Explaining what do"
)
running=True
run = openai.beta.threads.runs.create(
thread_id=thread.id,
assistant_id="My assistant ID")
count=0
tool_outputs = []
while run.status != 'completed':
run = openai.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id
)
messages = openai.beta.threads.messages.list(
thread_id=thread.id
)
#print(run)
#print(messages)
if run.status == "requires_action" and count==0:
tool_call_id = None
try:
for message in run.messages:
#this part is so ugly and take me time to make it work, i didnt find it in the doc, its to execute the function
if "tool_call_id" in message.content:
tool_call_id = message.content.split("tool_call_id: ")[1].split()[0]
print(tool_call_id)
break
if tool_call_id:
count=1
print(tool_call_id)
# Sent action
run = openai.beta.threads.runs.submit_tool_outputs(
thread_id=thread.id,
run_id=run.id,
tool_outputs=[
{
"tool_call_id": tool_call_id
}
]
)
if run.required_action and run.required_action.submit_tool_outputs and run.required_action.submit_tool_outputs.tool_calls:
for tool_call in run.required_action.submit_tool_outputs.tool_calls:
tool_call_id = tool_call.id
arguments = json.loads(tool_call.function.arguments)
response = arguments # arguments is the response
print(response)
tool_outputs.append({
"tool_call_id": tool_call_id,
"outputs": {"response": response}
})
return response
print(run.status)
run = openai.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id
)
if run.status =="succeeded":
#Actual code never enter here
messages = openai.beta.threads.messages.list(
thread_id=thread.id
)
return messages
elif run.status == "cancelled" or run.status == "failed":
raise "thread cancelled"
elif run.status == "in_progress":
#wait for next iteration
import time
time.sleep(5)
return None
> if run.status =="succeeded":
> #Actual code never enter here
āSucceededā is not a valid state. When it is successful you will have receive ācompletedā state. One way to manage this is do while(true) and inside handle all the possible states for the ārun.statusā.
Using switch case will make it simpler to read.
Make sure to break for default case, your code will be stuck in infinite loop if you fail to handle any case or if API is updated to return any state not mentioned in documents.
I did a litte refactor, could be helpfully for somebody:
def handle_requires_action(run, thread):
#this is uggly for me but i didnt understand very well how get the function_call, and why is needed do it from the messages, but works for now and i dont want lost time and credits in more attempts
tool_call_id = None
try:
for message in run.messages:
if "tool_call_id" in message.content:
tool_call_id = message.content.split("tool_call_id: ")[1].split()[0]
print(tool_call_id)
break
if tool_call_id:
print(tool_call_id)
#this call the function of the assistent in the active threat with "action_required" and move their status to "in_progress"
run = openai.beta.threads.runs.submit_tool_outputs(
thread_id=thread.id,
run_id=run.id,
tool_outputs=[
{
"tool_call_id": tool_call_id
}
]
)
except Exception as e:
print(e)
def handle_succeeded(run, thread):
#never called in my case
messages = openai.beta.threads.messages.list(
thread_id=thread.id
)
#print(run)
#print(messages)
return messages
def handle_cancelled_or_failed(run, thread):
raise "thread cancelled"
def handle_in_progress(run, thread):
time.sleep(5)
def get_json_from_article_new(article):
openai.api_key = openaikey
thread = openai.beta.threads.create()
openai.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content="describe action, not sure if this is really required"
)
run = openai.beta.threads.runs.create(
thread_id=thread.id,
assistant_id="Assistant id")
tool_outputs = []
status_handlers = {
"requires_action": handle_requires_action,
"succeeded": handle_succeeded,
"cancelled": handle_cancelled_or_failed,
"failed": handle_cancelled_or_failed,
"expired": handle_cancelled_or_failed,
"in_progress": handle_in_progress,
"queued": handle_in_progress
}
while run.status != 'completed':
run = openai.beta.threads.runs.retrieve(thread_id=thread.id, run_id=run.id)
print(run.status)
if run.required_action and run.required_action.submit_tool_outputs and run.required_action.submit_tool_outputs.tool_calls:
#Here i recovery the response that i need, and the function ends.
#Enter when the "in_progress" finish and go back to the state required_action but in the messages comes the response.
for tool_call in run.required_action.submit_tool_outputs.tool_calls:
tool_call_id = tool_call.id
arguments = json.loads(tool_call.function.arguments)
response = arguments
tool_outputs.append({
"tool_call_id": tool_call_id,
"outputs": {"response": response}
})
#return the result
return response
elif run.status in status_handlers:
status_handlers[run.status](run, thread)
else:
print(f"Unhandled status: {run.status}")
raise Exception("Unhandled status")
break
return None
I use JS and struggled to get the tool call id as well, and I found a possible solution. As @jlvanhulst pointed oput in his blog, ātool_calls is a list!ā I decided not to implemet his solution because 1. its in python and I use JS, 2. I am only calling one function anyway, so it is a bit overkill for me.
Anyway, tool_calls is a list, so I just need the ID of the first item, so for me in JS language as follows:
Why on earth do they make it so hard to use the assistance API? Seems to me the entire point is to use tools, so why the assistant does not handle passing arguments between the thread and the tools is beyond me. llama library does, but I have found the performance is severely degraded.