{ "cells": [ { "cell_type": "markdown", "id": "dfe5445091782f76", "metadata": {}, "source": [ "# Localized Agent\n", "\n", "This recipe will show you how to use Nimble to make a simple Q&A ChatBot based on Google Maps data.\n", "\n", "
\n", "

Mirascope Concepts Used

\n", "\n", "
\n", "\n", "
\n", "

Background

\n", "

\n", "In the past, users had to rely on search engines and manually browse through multiple web pages to research or answer questions. Large Language Models (LLMs) have revolutionized this process. They can efficiently utilize map data and extract relevant content. By leveraging this information, LLMs can quickly provide accurate answers to user queries, eliminating the need for active searching. Users can simply pose their questions and let the LLM work in the background, significantly streamlining the information retrieval process.\n", "

\n", "
" ] }, { "cell_type": "markdown", "id": "b135222f", "metadata": {}, "source": [ "## Setup\n", "\n", "We will need an API key for :\n", "\n", "- [Nimble API Key](https://nimbleway.com/) or alternatively directly from [Google Maps API](https://developers.google.com/maps/documentation/places/web-service/search)\n", "\n", "And of course, Mirascope." ] }, { "cell_type": "code", "execution_count": null, "id": "6397b5c6", "metadata": {}, "outputs": [], "source": [ "!pip install \"mirascope[openai]\"" ] }, { "cell_type": "code", "execution_count": null, "id": "3492216f", "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "os.environ[\"OPENAI_API_KEY\"] = \"YOUR_API_KEY\"\n", "# Set the appropriate API key for the provider you're using" ] }, { "cell_type": "markdown", "id": "03aa66c1", "metadata": {}, "source": [ "## Creating a Localized Recommender ChatBot\n", "\n", "### Setup Tools\n", "\n", "Let's start off with defining our tools `get_current_date`, `get_coordinates_from_location` , and `nimble_google_maps` for our agent:" ] }, { "cell_type": "code", "execution_count": 2, "id": "f41d7131b9a0bb13", "metadata": { "ExecuteTime": { "end_time": "2024-09-30T16:54:35.419209Z", "start_time": "2024-09-30T16:54:34.205876Z" } }, "outputs": [], "source": [ "import asyncio\n", "from datetime import datetime\n", "\n", "import aiohttp\n", "import requests\n", "from mirascope.core import BaseMessageParam, openai\n", "from pydantic import BaseModel\n", "\n", "NIMBLE_TOKEN = \"YOUR_NIMBLE_API_KEY\"\n", "\n", "\n", "class LocalizedRecommenderBase(BaseModel):\n", " history: list[BaseMessageParam | openai.OpenAIMessageParam] = []\n", "\n", " async def _nimble_google_maps_places(\n", " self, session: aiohttp.ClientSession, place_id: str\n", " ):\n", " \"\"\"\n", " Use Nimble to get the details of a place on Google Maps.\n", " \"\"\"\n", " url = \"https://api.webit.live/api/v1/realtime/serp\"\n", " headers = {\n", " \"Authorization\": f\"Basic {NIMBLE_TOKEN}\",\n", " \"Content-Type\": \"application/json\",\n", " }\n", " place_data = {\n", " \"parse\": True,\n", " \"search_engine\": \"google_maps_place\",\n", " \"place_id\": place_id,\n", " \"domain\": \"com\",\n", " \"format\": \"json\",\n", " \"render\": True,\n", " \"country\": \"US\",\n", " \"locale\": \"en\",\n", " }\n", " async with session.get(url, json=place_data, headers=headers) as response:\n", " data = await response.json()\n", " result = data[\"parsing\"][\"entities\"][\"Place\"][0]\n", " return {\n", " \"opening_hours\": result.get(\"place_information\", {}).get(\n", " \"opening_hours\", \"\"\n", " ),\n", " \"rating\": result.get(\"rating\", \"\"),\n", " \"name\": result.get(\"title\", \"\"),\n", " }\n", "\n", " async def _nimble_google_maps(self, latitude: float, longitude: float, query: str):\n", " \"\"\"\n", " Use Nimble to search for places on Google Maps.\n", " \"\"\"\n", " url = \"https://api.webit.live/api/v1/realtime/serp\"\n", " headers = {\n", " \"Authorization\": f\"Basic {NIMBLE_TOKEN}\",\n", " \"Content-Type\": \"application/json\",\n", " }\n", " search_data = {\n", " \"parse\": True,\n", " \"search_engine\": \"google_maps_search\",\n", " \"query\": query,\n", " \"coordinates\": {\"latitude\": latitude, \"longitude\": longitude},\n", " \"domain\": \"com\",\n", " \"format\": \"json\",\n", " \"render\": True,\n", " \"country\": \"US\",\n", " \"locale\": \"en\",\n", " }\n", " search_response = requests.post(url, headers=headers, json=search_data)\n", " search_json_response = search_response.json()\n", " search_results = [\n", " {\n", " \"place_id\": result.get(\"place_id\", \"\"),\n", " }\n", " for result in search_json_response[\"parsing\"][\"entities\"][\"SearchResult\"]\n", " ]\n", " results = []\n", " async with aiohttp.ClientSession() as session:\n", " tasks = [\n", " self._nimble_google_maps_places(session, results.get(\"place_id\", \"\"))\n", " for results in search_results\n", " ]\n", " results = await asyncio.gather(*tasks)\n", " return results\n", "\n", " async def _get_current_date(self):\n", " \"\"\"Get the current date and time.\"\"\"\n", " return datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n", "\n", " async def _get_coordinates_from_location(self, location_name: str):\n", " \"\"\"Get the coordinates of a location.\"\"\"\n", " base_url = \"https://nominatim.openstreetmap.org/search\"\n", " params = {\"q\": location_name, \"format\": \"json\", \"limit\": 1}\n", " headers = {\"User-Agent\": \"mirascope/1.0\"}\n", " response = requests.get(base_url, params=params, headers=headers)\n", " data = response.json()\n", "\n", " if data:\n", " latitude = data[0].get(\"lat\")\n", " longitude = data[0].get(\"lon\")\n", " return f\"Latitude: {latitude}, Longitude: {longitude}\"\n", " else:\n", " return \"No location found, ask me about a specific location.\"" ] }, { "cell_type": "markdown", "id": "79a0f657830733d3", "metadata": {}, "source": [ "\n", "A quick summary of each of the tools:\n", "\n", "- `_get_current_date` - Gets the current date, this is relevant if the user wants to ask if a place is open or closed.\n", "- `_get_coordinates_from_location` - Gets the latitude and longitude based on the user’s query using OSM’s Geolocation.\n", "- `_nimble_google_maps` - Gets google maps information using the Nimble API.\n", "\n", "### Creating the Agent\n", "\n", "We then create our Agent, giving it the tools we defined and history for memory. As this agent functions as a ChatBot, we implement streaming to enhance the user experience:" ] }, { "cell_type": "code", "execution_count": 3, "id": "56701583b5ac675b", "metadata": { "ExecuteTime": { "end_time": "2024-09-30T16:54:35.431485Z", "start_time": "2024-09-30T16:54:35.424714Z" } }, "outputs": [], "source": [ "from mirascope.core import prompt_template\n", "\n", "\n", "class LocalizedRecommenderBaseWithStep(LocalizedRecommenderBase):\n", " @openai.call(model=\"gpt-4o\", stream=True)\n", " @prompt_template(\n", " \"\"\"\n", " SYSTEM:\n", " You are a local guide that recommends the best places to visit in a place.\n", " Use the `_get_current_date` function to get the current date.\n", " Use the `_get_coordinates_from_location` function to get the coordinates of a location if you need it.\n", " Use the `_nimble_google_maps` function to get the best places to visit in a location based on the users query.\n", "\n", " MESSAGES: {self.history}\n", " USER: {question}\n", " \"\"\"\n", " )\n", " async def _step(self, question: str) -> openai.OpenAIDynamicConfig:\n", " return {\n", " \"tools\": [\n", " self._get_current_date,\n", " self._get_coordinates_from_location,\n", " self._nimble_google_maps,\n", " ]\n", " }" ] }, { "cell_type": "markdown", "id": "d5bd88f46a5c586", "metadata": {}, "source": [ "\n", "Since our tools are defined inside our agent, we need to use Mirascope `DynamicConfig` to give our LLM call access to tools.\n", "\n", "### Creating our run function\n", "\n", "Now it is time to create our `run` function. We will first prompt the user to ask a question. The LLM will continue to call tools until it has all the information needed to answer the user’s question:\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "de64c96d97080ea9", "metadata": { "ExecuteTime": { "end_time": "2024-09-30T16:54:35.451934Z", "start_time": "2024-09-30T16:54:35.440641Z" } }, "outputs": [], "source": [ "class LocalizedRecommender(LocalizedRecommenderBaseWithStep):\n", " async def _get_response(self, question: str):\n", " response = await self._step(question)\n", " tool_call = None\n", " output = None\n", " async for chunk, tool in response:\n", " if tool:\n", " output = await tool.call()\n", " tool_call = tool\n", " else:\n", " print(chunk.content, end=\"\", flush=True)\n", " if response.user_message_param:\n", " self.history.append(response.user_message_param)\n", " self.history.append(response.message_param)\n", " if tool_call and output:\n", " self.history += response.tool_message_params([(tool_call, str(output))])\n", " return await self._get_response(question)\n", " return\n", "\n", " async def run(self):\n", " while True:\n", " question = input(\"(User): \")\n", " if question == \"exit\":\n", " break\n", " print(\"(Assistant): \", end=\"\", flush=True)\n", " await self._get_response(question)\n", " print()" ] }, { "cell_type": "markdown", "id": "56287bad74baa321", "metadata": {}, "source": [ "\n", "We use Mirascope utility functions `user_message_param`, `message_param`, and `tool_message_params` to easily update our history so the LLM is aware of which tools its called and what the next steps are.\n", "\n", "### Results\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "ee3b6cd3a837536c", "metadata": { "ExecuteTime": { "end_time": "2024-09-30T16:54:42.400507Z", "start_time": "2024-09-30T16:54:35.462798Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(Assistant): Sure, I can help you find beautiful beaches! Could you please specify the location or city you are interested in?\n" ] } ], "source": [ "recommender = LocalizedRecommender(history=[])\n", "await recommender.run()" ] }, { "cell_type": "markdown", "id": "590343ed75eb504", "metadata": {}, "source": [ "\n", "The more information we give the LLM from the Nimble API, the more specific of a recommendation the LLM can give, such as information about outside seating, dietary restrictions, cuisine, and more.\n", "\n", "
\n", "

Additional Real-World Applications

\n", "\n", "
\n", "\n", "When adapting this recipe, consider:\n", "\n", "- Tailor the Nimble tool by pulling different information for your requirements.\n", "- Give the LLM access to the web to access more detailed information.\n", "- Connect the agent to a database for quicker data fetching.\n", "\n", "\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 5 }