Skip to content

Commit

Permalink
setups an endpoint that finds and lists attractions to a given coordi…
Browse files Browse the repository at this point in the history
…nate
  • Loading branch information
filipeximenes committed Aug 23, 2024
1 parent c530383 commit 4f36860
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 76 deletions.
5 changes: 5 additions & 0 deletions example/demo/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
views.AIAssistantChatThreadView.as_view(),
name="chat_thread",
),
path(
"tour-guide/",
views.TourGuideAssistantView.as_view(),
name="tour_guide",
),
# Catch all for react app:
path("", views.react_index, {"resource": ""}),
path("<path:resource>", views.react_index),
Expand Down
22 changes: 22 additions & 0 deletions example/demo/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import json

from django.contrib import messages
from django.contrib.auth.models import User
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.views import View
from django.views.generic.base import TemplateView

from pydantic import ValidationError
from tour_guide.ai_assistants import TourGuideAIAssistant
from weather.ai_assistants import WeatherAIAssistant

from django_ai_assistant.api.schemas import (
Expand Down Expand Up @@ -102,3 +108,19 @@ def post(self, request, *args, **kwargs):
request=request,
)
return redirect("chat_thread", thread_id=thread_id)


class TourGuideAssistantView(View):
def get(self, request, *args, **kwargs):
coordinates = request.GET.get("coordinate")

if not coordinates:
return JsonResponse({})

thread = create_thread(name="Tour Guide Chat", user=User.objects.first())

a = TourGuideAIAssistant()
data = a.run(f"My coordinates are: ({coordinates})", thread.id)
print(data)

return JsonResponse(json.loads(data))
83 changes: 24 additions & 59 deletions example/tour_guide/ai_assistants.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,9 @@
import json
import requests
from typing import Sequence

from osmapi import OsmApi

from django.utils import timezone

from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.tools import BaseTool

from django_ai_assistant import AIAssistant, method_tool


# # Note this assistant is not registered, but we'll use it as a tool on the other.
# # This one shouldn't be used directly, as it does web searches and scraping.
# class OpenStreetMapsAPITool(AIAssistant):
# id = "open_street_maps_api_tool" # noqa: A003
# instructions = (
# "You're a tool to find the nearby attractions of a given location. "
# "Use the Open Street Maps API to find nearby attractions around the location, up to a 500m diameter from the point. "
# "Then check results and provide only the nearby attractions and their details to the user."
# )
# name = "Open Street Maps API Tool"
# model = "gpt-4o"

# def get_instructions(self):
# # Warning: this will use the server's timezone
# # See: https://docs.djangoproject.com/en/5.0/topics/i18n/timezones/#default-time-zone-and-current-time-zone
# # In a real application, you should use the user's timezone
# current_date_str = timezone.now().date().isoformat()
# return f"{self.instructions} Today is: {current_date_str}."
from tour_guide.integrations import fetch_points_of_interest


def _tour_guide_example_json():
Expand All @@ -38,8 +12,7 @@ def _tour_guide_example_json():
"nearby_attractions": [
{
"attraction_name": f"<attraction-{i}-name-here>",
"attraction_description": f"<attraction-{i}-short-description-here>",
"attraction_image_url": f"<attraction-{i}-image-url-here>",
"attraction_description": f"<attraction-{i}-description-here>",
"attraction_url": f"<attraction-{i}-imdb-page-url-here>",
}
for i in range(1, 6)
Expand All @@ -61,14 +34,20 @@ class TourGuideAIAssistant(AIAssistant):
name = "Tour Guide Assistant"
instructions = (
"You are a tour guide assistant that offers information about nearby attractions. "
"The user is at a location, passed as a combination of latitude and longitude, and wants to know what to learn about nearby attractions. "
"The application will capture the user coordinates, and should provide a list of nearby attractions. "
"Use the available tools to suggest nearby attractions to the user. "
"If there are no interesting attractions nearby, "
"tell the user there's nothing to see where they're at. "
"Use three sentences maximum and keep your suggestions concise."
"Your response will be integrated with a frontend web application,"
"therefore it's critical to reply with only JSON output in the following structure: \n"
f"```json\n{_tour_guide_example_json()}\n```"
"You don't need to include all the found items, only include attractions that are relevant for a tourist. "
"Select the top 10 best attractions for a tourist, if there are less then 10 relevant items only return these. "
"Order items by the most relevant to the least relevant. "
"If there are no relevant attractions nearby, just keep the list empty. "
"Your response will be integrated with a frontend web application therefore it's critical that "
"it only contains a valid JSON. DON'T include '```json' in your response. "
"The JSON should be formatted according to the following structure: \n"
f"\n\n{_tour_guide_example_json()}\n\n\n"
"In the 'attraction_name' field provide the name of the attraction in english. "
"In the 'attraction_description' field generate an overview about the attraction with the most important information, "
"curiosities and interesting facts. "
"Only include a value for the 'attraction_url' field if you find a real value in the provided data otherwise keep it empty. "
)
model = "gpt-4o"

Expand All @@ -78,28 +57,14 @@ def get_instructions(self):
# In a real application, you should use the user's timezone
current_date_str = timezone.now().date().isoformat()

return "\n".join(
[
self.instructions,
f"Today is: {current_date_str}",
f"The user's current location, considering latitude and longitude is: {self.get_user_location()}",
]
)

def get_tools(self) -> Sequence[BaseTool]:
return [
TavilySearchResults(),
# OpenStreetMapsAPITool().as_tool(description="Tool to query the Open Street Maps API for location information."),
*super().get_tools(),
]
return f"Today is: {current_date_str}. {self.instructions}"

@method_tool
def get_user_location(self) -> dict:
"""Get user's current location."""
return {"lat": "X", "lon": "Y"} # replace with actual coordinates

@method_tool
def get_nearby_attractions_from_api(self) -> dict:
api = OsmApi()
"""Get nearby attractions based on user's current location."""
return {}
def get_nearby_attractions_from_api(self, latitude: float, longitude: float) -> dict:
"""Find nearby attractions based on user's current location."""
return fetch_points_of_interest(
latitude=latitude,
longitude=longitude,
tags=["tourism", "leisure", "place", "building"],
radius=500,
)
56 changes: 39 additions & 17 deletions example/tour_guide/integrations.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,45 @@
from requests_oauth2client import OAuth2Client
import osmapi
from django.conf import settings
from typing import List

import requests

client_id = settings.OPEN_STREET_MAPS_CLIENT_ID
client_secret = settings.OPEN_STREET_MAPS_CLIENT_SECRET

# special value for redirect_uri for non-web applications
redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
def fetch_points_of_interest(
latitude: float, longitude: float, tags: List[str], radius: int = 500
) -> dict:
"""
Fetch points of interest from OpenStreetMap using Overpass API.
authorization_base_url = "https://master.apis.dev.openstreetmap.org/oauth2/authorize"
token_url = "https://master.apis.dev.openstreetmap.org/oauth2/token"
:param latitude: Latitude of the center point.
:param longitude: Longitude of the center point.
:param radius: Radius in meters to search for POIs around the center point.
:param tags: A list of OpenStreetMap tags to filter the POIs (e.g., ["amenity", "tourism"]).
:return: A list of POIs with their details.
"""
# Base URL for the Overpass API
overpass_url = "http://overpass-api.de/api/interpreter"

oauth2client = OAuth2Client(
token_endpoint=token_url,
authorization_endpoint=authorization_base_url,
redirect_uri=redirect_uri,
auth=(client_id, client_secret),
code_challenge_method=None,
)
# Construct the Overpass QL (query language) query
pois_query = "".join(
[
(
f"node[{tag}](around:{radius},{latitude},{longitude});"
f"way[{tag}](around:{radius},{latitude},{longitude});"
)
for tag in tags
]
)

api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org")
query = f"""
[out:json];
(
{pois_query}
);
out tags;
"""

response = requests.get(overpass_url, params={"data": query}, timeout=10)

response.raise_for_status()

data = response.json()
return data["elements"]

0 comments on commit 4f36860

Please sign in to comment.