Travel Booking Agent

A user says the last Friday of June and the following Sunday. The second date is relative to the first, not to today. Biruni lets you chain calls — pass the first result as reference_date for the second.

User says
Find flights to Lisbon departing the last Friday of June. Return the following Sunday.
extract()Your LLM resolve_time()Biruni — depart resolve_time()Biruni — return search_flights()Your flight API reply()Your chat

The agent

travel_agent.py
import httpx, os
from datetime import date

BIRUNI_URL = "https://biruni.dev/v1/resolve"


# ── Stubs: replace with your integrations ────────────────────

async def search_flights(origin: str, dest: str, depart: str, ret: str):
    """Search flights. (Amadeus, Duffel, Skyscanner ...)"""
    ...

async def reply(text: str):
    """Send a response back to the user. (Slack, chat UI, email ...)"""
    ...

def extract(message: str) -> dict:
    """
    Use your LLM to pull structured fields from the raw message.
    Returns: {
        "origin": str, "dest": str, "tz": str,
        "depart_phrase": str, "return_phrase": str,
    }
    """
    ...


# ── Biruni integration ──────────────────────────────────────

async def resolve_time(phrase: str, tz: str, ref: str | None = None) -> dict:
    async with httpx.AsyncClient() as client:
        r = await client.post(
            BIRUNI_URL,
            headers={"Authorization": f"Bearer {os.environ['BIRUNI_API_KEY']}"},
            json={
                "expression": phrase,
                "reference_date": ref or date.today().isoformat(),
                "timezone": tz,
            },
        )
        r.raise_for_status()
        return r.json()


# ── Agent entry point ────────────────────────────────────────

async def handle(message: str):
    fields = extract(message)

    departure = await resolve_time(fields["depart_phrase"], fields["tz"])

    if departure["status"] != "resolved":
        await reply("Couldn't pin down the departure date — could you rephrase?")
        return

    # Chain: use the departure date as the anchor for the return phrase
    ret = await resolve_time(
        fields["return_phrase"], fields["tz"],
        ref=departure["resolved"][:10],
    )

    if ret["status"] != "resolved":
        await reply("Got the departure, but the return date is unclear.")
        return

    await search_flights(
        fields["origin"], fields["dest"],
        departure["resolved"], ret["resolved"],
    )
    await reply(
        f"Searching {fields['origin']} → {fields['dest']}: "
        f"depart {departure['resolved'][:10]}, "
        f"return {ret['resolved'][:10]}."
    )