Akoya
Akoya was an AI travel planner: describe the trip you want, get an itinerary back day by day. We built it in early 2023, on the completion models that came before the chat API, months before function calling existed. The product is no longer operating. The engineering is the reason the page is still here.
YEAR
2024
CLIENT
Akoya
SCOPE
PLATFORM
Web
The Challenge
It is easy to forget how little existed in March 2023. There were no agent frameworks, no function calling, no orchestration libraries, no accumulated wisdom about how to structure a prompt. There was a completion endpoint and a token bill. Building a product on that meant answering questions nobody had settled yet. How do you keep a prompt maintainable when it is a paragraph of English with variables in it? How do you show a user a long generated answer without them staring at a spinner for thirty seconds? And how do you stop a public endpoint that costs money per call from becoming somebody's free API? Every one of those has a standard answer now. None of them did then.
Objectives
- Turn a few trip parameters into a useful day-by-day itinerary, plus a summary and local tips.
- Make the wait tolerable for output that takes tens of seconds to generate.
- Keep generation costs bounded on a public, unauthenticated-by-default surface.
- Keep the same trip producing the same plan.
Our Approach
Prompts as templates, not as string concatenation
Each generation — the day-by-day details, the itinerary summary, the tips — has its own Jinja2 template rendered with the trip's parameters. Prompts became files you could diff, review and change without touching request handling.
This is unremarkable practice now. In early 2023 the common alternative was f-strings scattered through view code.
Stream it, because thirty seconds is not a loading state
A full itinerary takes long enough that a spinner reads as broken. The details endpoint streams the completion to the browser over server-sent events, so text appears as it is generated. There was no framework helper for this; it is a streaming HTTP response with the content type set and caching turned off.
Throttle the expensive endpoints specifically
The AI endpoints carry their own throttle class, separate from the rest of the API. A generation endpoint is a public URL that costs real money every time someone loads it, and rate limiting it is the difference between a product and a donation.
This is the same argument the studio would make again years later about metering agent costs — it just arrived here first.
Temperature zero, deliberately
Every call is deterministic. A travel plan that reshuffles itself when the user refreshes is not a plan, and creative variance is not a feature when someone is deciding what to do on Tuesday.
The Solution
A Django and Django REST Framework backend over PostgreSQL, with three generation endpoints — itinerary, day-by-day details, and travel tips — each with its own prompt template, its own token ceiling, and a shared throttle. The details endpoint streams; the other two return complete.
A React and Vite front end with Material UI, Mapbox geocoding for destination search, React Query for server state, Formik and Yup for the trip form, and Google sign-in.
Under the Hood
Backend
Django 4.1 with Django REST Framework, PostgreSQL, Gunicorn, Jinja2 for prompt templates, the OpenAI Python library at 0.27 against text-davinci-003, server-sent events for streaming, and a custom DRF throttle scoped to the AI endpoints.
Frontend
React with Vite, Material UI, Tailwind, Mapbox GL geocoder, React Query, Formik and Yup, Framer Motion, Fuse.js for fuzzy search, Google OAuth.
The Decisions
Determinism over variety
Temperature zero throughout. The model was there to organise information the user had supplied, not to be interesting.
The cost control went in first, not after the first bill
Throttling the generation endpoints was part of building them. It is the cheapest possible version of the metering argument we would make about agent systems years later, and the reason it is worth pointing at now is that the instinct is older than the vocabulary.
Prompts belong in version control
Templating them made the product's behaviour reviewable. When the output was wrong, the fix was a diff on a template rather than an archaeology expedition through a view function.
Outcomes
We delivered the MVP and it went in front of real users. The company did not continue, and the product is no longer operating.
The page stays because the work is dated evidence of something we still say about ourselves: that we were building on language models before the tooling existed, and that templated prompts, streamed output, deterministic generation and metered spend were the choices we made when there was nothing to copy.
Other Case Studies

Speed Machine
A direct-to-customer insurance platform that integrates leading insurer APIs so people can...

Advantage
A multi-tenant insurance benefits portal PrishaPolicy offers to its corporate clients,...

Vantage
An internal insurance operations platform built for PrishaPolicy, unifying sales, policy...

Prisha Policy
Empowering a prominent insurance broker to safeguard client assets and liabilities through...

Centuro Global
An Angular platform and its AWS infrastructure for a compliance SaaS operating in 170+...

TellGo
An eSIM store on Medusa, migrated off a single supplier while live — the supplier we moved...

BillMart
Providing comprehensive trade finance solutions through Working Capital Loans, Channel...

H&S Store
An online store for a Kuwaiti electronics retailer, kept in step with the ERP, the...

Empathable
EmpathablePX — an AI patient-simulation platform where clinicians rehearse high-stakes...