February 20, 2024

Getting message from ChatGPT Assistants/Threads API

I’ve recently played with ChatGPT Assistant/Threads API and had to spend some time to get the actual response. For simple queries the message is included in the response, but for threads/run APIs it requires a bit more of engineering. Here’s how to get the message from the response.

I’ve implemented this in Go using the go-openai library. The steps followed can be replicated with any other library or by directly calling the API.

  1. Get an API key from OpenAI.

  2. Create an Assistant using the OpenAI UI or via API. Retrieve the Assistant ID.

  3. Create a Thread by appending messages to it. I’m using the CreateThreadAndRun method from the go-openai library, with a single message being sent - but you can send multiple messages if needed.

client := openai.NewClient("your-api-key")
model := openai.GPT4TurboPreview
resp, err := client.CreateThreadAndRun(ctx, openai.CreateThreadAndRunRequest{
	RunRequest: openai.RunRequest{Model: &model, AssistantID: "assistant-id"},
	Thread: openai.ThreadRequest{
		Messages: []openai.ThreadMessage{{Role: openai.ThreadMessageRoleUser,Content: query}},
	},
})
if err != nil {
	return nil, fmt.Errorf("failed to create thread and run: %w", err)
}

This will create a thread, and run it with the provided assistant. (it invokes the /threads/runs API)

The response will contain the run object, which among other fields contains runID and threadID.

We’ll use the runID to get the actual message from the response. The message may not be ready yet, so we have to poll the /runs/{runID} endpoint until it’s completed.

Once the run is completed, we can use the threadID to get the message from the /threads/{threadID} endpoint. The message will be in the last message in the response. Here’s the code to do that:

var requestCounter int
for requestCounter < 5 {
	time.Sleep(time.Second * time.Duration(requestCounter+1))
	run, err := client.RetrieveRun(ctx, resp.ThreadID, resp.ID)
	if err != nil {
		return "", fmt.Errorf("failed to retrieve thread: %w", err)
	}
	if run.Status != openai.RunStatusCompleted {
		requestCounter++
		continue
	}
	messageResp, err := client.ListMessage(ctx, resp.ThreadID, nil, nil, nil, nil)
	if err != nil {
		return "", fmt.Errorf("failed to list messages: %w", err)
	}
	if len(messageResp.Messages) == 0 {
		return "", errors.New("no messages returned")
	}
	for _, message := range messageResp.Messages {
		if message.Role == openai.ChatMessageRoleUser {
			continue
		}
		if len(message.Content) == 0 {
			return "", errors.New("no content in message")
		}
		return message.Content[0].TextValue}, nil
	}
	return "", errors.New("no message found")
}
return "", errors.New("timeout waiting for response")

The listMessage endpoint has limit, order, after, and before parameters, which can be used to paginate the messages if needed.

2024 © Emir Ribic - Some rights reserved; please attribute properly and link back. Code snippets are MIT Licensed

Powered by Hugo & Kiss.