Model or code? The pipeline behind an AI trip planner, and which steps are deterministic
10 October 2026
When you build a product on a language model, you keep facing the same question: should the model do this step, or should ordinary code?
The model is flexible but can give a different answer each time. Code gives the same answer every time but only handles what you wrote it for. Get the split wrong in one direction and the product is unreliable. Get it wrong in the other and you are back to writing rules for every case.
MooTrip is an AI trip planner where the model writes the itinerary and edits it on request. This article shows the pipeline a single edit goes through, the rules I ended up with for who does what, and three steps I moved from one side to the other. The checks that code runs on a model's output are usually called guardrails, and most of this is about where to put them.
The pipeline for one edit
| # | Step | Who | If it fails |
|---|---|---|---|
| 1 | Gather what is known: the plan, recent chat, exchange rates | Code | |
| 2 | Decide what the traveller is asking for | Model | |
| 3 | Write the changed days | Model | |
| 4 | Check each day as it arrives | Code | Stop and go back to step 3 |
| 5 | Merge the change into the plan | Code | |
| 6 | Check the whole plan | Code | Go back to step 3, once |
| 7 | Repairs and look-ups: links, photos, road times | Code | Drop the repair, keep the plan |
| 8 | Save a version and show what changed | Code |
Two of the eight steps belong to the model. The rest is code, and most of that code exists to check what the model wrote. Here is why each step sits where it does.
| Step | Who | Why |
|---|---|---|
| Gather the plan, recent chat, exchange rates | Code | These are facts. There is nothing to judge. |
| Decide what the traveller is asking for | Model | Understanding what a person meant needs judgement. |
| Write the changed days | Model | Choosing places, order, times and wording is the creative part. |
| Check each day, then the whole plan | Code | A rule can say whether two times overlap or a row went missing. |
| Send a failed plan back, once | Code decides, model fixes | Code can tell that it is wrong. Only the model can rewrite it sensibly. |
| Build links, find photos, look up road times | Code | These can be worked out or looked up. |
| Save and compare versions | Code | This has to be exact. |
The rules I ended up with
Judgement goes to the model. Anything with one right answer goes to code. What to see in Kyoto is judgement. The link to a place on a map is not.
Do not ask the model to write what code can work out. The model used to type booking and map links, and it typed broken ones, including a hotel link with the site name missing. Now the model writes the facts (the place, where from, where to) and code builds the link. Every field you take away is one fewer thing it can get wrong.
Code refuses. It does not rewrite. When code finds a problem with the plan, it sends the plan back with the reason. It does not quietly change what the model wrote. The third story below is how I got there.
An AI will change more than you asked, so compare the result with the request. Someone asked for different photos. The model returned the first day with only the rows it had touched, and six rows and the prices disappeared. Code now compares the plan before and after. If something is gone that the request did not ask to remove, the edit goes back, and if it happens twice the plan is left as it was.
Check your other data too. One trip warned of a 49-hour drive. The model had estimated 20 to 30 minutes per leg. The map service had placed a stop 2,400 km away. A deterministic step can still be wrong, so a lookup far beyond the model's estimate is now treated as the error.
Three steps that changed sides
Reading what the traveller meant
To tell whether a deletion was intended, I first had code look for words like "remove", "skip" and "swap" in the message. A day later I handed that to the model, in a first step that reads the message and lists what will change. Code still checks the result.
Nights and flights
An "add two days" edit left the old last day with a check-out and no hotel for the night, and every edit after it failed. The model had too much to keep track of. So I had code take over: the model wrote the night and the flights as fields, and code turned them into rows and kept them in the right place.
Two days later I reversed it. The model was writing the flights both ways, as fields and as rows, so the first and last days ended up with two flights each. Now the model writes every row itself, in time order. Code fills in only times, costs and ids, and checks: a day with no night or a trip with no flight home is sent back.
Overlapping times
When an edit made two rows overlap, code used to move the later rows along to make room. I removed that. The model now gives a start time and a length for every row, and a day where one row starts before the last one ends fails its check and goes back to the model.
The pattern
All three moved the same way: from code to the model, with code keeping the check.
Each time, I had started by having code fix or fill in the model's output. With the flights, that created a second author of the plan, and the two disagreed. What held up was one author and one inspector. The model writes everything a traveller will read. Code decides whether it is good enough to save.
One repair shows the same split in miniature. When two stops in different places have no journey between them, code spots the gap, a short model call writes the missing journey, and code keeps the result only if the plan still passes every check.
If you are drawing this line in your own product, a useful test for each step is: could I write a rule that says whether the result is right? If yes, code should check it. If producing the result needs taste, the model should write it. And if code can compute it outright, the model should never be asked.