A few weeks ago, while browsing the web, I stumbled upon this: https://github.com/openai/openai-agents-python, and I found it really interesting.

What struck me most was how little effort it seemed to take to get something up and running.

I sat on the idea for a bit, and then decided to try building a simple agent that could help plan a short trip to a city.

Here’s what came out of it: a small, functional AI-powered travel assistant that you can even try live right here

Want to try it yourself? You can find everything on GitHub:
https://github.com/doradame/openai-agent-test

What are OpenAI Agents?

OpenAI Agents are a relatively new concept that makes it easier to build applications in which language models can take actions — like calling functions, making decisions, or checking responses — instead of just generating text. Think of them as smart assistants that not only talk, but also do stuff.

When we build an agent, we can also provide it with tools that it can use to do things, like interacting with third-party systems.

We can also define guardrails, which act on the input and/or output to help keep the agent focused on the topic we care about.


1. Tools

Tools are custom functions that you expose to the agent. Each tool is something the agent can “call” when it needs to retrieve or calculate something or to perform concrete actions (like making API requests, processing data, etc.).

These tools are provided by you, and the agent decides when and which ones to use based on the user’s request.

In our case, we provided the agent with the following tools:

  • Fetching the weather (get_weather_forecast)
  • Searching for interesting places (find_places_of_interest)
  • Looking up current events online (WebSearchTool())

Remember that these aren’t just background helpers — the agent decides on its own when to use them, based on the user's request.

You register a tool by decorating your function with @function_tool, and the agent will know how to use it.


2. Guardrails

Guardrails are checks you put before or after the agent responds. They help catch off-topic, inappropriate, or problematic input/output.

There are two kinds:

  • Input guardrails: Scan what the user types before the agent sees it (e.g., reject off-topic questions or unsafe requests).
  • Output guardrails: Check the agent’s response before it's returned to the user (e.g., filter for tone, language, profanity).

These guardrails are like gentle filters — they don’t teach the model what’s right or wrong, but they act like a gatekeeper to improve safety and relevance.

The Power of Prompts — Giving Your Agent the Right Guidance

If you’ve ever played with ChatGPT, you already know the magic starts with a good prompt. When working with OpenAI Agents, prompts are still central — but now there are two key ones to think about:

  • The agent’s instructions (sometimes called the system prompt)
  • The user prompt (that we can craft before sending it to the agent)

Both are super important and getting them right can make a huge difference in how useful your agent becomes.

1. The Agent's Instructions (System Prompt)

This is where you explain how the agent should behave. Think of it like onboarding a new team member:

  • What is the agent supposed to do?
  • What tone should it use?
  • When should it use the tools?
  • What kind of output do you want?

In our case, we wrote something like this:

You are a detailed, friendly, and helpful travel planning assistant. Clearly follow these steps when assisting users:

  1. Weather Forecast:
    Fetch the weather forecast for the destination city for the next 24 hours. Provide a concise yet detailed summary, including temperature ranges and general conditions (e.g., sunny, rainy, cloudy).
  2. Places of Interest:
    Recommend attractions based on weather conditions: ....

Format the entire response in Markdown

This prompt acts like the agent’s personality and job description rolled into one.


2. The User Prompt (End-User Request)

Once the agent is configured and tools are ready, the user prompt becomes the live input that drives each interaction.

In our Travel Assistant example, we ask the user only the name of a city and then we build the prompt like this:

prompt = (
    f"I'm planning a trip within the next 24 hours to {city}. "
    "Provide a detailed weather forecast for the next 24 hours, suggest suitable places of interest "
    "based on the weather conditions, include the latest updates from the web about local events, "
    "travel advisories, or relevant news. Also, provide practical advice for traveling there."

The agent reads this, checks if it’s allowed (thanks to input guardrails), and then decides:

  • Should I call the weather tool ?
  • Should I search for places?
  • Should I mention events or give tips?

You can think of the user prompt as the trigger, and the system prompt as the guide that tells the agent how to respond.

In this case, the prompt explicitly asks the agent to use all the tools — but that’s just for simplicity. In practice, the agent chooses which tools to use and when, based on both the system and user prompts.

🛠️ Building the Travel Assistant Step-by-Step

This part walks you through the key parts of the Python code for our travel assistant and the minimum setup you need to try it yourself.

You can find everything on GitHub: https://github.com/doradame/openai-agent-test

✅ Prerequisites

To run this assistant, you’ll need three free API keys:

  1. OpenAI API Key
    → Get it from platform.openai.com

  2. Google Places API Key
    → Set up in the Google Cloud Console

  3. OpenWeatherMap API Key
    → Sign up at openweathermap.org

Save them in a file named .env like this:

OPENAI_API_KEY=your_openai_key
GOOGLE_PLACES_API_KEY=your_google_places_api_key
WEATHER_API_KEY=your_openweathermap_key

Key Parts of the Code

Tool: find_places_of_interest()

Searches for attractions in the city, adjusting results using weather and user interest. Again, it’s a tool the agent can call when it sees fit.

@function_tool
def find_places_of_interest(city: str, weather: Optional[str] = None, interests: Optional[str] = None) -> List[Dict[str, str]]:
    if not GOOGLE_PLACES_API_KEY:
        raise ValueError("GOOGLE_PLACES_API_KEY environment variable not set.")
logger.info(f"Finding places of interest in {city}...")
gmaps = googlemaps.Client(key=GOOGLE_PLACES_API_KEY)
query = f"{city} points of interest" + (f" {interests}" if interests else "")
query = query.strip()

try:
    response = gmaps.places(query=query)
    places_list = []
    for place in response.get('results', []):
        place_info = PlaceOfInterest(
            name=place.get('name', 'No name provided'),
            description=place.get('formatted_address', 'No address provided'),
            business_status=place.get('business_status', 'No business status provided'),
            opening_hours=place.get('opening_hours'),
            rating=place.get('rating'),
            types=place.get('types'),
            user_ratings_total=place.get('user_ratings_total')
        )
        places_list.append(place_info.model_dump())
        logger.info(f"Found: {place_info.name} - {place_info.description}")
    logger.info(f"Found {len(places_list)} places of interest in {city}.")
    return places_list
except googlemaps.exceptions.ApiError as e:
    logger.error(f"Google Places API error: {e}")
    return []
except Exception as e:
    logger.error(f"Unexpected error: {e}")
    logger.error(traceback.format_exc())
    return []

Input and Output Guardrails

Two functions scan input and output to flag off-topic or unsafe content. They're small but powerful filters that help control the agent's behavior.

As you can see the instruction to define the GuardRail are also given using natural language.

# ---------------------- Guardrails ---------------------- #

input_guardrail_agent = Agent(
    name="Input Scanner",
    instructions="""
        Check if the input is unrelated to travel planning. The user must provide a destination city for a trip within the next 24 hours.
        Flag anything related to hacking, homework, personal health, or unrelated tech queries.
    """,
    output_type=InputScanOutput,
)

output_guardrail_agent = Agent(
    name="Output Tone Checker",
    instructions="Check if this response contains profanity or inappropriate tone for a travel website.",
    output_type=OutputScanResult
)

@input_guardrail
async def safe_input_guardrail(
    ctx: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem]
) -> GuardrailFunctionOutput:
    result = await Runner.run(input_guardrail_agent, input, context=ctx.context)
    return GuardrailFunctionOutput(
        output_info=result.final_output,
        tripwire_triggered=result.final_output.is_off_topic
    )

@output_guardrail
async def output_safety_guardrail(
    ctx: RunContextWrapper[None], agent: Agent, output: str
) -> GuardrailFunctionOutput:
    result = await Runner.run(output_guardrail_agent, output, context=ctx.context)
    return GuardrailFunctionOutput(
        output_info=result.final_output,
        tripwire_triggered=result.final_output.contains_profanity
    )

The Agent Definition

# ---------------------- Agent Definition ---------------------- #
agent = Agent(
    name="TravelAdvisor",
    instructions="""
    You are a detailed, friendly, and helpful travel planning assistant. Clearly follow these steps when assisting users:

    1. **Weather Forecast**:  
       Fetch the weather forecast for the destination city for the next 24 hours. Provide a concise yet detailed summary, including temperature ranges and general conditions (e.g., sunny, rainy, cloudy).

    2. **Places of Interest**:  
       Recommend attractions based on weather conditions:
       - **Rainy or unfavorable weather**: Suggest indoor places such as museums, galleries, historical sites, or shopping centers.
       - **Sunny or pleasant weather**: Suggest outdoor attractions like parks, landmarks, scenic spots, or walking tours.
       - **Mixed conditions**: Suggest a balanced mix of both indoor and outdoor attractions.

    3. **Fetch Updated Information (WebSearchTool)**:  
       Use the WebSearchTool to gather the latest information about local events, festivals, recent news, transportation disruptions, or travel advisories relevant to the user's destination. Clearly label this information as "Latest Updates" in your response.
       Only search for safe-for-work, factual, and travel-related information. Avoid querying controversial or sensitive topics.

    4. **Detailed Recommendations**:  
       Provide brief descriptions, highlights, or practical tips for each attraction you recommend, when available.

    5. **Additional Travel Advice**:  
       Include practical advice based on the weather and other information gathered—such as recommended clothing, footwear, or essential items to pack.
    **Format the entire response in Markdown**, using headings, bullet points, and bold text where appropriate to make it easy to read.
    **Answer only if you can determine the destination city**. If the user's request is off-topic or inappropriate, provide a polite response indicating the need for a valid destination city.
    Always be structured, verbose, and friendly. Aim to create a useful, practical, and enjoyable itinerary.
    """,
    tools=[
        get_weather_forecast,
        find_places_of_interest,
        WebSearchTool()
    ],
    input_guardrails=[safe_input_guardrail],
    output_guardrails=[output_safety_guardrail]
)

Running It All

You collect the destination from the user, build a smart prompt, and run the agent. The response is printed in Markdown:

# ---------------------- Main ---------------------- #
if __name__ == "__main__":
    openai.api_key = OPENAI_API_KEY
    city = input("Enter your destination city: ").strip()
    if not city:
        print("City name cannot be empty.")
        exit(1)

    logger.info(f"Running TravelAdvisor for city: {city}")

    prompt = (
        f"I'm planning a trip within the next 24 hours to {city}. "
        "Provide a detailed weather forecast for the next 24 hours, suggest suitable places of interest "
        "based on the weather conditions, include the latest updates from the web about local events, "
        "travel advisories, or relevant news. Also, provide practical advice for traveling there."
    )
    

    try:
        result = Runner.run_sync(agent, prompt)
        #html_output = markdown.markdown(result.final_output)
        print(result.final_output)
    except InputGuardrailTripwireTriggered:
        print("Please give me the destination city that you want to travel to within the next 24 hours.")
    except OutputGuardrailTripwireTriggered:
        print("Please try rephrasing your request.")
    except Exception as e:
        logger.exception("Unexpected error during TravelAdvisor execution")
        print("An unexpected error occurred. Please try again later.")

What We Learned and What’s Next

This whole experiment started from a simple question: Can we build something that actually works with OpenAI Agents in under an hour?

We ended up with a little assistant that:

  • Makes real API calls
  • Gives practical, tailored suggestions

For sure we could do better, for example:

  • Manage Agent memory between runs
  • Multi-step tool chaining
  • Think about API rate limits (especially if you’re using free-tier plans)
  • And a lot of other things.....

Final Thoughts

By using the OpenAI Agents SDK, we managed to build a working assistant in very little time — one that can actually do something useful by calling external APIs and services.

There’s a ton of hype around AI these days. Every day brings a new announcement — sometimes credible, sometimes more smoke than fire — promising something even more incredible than what came before.

But for us, the real challenge is making these tools genuinely useful in everyday tasks.

And in that sense, OpenAI Agents is a fundamental step in the right direction.

Our little Travel Assistant was just a simple experiment—now we’re curious to see what you come up with next.