Live
Tangible Certification: Turning a Kubernetes Badge into a Gold NecklaceGoogle Data Cloud GA updates: agent‑centric tooling, hybrid Spanner, and expanded Lakehouse catalogCloudflare folds Deno runtime into Workers: practical impact on serverless deploymentsManaging Copilot Code Review Costs and License Scope with New Org‑Level ControlsEdge-native multimodal decisions: Clef-omni adds audio/video to Workers AI, Clef-flash gets cheaper, and Clef inference speeds upDetecting Silent Agent Failures: Verifying Outcomes Instead of Run StatusLocal Sandboxing and Multi‑Account Support Arrive in GitHub Copilot CLI, App, and VS CodeKubernetes shifts to cgroup v2: practical steps for engineersTangible Certification: Turning a Kubernetes Badge into a Gold NecklaceGoogle Data Cloud GA updates: agent‑centric tooling, hybrid Spanner, and expanded Lakehouse catalogCloudflare folds Deno runtime into Workers: practical impact on serverless deploymentsManaging Copilot Code Review Costs and License Scope with New Org‑Level ControlsEdge-native multimodal decisions: Clef-omni adds audio/video to Workers AI, Clef-flash gets cheaper, and Clef inference speeds upDetecting Silent Agent Failures: Verifying Outcomes Instead of Run StatusLocal Sandboxing and Multi‑Account Support Arrive in GitHub Copilot CLI, App, and VS CodeKubernetes shifts to cgroup v2: practical steps for engineers
AI Engineering

OpenAI API Tutorial: GPT-4o, Function Calling and Streaming

Overview

This tutorial walks you through integrating the OpenAI API with Python, covering three core capabilities: chatting with GPT-4o, using Function Calling to connect the model to external tools, and implementing Streaming for real-time token output. These are foundational patterns used in production AI applications and are directly relevant to concepts tested on the AWS Certified AI Practitioner (AIF-C01) exam.

Prerequisites

  • Python 3.9+ installed locally
  • An OpenAI account with API access (platform.openai.com)
  • Basic Python knowledge (functions, dictionaries, loops)
  • Familiarity with REST API concepts

Why This Matters for AIF-C01

The AIF-C01 exam tests your understanding of Foundation Models (FMs), prompt engineering, and how AI services are integrated into applications. Understanding how LLM APIs work — including tool use and output streaming — gives you practical context for exam scenarios involving Amazon Bedrock, Agents for Bedrock, and responsible AI patterns. The concepts here map directly to AWS equivalents you will encounter on the exam.

Getting Started

Install the official OpenAI Python SDK and set up your environment. Never hardcode API keys — use environment variables.

# Install the SDK
# pip install openai

import os
from openai import OpenAI

# Load API key from environment variable
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))

# Verify connectivity with a simple completion
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": "You are a helpful cloud infrastructure assistant."},
        {"role": "user", "content": "What is the difference between IaaS and PaaS?"}
    ],
    max_tokens=300
)

print(response.choices[0].message.content)

Set your API key in your shell before running any script:

# On Linux/macOS:
# export OPENAI_API_KEY="sk-proj-your-key-here"

# On Windows PowerShell:
# $env:OPENAI_API_KEY = "sk-proj-your-key-here"

Common pitfall: If you instantiate OpenAI() without passing api_key, the SDK automatically reads OPENAI_API_KEY from the environment. This is the recommended pattern for production code.

Function Calling with GPT-4o

Function Calling (also called Tool Use) allows the model to request execution of a function you define. The model does not run code — it returns a structured JSON payload telling your application which function to call and with what arguments. Your code executes the function and returns the result back to the model.

This maps directly to how Amazon Bedrock Agents work: the agent identifies which action group to invoke, your Lambda function runs, and the result is fed back into the agent's reasoning loop.

import json
import os
from openai import OpenAI

client = OpenAI()

# Step 1: Define the tool schema
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_ec2_instance_status",
            "description": "Retrieves the current status of an EC2 instance by instance ID.",
            "parameters": {
                "type": "object",
                "properties": {
                    "instance_id": {
                        "type": "string",
                        "description": "The EC2 instance ID, e.g. i-0abcd1234ef567890"
                    },
                    "region": {
                        "type": "string",
                        "description": "AWS region, e.g. us-east-1"
                    }
                },
                "required": ["instance_id", "region"]
            }
        }
    }
]

# Step 2: Simulated tool implementation
def get_ec2_instance_status(instance_id: str, region: str) -> dict:
    # In production, this would call boto3
    mock_data = {
        "i-0abcd1234ef567890": {"state": "running", "type": "t3.medium", "az": "us-east-1a"}
    }
    return mock_data.get(instance_id, {"state": "not-found"})

# Step 3: First API call — model decides whether to use the tool
messages = [
    {"role": "user", "content": "What is the status of EC2 instance i-0abcd1234ef567890 in us-east-1?"}
]

response = client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools,
    tool_choice="auto"  # Let the model decide
)

assistant_message = response.choices[0].message

# Step 4: Check if the model requested a tool call
if assistant_message.tool_calls:
    tool_call = assistant_message.tool_calls[0]
    func_name = tool_call.function.name
    func_args = json.loads(tool_call.function.arguments)

    print(f"Model requested: {func_name}({func_args})")

    # Step 5: Execute the function
    result = get_ec2_instance_status(**func_args)

    # Step 6: Append results and call the model again
    messages.append(assistant_message)
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": json.dumps(result)
    })

    final_response = client.chat.completions.create(
        model="gpt-4o",
        messages=messages
    )

    print(final_response.choices[0].message.content)

Common Pitfalls with Function Calling

  • Missing required fields: If your function schema marks a parameter as required but the user prompt does not supply enough context, the model may hallucinate values. Validate all arguments before calling real APIs.
  • Not appending the assistant message: You must include the assistant's tool_call message in the conversation history before appending the tool result, or the API will return a validation error.
  • Assuming the model always calls a tool: With tool_choice="auto", the model may respond directly without invoking any tool. Always check assistant_message.tool_calls before processing.

Streaming Responses

Streaming allows your application to display tokens as they are generated rather than waiting for the full response. This dramatically improves perceived performance in chatbots and CLI tools. Under the hood, the API returns Server-Sent Events (SSE), and the SDK handles chunking for you.

import os
from openai import OpenAI

client = OpenAI()

print("Streaming response from GPT-4o:\n")

# Pass stream=True to enable streaming
stream = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": "You are a concise AWS solutions architect."},
        {"role": "user", "content": "Explain the AWS shared responsibility model in 3 bullet points."}
    ],
    max_tokens=300,
    stream=True
)

# Iterate over chunks as they arrive
full_response = ""
for chunk in stream:
    delta = chunk.choices[0].delta
    if delta.content:  # content can be None in the first/last chunk
        print(delta.content, end="", flush=True)
        full_response += delta.content

print("\n\n--- Stream complete ---")
print(f"Total characters received: {len(full_response)}")

Streaming with Function Calls

You can combine streaming with tool use. When the model decides to call a function, the tool_calls data arrives across multiple chunks and must be assembled before execution:

import json
import os
from openai import OpenAI

client = OpenAI()

stream = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Get the status of i-0abcd1234ef567890 in us-east-1"}],
    tools=tools,  # reuse tools list from previous section
    tool_choice="auto",
    stream=True
)

collected_tool_calls = {}

for chunk in stream:
    delta = chunk.choices[0].delta

    # Accumulate streamed tool call fragments
    if delta.tool_calls:
        for tc in delta.tool_calls:
            idx = tc.index
            if idx not in collected_tool_calls:
                collected_tool_calls[idx] = {"id": "", "name": "", "arguments": ""}
            if tc.id:
                collected_tool_calls[idx]["id"] = tc.id
            if tc.function.name:
                collected_tool_calls[idx]["name"] = tc.function.name
            if tc.function.arguments:
                collected_tool_calls[idx]["arguments"] += tc.function.arguments

    if delta.content:
        print(delta.content, end="", flush=True)

# After stream ends, process accumulated tool calls
for idx, tc in collected_tool_calls.items():
    args = json.loads(tc["arguments"])
    print(f"\nExecuting {tc['name']} with args: {args}")

Exam Tips for AWS Certified AI Practitioner (AIF-C01)

  • Function Calling = Action Groups in Bedrock Agents: The exam tests Bedrock Agents heavily. Understand that agents use the same tool-use pattern — the FM decides which Lambda action to invoke based on the OpenAPI schema you provide.
  • Streaming relates to latency optimization: AIF-C01 may present scenarios asking how to improve user experience for generative AI applications. Streaming is the correct answer when reducing time-to-first-token matters.
  • Know the difference between zero-shot and few-shot prompting: The system message in the API call is your primary prompt engineering surface. The exam expects you to know when to use few-shot examples vs. fine-tuning.
  • Temperature and top-p control output randomness: Lower temperature (0.0–0.3) for factual/deterministic tasks; higher (0.7–1.0) for creative tasks. This is a common exam scenario.
  • Tokens = cost and context: max_tokens controls output length. Input + output tokens together must fit within the model's context window. GPT-4o supports 128K tokens. On AWS, Amazon Titan and Claude models have their own limits tested on the exam.
  • Responsible AI: The exam includes questions on content filtering and guardrails. AWS equivalent is Amazon Bedrock Guardrails. Always apply input/output filtering in production.

Summary

In this tutorial you built three working Python integrations with the OpenAI API:

  1. A basic GPT-4o chat completion to verify connectivity and understand the message format
  2. A Function Calling flow where the model routes user intent to a real function, executes it, and synthesizes the result — the same pattern used by Amazon Bedrock Agents
  3. A Streaming implementation that delivers tokens in real time, including the more advanced pattern of accumulating streamed tool call fragments

These patterns form the backbone of modern AI application development. Mastering them gives you both practical engineering skills and the conceptual foundation needed for the AWS Certified AI Practitioner (AIF-C01) exam, particularly in the areas of FM APIs, agent architectures, and prompt engineering.

Next Steps

  • Replicate these patterns using Amazon Bedrock with boto3 and the converse API
  • Explore Bedrock Agents and connect them to Lambda functions using the same tool-use logic
  • Review the AIF-C01 exam guide and map each concept here to the official exam domains