LLM Fundamentals for Testers

A large language model (LLM) is trained on a very large amount of text to predict the next token, then trained further to follow instructions. An application sends it one request containing instructions, the conversation so far and, often, descriptions of tools it may use. The model writes its answer one token at a time, each chosen from probable continuations, until it stops or reaches a length limit.

Example: a store’s support assistant receives "Where is my order 5521?". The application sends the system prompt, the message and the definition of a get_order_status tool. The model looks nothing up itself: it returns a request to call the tool with the order number. The application runs the lookup, adds the result and sends a second request, and only then does the model write the answer the customer sees.

Why it matters

Each mechanism is a source of defects. Sampling makes the same input give different answers; the context window decides what the model can see; output limits cut answers off; a system prompt is text, not an enforced rule; and a tool call turns generated text into an action. A tester who does not know this either writes exact-match tests that fail at random or accepts "the AI is like that" and lets real defects through.

Knowing the fundamentals lets you predict where an LLM feature will break, design tests for those places and write bug reports a developer can reproduce. The model is still a component under test, and every QA fundamental still applies.

Tokens and the context window

A tokenizer splits all text into tokens: common words, pieces of longer words, fragments of numbers, punctuation. Tokenizers differ between model families, so the same text can give different counts on different models, and a sentence in Arabic or Hindi can take more tokens than in English. Limits and cost are counted in tokens, and response time grows with the tokens generated, so use the provider’s token counter or the usage the API reports instead of estimating from characters.

The context window is the maximum number of tokens the model can handle in one request, usually input and output together. Everything the model knows about the situation must fit in it: the system prompt, tool definitions, the conversation, retrieved documents, tool results and room for the answer. The model remembers nothing between requests; a chat seems to remember only because the earlier messages are sent again with each request, usually by the application.

When a conversation outgrows the window, the application trims or summarises it, or the request fails, and an instruction or order number from the start can be lost. The maximum output tokens is a separate limit: an answer that reaches it is cut off, often mid-sentence, and the response’s stop reason says so. Both limits are boundaries, so apply boundary value analysis to them, with long Arabic and Hindi input too; generated filler text measured with the String Length Counter helps you grow an input step by step.

Illustrative split (every tokenizer differs):
  "Where is my order 5521?"  ->  Where| is| my| order| 55|21|?   (7 tokens)

Context budget for one request (example model with a 16,000-token window)
  System prompt                            1,200
  Tool definitions (3 tools)                 900
  Conversation so far (18 messages)       11,500
  New customer message                       400
  Reserved for the answer (max output)     2,000
  Total                                   16,000   <- window full

Try it: String Length Counter Try it: Lorem Ipsum Generator

How the next token is chosen: temperature and sampling

At each step the model gives every token in its vocabulary a probability of coming next. Always taking the most probable one is greedy decoding; many applications sample instead, picking at random in proportion to the probabilities, so answers vary from run to run.

Temperature rescales those probabilities: below 1 it favours the most likely token, above 1 it gives unlikely tokens more chances. Top-p (nucleus sampling) keeps the smallest set of most likely tokens whose probabilities add up to at least p; top-k keeps the k most likely. Not every model accepts every parameter, so record the values actually sent.

In the example below the store’s policy is 30 days, yet a wrong number is chosen in about one run in four at temperature 1.0 and four in ten at 2.0, and a long answer makes hundreds of such choices. A low temperature reduces variation but in practice does not guarantee identical output. Test at the production settings, run important cases several times and compare the runs, for example with Text Diff.

Context: "... unused items can be returned within"   (illustrative probabilities;
assume these four candidates hold all the probability)

Next token       temperature 0.5   temperature 1.0   temperature 2.0
" 30"                      91.5%             70.0%             48.6%
" 14"                       7.5%             20.0%             26.0%
" 7"                        0.9%              7.0%             15.4%
" thirty"                   0.2%              3.0%             10.1%

Wrong number (14 or 7)      8.4%             27.0%             41.3%

Try it: Text Diff

System prompts and tool use

A request is a list of messages with roles. The system prompt (some providers say system or developer message) holds the application’s instructions: role, rules, scope, language and format. Models are trained to give it priority, but it is not enforced: it is text in the same context as the user’s input. The model can fail to follow it, user text or a document can try to override it (prompt injection, LLM01:2025 in the OWASP Top 10 for LLM Applications), and it can leak (System Prompt Leakage, LLM07:2025). Keep secrets out of it, and enforce permissions, prices and limits in code.

With tool use, or function calling, the application describes each tool with a name, a description and a JSON Schema for its arguments, a document that lists the fields, types and values the arguments may contain and is itself written in JSON, the plain-text format of named fields and values, such as {"order_id": "5521"}, that most web APIs exchange. The model never runs anything: it returns a structured request to call a tool with arguments it generated, and the application validates them, runs the tool and sends the result back in a new request. Treat generated arguments as untrusted input; they can be malformed, or valid but wrong, such as an order number the customer never gave. The JSON Schema Validator checks logged arguments against the schema. Tools that change things are where excessive agency, LLM06:2025, turns a wrong answer into a wrong action.

One LLM request, end to end: the application assembles the context, the model writes the answer one sampled token at a time, and after a tool call the application runs the tool and sends its result back in a new request. Conceptual model
  1. 1Assemble the requestSystem prompt, tool definitions, conversation so far, new message, parameters
  2. 2Split into tokensLimits and cost are counted in tokens

    Branch: Too long: the application trims, summarises or the request fails; test what is lostCaution

    Gate: Fits in the context window

  3. 3Score every possible next tokenFrom the model’s training and the tokens in the context
  4. 4Pick one tokenTemperature and top-p decide how far from the most likely token the choice may go

    Repeats: Repeat token by token until the model stops or reaches the output limit

  5. 5Return the outputText or a tool call, with a stop reason and token usage

    Branch: Stopped at the output limit: the answer is cut offCaution

  6. 6The application actsShows the answer, or validates the tool arguments and runs the tool

Back to step 1 Tool result added to the conversation: a new request

Simplified: providers differ in field names, limits and how they count tokens.

Request 1 (simplified and provider-neutral; field names differ by provider)
  model: example-model-v3    temperature: 0.7    max output tokens: 500
  system: "You are the support assistant of an online store. Answer in the
           customer's language. Hand every refund request to a human agent."
  tools: get_order_status(order_id: a string of 4 to 10 digits)
  messages: user: "Where is my order 5521?"

Response 1   stop reason: tool call
             get_order_status({"order_id": "5521"})
Application  validates the argument, runs the lookup, adds the result:
             {"order_id": "5521", "status": "shipped", "eta": "2026-10-08"}
Request 2    the same context plus the tool call and its result
Response 2   stop reason: end of turn
             "Your order 5521 has shipped and should arrive on 8 October."

Try it: JSON Schema Validator

Typical failure modes

Most LLM defects fall into a few families, each traceable to a mechanism above; naming the mechanism points to the fix and to the regression test.

  • Hallucination: fluent, confident content that is false or unsupported, such as an invented policy or order status (NIST AI 600-1 calls it confabulation). The model produces likely text, not checked facts, so test questions whose answer is not available and expect "I don’t know" or a hand-off.
  • Instruction drift: wrong language, wrong format or out-of-scope advice, often late in long conversations. Refusing a legitimate request is a failure too.
  • Inconsistency: the same input is approved in one run and refused in another; for a decision that must be stable, the flip itself is the defect.
  • Truncation and lost context: an answer cut off at the output limit with unclosed JSON, or a fact gone after history trimming.
  • Malformed structured output: invalid JSON, missing or extra fields, or prose around the JSON that breaks the parser.
  • Tool-use errors: a wrong or missing tool call, invented arguments, a tool error covered by an invented result, loops, or an action without the required confirmation.
  • Injection and leakage: text from a user, a page or a document overrides the instructions or extracts the system prompt; the LLM security testing guide covers benign tests for these.

Worked example: tracing one wrong answer to its cause

A customer complains that the support assistant promised his second order "tomorrow", although it had not shipped. Instead of reporting "the bot invents dates", the tester reads the logged request for that turn as it was actually sent.

It is message 31, "And the second order?", and the response has no tool call. The order number was given in message 3, but the application keeps only the last 20 messages, so the model had no value for the required order_id argument and, instead of asking, wrote a likely-sounding answer. At temperature 0.7, re-sending the logged request five times gave an invented date four times and a question for the order number once.

  • Bug report: model and prompt version, parameters, the trimmed request, the stop reason, "invented date in 4 of 5 runs", and the cause: trimming dropped the order number and the prompt has no rule for a missing one.
  • New tests: a 30-message conversation with the order number only in message 3, and a question with no order number at all. Expected in every run: the assistant looks the order up or asks for the number, and never states a date without a tool result.
  • Question for the team: should key facts such as order numbers survive history trimming?

Common mistakes

  • Assuming temperature 0 guarantees identical output, or testing with sampling settings that differ from production.
  • Judging a case from one run; a case that passes in 3 of 5 runs is a defect when the decision must be stable.
  • Treating the system prompt as a security control or a safe place for secrets and permission rules.
  • Testing only short conversations and inputs, so the context window, history trimming and the output limit are never reached, least of all in Arabic or Hindi.
  • Trusting tool arguments and structured output because the model produced them, instead of validating them as untrusted input.
  • Calling every wrong answer "hallucination" without finding the mechanism, which leaves both the fix and the regression test unclear.

The QA perspective

Before designing tests for an LLM feature, ask for its request anatomy: the model and whether its version is pinned, the sampling parameters and output limit, the system prompt version, the tools and which of them have side effects, and the history-trimming rule. Treat each item as configuration under test: any change to it is a regression risk. Agree with the product owner which behaviours must hold in every run, such as declining medical advice, and which may vary in wording.

Ask the developers to log every response with the model and prompt version, the parameters, the request as sent, the stop reason and the token usage, without personal data the logs do not need, so that your bug reports can say what to re-send and how often it fails. These fundamentals add to your QA toolkit: requirements review, risk analysis, boundary values and regression testing still do most of the work.

Practice exercise

Diagnose four logged failures of a booking assistant

Your task

A clinic’s appointment assistant books visits with two tools; its configuration and four logged problems are below. For each log, name the failure mode and the most likely mechanism, and write one test case whose expected result every acceptable reply must meet.

Then list what the bug reports must contain. Pen and paper is enough.

Material
Assistant: clinic appointments      Model: example-model-v3 (pinned)
Parameters: temperature 0.9, max output tokens 150
System prompt v5: "Reply in the patient's language. Offer free slots with
  find_slots(date). Book with book_appointment(slot_id) only after the patient
  confirms a slot. Never give medical advice."

Log 1  user: "Is 10:30 tomorrow free?"
       tool call: find_slots(date: "2026-11-12")
       tool result: [{"slot_id": "S-1201", "time": "09:00"},
                     {"slot_id": "S-1202", "time": "11:15"}]
       reply: "Yes, 10:30 tomorrow is free."
Log 2  user: "Book me with Dr Salem on Sunday."
       tool call: book_appointment(slot_id: "S-0912")  (no find_slots, no confirmation)
Log 3  "Can I eat before my blood test?" sent 5 times:
       3 replies refer the patient to the clinic, 2 replies give advice
Log 4  user writes in Arabic     stop reason: max output tokens (150 of 150 used)
       reply: {"message": "...a long confirmation in Arabic...", "slot": "11:15",
               "status": "confir
Steps
  1. Note every setting that could matter: parameters, limits and tool rules.
  2. For each log, name the failure mode and the mechanism.
  3. Write one test case per log: input, number of runs, expected result.
  4. List the facts every bug report needs.
Show the model answer
Model answer

Log 1, hallucination: the reply contradicts the tool result, which does not list 10:30. Test: ask for a time the result does not list, 5 runs. Expected in every run: "not free", and only times from the tool result (09:00, 11:15).

Log 2, tool-use error with excessive agency: no find_slots call, no confirmation, and S-0912 came from no tool result. Test: "Book me on Sunday", 5 runs. Expected in every run: slots from find_slots are offered and nothing is booked before the patient confirms. Add an application test: the code rejects book_appointment for a slot no find_slots call returned, because the prompt rule alone enforces nothing.

Log 3, inconsistency in a policy decision, made likelier by temperature 0.9; each reply that gives advice also breaks the "Never give medical advice" rule (instruction drift). It is a defect, not flakiness. Test: the question 10 times at the production settings. Expected: 10 of 10 replies refer the patient to the clinic. Ask why a booking assistant needs such a high temperature.

Log 4, truncation: the long Arabic message used all 150 output tokens, so the parser receives unclosed JSON; 150 is too few for this reply. Tests: the longest expected reply, in Arabic too, 5 runs: every run ends with a normal stop reason and passes its JSON Schema; and when the stop reason is the output limit, the application shows an error or retries instead of parsing.

Every bug report needs the model and prompt version (v5), the parameters, the full request as sent, the tool calls and results, the stop reason, the token usage, and how many runs out of how many failed.

Frequently asked questions

Is a token the same as a word?

No. A token is a unit of the model’s tokenizer: a common word, part of a longer word, a punctuation mark or a space attached to a word. How many tokens a text takes depends on the tokenizer and on the language. For an exact count, use the provider’s token counter or the token usage the API returns with each response.

If I set the temperature to 0, can I compare outputs exactly?

Not safely. A low temperature reduces variation but in practice does not guarantee identical output across calls, and production may use a different setting. Compare exactly only what the output format fixes, such as a label or a JSON field, and check properties for the rest, as the LLM testing guide explains.

Does the model learn from my test conversations?

Not during the request: the model’s weights, the numbers it learned in training, do not change while it answers, and it uses only what is in the context. Whether a provider may use the data sent to it for later training depends on its terms and your contract, so check them, and never put real personal data into test prompts.

Do I need to understand how the model works inside?

No. A tester needs the behaviour at the interface: what goes into the context, how output is generated and limited, and how tools are called. Knowing the internal architecture can help, but it is not required to test an LLM application well.

References

Practise with Mutqan tools

Measure string length in characters, code points, UTF-8 bytes and UTF-16 units.

Formatting

Generate placeholder text as paragraphs, sentences or words — Latin, Arabic or Hindi.

Utilities

Compare two texts line by line or word by word and highlight differences.

Formatting

Validate a JSON document against a JSON Schema (draft-07 / 2019-09 / 2020-12 core keywords).

JSON & Data

Go deeper

Your progress on this topic

Part of the QA Roadmap