Mastering OpenAI Assistants API: Building an AI Financial Analyst to Forecast Stock Trend
A Complete Finance Use Case Implementation Leveraging Code Interpreter, Retrieval, and Customized Function Calling Capabilities
Financial statements, such as SEC Form 10-Q reports, are gold mines of crucial information about a company’s financial health. When these statements are released, stock prices often react, either positively or negatively, depending on the results and market expectations.
However, these documents are often lengthy and not easily comprehensible for individuals who aren’t finance experts.
In this post, I’ll guide you through on how to use OpenAI’s latest Assistants API to create an AI financial analyst. This AI will be capable of analyzing financial statements, extracting valuable insights, and saving you precious time.
I’ll demonstrate how to achieve this with practical code examples, leveraging the three superpowers unleashed by GPT-4:
- Retrieval
- Code Interpretor
- Function Calling, enabling you to define your custom functions.
The Colab notebook will be shared at the end of the article so you can explore the Assitants API easily!
You might wonder why not use a no-code Custom GPT for this task? Well, the Assistants API offers greater control, enhanced capabilities, and the potential to develop applications that go far beyond chatbots. With this API, you can equip your AI applications with up to 128 customized tools and seamlessly integrate them with other frameworks like AutoGen.
And, let’s not forget, there’s something inherently satisfying about understanding what’s happening under the hood and diving into the world of coding, isn’t there?
Table of Contents:
- Understanding the Basics 1.1. What Is OpenAI Assitants API ? 1.2. How Assitstants API Work? 1.3. Why Use Assistants API?
- Implementing the AI Financial Analyst 2.1. Designing the Application 2.2. Step-by-step Guide to Coding with the Assistants API 2.3. Hands-on Exemples on Retrieval 2.4. Hands-on Exemples on Code Interpretor 2.5. Hands-on Exemples on Custom functions
- Closing thoughts
Understanding the Basics
What Is OpenAI Assitants API ?
The Assistants API is a game changer feature recently introduced after OpenAI Dev Day. It simplifies the creation of assistant-like experiences and is powered by built-in capabilities such as Code Interpretation and Retrieval. With this API, you can integrate up to 128 tools, with custom functions for performing specific tasks.
An Assistant operates similar to an agent, autonomously using models, tools, and knowledge to respond to user queries.
How Assitstants API Work?
We can summarize the creation of an Assistant in 5 steps :
- Create an Assistant in the API by defining its custom instructions and picking a model.
- Create a Thread when a user starts a conversation.
- Add Messages to the Thread as the user ask questions.
- Run the Assistant on the Thread to trigger responses. This automatically calls the relevant tools.
- Retrieve and display the Assistant’s Response
We have seen several important Assistants API objects. Let’s also quickly recap what these objects represent with the table below:
Why Use Assistants API?
- Agent-like Experiences in Apps: Seamlessly integrate advanced, interactive agent-like experiences into your apps. Whether it’s for analyzing data or planning vacations, this API brings a new level of interaction.
- Code Interpreter Feature: Execute and visualize Python code directly within your applications, offering dynamic data processing and visual representation capabilities.
- External Knowledge Integration: Incorporating external knowledges is extremly easy with the API! You do not need to build your RAG pipeline anymore with various steps such as chunking, turning the text chunks into embedding, storing them in a vectordatabase etc. You only need to tell the assistant that you want to use the Retrieval tool which will take care of all these tehcnical details.
- Customizable Functionality: Utilize function calling to tailor your assistants for specific tasks, enhancing their utility and flexibility in your applications. You can add up to 128 functions.
- Versatility in Use Cases: The API offers a wide range of use cases, including natural language-based data analysis apps, coding assistants, and AI-powered vacation planners.
Implementing the AI Financial Analyst
Designing the Application
In this demo, we will create an AI Financial Analyst with the following essential capabilities:
- Answering General Finance Questions
- Guiding Through Financial Statements
- Conducting In-Depth Data Analysis
- Emailing Summaries and Key Insights
Step-by-step Guide to Coding with the Assistants API
In this guide, we’ll focus on creating the Finance Assistant directly through the Assistants API. Alternatively, you can use Assitants Playground to help you create the assistant which is the the easiest way to get started.
Step 1 — Install Dependencies
Run the command below the install the lastest OpenAI package.
!pip install --upgrade openai
Set up your OpenAI API key. If you haven’t obtained an OpenAI API key yet, visit the OpenAI developer portal, sign up, and request an API key. Make sure to securely save the key.
OPENAI_KEY="sk-xxxx" #Your OpenAI API Key
We’ll also create a helper function that formats and prints the Assistants API object in a readable JSON format.
import json
def show_json(obj):
display(json.loads(obj.model_dump_json()))
Step 2 — Create the Finance Assitant
Creating an Assistant is a simple process. You need to specify:
- Name : Define your assistant name
- Model Name: Choose the model you want to use.
- Instructions: Define the instructions you want the Assistant to follow.
- Tools: Specify the tools you want the Assistant to utilize. In this case, we’ll use Retrieval and Code Interpreter Tool.
from openai import OpenAI
client = OpenAI(api_key=OPENAI_KEY)
assistant = client.beta.assistants.create(
name = "Finance Insight Analyst",
instructions = "You are a helpful financial analyst expert and your are tailored for in-depth SEC 10-Q filings analysis, focusing on management discussions and financial results.",
tools = [{"type":"code_interpreter"}, {"type": "retrieval"}],
model = "gpt-4-1106-preview"
)
Once you’ve done this, your Assistant is created! Let’s have a look.
Whether you create your Assistant through the Dashboard or with the API, it’s important to keep track of the Assistant ID. This ID is how you’ll refer to your Assistant when working with Threads and Runs.
Step 3 — Create a Thread
We’ll create a Thread which will hold a conversation session beween an Assistant and a user. We do not need to send the entire history each time like in the preivous ChatCompletion API but to be noted you will still be charged for the tokens of the entire conversation history with each Run.
thread1 = client.beta.threads.create()
Step 4: Submit a Message to The Thread And Trigger The Run
We’ll consolidate the following steps:
- Add Messages to the Thread: As the user asks questions, we’ll add their messages to the Thread.
- Run the Assistant on the Thread: This action triggers responses from the Assistant and automatically calls the relevant tools.
It’s important to note that creating a Run is an asynchronous operation. Initially, it returns with the Run’s metadata, including a status that starts as “queued.” This status updates as the Assistant performs operations, such as using tools and adding messages. To determine when the Assistant has completed processing, we can continuously check the Run’s status in a loop.
To make this process more manageable, we’ll define the following functions that can be reused:
submit_message:
This function submits a new user message to a thread and runs the Assistant on the thread.wait_on_run:
This function helps us track when the Assistant has finished processing so we can retrieve a response from the Assistant.get_response:
Once the Run is completed, we use this function to retrieve the response from the thread.
def submit_message(assistant_id, thread, user_message):
client.beta.threads.messages.create(
thread_id=thread.id, role="user", content=user_message
)
return client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant_id,
)
import time
# Waiting in a loop
def wait_on_run(run, thread):
while run.status == "queued" or run.status == "in_progress":
run = client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id,
)
time.sleep(0.5)
return run
def get_response(thread):
return client.beta.threads.messages.list(thread_id=thread.id, order="asc")
Step 5 — Get the Response
First, let’s define a helper function to print user messages and assistant messages in a more visually readable format.
# Pretty printing helper
def pretty_print(messages):
print("# Messages")
for m in messages:
print(f"{m.role}: {m.content[0].text.value}")
print()
Now, let’s try submitting a message, start the run, and get the responses after the run has finished.
run1 = submit_message(FINANCE_ASSISTANT_ID, thread1, "What is SEC Q-10 filing?")
run1 = wait_on_run(run1, thread1)
pretty_print(get_response(thread1))
Hands-on Exemples on Retrieval
Now let’s upload a financial statement and ask the assitant to help us walkthrough it.
# Upload the file
file = client.files.create(
file=open(
"tsla-20231018-10Q.pdf",
"rb",
),
purpose="assistants",
)
Once the financial statement is uploaded, we’ll need to update the Assistant by providing it with the file ID. Files uploaded using the OpenAI Files API are stored and can be easily reused in the future by referencing their unique IDs.
# Update Assistant
assistant = client.beta.assistants.update(
FINANCE_ASSISTANT_ID,
file_ids=[file.id],
)
show_json(assistant)
We can now instruct the Assistant to provide a summary of the key takeaways from the financial statement. This saves us the time and effort of reading through the entire document.
thread = client.beta.threads.create()
run = submit_message(FINANCE_ASSISTANT_ID, thread, "Summarize the management discussion in this 10K filing and list key takeaways from this financial result")
run = wait_on_run(run, thread)
pretty_print(get_response(thread))
As you can see, implementing Retrieval Augmented Generation with the built-in Retrieval capability is quite straightforward. However, please be aware that using OpenAI-hosted tools may come with an additional fee.
Hands-on Exemples on Code Interpreter
The Code Interpreter is a very powerful tool. It not only executes Python code but also conducts data analysis and derives insights from datasets.
In this section, we’ll explore how to harness the Code Interpreter for the following purposes:
- Performing Additional Data Analysis: We’ll use the Code Interpreter to conduct supplementary data analysis using the information within the financial statements.
- Extracting Data and Saving in CSV Format: Additionally, we’ll demonstrate how to extract data from the financial statements and save it in a CSV file.
Performing Additional Data Analysis
We’ll ask the assistant to generate a graph on the Tesla’s operational margin.
thread3 = client.beta.threads.create()
run3 = submit_message(FINANCE_ASSISTANT_ID, thread3, "can you draw a graph based on operational margins in the file?")
run3 = wait_on_run(run3, thread3)
messages = get_response(thread3)
Then we save the graph into a local file.
file_path = messages.data[-1].content[0].image_file.file_id
file_name = client.files.with_raw_response.retrieve_content(file_path)
output_file_name = "margin.png"
with open(output_file_name, "wb") as file:
file.write(file_name.content)
We can display the graph in the notebook using the code below
from IPython.display import Image, display
# If your image is uploaded to the Colab environment
image_path = output_file_name
display(Image(filename=image_path))
Extracting Data and Saving in CSV Format
We can also use the Code Interpreter to extract financial data from the statement and save it in a csv file.
run = submit_message(FINANCE_ASSISTANT_ID, thread, "Can you extract the financial summary data et put it in CSV")
run = wait_on_run(run, thread)
messages = get_response(thread)
file_path = messages.data[-1].content[0].text.annotations[0].file_path.file_id
file_name = client.files.with_raw_response.retrieve_content(file_path)
output_file_name = "data.csv"
with open(output_file_name, "wb") as file:
file.write(file_name.content)
Great! The code interpreter successfully extract the data we needed!
Hands-on Exemples on Custom Functions
Function calling empowers you to specify functions to the Assistants and have it intelligently identify the functions to execute along with their respective arguments.
In this example, we’ll create a Custom Function that can send an email to the user, such as sending a summary of the financial statement.
To achieve this, we’ll follow these four steps:
- Define your functions
Begin by defining the function(s) you want to use. Here we’ll mock a function that send an email to the user and it requires 3 parameters: email, textbody and subject.
def function_send_email(email, textbody, subject="Financial Statement Summary"):
#your implementation to be added here
print("Email sent")
2. Define function interfaces in JSON Format
Let’s define the interface of this function in JSON format, so our Assistant can know how to call it.
function_send_email = {
"name": "send_email",
"description": "A function that takes in a user email, a subject line and body text and sends an email to the email address provided",
"parameters":{
"type":"object",
"properties":{
"email":{
"type": "string",
"description": "the email address of the user who receive the email"
},
"subject":{
"type":"string",
"description": "subject line of the email"
},
"textbody":{
"type": "string",
"description": "the body of the email."
}
}
},
"required":["email", "subject", "textbody"]
3. Update the Assistant with your custom functions
Integrate your custom functions with the Assistant.
assistant = client.beta.assistants.update(
FINANCE_ASSISTANT_ID,
tools=[
{"type": "code_interpreter"},
{"type": "retrieval"},
{"type": "function", "function": function_send_email},
],
)
4. Adapt the wait_on_run function
During a Run, the Assistant can then indicate it wants to call one or more functions you specified. Then the Run will move to a required_action state . You are then responsible for calling the Function, and providing the output back to the Assistant.
We’’ll make necessary modifications to the wait_on_run
function so that when it encounters a run in the ‘required_action’ state, it will call the function and submit the output back to the Assistant.
# Waiting in a loop
def wait_on_run(run, thread):
while run.status == "queued" or run.status == "in_progress":
run = client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id,
)
time.sleep(0.5)
if run.status == "requires_action":
tools_to_call = run.required_action.submit_tool_outputs.tool_calls
tool_output_array = []
for each_tool in tools_to_call:
tool_id = each_tool.id
function_name = each_tool.function.name
function_arg = each_tool.function.arguments
if function_name == "send_email":
arguments = json.loads(function_arg)
send_email(arguments["email"], arguments["subject"], arguments["textbody"])
output = "Mail sent OK"
tool_output_array.append({"tool_call_id": tool_id, "output": output})
#Return results to the run operation
run = client.beta.threads.runs.submit_tool_outputs(
thread_id = thread.id,
run_id = run.id,
tool_outputs = tool_output_array
)
return run
Final Thoughts
In this post, we’ve explored the versatility of the Assistants API, using various tools to craft a robust AI Financial Analyst. The OpenAI Assistant, functioning as an intelligent agent, autonomously determines which tools and functions to employ.
The Assistants API can also be used in following ways :
- Parallel Function Calls: calling multiple tools in a single Step
- Multi-Assistant Thread Runs: single Thread with Messages from multiple Assistants
This technology can simplify complex tasks and provide valuable insights into financial data. But be mindful that GPT-4 is still much more expensive than GPT-3.5, and the use of OpenAI-hosted tools comes with additional costs.
Last but not least, the code snipets in this post are not specific to to our Financial Analyst at all. They can be easily adapted to any new assistant by simply changing the assistant ID. This demonstrates the versatility of the Assistants API. Feel free to customize the code to suit your needs!
As promised, please find the Colab notebook here.
Before you go! 🦸🏻♀️
If you liked my story and you want to support me:
- Throw some Medium love 💕(claps, comments and highlights), your support means the world to me.👏
- Follow me on Medium and subscribe to get my latest article🫶