Assistant calls and responses

I am a neophyte Python and OpenAI developer, who used to be a competent C/C++ programmer. I think I am missing some methods probably very obvious to seasoned programmers in this space.

Following Assistants, how-it-works, creating-assistants:
→ I am successful with OpenAI Assistant calls like:
from openai import OpenAI
client = OpenAI()

assistant = client.beta.assistants.create(...)
thread = client.beta.threads.create()
message = client.beta.threads.messages.create(...)

(Obviously also with arguments in place of … in those calls)

→ And what I get back from run(…) is of this form: (/n’s added)
SyncCursorPage[ThreadMessage](data=[ThreadMessage(id=‘msg_3Fn9nQJXMDbQKv7tDVoHUf42’,
assistant_id=‘asst_AvD1LtW8iZCevetBioCujuta’, content=[MessageContentText(text=Text(annotations=,
value=‘The solution to the equation 3x + 11 = 14 is x = 1.’), type=‘text’)], created_at=1706145471,
(etc.)

But what I see in all the APIreferences is very different:

→ Response messages like:
{
“id”: “asst_abc123”,
“object”: “assistant”,
“created_at”: 1698984975,
“name”: “Math Tutor”,
“description”: null,
“model”: “gpt-4”,
“instructions”: “You are a personal math tutor. When asked a question, write and run Python code to answer the question.”,
“tools”: [
{
“type”: “code_interpreter”
}
],
“file_ids”: ,
“metadata”: {}
}
→ From curl calls such as:
curl (URL)
-H “Content-Type: application/json”
-H “Authorization: Bearer $OPENAI_API_KEY”
-H “OpenAI-Beta: assistants=v1”

Can anyone explain for me:

  1. What’s going on with these two different “ways” to call an OpenAI Assistant?
  2. From Python, how do I “use” the above forms of response, e.g., how do I know how many messages are in: SyncCursorPage[ThreadMessage]
    – How do I extracxt each of them?
    – Is there an API to parse this structure? Or what?

Apologize if this is a “dumb” question, as I said in my opening, I think I am missing some methods probably very obvious to seasoned programmers in this space.

So, the responses in the API reference documentation should be taken as a guideline. When alternating between languages in the documentation the query/request format changes but the response is always the same.

I’m not much of a Python programmer myself, but it seems like they are wrapped using Pydantic (Which to me seems to be some way to apply and enforce structures/schemas using typing/models). I believe this is new, and why it’s not documented clearly.

It’s kind of nice, I guess. If you’re using an editor equipped for it you can access it’s attributes instead of blind-shooting the property and getting a traditional Python facepalming run-time error.

I’ve run into this issue as well when using Jupyter with Python. AFAIK the structure is the same, the only difference being that it’s all inside of a “data” object, maybe? Not sure 100%. Everything else is the same.

The same way you would with any other language. It’s Pagination.

get https://api.openai.com/v1/threads/{thread_id}/messages

Query parameters
limit
integer
Optional
Defaults to 20

A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.
order
string
Optional
Defaults to desc

Sort order by the created_at timestamp of the objects. asc for ascending order and desc for descending order.
after
string
Optional

A cursor for use in pagination. after is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.
before
string
Optional

A cursor for use in pagination. before is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.

https://platform.openai.com/docs/api-reference/messages/listMessages

tl;dr it’s just Python library (Pydantic) verbiage. Besides the potential data object it’s practically the same.

Not dumb at all. I have scratched my head a couple times when returning back from my comfortable languages to Python and having failures parsing an OpenAI response.

1 Like

Agreed with RonaldGRuckus: the API reference documentation should be taken as a guide.

I realized this when implementing “Create thread and run” for assistants. The reference documentation indicates a run ID (e.g., “run_abc123”, but the result I actually received was an ID for messages (e.g., “msg_abc123”). There is no “message ID” in the response specifications for “Create thread and run” (at least I haven’t seen one).

So, you’ll have to keep checking the actual results as you experiment with the calls.


1 Like

Hi @Stawsh

Look how I do it using JS.

then I poll.


Let me know if it helped! I can convert my class to python if you need it.

1 Like

Hi, @mouimet

Yes, convert this to Python please.

I second Marshall’s request to convert to Python.

@marshall.d.willman @Stawsh

Ok let me convert it and I will record myself to show you how I use the AI to code with intentions. I will not touch my keyboard and only talk with iBrain. You will see that copilot is cute compared to my implementation. :slight_smile:

I will send by the end of the day.

Cheers,
Mart

@marshall.d.willman @Stawsh

Here is the migration in video.

Code Migration by Voice: From JavaScript to Python with AI iBrain One (youtube.com)

Assistant Class

import asyncio
import json
import openai

#
# Please note that I am using my own package of pub/sub for event driven
# communication between the class and the other component.
# Therefore `bstack` calls are commented
# https://www.npmjs.com/package/@brainstack/hub
# 
class OpenAIService:
    def __init__(self, apiKey):
        self.openai = openai.OpenAI(api_key=apiKey)
        self.thread = None

    def initThread(self):
        self.thread = self.openai.beta.threads.create()

    def pollAnswers(self, threadId, runId):
        completed = False
        while not completed:
            try:
                response = self.openai.beta.threads.runs.retrieve(
                    thread_id=threadId,
                    run_id=runId
                )

                # bstack.emit(
                #     'status.update',
                #     {
                #         'status': response.status,
                #         'id': response.id,
                #         'created_at': response.created_at,
                #         'completed_at': response.completed_at
                #     }
                # )

                if response.status == 'completed':
                    print('Run completed!')
                    respmsg = self.openai.beta.threads.messages.list(threadId)
                    print(respmsg.data[0].content[0].text.value)

                    outgoingMessages = respmsg.data[0].content[0].text.value
                    completed = True
                    # bstack.emit('chat.message', {'message': outgoingMessages})
                    break

                if response.status == 'expired':
                    print('Task expired!')
                    respmsg = self.openai.beta.threads.messages.list(threadId)
                    print(respmsg.data[0].content[0].text.value)

                    outgoingMessages = respmsg.data[0].content[0].text.value
                    completed = True
                    # bstack.emit('chat.message', {'message': outgoingMessages})
                    break

                if response.status == 'requires_action':
                    print('Run Action Required!')

                    tool_outputs = []
                    for ra in response.required_action.submit_tool_outputs.tool_calls:
                        if ra.type == 'function' and ra.function.name in functions:
                            print(f'Running {ra.function.name} with arguments {ra.function.arguments}')
                            ret = functions[ra.function.name](ra.function.arguments)
                            print('Returned', ret)
                            tool_outputs.append({
                                'tool_call_id': ra.id,
                                'output': json.dumps(ret)
                            })
                        else:
                            tool_outputs.append({
                                'tool_call_id': ra.id,
                                'output': 'There is a problem, function not found!'
                            })

                    run = self.openai.beta.threads.runs.submitToolOutputs(
                        threadId,
                        runId,
                        {'tool_outputs': tool_outputs}
                    )

                if response.status in ('failed', 'cancelled', 'expired', 'cancelling'):
                    print(f'Completed {response.status}')
                    completed = True
                    break

                if response.status in ('in_progress', 'queued'):
                    print(f'Run {response.status}')
                    asyncio.sleep(2)  # Poll every 2 seconds

            except Exception as e:
                print('Error in polling (retrying):', str(e))
                # Handle the exception or retry logic as necessary

    def handleCreateMessage(self, message):
        msg = self.openai.beta.threads.messages.create(thread_id=self.thread.id,
            role='user',
            content=message
        )

    def handleCancelRun(self, runId):
        return self.openai.beta.threads.runs.cancel(self.thread.id, runId)

    def handleRunStep(self, assistantId):
        print(f'RUN STEP: {assistantId}')
        r = self.openai.beta.threads.runs.create(thread_id=self.thread.id,assistant_id=assistantId)
        self.pollAnswers(self.thread.id, r.id)

Example

from openaiService import OpenAIService


IBRAIN="your_assistant_key"
api_key="you_api_key"

service = OpenAIService(api_key)

async def main():
    service.initThread()

    message = "Hello, bot!"
    service.handleCreateMessage(message)

    service.handleRunStep(IBRAIN)

if __name__ == '__main__':
    import asyncio
    asyncio.run(main())

Let me know if that helps, cheers.
Mart

2 Likes

@mouimet

Very much appreciated! Apologies for the delay in my reply; lots going on these days. Keep up the great work :+1:

1 Like

@mouimet, thanks for the Python code. I will let you know if and how well it works for me as a neophyte Python and OpenAI programmer, and what I learn from it.

1 Like