← Course overview

Lesson 5 of 8 · 30 minutes

In this lesson

Open Weight Models · Windows · Lesson 5

Use the local Ollama API

Send chat requests from your terminal and Python, then read and validate the response.

Windows version. Use PowerShell for terminal commands. The exercises and setup instructions below are tailored to this version.

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.

Send a request with PowerShell

Use Invoke-RestMethod in PowerShell. The hashtable becomes JSON through ConvertTo-Json; the command sends it to your local chat endpoint and parses the JSON response. The final expression extracts message.content. The request allows up to 120 seconds and reports a caught connection or HTTP failure.

The supplied workshop note is fictional. Check whether the actual response preserves 09:30. This example uses native PowerShell syntax rather than shell-specific curl quoting.

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

Return to CyberCorps-Ollama using the first block, then open local_chat.py in Notepad. Paste the complete Python example below. Use Save As with file type All files, the exact name local_chat.py, and UTF-8 encoding. Confirm that the file is not named local_chat.py.txt.

Run the final PowerShell block after saving. If Lesson 2 established python as your working Python 3 command, replace py -3 with python. No third-party package or virtual-environment activation is required by this standard-library example.

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.

PowerShell: open the client file in Notepad · powershell

$courseFolder = Join-Path $env:USERPROFILE "CyberCorps-Ollama"
Set-Location $courseFolder
notepad .\local_chat.py

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())

PowerShell: verify the filename and run · powershell

Get-Item .\local_chat.py | Select-Object Name, Length
py -3 .\local_chat.py

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.

Put it into practice

Record a two-turn API conversation

  1. Open the native Ollama app and check gemma3:1b appears in ollama list from PowerShell.

  2. Run the Invoke-RestMethod example. Identify model in the request and message.content in the parsed response; check the answer against 09:30.

  3. Save local_chat.py in CyberCorps-Ollama as UTF-8 plain text with the exact .py extension. Run py -3 .\local_chat.py and preserve both answers.

  4. Explain which four messages the Python code sends on its second request and why the original note is still present.

  5. Change one test call to the made-up model name course-missing-model, record the actual error, and restore gemma3:1b. Do not download another model to make the error disappear.

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.

Check your understanding

Choose an answer for each question, then check your reasoning.

1. Where is the assistant's text in a non-streaming /api/chat response?
2. A follow-up says 'explain that again'. What should your client do?

Answer each question to continue.