If I understand correctly, the general structure for managing tool-calls is:
# recursive run handler
def handle_run_requires_action(run):
if run.status == 'completed':
messages = client.beta.threads.messages.list(thread_id=run.thread_id)
latest_message = messages.data[0].content[0].text.value
print(latest_message)
return
if run.status != 'requires_action':
print(f"Unexpected run status: {run.status}")
return
tool_outputs = []
for tool_call in run.required_action.submit_tool_outputs.tool_calls:
# handle your tool calls and get the output, using an example:
output = {'status':'success', 'message':'tell the user tool_a is cool'}
# convert to a JSON string
output = json.dumps(output)
tool_outputs.append({"tool_call_id": tool_call.id, "output": output})
tool_run = client.beta.threads.runs.submit_tool_outputs_and_poll(
run_id=run.id,
thread_id=run.thread_id,
tool_outputs = tool_outputs
)
handle_run_requires_action(tool_run)
# initial run
run = client.beta.threads.runs.create_and_poll(
# include thread, assistant, etc...
additional_messages=[
{'role':'user', 'content': user_message}
],
tools=[tool_a, tool_b]
)
handle_run_requires_action(run)
This works fine, as long as your tools are all-or-nothing success/failure. But while experimenting, I came up with some questions:
My first question is: can an OpenAI rep provide any guidance on the format of output? Iāve tried giving it various parameters:
- status: either āsuccessā or āfailureā. Strangely, when this is āfailureā and multiple tools are available, sometimes the assistant will try calling another tool with similar parameters.
- message: text that is interpreted by the assistant and shown to the user. This has the most influence on the assistant. Additional instructions to the assistant can be provided here and itāll listenā¦sometimes.
- error: a more technical error message targeted toward the assistant rather than the user. Doesnāt seem to do much
- additional_instructions: seems to be very effective. This is the only way to get the assistant to give up and not retry a tool with āfailureā status sometimes.
- call_tools: true/false. Only seems to work in edge cases, providing a little extra push needed to not retry a failed tool call.
- data: dictionary with various information related to a message. Usually seems to be taken into consideration by the assistant.
My second question is: are we expected to perform fine tuning for function calling to get reasonably accurate performance, or should we be able to handle it with properly formatted tool outputs in most cases?
The final question: How can we get clarification for a tool while the Run has the status requires_action
? Frequently it is desirable to have a tool that isnāt strictly pass or fail. For example, suppose you have a tool for querying a nutrition database and the user asks for the nutrition values for āapplesā. The database contains specific kinds of apples like āfuji applesā and āgala applesā. The tool detects these similar items and wants to ask the user if they meant one of the similar items, rather than tell the user that āapplesā was not found. But you canāt add any messages to the thread or call a new run on the thread until the current thread has had submit_tool_outputs
called.
I made a version that uses a chat completion supplied with the necessary parameters to ask the user for clarification and get new arguments for the function, and while this will give me successful output data that I can use to call submit_tool_outputs
, there is some context lost in the thread, because it is unaware of the clarification question and the userās response. This can lead to wrong-sounding responses like āHere is the nutrition data for applesā even though it should say āHere is the nutrition data for fuji applesā. Is it possible to add the messages of the chat completion to the thread before it formats its response from submit_tool_outputs
? Or is there a completely different way that I should be handling this?