# Understand a model response before acting

Original LockedIn Labs practice · checked 21 October 2026.
Companion: the AI platform lesson in each Google Cloud, AWS and Azure infrastructure course.
Read online: [Model response lab](/training/guides/cloud-responses).

Allow 45–60 minutes. Begin with the service-call explanation, compare the three
providers, then run a small local program and change one response. No cloud account
or model key is needed for the local exercise.

## Start with something familiar

Your application already calls services: a database query, a payment request,
an email API. A managed model request is another service call. The difference is
that generated output needs its own checks; a successful HTTP response is not
proof that the answer is correct or that a proposed action has happened.

A **model** is trained software that produces predictions or generated output
from inputs. **Inference** means running it on an input. An **endpoint** is the
service address and API route. A **software development kit (SDK)** is a library
your code uses to make calls. A **deployment** binds a model to a serving
configuration; the exact resource and naming rules depend on the provider.
An **agent** adds a loop that can use model results, authorized tools and
observations to work toward a task. An inference call is one part of that loop.

Draw this path and label who operates each box:

    Application and its SDK → Service endpoint → Selected model
    Application checks      ← Response content + completion state

The SDK runs with your application. The model computation in these examples runs
in a provider service. Installing a package does not create cloud resources, grant
access or download the hosted model's weights. A managed agent runtime can host
the application's loop, but it remains a different responsibility from inference.

## 1. Name the target precisely

| Example API | What selects the target? | What else must you verify? |
| --- | --- | --- |
| Google Cloud Gemini `generateContent` | Publisher model resource under a project and location | Exact model ID, supported API/location, workload identity and quota |
| Amazon Bedrock `Converse` | `modelId`, which can identify a model or an inference profile | Model access, invocation permission, profile routing destinations and API support |
| Azure OpenAI `Responses` | Your deployment name in the `model` field | Deployment's actual model/version, API support, access, capacity and processing geography |

These are selected APIs, not interchangeable interfaces for every model sold by
these vendors. In particular, the Azure example is not a universal Claude API.
Do not infer processing residency from a nearby VM or the client's endpoint name.

For each provider, fill in this proposed deployment card using current official
documentation. Never include a key, access token or real customer record.

- Platform, API and SDK/version:
- Owning project/account/subscription and resource:
- Endpoint address and target identifier:
- Actual model/version behind that target:
- Workload identity and required access:
- Processing location/routing and relevant data terms:
- Input constraints and output allowance:
- Timeout, bounded retry/continuation policy and cost owner:
- How the app validates evidence and proposed actions:
- What you observed, versus what you have only designed:

Explain why a portal demonstration by its creator is not an acceptance test for
the application's own identity. Explain why a catalog listing is not an access
grant or a capacity reservation.

## 2. Compare three successful HTTP responses

Everything below is synthetic. These are selected fields, not full API responses.
The imaginary task summarizes ticket T-104. No cloud account is needed and no
tools or models are executed. The function case comes from a separate request
that declared a custom client-side `get_ticket` function.

### Google Cloud

```json
{"candidates":[{"finishReason":"STOP","content":{"role":"model","parts":[{"text":"Ticket T-104 is waiting for a review."}]}}]}
```

```json
{"candidates":[{"finishReason":"MAX_TOKENS","content":{"role":"model","parts":[{"text":"Ticket T-104 is waiting for"}]}}]}
```

```json
{"candidates":[{"finishReason":"STOP","content":{"role":"model","parts":[{"functionCall":{"name":"get_ticket","args":{"ticket_id":"T-104"}}}]}}]}
```

### AWS

```json
{"stopReason":"end_turn","output":{"message":{"role":"assistant","content":[{"text":"Ticket T-104 is waiting for a review."}]}}}
```

```json
{"stopReason":"max_tokens","output":{"message":{"role":"assistant","content":[{"text":"Ticket T-104 is waiting for"}]}}}
```

```json
{"stopReason":"tool_use","output":{"message":{"role":"assistant","content":[{"toolUse":{"toolUseId":"synthetic-call-1","name":"get_ticket","input":{"ticket_id":"T-104"}}}]}}}
```

### Azure OpenAI

```json
{"status":"completed","output":[{"type":"message","role":"assistant","content":[{"type":"output_text","text":"Ticket T-104 is waiting for a review."}]}]}
```

```json
{"status":"incomplete","incomplete_details":{"reason":"max_output_tokens"},"output":[{"type":"message","role":"assistant","content":[{"type":"output_text","text":"Ticket T-104 is waiting for"}]}]}
```

```json
{"status":"completed","output":[{"type":"function_call","call_id":"synthetic-call-1","name":"get_ticket","arguments":"{\"ticket_id\":\"T-104\"}"}]}
```

For each response, record:

1. Which field says why generation finished?
2. Which field says what kind of content was returned?
3. Is the next step to validate text, hold incomplete work, or authorize a function?
4. What does this response fail to prove?

Then consider a fourth capture: HTTP 200 with body `{}`. Do not fill in missing
fields from memory. State what you would withhold, which safe diagnostic you would
retain, and what you would inspect next.

## 3. Run the response adapter

An **adapter** is a small piece of code that translates one interface into another.
Think of three shipping companies sending different tracking messages. Your app
still needs one clear decision about a delivery. Here the vendor field names differ,
but your application needs to decide what to do with the result.

This adapter runs in your Python process. It is not an agent, model or cloud SDK.
It reads saved, fictional JSON fragments and produces one of four decisions:

| Evidence in this exercise | Decision | What the application still owes the user |
| --- | --- | --- |
| Recognized text and normal completion | `validate` | Check the text against permitted evidence and the output contract |
| Recognized text and output allowance exhausted | `incomplete` | Keep it unfinished and choose a bounded recovery path |
| Recognized custom function proposal and matching completion state | `authorize` | Check the trusted caller's permission before any execution |
| Missing, mixed, contradictory or unsupported fields | `investigate` | Hold the result and inspect the contract instead of inventing success |

**Authorize is a next step, not an authorization grant.** The lab cannot identify
a real caller or check their ticket access. It never invokes the function.

Download the [Python response lab](/templates/cloud-model-response-lab.py) into
a new training folder. Read it before running. Use Python 3.11 or later; it uses
only the standard library and does not install packages, read credentials, make
network calls or write files. The `python3` command may be `python` or `py -3` in
your environment.

First predict the decision for Google Cloud's function proposal. Then run:

```bash
python3 --version
python3 cloud-model-response-lab.py --provider gcp --case tool --predict authorize
```

The terminal shows the JSON excerpt, the observed `finishReason=STOP; content=tool`,
the `authorize` decision and the next check. The program actually parses the JSON
and applies local rules; it does not ask a model to grade your answer. Exit code
0 means the prediction matched, not that an operation was approved or executed.
A wrong prediction returns 1 with an explanation; use it to revise your reasoning.

Now test the same idea with different vendor fields:

```bash
python3 cloud-model-response-lab.py --provider aws --case limit --predict incomplete
python3 cloud-model-response-lab.py --provider azure --case tool --predict authorize
python3 cloud-model-response-lab.py --provider azure --case mixed --predict investigate
python3 cloud-model-response-lab.py --check
```

Expect `stopReason=max_tokens` for the AWS case and `status=completed; content=tool`
for Azure's proposal. The mixed case is held because this small adapter deliberately
supports one item at a time. That is our teaching policy, not a claim that providers
cannot return multiple items. `--check` tests all three providers and boundary
mutations, including complete-looking text that still has an output-limit finish.

Every provider has `text`, `limit`, `tool`, `unknown`, `mixed`, `bad-arguments`,
`unknown-stop` and `malformed` cases. Use `--help` to inspect the controls. Try
`--case bad-arguments`: a numeric ticket ID does not satisfy this exercise's
string-valued function contract. Well-formed JSON alone is not enough.

## 4. Change one response and explain the difference

Copy one JSON excerpt from section 2 into a local file named `response.json`.
Keep synthetic data only. Before editing it, write your predicted decision and
the one field that supports it. Run your own excerpt through the same adapter:

```bash
python3 cloud-model-response-lab.py --provider gcp --stdin < response.json
```

Use the matching provider name for your excerpt. The `<` passes the file to the
program's standard input; the program does not open paths or execute the contents.
It accepts at most 8,192 characters. If your shell does not support this syntax,
use the supplied cases or edit a copy of `specimens()` in the downloaded script.
No cloud SDK or credentials are required. Do not use `eval` or execute model-supplied
code. A function name in JSON is data, not permission to invoke arbitrary code.

Try these mutations and explain the changed outcome:

- Replace the normal finish with the token-limit finish but retain complete-looking text.
  Expected: `incomplete`; appearance does not override completion evidence.
- Keep `STOP` or `completed`, but replace text with the custom function item.
  Expected: `authorize`; normal completion does not mean final prose.
- Remove the content entirely, or replace its type with an unknown value.
  Expected: `investigate`; no invented answer.
- Mix a function proposal and text in one result.
  Do not silently publish the text or drop the proposal. For this deliberately
  narrow adapter, classify the mixed shape as `investigate` until you design an
  explicit multi-item policy.
- Supply malformed JSON. Your error path must preserve the failure without
  trying to repair or execute it automatically.

The adapter accepts exact selected excerpt shapes, including a single candidate
and content item. Extra fields, refusals, filters, unfamiliar finish states and
multiple items deliberately go to `investigate`. These may be valid provider
responses; they are outside this small lab's contract. Duplicate JSON keys,
non-standard constants and invalid JSON also stay explicit failures. Do not use
this exercise as a complete production response decoder.

Retain the fixture, expected decision, observed decision and one explanation.
Ask another learner to add a counterexample. Passing these local cases proves
only the behavior of your adapter for the tested shapes, not vendor access or
production correctness. Delete the disposable directory when finished; there
are no cloud resources to clean up.

## 5. Compare with the reasoning

Normal text goes to evidence and output checks before display. An output-limit
result remains incomplete; review input size, allowance and bounded recovery.
A client-side function call is a proposal. Validate its name and arguments, bind
the trusted caller and authorize the requested record before any execution. Send
back only a permitted result if the workflow continues. An unknown response needs
an explicit failure path, not an optimistic default.

In a real client, add documented handling for refusals, safety filters, service
errors, streaming, timeouts, retries and multiple content items. Distinguish your
client-side functions from provider-hosted tools. Do not claim this small exercise
covers every provider behavior or grants a certification.

To deepen the exercise, add one clearly specified case to your own adapter and a
test that would fail if you silently dropped part of the response. For a real client,
choose the current provider SDK and documented response types, then implement the
full content, completion and error policy for that API. Keep permission checks in
the application; a model cannot grant them.

Continue with the guided AI-platform lesson for [Google Cloud](/app/academy?course=gcp-infrastructure&lesson=ai-platform&view=focus),
[AWS](/app/academy?course=aws-infrastructure&lesson=ai-platform&view=focus) or
[Azure](/app/academy?course=azure-infrastructure&lesson=ai-platform&view=focus).
Those lessons require training access; this lab remains free.

## Official references

- [Google Cloud GenerateContent](https://docs.cloud.google.com/gemini-enterprise-agent-platform/reference/models/inference)
- [Google Cloud response and finish reasons](https://docs.cloud.google.com/gemini-enterprise-agent-platform/reference/rest/v1/GenerateContentResponse)
- [Google Cloud function calling](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/tools/function-calling)
- [AWS Converse API](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html)
- [AWS inference profiles](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles.html)
- [Microsoft Foundry endpoints](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/concepts/endpoints)
- [Azure OpenAI Responses](https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/responses)
- [Microsoft Responses reference](https://learn.microsoft.com/en-us/rest/api/microsoft-foundry/azureopenai/responses?view=rest-microsoft-foundry-v1-preview)
