Most AI features do not fail inside the model. They fail at the seam between model output and your code. That seam is where free tiers become production incidents.
I keep meeting the same four shapes there. Here is the catalog: symptom, root cause, replacement. The code below is a harness, not a benchmark.
Why the boundary, not the model
Model news moves weekly. Integration patterns barely move. Free model access made calls cheap; it did not make output shapes stable.
So the durable skill is not prompt poetry. It is contract discipline around an unreliable producer. Ask yourself one question before shipping any AI feature. What does my code do with a payload it did not expect?
Anti-pattern 1: Stream first, validate never
Symptom: users see half an answer, then a red toast. Your logs show duplicate calls for one user action.
Root cause: you wired the token stream straight into the UI. Validation happens after rendering, so a broken payload already reached a human.
Replacement pattern: buffer, validate, commit.
- Stream tokens into a server-side shadow buffer.
- On stream close, parse and check the schema.
- Emit exactly one of two events:
resultorrejected. - Keep the raw buffer for a day so you can replay it.
Streaming status is fine. Streaming unvalidated content is not.
Anti-pattern 2: The prompt is a string literal
Symptom: a refactor changes behavior and nobody notices. Someone "cleans up" whitespace, and your JSON rate drops.
Root cause: the prompt lives inline with application code. No fixture, no hash, no diff review.
Replacement pattern: prompts as versioned files with a pinned hash.
- Store each prompt in
prompts/<name>.txt. - Record
sha256(prompt)in every stored result row. - Add one golden fixture per prompt to CI.
- Fail the build when the hash changes without a fixture update.
A prompt change is a schema change. Treat it like one.
Anti-pattern 3: The anonymous fallback
Symptom: two runs of the same input give different answers, and your table cannot explain why. Support tickets become archaeology.
Root cause: you added a fallback path and saved only the text. The row does not name which model, version, or prompt produced it.
Replacement pattern: stamp provenance into the artifact, not the log file.
Add three columns to the table that stores results: model_id, prompt_sha, attempt. Then make one query your daily check.
SELECT model_id, COUNT(*)
FROM results
WHERE created_at > now() - interval '1 day'
GROUP BY 1 ORDER BY 2 DESC;
If an unexpected model_id shows up, you learned something. If the column is missing, you learned nothing, forever.
Anti-pattern 4: No capability contract
Symptom: the demo works on the model you tested. A different model returns prose, a markdown fence, or a truncated object.
Root cause: the expected output shape lives in your head and in a comment. Nothing executable enforces it.
Replacement pattern: explicit schema plus exactly one repair attempt.
- Validate required keys and types in code, not in prose.
- On failure, send one repair prompt with the validation error attached.
- Cap repairs at one. Two repairs usually means your contract is wrong.
- Return a typed error to the caller instead of a string guess.
A harness you can run in five minutes
I want a test loop that fails for the right reasons. This is the setup where MonkeyCode's free model access and free server option are relevant. The operator states free model access, currently advertised at 10 million tokens, plus a free server option. Verify the current numbers and terms in the console before you rely on them.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
First, a mock model with no dependencies beyond the Python 3.11 standard library. It returns invalid JSON on the first call, then valid JSON.
# mock_model.py
import json, os, threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
STATE = {"calls": 0}
LOCK = threading.Lock()
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
self.rfile.read(int(self.headers.get("Content-Length", 0)))
with LOCK:
STATE["calls"] += 1
n = STATE["calls"]
if os.environ.get("MOCK_MODE", "flaky") == "flaky" and n == 1:
content = '{"answer": "yes", "confidence": ' # truncated
else:
content = json.dumps({"answer": "yes", "confidence": 0.9})
out = json.dumps({"id": f"mock-{n}",
"choices": [{"message": {"content": content}}]}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(out)))
self.end_headers()
self.wfile.write(out)
def log_message(self, *args):
pass
if __name__ == "__main__":
ThreadingHTTPServer(("127.0.0.1", int(os.environ.get("PORT", "8765"))),
Handler).serve_forever()
Second, the client with a budget ledger, one validator, and one repair.
# contract.py
import json, os, urllib.request
BASE = os.environ.get("MODEL_BASE", "http://127.0.0.1:8765")
BUDGET = int(os.environ.get("CALL_BUDGET", "3"))
LEDGER = {"spent": 0}
REQUIRED = {"answer": str, "confidence": (int, float)}
def validate(payload):
if not isinstance(payload, dict):
return ["not an object"]
problems = []
for key, typ in REQUIRED.items():
if key not in payload:
problems.append(f"missing {key}")
elif not isinstance(payload[key], typ):
problems.append(f"{key} has wrong type")
c = payload.get("confidence")
if isinstance(c, (int, float)) and not 0 <= c <= 1:
problems.append("confidence out of range")
return problems
def call(prompt, hint=""):
if LEDGER["spent"] >= BUDGET:
raise RuntimeError("budget exhausted")
LEDGER["spent"] += 1
body = json.dumps({"prompt": prompt + hint}).encode()
req = urllib.request.Request(f"{BASE}/v1/chat", data=body,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read())["choices"][0]["message"]["content"]
def get_answer(prompt):
raw = call(prompt)
try:
parsed = json.loads(raw)
except json.JSONDecodeError:
parsed = None
if not validate(parsed):
return parsed, "ok"
raw = call(prompt, "\nReturn JSON only. No prose.")
try:
parsed = json.loads(raw)
except json.JSONDecodeError:
return None, "unparseable after one repair"
problems = validate(parsed)
return (parsed, "ok") if not problems else (None, "; ".join(problems))
Third, run it. Expected output is the repaired object, with two calls spent.
python -m unittest test_contract -v
python mock_model.py &
python -c "import contract; print(contract.get_answer('is 2+2 4?')); print(contract.LEDGER)"
# ({'answer': 'yes', 'confidence': 0.9}, 'ok')
# {'spent': 2}
When you move off the mock, change one variable: export MODEL_BASE=<your endpoint>. If you take the free server option, confirm the runtime you actually get before you plan around it.
What the harness proves, and what it does not
It proves your parser, your validator, and your repair path behave. That is the part you control. It does not measure model quality, latency, or cost per answer.
Three honest limits:
- One repair masks a broken contract. Fix the prompt instead.
- A local mock cannot reproduce remote rate limits. Test those separately.
- The ledger counts calls, not tokens. Do not confuse the two.
Decision table
| Situation | Free model access | Paid tier |
|---|---|---|
| Prototype with strict schema checks | Good fit | Overkill |
| Load test at production concurrency | No | Yes |
| Latency-sensitive user path | Measure first | Usually |
| Regulated data | Check terms first | Check terms first |
| Batch backfills with retries | Fine with a ledger | Fine |
Who should skip this
Skip this approach if you need a contractual SLA. Skip it if your output must be identical across runs. Skip it if you cannot inspect the data handling terms for the region you serve.
Free access is a development convenience. It is not a capacity plan.
The one habit that survives
Write the validator before the prompt. Then ask what your code does with garbage. If the answer is "it shows the user", you found your next anti-pattern.
If you want a sandbox for this harness, MonkeyCode's free model access is a low-risk place to start. Keep the mock in CI, and keep the validation strict.
Top comments (0)