Open Weight Models · Lesson 5
Use the local Ollama API
Send chat requests from your terminal and Python, then read and validate the response.
What you will learn
- Send a non-streaming chat request to your local Ollama service.
- Extract an answer from JSON and distinguish an API failure from an inaccurate answer.
- Use a Python client with a timeout and explicit conversation history.
From a chat window to a request
An API lets your own program ask the model a question. For this lesson, Ollama must already be running and gemma3:1b must be available from lesson 3. The program runs on the same computer as the service; you do not need an API key for this local endpoint.
Send JSON to http://localhost:11434/api/chat. The model field selects gemma3:1b, and messages contains role/content objects. Set stream to false to receive one complete JSON response instead of processing a stream. The answer is in message.content, not in a top-level response field.
A successful HTTP request only establishes that the service answered. Your application must still check the answer against its task. In our example, the supplied note says the workshop starts at 09:30; a fluent answer giving 10:00 is a content failure even if the request succeeded.
Try a request in your terminal
Choose the example for your shell. Both send the same short, fictional note and allow up to 120 seconds for the request. A first response may take longer while the model loads. The curl example prints the full JSON; PowerShell extracts the answer from the parsed response.
Read the returned text and compare its time with the note. Keep a copy of the actual response for your exercise record. The snippets below are requests to run, not a claim about an answer your model has already produced.
macOS or Linux: POSIX shell with curl · bash
curl --fail --show-error --max-time 120 \
http://localhost:11434/api/chat \
-H 'Content-Type: application/json' \
-d '{
"model": "gemma3:1b",
"stream": false,
"messages": [
{"role": "user", "content": "Note: the workshop starts at 09:30. Using only this note, what time does it start?"}
]
}'Windows: PowerShell · powershell
$payload = @{
model = "gemma3:1b"
stream = $false
messages = @(
@{
role = "user"
content = "Note: the workshop starts at 09:30. Using only this note, what time does it start?"
}
)
}
$request = @{
Uri = "http://localhost:11434/api/chat"
Method = "Post"
ContentType = "application/json"
Body = ($payload | ConvertTo-Json -Depth 5)
TimeoutSec = 120
ErrorAction = "Stop"
}
try {
$result = Invoke-RestMethod @request
$result.message.content
} catch {
Write-Error "Local Ollama request failed: $_"
}Build a small Python client
Save the following as local_chat.py in a project folder. It uses Python 3's standard library, so there is no package to install. Run python3 local_chat.py on macOS/Linux, or py -3 local_chat.py on Windows if you use the Python launcher.
The chat function returns a validated assistant message. It gives separate, readable failures for HTTP errors, connection problems, timeouts, and unexpected response data. The timeout limits waiting on network operations; it is not a promise about inference speed. This client deliberately does not retry automatically.
local_chat.py · python
import json
import sys
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
URL = "http://localhost:11434/api/chat"
def chat(messages, model="gemma3:1b"):
payload = {"model": model, "messages": messages, "stream": False}
request = Request(
URL,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urlopen(request, timeout=120) as response:
result = json.loads(response.read().decode("utf-8"))
except HTTPError as error:
detail = error.read(2000).decode("utf-8", errors="replace")
raise RuntimeError(f"HTTP {error.code}: {detail}") from error
except URLError as error:
raise RuntimeError(
f"Cannot reach local Ollama: {error.reason}. Check the service."
) from error
except TimeoutError as error:
raise RuntimeError("Ollama did not respond before the timeout.") from error
except (OSError, ValueError, UnicodeError) as error:
raise RuntimeError(f"Could not read an Ollama response: {error}") from error
if not isinstance(result, dict):
raise RuntimeError("Expected a JSON object from Ollama.")
if result.get("error"):
raise RuntimeError(f"Ollama reported: {result['error']}")
message = result.get("message")
if not isinstance(message, dict):
raise RuntimeError("The response has no message object.")
content = message.get("content")
if message.get("role") != "assistant" or not isinstance(content, str):
raise RuntimeError("The response has no valid assistant text.")
if not content.strip():
raise RuntimeError("Ollama returned an empty answer.")
return {"role": "assistant", "content": content}
def main():
history = [
{"role": "system", "content": "Use the supplied notes. Do not invent missing facts."},
{"role": "user", "content": "Note: the workshop starts at 09:30. When does it start?"},
]
try:
first_answer = chat(history)
print("First answer:", first_answer["content"])
history.append(first_answer)
history.append({"role": "user", "content": "Write that time in words."})
second_answer = chat(history)
print("Follow-up:", second_answer["content"])
except RuntimeError as error:
print(f"Request failed: {error}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())Your program owns the conversation
Treat each chat request as stateless: the caller supplies the conversation it wants the model to use. The second request above includes the original note, the first assistant answer, and the follow-up. Keeping a model loaded in memory does not give a new request your application's previous messages.
For independent tests, start a new messages list. For a conversation, preserve relevant turns in order and keep the history within your context budget. Do not quietly mix one learner's messages with another's. If you remove older turns, decide which source notes the next answer still needs.
A 400 response can indicate malformed input; a 404 can indicate an unavailable model. Read the error, check the exact model name, and correct the cause. A timeout is not evidence of a bad answer, and an invented fact is not fixed by increasing the timeout.
Keep the example local
Use the downloaded gemma3:1b model and the loopback address shown here. A local Ollama endpoint can also route requests to cloud models when configured to do so; the address alone does not prove where inference happens. Lesson 7 explains the distinction.
Record a two-turn API conversation
Check that your existing Ollama service is available and gemma3:1b appears in ollama list.
Run the terminal example for your operating system. Identify the model field in your request and message.content in the response.
Save and run local_chat.py. Record both actual answers and compare them with the 09:30 note.
Inspect the code constructing the second request. Write down which four messages it sends and why the original note is still included.
Temporarily use a made-up model name in one call, such as course-missing-model. Record the reported failure, then restore gemma3:1b; there is no need to download another model.
You have completed this task when…
- A local non-streaming request completes, and you can locate the assistant text within the JSON structure.
- The Python client prints two answers or reports a clear, investigated failure rather than claiming success.
- Your evidence explains caller-managed history and distinguishes an HTTP error from an answer that fails the fact check.
Official documentation
Use these references for platform requirements, current options, and further detail.