Coding a chatless assistant with Django and OpenAI

Filipe Ximenes
October 18, 2024

Are you tired of the same old chat-based AI applications? Most AI applications we encounter today are based on chat interfaces. However, many other exciting ways to use large language models (LLMs) don't involve direct conversation. In this article, we'll explore one such application by building a location-aware AI tour guide using Django and OpenAI.

This guide suits developers familiar with Python and Django eager to explore AI integration in web applications. If you're new to either Django or AI, you might find some concepts challenging, but the step-by-step approach should help you follow along.

The concept

Imagine having a personal AI tour guide in your pocket, ready to unveil hidden gems in any city you visit. That's precisely what we're going to build using django-ai-assistant. This powerful tool lets us quickly create an AI agent that autonomously gathers information, generates structured JSON output, and seamlessly integrates with a frontend application.

The inspiration for this project stems from my own travel beliefs. I've never been one for rigid itineraries or hour-by-hour planning. Instead, I prefer a more spontaneous approach: a bit of pre-trip research, a map sprinkled with pins, and the freedom to choose my daily adventure on a whim. It's a liberating way to travel, though it comes with its own set of challenges. I've missed out on some attractions that required advance booking, but the thrill of unexpected discoveries has always made up for it.

Yet, even as a champion of improvised exploration, I've often wished for a tool to enhance my wanderings. Picture an app that whispers in your ear about the hidden history of the street you're walking down or alerts you to a tucked-away café that serves the best espresso in town. It would be like having a knowledgeable local friend, always ready with a suggestion or an interesting tidbit.

The final product would be a sleek mobile app that whispers local insights directly into your earbuds as you explore—if such an app already exists, I'd love to know about it! For our MVP, however, we're starting with a more basic approach: a web application featuring a single button. The user's location is sent to our LLM with one click, which returns a curated list of nearby attractions. It's a simple interface that packs a punch, demonstrating the core functionality without the complexity of a full-fledged mobile app.

This streamlined version will serve as our playground for exploring the integration of AI into web applications. It's a perfect starting point for those looking to dip their toes into AI-enhanced web development. Let's dive in and bring this concept to life!

Setting up the environment

I'll guide you step by step in building the app while explaining how things work. As an example application, this version is already available to test in the django-ai-assistant repository, so check it out if you want to play with it.

Assuming you already have a Django project set up, start by installing django-ai-assistant running:

pip install django-ai-assistant

We can then create a new Django app or go to an existing one to create a file called ai_assistants.py:

cd my_django_app
touch ai_assistants.py

Building the assistant

For this example, we will use the OpenAI model GPT-4o because it has valuable features for our application. Visit OpenAI, generate an API key, and ensure you have credits available. You can also use any other of the models supported by Langchain.

Now, you can set an environment variable with the API key. Notice that the name of the variable will change depending on the model you are going to use:

export OPENAI_API_KEY="your-key-here"

We can now start coding. The first thing is setting up the assistant by configuring the model and defining its instructions. Add the following to ai_assistants.py:

from django.utils import timezone
from django_ai_assistant import AIAssistant

class TourGuideAIAssistant(AIAssistant):
    id = "tour_guide_assistant"
    name = "Tour Guide Assistant"
    instructions = (
        "You are a tour guide assistant that offers "
        "information about nearby attractions. "
        "You will receive the user coordinates and "
        "should use available tools to find nearby attractions. "
        "Only include in your response the items that "
        "are relevant to a tourist visiting the area. "
        "Only call the find_nearby_attractions tool once."
    )
    model = "gpt-4o"

    def get_instructions(self):
        current_date_str = timezone.now().date().isoformat()
        return f"Today is: {current_date_str}. {self.instructions}"

The get_instructions method is essential so you can dynamically provide the LLM with information about the current time. However, notice that the instructions prompt mentions that it should use tools to find nearby attractions.

Integrating with OpenStreetMap

You can get reasonably good information about popular tourist destinations by providing the assistant with location coordinates, relying only on the LLM built-in knowledge. However, we want to enhance this using a map API for better data.

We will give our assistant a tool that allows it to query the OpenStreetMap API and get a list of attractions in a given radius:

from django_ai_assistant import method_tool
from tour_guide.integrations import fetch_points_of_interest
import json

class TourGuideAIAssistant(AIAssistant):
    # Assuming the rest of the class definition remains unchanged
    ...

    @method_tool
    def find_nearby_attractions(self, latitude: float, longitude: float) -> str:
        """
        Find nearby attractions based on user's current location.
        Returns a JSON with the list of all types of points of interest,
        which may or may not include tourist attractions.
        """
        return json.dumps(
            fetch_points_of_interest(
                latitude=latitude,
                longitude=longitude,
                tags=["tourism", "leisure", "place", "building"],
                radius=500,
            )
        )

The first thing to notice in this code is the @method_tool decorator, which informs the assistant that this tool can be used. It will automatically configure it to ensure the LLM knows about it and how to use it if and when it judges it necessary. The second thing to notice is that the method is type-annotated. This is necessary because it informs the LLM how to pass the parameters to the tool.

The next thing is the docstring; it will also be passed on to the LLM so it knows what the tool can do and will help it decide when to call it. The last thing is to call the fetch_points_of_interest function, which calls the OpenStreetMap API and returns the JSON directly to the LLM. Notice there's no need to clean up the data; modern LLMs are capable of working with JSON.

You can look it up here if you want to see how fetch_points_of_interest is implemented. Feel free to copy this code directly to your application. You can also replace it with any other map API you prefer (such as Google Maps).

What we have should already work. You can test it by calling the assistant in your Python shell:

from my_app.ai_assistants import TourGuideAIAssistant

a = TourGuideAIAssistant()
attractions_text = a.run(
    "My coordinates are: (41.891128649011904, 12.491951947209131)"
)
print(attractions_text)

You will now notice the result is just a bunch of text, which probably has the information we are looking for but is not formatted in JSON, so we can't send it to our frontend application. Fixing that in django-ai-assistant is relatively easy as it supports JSON-structured outputs.

We are going to use Pydantic to define the format of the JSON data we want to return:

from pydantic import BaseModel, Field

class Attraction(BaseModel):
    attraction_name: str = Field(
        description="The name of the attraction in english"
    )
    attraction_description: str = Field(
        description="The description of the attraction, "
                    "provide information in an entertaining way"
    )
    attraction_url: str = Field(
        description="The URL of the attraction, "
                    "keep empty if you don't have this information"
    )

class TourGuide(BaseModel):
    nearby_attractions: list[Attraction] = Field(
        description="The list of nearby attractions"
    )

And pass the TourGuide class to the TourGuideAIAssistant:

class TourGuideAIAssistant(AIAssistant):
    ...
    structured_output = TourGuide
    ...

That's all! The LLM will now use the data it collected using tools and return a Pydantic TourGuide object that can be easily converted to JSON. We can now add a view that receives coordinates and calls the assistant:

from django.http import JsonResponse
from django.views import View
from my_app.ai_assistants import TourGuideAIAssistant

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

        if not coordinate:
            return JsonResponse({})

        a = TourGuideAIAssistant()
        data = a.run(f"My coordinates are: ({coordinate})")

        return JsonResponse(data.model_dump())

The code in the frontend is also quite simple:

navigator.geolocation.getCurrentPosition(
  (position: any) => {
    const response = await fetch(`/tour-guide/coordinate=${latitude},${longitude}`);
    const tour_guide_data = await response.json();
    console.log(tour_guide_data);
  },
  (error) => console.log(error)
);

Before we wrap up, I invite you to witness the fruits of our labor in action! Below, you'll find a brief screen recording that showcases our AI-powered tour guide app at work.

Conclusion

We've journeyed through creating an AI assistant that leverages custom tools, generates structured JSON data, and seamlessly integrates with a frontend application. This project demonstrates the versatility of django-ai-assistant and showcases how Large Language Models (LLMs) can be applied beyond traditional chatbots.

As you consider expanding this application, here are some thought-provoking directions to explore:

  • Develop a feature that allows users to explore specific attractions more deeply, utilizing an additional assistant to scour the web for comprehensive information;
  • Expand the scope by increasing the search radius and transforming the assistant into a day-trip planner. Consider integrating Django's database capabilities to store and retrieve generated itineraries.

For inspiration on implementing these features, the movie assistant example in the django-ai-assistant documentation provides valuable insights and techniques.

This tutorial has offered a glimpse into the potential of django-ai-assistant and LLMs in creating innovative, non-chat-based AI applications. I highly recommend reading "Django AI Assistant: Streamlining Development with LLMs" to deepen your understanding of this topic. This article provides an excellent overview of how django-ai-assistant integrates with LangChain, offering a complementary perspective to what we've covered here.

As you continue your journey in AI-enhanced web development, remember that the possibilities extend far beyond what we've explored today. Whether you're building tools for travel, education, or business, the combination of Django and AI opens up a world of creative solutions.

Happy coding! 👨💻 👩