我删除了LLM的电话,取而代之的是200行模板代码

2026年8月25日1 次浏览来源:Dev.to阅读原文

正文保留英文原文(机翻易破坏代码与排版),标题/摘要已提供中文

The feature was a letter generator.

Somebody fills in a few fields and gets a finished letter of recommendation, resignation letter or notice letter, in plain text, ready to paste into an email.

The obvious build is a prompt and a model call.

I wrote the deterministic version instead: a pure function, about two hundred lines, no network, no key, no tokens.

I want to lay out the reasoning, because "just call a model" is the default now and the default is not always right.

The three reasons, in order of weight

1.

The output is short and the shape is fixed.

A recommendation letter is a date block, a greeting, three or four paragraphs, a sign off and a name.

There is no structural variation to discover.

Generation is valuable when the space of good outputs is large and you cannot enumerate it.

Here the space is small enough to write down, and once you have written it down the model is doing an expensive approximation of a statement.

2.

It is a legal-adjacent document.

Not legal advice, but it goes into an employment record.

A resignation letter that invents a notice period, or a reference that invents a fact about a person, is a real problem for the person who sent it.

Templates cannot hallucinate.

Everything specific in the output either came from a form field or is a sentence I wrote and can be held to.

3.

Zero marginal cost changes what the product can be.

This is the one that actually decided it.

A model call costs money per use, and anything that costs money per use needs an account, a rate limit and eventually a card.

A pure function costs nothing, so the tool can stay open with no signup, forever, without a business case.

That is a product decision expressed as an architecture decision, and it only works if the code path is free.

What the code looks like The whole engine is one exported function over one input type.

Tone is not a prompt instruction, it is a dimension of the data.

Two tiny functions carry most of it: The bodies are arrays of paragraphs, assembled conditionally.

A recommendation body opens differently depending on whether the writer told us how they know the subject: That ternary is the whole trick, repeated maybe fifteen times.

It is not clever.

Clever was never the requirement.

What you get for free that a model call does not give you Determinism, which means testability.

Same input, same bytes out.

A snapshot test over the full cross product of three kinds and three tones is nine assertions and runs in milliseconds.

Testing a model call means either mocking it, in which case you are testing your mock, or asserting fuzzy properties of real output and paying for the privilege on every CI run.

Offline and instant.

No spinner, no failure state, no retry logic, no timeout, no "the service is busy" copy to write and translate.

The letter updates as the user types because rendering it is a function call.

A real validator instead of an implicit one.

With a model you tend to send whatever you have and hope.

With a template you have to decide what is required, which forces the product question into the open: Note that the required set differs by kind.

A recommendation has no last working day.

A resignation has no subject.

A single prompt would have blurred those together and produced something plausible for a missing field, which is worse than refusing.

Localisation is mechanical.

Nine strings per tone, translated once, correct forever.

The same feature backed by a model needs the prompt tuned per language and the output checked per language by someone who reads it.

Where the approach genuinely loses I am not going to pretend this scales to everything.

It cannot say the specific thing.

The generated paragraphs are competent and generic, and generic is exactly the part of a reference letter that carries no weight.

A hiring manager skims "demonstrated consistent judgement" and stops at "she rewrote our billing reconciliation and the month-end close went from four days to one".

So the engine has a field, free text, dropped verbatim into the middle of the letter.

That is not a limitation I worked around, it is the correct division of labour.

The tool writes the scaffolding nobody reads.

The human writes the one sentence that does the work.

A model would have written a fluent guess at that sentence, and a fluent guess about a real person is precisely the thing you do not want in a reference.

It cannot rewrite arbitrary prose.

Paste in three rambling paragraphs and ask for them tightened, and templates have nothing to offer.

That is a genuine generation task and I would use a model for it.

Adding a kind costs a function.

A new letter type means new code, not a new prompt string.

For three kinds that is fine.

For thirty I would be rethinking it.

The heuristic I took away Reach for generation when the output space is large, variable, and you cannot enumerate the good answers.

Reach for templates when the output space is small, the shape is fixed, and being wrong is expensive.

Short formal documents sit squarely in the second category, and the industry keeps building them with the first tool because the first tool is what everyone is holding.

The engine described here runs the letter of recommendation template tool on the resume builder I maintain.

No account, no card, no model call, and the plain text output is free to copy.

It renders in whatever time a string concatenation takes, which is the entire point.

分享