💬🤖 How to Build a Chatbot

LlamaIndex serves as a bridge between your data and Language Learning
Models (LLMs), providing a toolkit that enables you to establish a
query interface around your data for a variety of tasks, such as
question-answering and summarization.
In this tutorial, we'll walk you through building a context-augmented
chatbot using a Data Agent. This agent, powered by LLMs, is capable of
intelligently executing tasks over your data. The end result is a
chatbot agent equipped with a robust set of data interface tools
provided by LlamaIndex to answer queries about your data.
**Note**: This tutorial builds upon initial work on creating a query
interface over SEC 10-K filings - check it out here.
Context
In this guide, we’ll build a "10-K Chatbot" that uses raw UBER 10-K
HTML filings from Dropbox. Users can interact with the chatbot to ask
questions related to the 10-K filings.
Preparation
   import os
   import openai
   os.environ["OPENAI_API_KEY"] = "sk-..."
   openai.api_key = os.environ["OPENAI_API_KEY"]
   import nest_asyncio
   nest_asyncio.apply()
   # set text wrapping
   from IPython.display import HTML, display
   def set_css():
       display(
           HTML(
               """
     <style>
       pre {
           white-space: pre-wrap;
       }
     </style>
     """
           )
       )
   get_ipython().events.register("pre_run_cell", set_css)
Ingest Data
Let's first download the raw 10-k files, from 2019-2022.
   # NOTE: the code examples assume you're operating within a Jupyter notebook.
   # download files
   !mkdir data
   !wget "https://www.dropbox.com/s/948jr9cfs7fgj99/UBER.zip?dl=1" -O data/UBER.zip
   !unzip data/UBER.zip -d data
   <IPython.core.display.HTML object>
   --2023-09-22 11:13:42--  https://www.dropbox.com/s/948jr9cfs7fgj99/UBER.zip?dl=1
   Resolving www.dropbox.com (www.dropbox.com)... 2620:100:601f:18::a27d:912, 162.125.5.18
   Connecting to www.dropbox.com (www.dropbox.com)|2620:100:601f:18::a27d:912|:443... connected.
   HTTP request sent, awaiting response... 302 Found
   Location: /s/dl/948jr9cfs7fgj99/UBER.zip [following]
   --2023-09-22 11:13:43--  https://www.dropbox.com/s/dl/948jr9cfs7fgj99/UBER.zip
   Reusing existing connection to [www.dropbox.com]:443.
   HTTP request sent, awaiting response... 302 Found
   Location: https://uc5e96fc71f5bcad342d7ef5261b.dl.dropboxusercontent.com/cd/0/get/CEMPMHdxNS2yZDvMeO8IVhjAHBo1ExUFCUxxR3rUUAuuAn2VBlNyyyzCCERRU4Uj9cVyRgHADCluk4Kqqe1NWdxiC1Uh1u85EJEPIlVuW1gK9-KC3EcD0tD7u21w14I6d80gfspvvfKJCFzc15556zTV/file?dl=1# [following]
   --2023-09-22 11:13:43--  https://uc5e96fc71f5bcad342d7ef5261b.dl.dropboxusercontent.com/cd/0/get/CEMPMHdxNS2yZDvMeO8IVhjAHBo1ExUFCUxxR3rUUAuuAn2VBlNyyyzCCERRU4Uj9cVyRgHADCluk4Kqqe1NWdxiC1Uh1u85EJEPIlVuW1gK9-KC3EcD0tD7u21w14I6d80gfspvvfKJCFzc15556zTV/file?dl=1

💬🤖 How to Build a Chatbot

   Resolving uc5e96fc71f5bcad342d7ef5261b.dl.dropboxusercontent.com (uc5e96fc71f5bcad342d7ef5261b.dl.dropboxusercontent.com)... 2620:100:601f:15::a27d:90f, 162.125.5.15
   Connecting to uc5e96fc71f5bcad342d7ef5261b.dl.dropboxusercontent.com (uc5e96fc71f5bcad342d7ef5261b.dl.dropboxusercontent.com)|2620:100:601f:15::a27d:90f|:443... connected.
   HTTP request sent, awaiting response... 200 OK
   Length: 1820227 (1,7M) [application/binary]
   Saving to: ‘data/UBER.zip’
   data/UBER.zip       100%[===================>]   1,74M  3,12MB/s    in 0,6s
   2023-09-22 11:13:45 (3,12 MB/s) - ‘data/UBER.zip’ saved [1820227/1820227]
   Archive:  data/UBER.zip
      creating: data/UBER/
     inflating: data/UBER/UBER_2021.html
     inflating: data/__MACOSX/UBER/._UBER_2021.html
     inflating: data/UBER/UBER_2020.html
     inflating: data/__MACOSX/UBER/._UBER_2020.html
     inflating: data/UBER/UBER_2019.html
     inflating: data/__MACOSX/UBER/._UBER_2019.html
     inflating: data/UBER/UBER_2022.html
     inflating: data/__MACOSX/UBER/._UBER_2022.html
To parse the HTML files into formatted text, we use the Unstructured
library. Thanks to LlamaHub, we can directly integrate with
Unstructured, allowing conversion of any text into a Document format
that LlamaIndex can ingest.
First we install the necessary packages:
   !pip install llama-hub unstructured
   <IPython.core.display.HTML object>
   Collecting llama-hub
     Obtaining dependency information for llama-hub from https://files.pythonhosted.org/packages/3f/af/3bc30c2b7ca1bdd7a193f67443539f6667a6b77dd62e54f2c5c8464ad4cb/llama_hub-0.0.31-py3-none-any.whl.metadata
     Downloading llama_hub-0.0.31-py3-none-any.whl.metadata (8.8 kB)
   Requirement already satisfied: unstructured in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (0.10.15)
   Collecting atlassian-python-api (from llama-hub)
     Obtaining dependency information for atlassian-python-api from https://files.pythonhosted.org/packages/ca/ed/3577ccec639736c8e4660423be68cf1a4a7040bf543b3144793760792949/atlassian_python_api-3.41.2-py3-none-any.whl.metadata
     Downloading atlassian_python_api-3.41.2-py3-none-any.whl.metadata (8.7 kB)
   Collecting html2text (from llama-hub)
     Downloading html2text-2020.1.16-py3-none-any.whl (32 kB)

💬🤖 How to Build a Chatbot

   Requirement already satisfied: llama-index>=0.6.9 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-hub) (0.8.29.post1)
   Requirement already satisfied: psutil in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-hub) (5.9.5)
   Collecting retrying (from llama-hub)
     Downloading retrying-1.3.4-py3-none-any.whl (11 kB)
   Requirement already satisfied: chardet in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from unstructured) (5.2.0)
   Requirement already satisfied: filetype in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from unstructured) (1.2.0)
   Requirement already satisfied: python-magic in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from unstructured) (0.4.27)
   Requirement already satisfied: lxml in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from unstructured) (4.9.3)
   Requirement already satisfied: nltk in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from unstructured) (3.8.1)
   Requirement already satisfied: tabulate in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from unstructured) (0.9.0)
   Requirement already satisfied: requests in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from unstructured) (2.31.0)
   Requirement already satisfied: beautifulsoup4 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from unstructured) (4.12.2)
   Requirement already satisfied: emoji in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from unstructured) (2.8.0)
   Requirement already satisfied: dataclasses-json in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from unstructured) (0.5.14)
   Requirement already satisfied: tiktoken in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (0.5.1)
   Requirement already satisfied: langchain>=0.0.293 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (0.0.295)
   Requirement already satisfied: sqlalchemy>=2.0.15 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (2.0.21)
   Requirement already satisfied: numpy in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (1.26.0)
   Requirement already satisfied: tenacity<9.0.0,>=8.2.0 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (8.2.3)
   Requirement already satisfied: openai>=0.26.4 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (0.28.0)

💬🤖 How to Build a Chatbot

   Requirement already satisfied: pandas in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (2.1.0)
   Requirement already satisfied: urllib3<2 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (1.26.16)
   Requirement already satisfied: fsspec>=2023.5.0 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (2023.9.1)
   Requirement already satisfied: typing-inspect>=0.8.0 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (0.9.0)
   Requirement already satisfied: typing-extensions>=4.5.0 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (4.8.0)
   Requirement already satisfied: nest-asyncio in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from llama-index>=0.6.9->llama-hub) (1.5.8)
   Collecting deprecated (from atlassian-python-api->llama-hub)
     Obtaining dependency information for deprecated from https://files.pythonhosted.org/packages/20/8d/778b7d51b981a96554f29136cd59ca7880bf58094338085bcf2a979a0e6a/Deprecated-1.2.14-py2.py3-none-any.whl.metadata
     Downloading Deprecated-1.2.14-py2.py3-none-any.whl.metadata (5.4 kB)
   Requirement already satisfied: six in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from atlassian-python-api->llama-hub) (1.16.0)
   Requirement already satisfied: oauthlib in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from atlassian-python-api->llama-hub) (3.2.2)
   Requirement already satisfied: requests-oauthlib in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from atlassian-python-api->llama-hub) (1.3.1)
   Requirement already satisfied: soupsieve>1.2 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from beautifulsoup4->unstructured) (2.5)
   Requirement already satisfied: marshmallow<4.0.0,>=3.18.0 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from dataclasses-json->unstructured) (3.20.1)
   Requirement already satisfied: click in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from nltk->unstructured) (8.1.7)
   Requirement already satisfied: joblib in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from nltk->unstructured) (1.3.2)
   Requirement already satisfied: regex>=2021.8.3 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from nltk->unstructured) (2023.8.8)

💬🤖 How to Build a Chatbot

   Requirement already satisfied: tqdm in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from nltk->unstructured) (4.66.1)
   Requirement already satisfied: charset-normalizer<4,>=2 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from requests->unstructured) (3.2.0)
   Requirement already satisfied: idna<4,>=2.5 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from requests->unstructured) (3.4)
   Requirement already satisfied: certifi>=2017.4.17 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from requests->unstructured) (2023.7.22)
   Requirement already satisfied: PyYAML>=5.3 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from langchain>=0.0.293->llama-index>=0.6.9->llama-hub) (6.0.1)
   Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from langchain>=0.0.293->llama-index>=0.6.9->llama-hub) (3.8.5)
   Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from langchain>=0.0.293->llama-index>=0.6.9->llama-hub) (4.0.3)
   Requirement already satisfied: langsmith<0.1.0,>=0.0.38 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from langchain>=0.0.293->llama-index>=0.6.9->llama-hub) (0.0.38)
   Requirement already satisfied: numexpr<3.0.0,>=2.8.4 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from langchain>=0.0.293->llama-index>=0.6.9->llama-hub) (2.8.6)
   Requirement already satisfied: pydantic<3,>=1 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from langchain>=0.0.293->llama-index>=0.6.9->llama-hub) (1.10.12)
   Requirement already satisfied: packaging>=17.0 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from marshmallow<4.0.0,>=3.18.0->dataclasses-json->unstructured) (23.1)
   Requirement already satisfied: greenlet!=0.4.17 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from sqlalchemy>=2.0.15->llama-index>=0.6.9->llama-hub) (2.0.2)
   Requirement already satisfied: mypy-extensions>=0.3.0 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from typing-inspect>=0.8.0->llama-index>=0.6.9->llama-hub) (1.0.0)
   Requirement already satisfied: wrapt<2,>=1.10 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from deprecated->atlassian-python-api->llama-hub) (1.15.0)

💬🤖 How to Build a Chatbot

   Requirement already satisfied: python-dateutil>=2.8.2 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index>=0.6.9->llama-hub) (2.8.2)
   Requirement already satisfied: pytz>=2020.1 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index>=0.6.9->llama-hub) (2023.3.post1)
   Requirement already satisfied: tzdata>=2022.1 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from pandas->llama-index>=0.6.9->llama-hub) (2023.3)
   Requirement already satisfied: attrs>=17.3.0 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.3->langchain>=0.0.293->llama-index>=0.6.9->llama-hub) (23.1.0)
   Requirement already satisfied: multidict<7.0,>=4.5 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.3->langchain>=0.0.293->llama-index>=0.6.9->llama-hub) (6.0.4)
   Requirement already satisfied: yarl<2.0,>=1.0 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.3->langchain>=0.0.293->llama-index>=0.6.9->llama-hub) (1.9.2)
   Requirement already satisfied: frozenlist>=1.1.1 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.3->langchain>=0.0.293->llama-index>=0.6.9->llama-hub) (1.4.0)
   Requirement already satisfied: aiosignal>=1.1.2 in /home/jtorres/llama_index/.venv/lib/python3.10/site-packages (from aiohttp<4.0.0,>=3.8.3->langchain>=0.0.293->llama-index>=0.6.9->llama-hub) (1.3.1)
   Downloading llama_hub-0.0.31-py3-none-any.whl (9.8 MB)
      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 9.8/9.8 MB 16.4 MB/s eta 0:00:0000:0100:01
   Downloading atlassian_python_api-3.41.2-py3-none-any.whl (167 kB)
      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 167.2/167.2 kB 20.8 MB/s eta 0:00:00

💬🤖 How to Build a Chatbot

   Downloading Deprecated-1.2.14-py2.py3-none-any.whl (9.6 kB)
   Installing collected packages: retrying, html2text, deprecated, atlassian-python-api, llama-hub
   Successfully installed atlassian-python-api-3.41.2 deprecated-1.2.14 html2text-2020.1.16 llama-hub-0.0.31 retrying-1.3.4
Then we can use the "UnstructuredReader" to parse the HTML files into
a list of "Document" objects.
   from llama_hub.file.unstructured.base import UnstructuredReader
   from pathlib import Path
   years = [2022, 2021, 2020, 2019]
   loader = UnstructuredReader()
   doc_set = {}
   all_docs = []
   for year in years:
       year_docs = loader.load_data(
           file=Path(f"./data/UBER/UBER_{year}.html"), split_documents=False
       )
       # insert year metadata into each year
       for d in year_docs:
           d.metadata = {"year": year}
       doc_set[year] = year_docs
       all_docs.extend(year_docs)
   <IPython.core.display.HTML object>
   [nltk_data] Downloading package punkt to /home/jtorres/nltk_data...
   [nltk_data]   Package punkt is already up-to-date!
   [nltk_data] Downloading package averaged_perceptron_tagger to
   [nltk_data]     /home/jtorres/nltk_data...
   [nltk_data]   Package averaged_perceptron_tagger is already up-to-
   [nltk_data]       date!
Setting up Vector Indices for each year
We first setup a vector index for each year. Each vector index allows
us to ask questions about the 10-K filing of a given year.
We build each index and save it to disk.
   # initialize simple vector indices
   # NOTE: don't run this cell if the indices are already loaded!
   from llama_index import VectorStoreIndex, ServiceContext, StorageContext
   index_set = {}
   service_context = ServiceContext.from_defaults(chunk_size=512)
   for year in years:
       storage_context = StorageContext.from_defaults()
       cur_index = VectorStoreIndex.from_documents(
           doc_set[year],
           service_context=service_context,
           storage_context=storage_context,
       )
       index_set[year] = cur_index
       storage_context.persist(persist_dir=f"./storage/{year}")
   <IPython.core.display.HTML object>
To load an index from disk, do the following
   # Load indices from disk
   from llama_index import load_index_from_storage
   index_set = {}
   for year in years:
       storage_context = StorageContext.from_defaults(persist_dir=f"./storage/{year}")
       cur_index = load_index_from_storage(
           storage_context, service_context=service_context
       )
       index_set[year] = cur_index
   <IPython.core.display.HTML object>
Setting up a Sub Question Query Engine to Synthesize Answers Across 10-K Filings
Since we have access to documents of 4 years, we may not only want to
ask questions regarding the 10-K document of a given year, but ask
questions that require analysis over all 10-K filings.
To address this, we can use a Sub Question Query Engine. It decomposes
a query into subqueries, each answered by an individual vector index,
and synthesizes the results to answer the overall query.
LlamaIndex provides some wrappers around indices (and query engines)
so that they can be used by query engines and agents. First we define
a "QueryEngineTool" for each vector index. Each tool has a name and a
description; these are what the LLM agent sees to decide which tool to

💬🤖 How to Build a Chatbot

choose.
   from llama_index.tools import QueryEngineTool, ToolMetadata
   individual_query_engine_tools = [
       QueryEngineTool(
           query_engine=index_set[year].as_query_engine(),
           metadata=ToolMetadata(
               name=f"vector_index_{year}",
               description=f"useful for when you want to answer queries about the {year} SEC 10-K for Uber",
           ),
       )
       for year in years
   ]
   <IPython.core.display.HTML object>
Now we can create the Sub Question Query Engine, which will allow us
to synthesize answers across the 10-K filings. We pass in the
"individual_query_engine_tools" we defined above, as well as a
"service_context" that will be used to run the subqueries.
   from llama_index.query_engine import SubQuestionQueryEngine
   query_engine = SubQuestionQueryEngine.from_defaults(
       query_engine_tools=individual_query_engine_tools,
       service_context=service_context,
   )
   <IPython.core.display.HTML object>
Setting up the Chatbot Agent
We use a LlamaIndex Data Agent to setup the outer chatbot agent, which
has access to a set of Tools. Specifically, we will use an
OpenAIAgent, that takes advantage of OpenAI API function calling. We
want to use the separate Tools we defined previously for each index
(corresponding to a given year), as well as a tool for the sub
question query engine we defined above.
First we define a "QueryEngineTool" for the sub question query engine:
   query_engine_tool = QueryEngineTool(
       query_engine=query_engine,
       metadata=ToolMetadata(
           name="sub_question_query_engine",
           description="useful for when you want to answer queries that require analyzing multiple SEC 10-K documents for Uber",
       ),
   )
   <IPython.core.display.HTML object>
Then, we combine the Tools we defined above into a single list of
tools for the agent:
   tools = individual_query_engine_tools + [query_engine_tool]
   <IPython.core.display.HTML object>
Finally, we call "OpenAIAgent.from_tools" to create the agent, passing
in the list of tools we defined above.
   from llama_index.agent import OpenAIAgent
   agent = OpenAIAgent.from_tools(tools, verbose=True)
   <IPython.core.display.HTML object>
Testing the Agent
We can now test the agent with various queries.
If we test it with a simple "hello" query, the agent does not use any
Tools.
   response = agent.chat("hi, i am bob")
   print(str(response))
   <IPython.core.display.HTML object>
   Hello Bob! How can I assist you today?
If we test it with a query regarding the 10-k of a given year, the
agent will use the relevant vector index Tool.
   response = agent.chat("What were some of the biggest risk factors in 2020 for Uber?")
   print(str(response))
   <IPython.core.display.HTML object>
   === Calling Function ===
   Calling function: vector_index_2020 with args: {
     "input": "biggest risk factors"
   }
   Got output: The biggest risk factors mentioned in the context are:
   1. The adverse impact of the COVID-19 pandemic and actions taken to mitigate it on the business.
   2. The potential reclassification of drivers as employees, workers, or quasi-employees instead of independent contractors.
   3. Intense competition in the mobility, delivery, and logistics industries, with low barriers to entry and well-capitalized competitors.
   4. The need to lower fares or service fees and offer driver incentives and consumer discounts to remain competitive.
   5. Significant losses incurred and the uncertainty of achieving profitability.
   6. The risk of not attracting or maintaining a critical mass of platform users.

💬🤖 How to Build a Chatbot

   7. Operational, compliance, and cultural challenges related to the workplace culture and forward-leaning approach.
   8. The potential negative impact of international investments and the challenges of conducting business in foreign countries, including operational and compliance challenges, localization requirements, restrictive laws and regulations, competition from local companies, social acceptance, technological compatibility, improper business practices, legal uncertainty, difficulties in managing international operations, currency exchange rate fluctuations, and regulations governing local currencies.
   ========================
   In 2020, some of the biggest risk factors for Uber were:
   1. The adverse impact of the COVID-19 pandemic and the measures taken to mitigate it on the business.
   2. The potential reclassification of drivers as employees, workers, or quasi-employees instead of independent contractors.
   3. Intense competition in the mobility, delivery, and logistics industries, with low barriers to entry and well-capitalized competitors.
   4. The need to lower fares or service fees and offer driver incentives and consumer discounts to remain competitive.
   5. Significant losses incurred and uncertainty about achieving profitability.
   6. The risk of not attracting or maintaining a critical mass of platform users.
   7. Operational, compliance, and cultural challenges related to the workplace culture and forward-leaning approach.
   8. The potential negative impact of international investments and the challenges of conducting business in foreign countries, including operational and compliance challenges, localization requirements, restrictive laws and regulations, competition from local companies, social acceptance, technological compatibility, improper business practices, legal uncertainty, difficulties in managing international operations, currency exchange rate fluctuations, and regulations governing local currencies.
   These risk factors highlight the challenges and uncertainties faced by Uber in 2020.
Finally, if we test it with a query to compare/contrast risk factors
across years, the agent will use the Sub Question Query Engine Tool.
   cross_query_str = "Compare/contrast the risk factors described in the Uber 10-K across years. Give answer in bullet points."
   response = agent.chat(cross_query_str)
   print(str(response))
   <IPython.core.display.HTML object>
   === Calling Function ===
   Calling function: sub_question_query_engine with args: {
     "input": "Compare/contrast the risk factors described in the Uber 10-K across years"
   }
   Generated 4 sub questions.
   [vector_index_2022] Q: What are the risk factors described in the 2022 SEC 10-K for Uber?
   [vector_index_2021] Q: What are the risk factors described in the 2021 SEC 10-K for Uber?
   [vector_index_2020] Q: What are the risk factors described in the 2020 SEC 10-K for Uber?
   [vector_index_2019] Q: What are the risk factors described in the 2019 SEC 10-K for Uber?
   [vector_index_2022] A: The risk factors described in the 2022 SEC 10-K for Uber include the potential adverse effect on their business if drivers were classified as employees instead of independent contractors, the highly competitive nature of the mobility, delivery, and logistics industries, the need to lower fares or service fees to remain competitive in certain markets, the company's history of significant losses and the expectation of increased operating expenses in the future, and the potential impact on their platform if they are unable to attract or maintain a critical mass of drivers, consumers, merchants, shippers, and carriers.
   [vector_index_2019] A: The risk factors described in the 2019 SEC 10-K for Uber include the loss of their license to operate in London, the complexity of their business and operating model due to regulatory uncertainties, the potential for additional regulations for their other products in the Other Bets segment, the evolving laws and regulations regarding the development and deployment of autonomous vehicles, and the increasing number of data protection and privacy laws around the world. Additionally, there are legal proceedings, litigation, claims, and government investigations that Uber is involved in, including those related to the classification of drivers and compliance with applicable laws, which could impose a significant burden on the company.

💬🤖 How to Build a Chatbot

   [vector_index_2021] A: The risk factors described in the 2021 SEC 10-K for Uber include the adverse impact of the COVID-19 pandemic and actions taken to mitigate it on their business, the potential reclassification of drivers as employees instead of independent contractors, intense competition in the mobility, delivery, and logistics industries, the need to lower fares and offer incentives to remain competitive, significant losses incurred and the expectation of increased operating expenses, the importance of attracting and maintaining a critical mass of drivers, consumers, merchants, shippers, and carriers, and the uncertainty surrounding the impact of COVID-19 on their business and financial position. Additionally, the classification of drivers is being challenged in courts and by government agencies, which could have legal and financial implications for the company.
   [vector_index_2020] A: The risk factors described in the 2020 SEC 10-K for Uber include the adverse impact of the COVID-19 pandemic on their business, the potential reclassification of drivers as employees instead of independent contractors, intense competition in the mobility, delivery, and logistics industries, the need to lower fares and offer incentives to remain competitive, significant losses and the uncertainty of achieving profitability, the importance of attracting and maintaining a critical mass of platform users, operational and compliance challenges, inquiries and investigations from government agencies, risks related to data security breaches, the need to introduce new or upgraded products and features, and the need to invest in the development of new offerings to retain and attract users.
   Got output: The risk factors described in the Uber 10-K reports across the years include the potential reclassification of drivers as employees instead of independent contractors, intense competition in the mobility, delivery, and logistics industries, the need to lower fares and offer incentives to remain competitive, significant losses incurred and the expectation of increased operating expenses, the importance of attracting and maintaining a critical mass of drivers, consumers, merchants, shippers, and carriers, and the impact of the COVID-19 pandemic on their business. Additionally, there are legal and regulatory uncertainties, such as the evolving laws and regulations regarding autonomous vehicles, data protection and privacy laws, and the potential for additional regulations for their other products. The reports also mention the operational and compliance challenges, inquiries and investigations from government agencies, and the risks associated with data security breaches. It is worth noting that specific risk factors may vary from year to year based on the prevailing circumstances and developments in the industry and regulatory environment.
   ========================
   Here are the key points comparing and contrasting the risk factors described in the Uber 10-K reports across years:
   2022:
   - Potential reclassification of drivers as employees instead of independent contractors.
   - Intense competition in the mobility, delivery, and logistics industries.
   - Need to lower fares and offer incentives to remain competitive.
   - Significant losses incurred and expectation of increased operating expenses.
   - Importance of attracting and maintaining a critical mass of drivers, consumers, merchants, shippers, and carriers.
   - Impact of the COVID-19 pandemic on their business.
   - Legal and regulatory uncertainties, including evolving laws and regulations regarding autonomous vehicles and data protection and privacy laws.
   - Operational and compliance challenges.
   - Inquiries and investigations from government agencies.
   - Risks associated with data security breaches.
   2021:
   - Similar risk factors as in 2022, including potential reclassification of drivers, intense competition, need to lower fares, significant losses, and the impact of the COVID-19 pandemic.
   - Emphasis on the importance of maintaining a critical mass of drivers, consumers, merchants, shippers, and carriers.
   - Mention of legal and regulatory uncertainties, such as evolving laws and regulations regarding autonomous vehicles and data protection and privacy laws.
   - Operational and compliance challenges.

💬🤖 How to Build a Chatbot

   - Inquiries and investigations from government agencies.
   - Risks associated with data security breaches.
   2020:
   - Similar risk factors as in 2021, including potential reclassification of drivers, intense competition, need to lower fares, significant losses, and the impact of the COVID-19 pandemic.
   - Emphasis on the importance of maintaining a critical mass of drivers, consumers, merchants, shippers, and carriers.
   - Mention of legal and regulatory uncertainties, such as evolving laws and regulations regarding autonomous vehicles and data protection and privacy laws.
   - Operational and compliance challenges.
   - Inquiries and investigations from government agencies.
   - Risks associated with data security breaches.
   2019:
   - Similar risk factors as in 2020, including potential reclassification of drivers, intense competition, need to lower fares, significant losses, and the impact of the COVID-19 pandemic.
   - Emphasis on the importance of maintaining a critical mass of drivers, consumers, merchants, shippers, and carriers.
   - Mention of legal and regulatory uncertainties, such as evolving laws and regulations regarding autonomous vehicles and data protection and privacy laws.
   - Operational and compliance challenges.
   - Inquiries and investigations from government agencies.
   - Risks associated with data security breaches.
   Please note that these are just the key points, and there may be additional risk factors mentioned in each year's 10-K report.
Setting up the Chatbot Loop
Now that we have the chatbot setup, it only takes a few more steps to
setup a basic interactive loop to chat with our SEC-augmented chatbot!
   agent = OpenAIAgent.from_tools(tools)  # verbose=False by default
   while True:
       text_input = input("User: ")
       if text_input == "exit":
           break
       response = agent.chat(text_input)
       print(f"Agent: {response}")
   # User: What were some of the legal proceedings against Uber in 2022?
   <IPython.core.display.HTML object>
   Agent: In 2022, Uber is facing several legal proceedings. Here are some of them:
   1. California: The state Attorney General and city attorneys filed a complaint against Uber and Lyft, alleging that drivers are misclassified as independent contractors. A preliminary injunction was issued but stayed pending appeal. The Court of Appeal affirmed the lower court's ruling, and Uber filed a petition for review with the California Supreme Court. However, the Supreme Court declined the petition for review. The lawsuit is ongoing, focusing on claims by the California Attorney General for periods prior to the enactment of Proposition 22.
   2. Massachusetts: The Attorney General of Massachusetts filed a complaint against Uber, alleging that drivers are employees entitled to wage and labor law protections. Uber's motion to dismiss the complaint was denied, and a summary judgment motion is pending.
   3. New York: Uber is facing allegations of misclassification and employment violations by the state Attorney General. The resolution of this matter is uncertain.
   4. Switzerland: Several administrative bodies in Switzerland have issued rulings classifying Uber drivers as employees for social security or labor purposes. Uber is challenging these rulings before the Social Security and Administrative Tribunals.
   These are some of the legal proceedings against Uber in 2022. The outcomes and potential losses in these cases are uncertain.


OpenAI Agent Query Planning

In this demo, we explore adding a "QueryPlanTool" to an "OpenAIAgent".
This effectively enables the agent to do advanced query planning, all
through a single tool!
The "QueryPlanTool" is designed to work well with the OpenAI Function
API. The tool takes in a set of other tools as input. The tool
function signature contains of a QueryPlan Pydantic object, which can
in turn contain a DAG of QueryNode objects defining a compute graph.
The agent is responsible for defining this graph through the function
signature when calling the tool. The tool itself executes the DAG over
any corresponding tools.
In this setting we use a familiar example: Uber 10Q filings in March,
June, and September of 2022.
   # # uncomment to turn on logging
   # import logging
   # import sys
   # logging.basicConfig(stream=sys.stdout, level=logging.INFO)
   # logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout))
   %load_ext autoreload
   %autoreload 2
   from llama_index import (
       SimpleDirectoryReader,
       LLMPredictor,
       ServiceContext,
       GPTVectorStoreIndex,
   )
   from llama_index.response.pprint_utils import pprint_response
   from llama_index.llms import OpenAI
   llm = OpenAI(temperature=0, model="gpt-4")
   service_context = ServiceContext.from_defaults(llm=llm)
Load data
   march_2022 = SimpleDirectoryReader(
       input_files=["../data/10q/uber_10q_march_2022.pdf"]
   ).load_data()
   june_2022 = SimpleDirectoryReader(
       input_files=["../data/10q/uber_10q_june_2022.pdf"]
   ).load_data()
   sept_2022 = SimpleDirectoryReader(
       input_files=["../data/10q/uber_10q_sept_2022.pdf"]
   ).load_data()
Build indices
We build a vector index / query engine over each of the documents
(March, June, September).
   march_index = GPTVectorStoreIndex.from_documents(march_2022)
   june_index = GPTVectorStoreIndex.from_documents(june_2022)
   sept_index = GPTVectorStoreIndex.from_documents(sept_2022)
   march_engine = march_index.as_query_engine(
       similarity_top_k=3, service_context=service_context
   )
   june_engine = june_index.as_query_engine(
       similarity_top_k=3, service_context=service_context
   )
   sept_engine = sept_index.as_query_engine(
       similarity_top_k=3, service_context=service_context
   )
OpenAI Function Agent with a Query Plan Tool
Use OpenAIAgent, built on top of the OpenAI tool use interface.
Feed it our QueryPlanTool, which is a Tool that takes in other tools.
And the agent to generate a query plan DAG over these tools.
   from llama_index.tools import QueryEngineTool
   query_tool_sept = QueryEngineTool.from_defaults(
       query_engine=sept_engine,
       name="sept_2022",
       description=f"Provides information about Uber quarterly financials ending September 2022",
   )
   query_tool_june = QueryEngineTool.from_defaults(
       query_engine=june_engine,
       name="june_2022",
       description=f"Provides information about Uber quarterly financials ending June 2022",
   )
   query_tool_march = QueryEngineTool.from_defaults(
       query_engine=march_engine,
       name="march_2022",
       description=f"Provides information about Uber quarterly financials ending March 2022",
   )
   # define query plan tool
   from llama_index.tools import QueryPlanTool
   from llama_index import get_response_synthesizer

OpenAI Agent Query Planning

   response_synthesizer = get_response_synthesizer(service_context=service_context)
   query_plan_tool = QueryPlanTool.from_defaults(
       query_engine_tools=[query_tool_sept, query_tool_june, query_tool_march],
       response_synthesizer=response_synthesizer,
   )
   query_plan_tool.metadata.to_openai_function()
   {'name': 'query_plan_tool',
    'description': '        This is a query plan tool that takes in a list of tools and executes a query plan over these tools to answer a query. The query plan is a DAG of query nodes.\n\nGiven a list of tool names and the query plan schema, you can choose to generate a query plan to answer a question.\n\nThe tool names and descriptions are as follows:\n\n\n\n        Tool Name: sept_2022\nTool Description: Provides information about Uber quarterly financials ending September 2022 \n\nTool Name: june_2022\nTool Description: Provides information about Uber quarterly financials ending June 2022 \n\nTool Name: march_2022\nTool Description: Provides information about Uber quarterly financials ending March 2022 \n        ',
    'parameters': {'title': 'QueryPlan',
     'description': "Query plan.\n\nContains a list of QueryNode objects (which is a recursive object).\nOut of the list of QueryNode objects, one of them must be the root node.\nThe root node is the one that isn't a dependency of any other node.",
     'type': 'object',
     'properties': {'nodes': {'title': 'Nodes',
       'description': 'The original question we are asking.',
       'type': 'array',
       'items': {'$ref': '#/definitions/QueryNode'}}},
     'required': ['nodes'],
     'definitions': {'QueryNode': {'title': 'QueryNode',
       'description': 'Query node.\n\nA query node represents a query (query_str) that must be answered.\nIt can either be answered by a tool (tool_name), or by a list of child nodes\n(child_nodes).\nThe tool_name and child_nodes fields are mutually exclusive.',
       'type': 'object',
       'properties': {'id': {'title': 'Id',
         'description': 'ID of the query node.',
         'type': 'integer'},
        'query_str': {'title': 'Query Str',
         'description': 'Question we are asking. This is the query string that will be executed. ',
         'type': 'string'},
        'tool_name': {'title': 'Tool Name',
         'description': 'Name of the tool to execute the `query_str`.',
         'type': 'string'},
        'dependencies': {'title': 'Dependencies',
         'description': 'List of sub-questions that need to be answered in order to answer the question given by `query_str`.Should be blank if there are no sub-questions to be specified, in which case `tool_name` is specified.',
         'type': 'array',
         'items': {'type': 'integer'}}},
       'required': ['id', 'query_str']}}}}
   from llama_index.agent import OpenAIAgent
   from llama_index.llms import OpenAI
   agent = OpenAIAgent.from_tools(
       [query_plan_tool],
       max_function_calls=10,
       llm=OpenAI(temperature=0, model="gpt-4-0613"),
       verbose=True,
   )
   response = agent.query("What were the risk factors in sept 2022?")
   from llama_index.tools.query_plan import QueryPlan
   query_plan = QueryPlan(
       **{
           "root": {
               "query_str": "risk factors",
               "tool_name": "sept_2022",
               "child_nodes": [],
           }
       }

OpenAI Agent Query Planning

   )
   QueryPlan.schema()
   {'title': 'QueryPlan',
    'description': 'Query plan.\n\nContains the root QueryNode (which is a recursive object).\nThe root node should contain the original query string to be executed.\n\nExample query plan in JSON format:\n\n```json\n{\n    "root": {\n        "query_str": "Compare the demographics of France and Italy.",\n        "child_nodes": [\n            {\n                "query_str": "What are the demographics of France?",\n                "tool_name": "france_demographics",\n                "child_nodes": []\n            },\n            {\n                "query_str": "What are the demographics of Italy?",\n                "tool_name": "italy_demographics",\n                "child_nodes": []\n            }\n        ]\n    }\n}\n```',
    'type': 'object',
    'properties': {'root': {'title': 'Root',
      'description': 'Root node of the query plan. Should contain the original query string to be executed.',
      'allOf': [{'$ref': '#/definitions/QueryNode'}]}},
    'required': ['root'],
    'definitions': {'QueryNode': {'title': 'QueryNode',
      'description': 'Query node.\n\nA query node represents a query (query_str) that must be answered.\nIt can either be answered by a tool (tool_name), or by a list of child nodes\n(child_nodes).\nThe tool_name and child_nodes fields are mutually exclusive.',
      'type': 'object',
      'properties': {'query_str': {'title': 'Query Str',
        'description': 'Question we are asking. This is the query string that will be executed. We will either provide a tool to execute the query, or a list of child nodes containing sub-questions that will be executed first, and the results of which will be used as context to execute the current query string.',
        'type': 'string'},
       'tool_name': {'title': 'Tool Name',
        'description': 'Name of the tool to execute the `query_str`.',
        'type': 'string'},
       'child_nodes': {'title': 'Child Nodes',
        'description': 'List of child nodes representing sub-questions that need to be answered in order to answer the question given by `query_str`.Should be blank if `tool_name` is specified.',
        'type': 'array',
        'items': {'$ref': '#/definitions/QueryNode'}}},
      'required': ['query_str', 'child_nodes']}}}
   response = agent.query("Analyze Uber revenue growth in March, June, and September")
   === Calling Function ===
   Calling function: query_plan_tool with args: {
     "nodes": [
       {
         "id": 1,
         "query_str": "What is Uber's revenue for March 2022?",
         "tool_name": "march_2022",
         "dependencies": []
       },
       {
         "id": 2,
         "query_str": "What is Uber's revenue for June 2022?",
         "tool_name": "june_2022",
         "dependencies": []
       },
       {
         "id": 3,
         "query_str": "What is Uber's revenue for September 2022?",
         "tool_name": "sept_2022",
         "dependencies": []
       },
       {
         "id": 4,
         "query_str": "Analyze Uber revenue growth in March, June, and September",
         "tool_name": "revenue_growth_analyzer",
         "dependencies": [1, 2, 3]
       }
     ]
   }
   Executing node {"id": 4, "query_str": "Analyze Uber revenue growth in March, June, and September", "tool_name": "revenue_growth_analyzer", "dependencies": [1, 2, 3]}

OpenAI Agent Query Planning

   Executing 3 child nodes
   Executing node {"id": 1, "query_str": "What is Uber's revenue for March 2022?", "tool_name": "march_2022", "dependencies": []}
   Selected Tool: ToolMetadata(description='Provides information about Uber quarterly financials ending March 2022', name='march_2022', fn_schema=None)
   Executed query, got response.
   Query: What is Uber's revenue for March 2022?
   Response: Uber's revenue for March 2022 was $6.854 billion.
   Executing node {"id": 2, "query_str": "What is Uber's revenue for June 2022?", "tool_name": "june_2022", "dependencies": []}
   Selected Tool: ToolMetadata(description='Provides information about Uber quarterly financials ending June 2022', name='june_2022', fn_schema=None)
   Executed query, got response.
   Query: What is Uber's revenue for June 2022?
   Response: Uber's revenue for June 2022 cannot be determined from the provided information. However, the revenue for the three months ended June 30, 2022, was $8,073 million.
   Executing node {"id": 3, "query_str": "What is Uber's revenue for September 2022?", "tool_name": "sept_2022", "dependencies": []}
   Selected Tool: ToolMetadata(description='Provides information about Uber quarterly financials ending September 2022', name='sept_2022', fn_schema=None)
   Executed query, got response.
   Query: What is Uber's revenue for September 2022?
   Response: Uber's revenue for the three months ended September 30, 2022, was $8.343 billion.
   Got output: Based on the provided context information, we can analyze Uber's revenue growth as follows:
   - In March 2022, Uber's revenue was $6.854 billion.
   - For the three months ended June 30, 2022, Uber's revenue was $8,073 million (or $8.073 billion). However, we do not have the specific revenue for June 2022.
   - For the three months ended September 30, 2022, Uber's revenue was $8.343 billion.
   From this information, we can observe that Uber's revenue has been growing between the periods mentioned. The revenue increased from $6.854 billion in March 2022 to $8.073 billion for the three months ended June 2022, and further increased to $8.343 billion for the three months ended September 2022. However, we cannot provide a month-by-month analysis for June and September as the specific monthly revenue figures are not available.
   ========================
   print(str(response))
   Based on the provided context information, we can analyze Uber's revenue growth for the three-month periods ending in March, June, and September.
   1. For the three months ended March 31, 2022, Uber's revenue was $6.854 billion.

OpenAI Agent Query Planning

   2. For the three months ended June 30, 2022, Uber's revenue was $8.073 billion.
   3. For the three months ended September 30, 2022, Uber's revenue was $8.343 billion.
   To analyze the growth, we can compare the revenue figures for each period:
   - From March to June, Uber's revenue increased by $1.219 billion ($8.073 billion - $6.854 billion), which represents a growth of approximately 17.8% (($1.219 billion / $6.854 billion) * 100).
   - From June to September, Uber's revenue increased by $0.270 billion ($8.343 billion - $8.073 billion), which represents a growth of approximately 3.3% (($0.270 billion / $8.073 billion) * 100).
   In summary, Uber experienced significant revenue growth of 17.8% between the three-month periods ending in March and June, followed by a smaller growth of 3.3% between the periods ending in June and September.
   response = agent.query(
       "Analyze changes in risk factors in march, june, and september for Uber"
   )
   print(str(response))
   # response = agent.query("Analyze both Uber revenue growth and risk factors over march, june, and september")
   print(str(response))
   Based on the provided context information, we can analyze Uber's revenue growth for the three-month periods ending in March, June, and September.
   1. For the three months ended March 31, 2022, Uber's revenue was $6.854 billion.
   2. For the three months ended June 30, 2022, Uber's revenue was $8.073 billion.
   3. For the three months ended September 30, 2022, Uber's revenue was $8.343 billion.
   To analyze the growth, we can compare the revenue figures for each period:
   - From March to June, Uber's revenue increased by $1.219 billion ($8.073 billion - $6.854 billion), which represents a growth of approximately 17.8% (($1.219 billion / $6.854 billion) * 100).
   - From June to September, Uber's revenue increased by $0.270 billion ($8.343 billion - $8.073 billion), which represents a growth of approximately 3.3% (($0.270 billion / $8.073 billion) * 100).
   In summary, Uber experienced significant revenue growth of 17.8% between the three-month periods ending in March and June, followed by a smaller growth of 3.3% between the periods ending in June and September.
   response = agent.query(
       "First look at Uber's revenue growth and risk factors in March, "
       + "then revenue growth and risk factors in September, and then compare and contrast the two documents?"
   )
   response


OpenAI Agent with Query Engine Tools

Build Query Engine Tools
   from llama_index import (
       SimpleDirectoryReader,
       VectorStoreIndex,
       StorageContext,
       load_index_from_storage,
   )
   from llama_index.tools import QueryEngineTool, ToolMetadata
   try:
       storage_context = StorageContext.from_defaults(persist_dir="./storage/lyft")
       lyft_index = load_index_from_storage(storage_context)
       storage_context = StorageContext.from_defaults(persist_dir="./storage/uber")
       uber_index = load_index_from_storage(storage_context)
       index_loaded = True
   except:
       index_loaded = False
   if not index_loaded:
       # load data
       lyft_docs = SimpleDirectoryReader(
           input_files=["../data/10k/lyft_2021.pdf"]
       ).load_data()
       uber_docs = SimpleDirectoryReader(
           input_files=["../data/10k/uber_2021.pdf"]
       ).load_data()
       # build index
       lyft_index = VectorStoreIndex.from_documents(lyft_docs)
       uber_index = VectorStoreIndex.from_documents(uber_docs)
       # persist index
       lyft_index.storage_context.persist(persist_dir="./storage/lyft")
       uber_index.storage_context.persist(persist_dir="./storage/uber")
   lyft_engine = lyft_index.as_query_engine(similarity_top_k=3)
   uber_engine = uber_index.as_query_engine(similarity_top_k=3)
   query_engine_tools = [
       QueryEngineTool(
           query_engine=lyft_engine,
           metadata=ToolMetadata(
               name="lyft_10k",
               description="Provides information about Lyft financials for year 2021. "
               "Use a detailed plain text question as input to the tool.",
           ),
       ),
       QueryEngineTool(
           query_engine=uber_engine,
           metadata=ToolMetadata(
               name="uber_10k",
               description="Provides information about Uber financials for year 2021. "
               "Use a detailed plain text question as input to the tool.",
           ),
       ),
   ]
Setup OpenAI Agent
   from llama_index.agent import OpenAIAgent
   agent = OpenAIAgent.from_tools(query_engine_tools, verbose=True)
Let's Try It Out!
   agent.chat_repl()
   ===== Entering Chat REPL =====
   Type "exit" to exit.
   === Calling Function ===
   Calling function: lyft_10k with args: {
     "input": "What was Lyft's revenue growth in 2021?"
   }
   Got output:
   Lyft's revenue growth in 2021 was 36%.
   ========================
   === Calling Function ===
   Calling function: uber_10k with args: {
     "input": "What was Uber's revenue growth in 2021?"
   }
   Got output:
   Uber's revenue growth in 2021 was 57%.
   ========================
   Assistant: Lyft's revenue growth in 2021 was 36%, while Uber's revenue growth in 2021 was 57%.


Build your own OpenAI Agent

With the new OpenAI API that supports function calling, it's never
been easier to build your own agent!
In this notebook tutorial, we showcase how to write your own OpenAI
agent in **under 50 lines of code**! It is minimal, yet feature
complete (with ability to carry on a conversation and use tools).
Initial Setup
Let's start by importing some simple building blocks.
The main thing we need is:
1. the OpenAI API (using our own "llama_index" LLM class)
2. a place to keep conversation history
3. a definition for tools that our agent can use.
   import json
   from typing import Sequence, List
   from llama_index.llms import OpenAI, ChatMessage
   from llama_index.tools import BaseTool, FunctionTool
   import nest_asyncio
   nest_asyncio.apply()
Let's define some very simple calculator tools for our agent.
   def multiply(a: int, b: int) -> int:
       """Multiple two integers and returns the result integer"""
       return a * b
   multiply_tool = FunctionTool.from_defaults(fn=multiply)
   def add(a: int, b: int) -> int:
       """Add two integers and returns the result integer"""
       return a + b
   add_tool = FunctionTool.from_defaults(fn=add)
Agent Definition
Now, we define our agent that's capable of holding a conversation and
calling tools in **under 50 lines of code**.
The meat of the agent logic is in the "chat" method. At a high-level,
there are 3 steps:
1. Call OpenAI to decide which tool (if any) to call and with what
   arguments.
2. Call the tool with the arguments to obtain an output
3. Call OpenAI to synthesize a response from the conversation context
   and the tool output.
The "reset" method simply resets the conversation context, so we can
start another conversation.
   class YourOpenAIAgent:
       def __init__(
           self,
           tools: Sequence[BaseTool] = [],
           llm: OpenAI = OpenAI(temperature=0, model="gpt-3.5-turbo-0613"),
           chat_history: List[ChatMessage] = [],
       ) -> None:
           self._llm = llm
           self._tools = {tool.metadata.name: tool for tool in tools}
           self._chat_history = chat_history
       def reset(self) -> None:
           self._chat_history = []
       def chat(self, message: str) -> str:
           chat_history = self._chat_history
           chat_history.append(ChatMessage(role="user", content=message))
           functions = [
               tool.metadata.to_openai_function() for _, tool in self._tools.items()
           ]
           ai_message = self._llm.chat(chat_history, functions=functions).message
           chat_history.append(ai_message)
           function_call = ai_message.additional_kwargs.get("function_call", None)
           if function_call is not None:
               function_message = self._call_function(function_call)
               chat_history.append(function_message)
               ai_message = self._llm.chat(chat_history).message
               chat_history.append(ai_message)
           return ai_message.content
       def _call_function(self, function_call: dict) -> ChatMessage:
           tool = self._tools[function_call["name"]]
           output = tool(**json.loads(function_call["arguments"]))
           return ChatMessage(
               name=function_call["name"],
               content=str(output),
               role="function",
               additional_kwargs={"name": function_call["name"]},
           )
Let's Try It Out!
   agent = YourOpenAIAgent(tools=[multiply_tool, add_tool])
   agent.chat("Hi")
   'Hello! How can I assist you today?'
   agent.chat("What is 2123 * 215123")

Build your own OpenAI Agent

   'The product of 2123 multiplied by 215123 is 456,706,129.'
Our (Slightly Better) "OpenAIAgent" Implementation
We provide a (slightly better) "OpenAIAgent" implementation in
LlamaIndex, which you can directly use as follows.
In comparison to the simplified version above:
* it implements the "BaseChatEngine" and "BaseQueryEngine" interface,
  so you can more seamlessly use it in the LlamaIndex framework.
* it supports multiple function calls per conversation turn
* it supports streaming
* it supports async endpoints
* it supports callback and tracing
   from llama_index.agent import OpenAIAgent
   from llama_index.llms import OpenAI
   llm = OpenAI(model="gpt-3.5-turbo-0613")
   agent = OpenAIAgent.from_tools([multiply_tool, add_tool], llm=llm, verbose=True)
Chat
   response = agent.chat("What is (121 * 3) + 42?")
   print(str(response))
   === Calling Function ===
   Calling function: multiply with args: {
     "a": 121,
     "b": 3
   }
   Got output: 363
   ========================
   === Calling Function ===
   Calling function: add with args: {
     "a": 363,
     "b": 42
   }
   Got output: 405
   ========================
   (121 * 3) + 42 is equal to 405.
   # inspect sources
   print(response.sources)
   [ToolOutput(content='363', tool_name='multiply', raw_input={'args': (), 'kwargs': {'a': 121, 'b': 3}}, raw_output=363), ToolOutput(content='405', tool_name='add', raw_input={'args': (), 'kwargs': {'a': 363, 'b': 42}}, raw_output=405)]
Async Chat
   response = await agent.achat("What is 121 * 3?")
   print(str(response))
   === Calling Function ===
   Calling function: multiply with args: {
     "a": 121,
     "b": 3
   }
   Got output: 363
   ========================
   121 * 3 is equal to 363.
Streaming Chat
Here, every LLM response is returned as a generator. You can stream
every incremental step, or only the last response.
   response = agent.stream_chat(
       "What is 121 * 2? Once you have the answer, use that number to write a story about a group of mice."
   )
   response_gen = response.response_gen
   for token in response_gen:
       print(token, end="")
   === Calling Function ===
   Calling function: multiply with args: {
     "a": 121,
     "b": 2
   }
   Got output: 242
   ========================
   121 * 2 is equal to 242.
   Once upon a time, in a small village, there was a group of mice who lived happily in a cozy little burrow. The leader of the group was a wise and courageous mouse named Milo. Milo was known for his intelligence and his ability to solve problems.
   One day, Milo gathered all the mice together and announced that they needed to find a new home. Their current burrow had become overcrowded, and they needed more space to live comfortably. The mice were excited about the idea of exploring new territories.
   With their tiny paws and keen senses, the mice set out on their journey. They traveled through fields, forests, and streams, searching for the perfect place to call home. Along the way, they encountered various challenges, such as crossing treacherous rivers and avoiding hungry predators.
   After days of searching, the mice stumbled upon a hidden meadow surrounded by tall grass and blooming flowers. It was a peaceful and serene place, far away from the hustle and bustle of the village. The mice knew they had found their new home.

Build your own OpenAI Agent

   Using their collective strength and determination, the mice began building their new burrow. They dug tunnels and created intricate chambers, ensuring that each mouse had enough space to live comfortably. Milo, with his exceptional leadership skills, organized the mice into different teams, assigning tasks to each member.
   As the mice settled into their new home, they realized that they had created a harmonious community. They worked together, sharing food, and looking out for one another. Milo's wisdom and guidance helped them overcome any obstacles they faced.
   The mice flourished in their new meadow, living happily ever after. They grew in numbers and became known as the Meadow Mice, admired by other animals for their unity and resilience. Milo's legacy lived on, as he continued to lead and inspire the mice for generations to come.
   And so, the story of the group of mice who found their new home after multiplying their efforts by 121 * 2 became a tale of courage, teamwork, and the power of determination.
Async Streaming Chat
   response = await agent.astream_chat(
       "What is 121 + 8? Once you have the answer, use that number to write a story about a group of mice."
   )
   response_gen = response.response_gen
   async for token in response.async_response_gen():
       print(token, end="")
   === Calling Function ===
   Calling function: add with args: {
     "a": 121,
     "b": 8
   }
   Got output: 129
   ========================
   121 + 8 is equal to 129.
   Once upon a time, in a lush green forest, there was a group of mice who lived in harmony. They were known as the Forest Friends, and their leader was a wise and kind-hearted mouse named Oliver.
   One sunny day, as the mice were going about their daily activities, they stumbled upon a mysterious object hidden beneath a pile of leaves. It was a magical acorn, shimmering with a golden glow. The acorn had the power to grant a wish to whoever possessed it.
   Excited by the discovery, Oliver gathered all the mice together and shared the news. They decided to use the wish to make their forest home even more beautiful and abundant. With their hearts filled with hope, they held the magical acorn and made their wish.
   As the mice closed their eyes and made their wish, a gentle breeze swept through the forest. When they opened their eyes, they couldn't believe what they saw. The forest had transformed into a magical wonderland, with vibrant flowers, sparkling streams, and towering trees that reached the sky.
   The mice explored their enchanted forest, marveling at the beauty that surrounded them. The streams were filled with crystal-clear water, teeming with fish and other aquatic creatures. The trees bore fruits of all kinds, providing an abundance of food for the mice and other forest animals.
   With their newfound paradise, the Forest Friends thrived. They lived in harmony with nature, sharing their blessings with other creatures. Oliver, as their wise leader, ensured that everyone had enough food and shelter. The mice worked together, building cozy burrows and gathering food for the winter.
   Word of the magical forest spread far and wide, attracting animals from all corners of the land. The Forest Friends welcomed them with open arms, creating a diverse and vibrant community. The mice, with their kind hearts and generous spirits, became known as the Guardians of the Enchanted Forest.
   As time passed, the Forest Friends continued to cherish their magical home. They lived in peace and harmony, always grateful for the gift they had received. Oliver, the wise leader, taught the younger mice the importance of unity and respect for nature.
   And so, the story of the group of mice who discovered a magical acorn and transformed their forest home after adding their efforts by 121 + 8 became a tale of hope, gratitude, and the power of a shared dream. The Forest Friends lived happily ever after, forever grateful for the magic that had brought them together.

Build your own OpenAI Agent

Agent with Personality
You can specify a system prompt to give the agent additional
instruction or personality.
   from llama_index.agent import OpenAIAgent
   from llama_index.llms import OpenAI
   from llama_index.prompts.system import SHAKESPEARE_WRITING_ASSISTANT
   llm = OpenAI(model="gpt-3.5-turbo-0613")
   agent = OpenAIAgent.from_tools(
       [multiply_tool, add_tool],
       llm=llm,
       verbose=True,
       system_prompt=SHAKESPEARE_WRITING_ASSISTANT,
   )
   response = agent.chat("Hi")
   print(response)
   Greetings, fair traveler! How may I assist thee on this fine day?
   response = agent.chat("Tell me a story")
   print(response)
   Of course, dear friend! Allow me to weave a tale for thee in the style of Shakespeare.
   Once upon a time, in a land far away, there lived a noble knight named Sir William. He was known throughout the kingdom for his bravery and chivalry. One fateful day, as Sir William rode through the enchanted forest, he stumbled upon a hidden glade.
   In the glade, he discovered a beautiful maiden named Lady Rosalind. She was fair of face and gentle of heart, and Sir William was instantly captivated by her beauty. They spent hours conversing, sharing stories, and laughing together.
   As the days turned into weeks, Sir William and Lady Rosalind's bond grew stronger. They found solace in each other's company and discovered a love that was pure and true. However, their happiness was short-lived, for an evil sorcerer named Malachi had set his sights on Lady Rosalind.
   Malachi, consumed by jealousy and darkness, sought to claim Lady Rosalind for himself. He devised a wicked plan to separate the two lovers and cast a spell upon Sir William, turning him into a statue of stone. Lady Rosalind, heartbroken and determined, vowed to find a way to break the curse and save her beloved.
   With unwavering courage, Lady Rosalind embarked on a perilous journey to seek the help of a wise old wizard. She traveled through treacherous mountains, crossed raging rivers, and faced many trials along the way. Finally, after much hardship, she reached the wizard's humble abode.
   The wizard, known as Merlin, listened to Lady Rosalind's tale of love and woe. He sympathized with her plight and agreed to aid her in breaking the curse. Together, they devised a plan to confront Malachi and restore Sir William to his human form.
   On the eve of the full moon, Lady Rosalind and Merlin ventured into the heart of Malachi's lair. They faced countless obstacles and battled fierce creatures, but their determination never wavered. Finally, they reached the chamber where Sir William stood, frozen in stone.
   With a wave of his staff and a powerful incantation, Merlin shattered the curse that held Sir William captive. As the first rays of dawn broke through the darkness, Sir William's eyes fluttered open, and he beheld Lady Rosalind standing before him.
   Their love, stronger than ever, triumphed over the forces of evil. Sir William and Lady Rosalind returned to the kingdom, where they were hailed as heroes. They lived a long and joyous life together, their love serving as a beacon of hope for all who heard their tale.
   And so, dear friend, ends the story of Sir William and Lady Rosalind, a tale of love, bravery, and the power of true devotion. May it inspire thee to seek love and adventure in thy own journey through life.