Using GPT's Function Calling Capability: Stock Information Demo
Tags: ai, gpt3, python, nlp
Published: 2023-06-16 10:07:07

The ability to extract insights from user input has always been an excellent use case for OpenAI APIs. I have seen a lot of people using GPT APIs to perform NLP tasks. From Intent Detection, Classification to NER (Named Entity Recognition) it's quite useful, especially for developers who do not have extensive ML backgrounds.

And this new capability of "Function Calling" takes this use case to a whole new level. In this article, we are going to explore how we can use this feature to get real data about a stock in a few simple lines of Python.

### Understanding the core problem

Getting Stocks data is not difficult, it's only a matter of simple API calls.

Here are the tricky parts :

* Identifying what the user wants to do
    
* Mapping the users intent to the solution function
    
* Extracting data that our function expects
    
* Chaining the prompts together to respond
    

Before the availability of the function calling feature, we had to juggle all these things which led to complicated flow. Sure frameworks like `langchain` can help us simplify this juggling. But the ability of the model itself to do this for us is quite amazing.

I am going to demonstrate this with a simple example :

> Answer user's questions about stock prices & other information

### Get your API keys

To get the stock price data, we are going to use [Polygon](https://polygon.io/),

You can signup there and then access your API keys at: [https://polygon.io/dashboard/api-keys](https://polygon.io/dashboard/api-keys)

Polygon has a Python client but to keep it more abstract, we are going to use its REST API

If you don't already have your OpenAI API keys, get them from here: [https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys)

### Let's get into the Code

First, let's install `openai` and `reuests`

```python
!pip install openai
!pip install requests
```

Now, let's setup some global variables and API keys (OpenAI & Polygon)

```python
import openai

global OPENAI_API_KEY
global POLYGON_API_KEY

OPENAI_API_KEY="Your OpenAI API Key goes here" 
POLYGON_API_KEY="Your Polygon API Key goes here" 

openai.api_key = OPENAI_API_KEY
```

Imports, some more global variables and setup code

```python
import datetime
import requests
import json

global today_str

today = datetime.date.today()
today_str = today.strftime('%Y-%m-%d')
```

### Functions to fetch Stocks data

Let's write a function that we can use to get stock prices data from the polygon API

```python
def get_stock_price_by_date(ticker_symbol, date):
    url = f"https://api.polygon.io/v1/open-close/{ticker_symbol}/{date}?adjusted=true&apiKey={POLYGON_API_KEY}"
    try:
        response = requests.request(method='GET', url=url)
        response.raise_for_status()
        # print(response.text)
        return json.dumps(response.text)
    except Exception as e:
        print(f"An error occurred: {e}")
        return None
```

You can try calling this function with manual parameter input, to make sure it works as expected

```python
print(get_stock_price_by_date(ticker_symbol="GOOGL", date="2023-06-16"))
```

```json
{
	"status": "OK",
	"from": "2023-06-15",
	"symbol": "GOOGL",
	"open": 123.14,
	"high": 125.46,
	"low": 122.4,
	"close": 125.09,
	"volume": 3.5146268e+07,
	"afterHours": 124.85,
	"preMarket": 123.48
}
```

Let's write another function that fetches the data about stock according to the SEC filing

```python
def get_stock_sec_details(ticker_symbol, date = today_str):
    url = f"https://api.polygon.io/v3/reference/tickers/{ticker_symbol}?date={date}&apiKey={POLYGON_API_KEY}"
    try:
        response = requests.request(method='GET', url=url)
        response.raise_for_status()
        # print(response.text)
        return json.dumps(response.text)
    except Exception as e:
        print(f"An error occurred: {e}")
        return None
```

We can try calling this function to check how it responds

```python
print(get_stock_sec_details(ticker_symbol="AAPL"))
```

```json
{
	"request_id": "f7d8eb4aac2a75fbb4b8df1f25ab4ead",
	"results": {
		"ticker": "AAPL",
		"name": "Apple Inc.",
		"market": "stocks",
		"locale": "us",
		"primary_exchange": "XNAS",
		"type": "CS",
		"active": true,
		"currency_name": "usd",
		"cik": "0000320193",
		"composite_figi": "BBG000B9XRY4",
		"share_class_figi": "BBG001S5N8V8",
		"market_cap": 2.92569585902e+12,
		"phone_number": "(408) 996-1010",
		"address": {
			"address1": "ONE APPLE PARK WAY",
			"city": "CUPERTINO",
			"state": "CA",
			"postal_code": "95014"
		},
		"description": "Apple designs a wide variety of consumer electronic devices, including smartphones (iPhone), tablets (iPad), PCs (Mac), smartwatches (Apple Watch), and AirPods, among others. In addition, Apple offers its customers a variety of services such as Apple Music, iCloud, Apple Care, Apple TV+, Apple Arcade, Apple Fitness, Apple Card, and Apple Pay, among others. Apple's products include internally developed software and semiconductors, and the firm is well known for its integration of hardware, software, semiconductors, and services. Apple's products are distributed online as well as through company-owned stores and third-party retailers.",
		"sic_code": "3571",
		"sic_description": "ELECTRONIC COMPUTERS",
		"ticker_root": "AAPL",
		"homepage_url": "https://www.apple.com",
		"total_employees": 164000,
		"list_date": "1980-12-12",
		"branding": {
			"logo_url": "https://api.polygon.io/v1/reference/company-branding/d3d3LmFwcGxlLmNvbQ/images/2023-05-01_logo.svg",
			"icon_url": "https://api.polygon.io/v1/reference/company-branding/d3d3LmFwcGxlLmNvbQ/images/2023-05-01_icon.jpeg"
		},
		"share_class_shares_outstanding": 15728700000,
		"weighted_shares_outstanding": 15728702000,
		"round_lot": 100
	},
	"status": "OK"
}
```

### Utilizing Function Calling

Now is the part where we describe our function to OpenAI API

```python
def run_conversation(message):
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0613",
        messages=[{"role": "user", "content": f"Date today is {today_str}. Provide answer with a date. {message}"}],
        functions=[
            {
                "name": "get_stock_price_by_date",
                "description": "Get stock prices by date",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "ticker_symbol": {
                            "type": "string",
                            "description": "The ticker symbol for the stock listed on NASDAQ exchange",
                        },
                        "date": {
                            "type": "string", 
                            "description": "date string in %Y-%m-%d format"
                        }
                    },
                    "required": ["stock_ticker", "date"],
                },
            },
            {
                "name": "get_stock_sec_details",
                "description": "Get SEC filing details by stock ticker symbol",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "ticker_symbol": {
                            "type": "string",
                            "description": "The ticker symbol for the stock listed on NASDAQ exchange",
                        },
                        "date": {
                            "type": "string", 
                            "description": "date string in %Y-%m-%d format"
                        }
                    },
                    "required": ["stock_ticker"],
                },
            }
        ],
        function_call="auto",
    )
```

There are 2 differences between a standard chat completion call and this one.

1. Model: To use the function calling feature we have to use the latest models `gpt-3.5-turbo-0613` or `gpt-4-0613` (GPT 4)
    
2. Additional Parameters to describe the function : `functions` & `function_call`
    
* `functions` expect an array of the function descriptions, you can provide more functions to this array and the model will try to detect which function is most suitable for the given user input
        
* `function_call` to indicate whether to generate a model-facing (`auto` response (`none` a response that will be fed to the model on subsequent call) or a user-facing response that will be presented to the user
        

Now based on the model's response we'll call the actual function, here you can check which function was found most suitable for the model

Here is how we can do this

```python
## Continuing the `run_conversation` function

internal_message = response["choices"][0]["message"]

    if internal_message.get("function_call"):
        function_name = internal_message["function_call"]["name"]
        function_args = json.loads(internal_message["function_call"]["arguments"])
        function_response = ""
           
        # Printing which function is detected
        print(function_name)
        
        # If model detected stock price info is required
        if function_name == "get_stock_price_by_date":
          function_response = get_stock_price_by_date(
              ticker_symbol=function_args.get("ticker_symbol"),
              date=function_args.get("date"),
          )

        # If model detected stock SEC filing info is required
        if function_name == "get_stock_sec_details":
          function_response = get_stock_sec_details(
              ticker_symbol=function_args.get("ticker_symbol")
          )

        
        second_response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo-0613",
            messages=[
                {"role": "user", "content": message},
                internal_message,
                {
                    "role": "function",
                    "name": function_name,
                    "content": function_response,
                },
            ],
        )
        return second_response
```

One thing to note here is that the function description (objects within the `functions` array) will be counted against input tokens

### Alright, let's test

```python
print(run_conversation("tell me apple volume 3 days ago")["choices"][0]["message"]["content"])
```

> get\_stock\_price\_by\_date
> 
> The volume for Apple (AAPL) three days ago, on June 13, 2023, was approximately 54,867,129 shares.

```python
print(run_conversation("how many outstanding google shares are there")["choices"][0]["message"]["content"])
```

> get\_stock\_sec\_details
> 
> As of now, there are approximately 5,874,000,000 outstanding Google shares.

So to conclude, by using the function calling feature -

* The model can detect which function should be called to fulfill the user's request
    
* It can extract required info from the user's input and provide us with function arguments
    

You can find the full source code of this article at - [https://github.com/anantrp/ai-experiments/blob/main/openai\_function\_calling\_stock\_prices\_demo.ipynb](https://github.com/anantrp/ai-experiments/blob/main/openai_function_calling_stock_prices_demo.ipynb)

You can read more about Function Calling at - [https://platform.openai.com/docs/guides/gpt/function-calling](https://platform.openai.com/docs/guides/gpt/function-calling)

This can go beyond simple retrieval capabilities and we can build actuator functions (functions that can perform actions) such as sending an email, ordering products, taking action on our custom software systems and much more

With Function Calling capability we have great potential to build the AI agents of tomorrow

---

I hope you find this valuable, if you did - that's awesome **👌** share it with your folks who'll find it useful too. If you have any suggestions/comments please feel free.

Happy coding!