๐Ÿ“‘ Table of Contents

๐Ÿ“˜ Related pages: Tool Calling ยท Reasoning, Tool Calls & Streaming (reasoning_content, streamed tool call deltas, the think switch) ยท Structured Output & tool_choice (tool_choice, parallel_tool_calls, response_format JSON / JSON schema) ยท Request Parameters & Long Prompts (sampling fields, seed, unknown fields, prefill chunking)


๐Ÿ“ก How Does the OpenAI Standard Work?

The OpenAI API standard defines a simple and powerful way for applications to communicate with language models โ€” whether running in the cloud or locally.

It uses a multi-role chat format, which includes three types of messages:

Role Description
๐Ÿ›  System Used to give high-level instructions to the model. For example: โ€œYou are a helpful assistant,โ€ or โ€œYou are a coding tutor that only explains using examples.โ€ It sets the tone, rules, or available tools for the model.
๐Ÿ‘ค User Messages from the application to the model. These often come from the end-user (e.g., a typed prompt like: โ€œExplain black holes.โ€).
๐Ÿค– Assistant Responses generated by the model and returned to the application. These are answers to the user prompts.

This structure makes it easy to build multi-turn conversations with consistent behavior.


๐Ÿ“š Developer Support

OpenAI provides official libraries in multiple programming languages to help developers follow the standard easily: Python, JavaScript, .NET, Java, and Go.

These libraries make it easy to send prompts, receive completions, and integrate with local or cloud-based OpenAI-compatible servers.

FLM follows the OpenAI API format in Server Mode.

The following endpoints are actively supported and maintained:

  • v1/models
  • v1/chat/completions
  • v1/audio/transcriptions
  • v1/embeddings

๐Ÿš€ Quick Test: Use OpenAI SDK with FastFlowLM in Python

You can try this instantly in any Python environment โ€” including Jupyter Notebook. Follow the steps below by copying each block into a notebook cell.


โœ… Step 0: Start FastFlowLM in Server Mode

Open PowerShell or terminal and launch the model server:

flm serve llama3.2:1b

๐Ÿง  This loads the model and starts the FastFlowLM OpenAI-compatible API at http://127.0.0.1:52625/v1.


โœ… Step 1: Install the OpenAI Python SDK

pip install --upgrade openai

โœ… Step 2: Send a Chat Request to FastFlowLM

# Quick Start
from openai import OpenAI

# Connect to local FastFlowLM server
client = OpenAI(
    base_url="http://127.0.0.1:52625/v1",  # FastFlowLM's local API endpoint
    api_key="flm"  # Dummy key (FastFlowLM doesnโ€™t require authentication)
)

# Send a chat-style prompt using OpenAI API format
response = client.chat.completions.create(
    model="llama3.2:1b",  # Replace with any model you've launched with `flm serve`
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Why is the sky blue?"}
    ]
)

# Show the model's response
print(response.choices[0].message.content)

# cleanup
del response, client
import gc
gc.collect()

๐Ÿ“Œ Notes

  • ๐Ÿง  You can replace "llama3.2:1b" with any other model available via flm run or flm pull.
  • ๐Ÿ–ฅ Make sure the FastFlowLM server is running in the background (flm serve ...).
  • ๐Ÿ”’ No real API key is needed โ€” just pass "flm" as a placeholder.
  • โšก FastFlowLM runs fully offline and is optimized for AMD Ryzenโ„ข AI NPUs.

โœ… This setup is perfect for quick offline LLM testing using standard OpenAI tooling.


๐Ÿงช More Examples

๐Ÿš€ Ah โ€” that was easy, right?
Now letโ€™s kick things up a notch with some awesome next-level examples!


๐Ÿ’ฌ Example: Multi-turn Chat (Conversation History)

Use this pattern when you want the model to remember previous turns in the conversation:

# Multi-turn conversation
from openai import OpenAI

messages = [
    {"role": "system", "content": "You are a creative writing assistant."},
    {"role": "user", "content": "Write the beginning of a fantasy story."},
]

client = OpenAI(base_url="http://127.0.0.1:52625/v1", api_key="flm")
response = client.chat.completions.create(model="llama3.2:1b", messages=messages)
print(response.choices[0].message.content)

# Add the assistant response and continue the conversation
messages.append({"role": "assistant", "content": response.choices[0].message.content})
messages.append({"role": "user", "content": "Continue the story with a twist."})
response = client.chat.completions.create(model="llama3.2:1b", messages=messages)

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

# cleanup
del response, client
import gc
gc.collect()

โš ๏ธ The OpenAI API (and FastFlowLM server mode) is stateless โ€” you must resend the full conversation each time. No KV cache is kept between turns.
๐ŸŒ€ This means all previous messages are reprocessed (prefill), which adds latency for long chats.
โšก FastFlowLMโ€™s CLI mode uses a real KV cache, making multi-turn responses much faster โ€” especially with long conversations.
๐Ÿง  FastFlowLM is optimized for long sequences with large KV caches, ideal for 32kโ€“256k context windows.


๐Ÿ” Example: Streamed Output (Real-Time Response)

Display the modelโ€™s output as it generates, token-by-token:

# Streaming
from openai import OpenAI
import gc, sys

client = OpenAI(base_url="http://127.0.0.1:52625/v1/", api_key="flm")

stream = client.chat.completions.create(
    model="llama3.2:1b",
    messages=[
        {"role": "system", "content": "You are a fast, concise assistant."},
        {"role": "user", "content": "Summarize the plot of Hamlet in 3 sentences."}
    ],
    stream=True
)

print("=== STREAMING BEGIN ===")
try:
    for chunk in stream:
        content = chunk.choices[0].delta.content
        if content:
            print(content, end="", flush=True)
    print("\n=== STREAMING END ===")
except Exception as e:
    sys.stderr.write(f"\n[STREAM ERROR] {e}\n")

#cleanup
del stream, client
gc.collect()

As with OpenAI, a stream reports no token usage unless you ask for it with stream_options={"include_usage": True} (stream_usage=True in LangChain). Then every chunk carries usage: null, and one last chunk before data: [DONE] has an empty choices list and the usage of the whole request. On /v1/chat/completions that usage also includes FLMโ€™s timing and KV cache figures (prefill_speed_tps, decoding_speed_tps, active_kv_tokens, โ€ฆ). Since that last chunk has no choices, guard chunk.choices before indexing it:

stream = client.chat.completions.create(
    model="llama3.2:1b",
    messages=[{"role": "user", "content": "Hi!"}],
    stream=True,
    stream_options={"include_usage": True},
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
    if chunk.usage:
        print(f"\n{chunk.usage.prompt_tokens} prompt + {chunk.usage.completion_tokens} completion tokens")

๐Ÿ“„ Example: Use a File as the Prompt

You can load a full .txt file as a prompt โ€” useful for long documents or testing large context windows.

๐Ÿ‘‰ Download the sample prompt

Download to Downloads folder. This contains over 38k tokens, so it may take longer to prompt. FastFlowLM supports full context length (32kโ€“128k), making it ideal for processing long documents like this

# Use a text file to prompt
from openai import OpenAI

with open("C:\\Users\\<username>\\Downloads\\alice_in_wonderland.txt", "r", encoding="utf-8") as f:
    user_prompt = f.read()

client = OpenAI(base_url="http://127.0.0.1:52625/v1", api_key="flm")

response = client.chat.completions.create(
    model="llama3.2:1b",
    messages=[
        {"role": "system", "content": "You are a story rewriter."},
        {"role": "user", "content": user_prompt}
    ]
)

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

# cleanup
del response, client
import gc
gc.collect()

๐Ÿ“Š Example: Batch Requests (Multiple Prompts)

Loop over a list of prompts and generate answers โ€” useful for eval or bulk testing.

# Batched prompts
from openai import OpenAI

prompts = [
    "Summarize the causes of World War I.",
    "Describe how a transistor works.",
    "What are the key themes in โ€˜To Kill a Mockingbirdโ€™?",
]

client = OpenAI(base_url="http://127.0.0.1:52625/v1", api_key="flm")

for prompt in prompts:
    response = client.chat.completions.create(
        model="llama3.2:1b",
        messages=[
            {"role": "system", "content": "You are a concise academic tutor."},
            {"role": "user", "content": prompt}
        ]
    )
    print(f"\n๐Ÿ“ Prompt: {prompt}\n๐Ÿ” Answer: {response.choices[0].message.content}")

# cleanup
del response, client
import gc
gc.collect()

๐Ÿงฌ Example: Use Temperature, Top-p, and Presence Penalty

Control randomness and creativity โ€” for brainstorming or open-ended tasks.

# Change hyper parameters
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:52625/v1", api_key="flm")

response = client.chat.completions.create(
    model="llama3.2:1b",
    messages=[
        {"role": "system", "content": "You are a creative brainstorming partner."},
        {"role": "user", "content": "Give me 5 startup ideas that combine AI and education."}
    ],
    temperature=0.9,      # More randomness
    top_p=0.95,           # Nucleus sampling
    presence_penalty=0.5, # Encourage novelty
)

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

# cleanup
del response, client
import gc
gc.collect()

โš ๏ธ Sampling settings stay in effect for later requests that donโ€™t send them, until the model is reloaded; seed applies to its own request only. What each field does, seed, the penalty window and model defaults: Request Parameters & Long Prompts.


๐ŸŽฒ Example: Token Log Probabilities

/v1/chat/completions returns per-token log probabilities as OpenAI does. Set logprobs=True, and top_logprobs (0โ€“100) for the most likely alternatives at each position:

from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:52625/v1", api_key="flm")

response = client.chat.completions.create(
    model="llama3.2:1b",
    messages=[{"role": "user", "content": "Say hello."}],
    logprobs=True,
    top_logprobs=2,
)

for t in response.choices[0].logprobs.content:
    alternatives = ", ".join(f"{a.token!r} {a.logprob:.3f}" for a in t.top_logprobs)
    print(f"{t.token!r:12} {t.logprob:8.4f}   [{alternatives}]")

Streaming works the same way: each content chunk carries choices[0].logprobs.content for the tokens in that chunk.

  • The log probabilities are the modelโ€™s own distribution, before temperature, top-k/top-p, penalties, or the grammar from tool_choice / response_format are applied. Under a grammar, the chosen token can be one the model itself thought unlikely.
  • Only content has log probabilities. Tokens of reasoning_content and of tool calls are left out, and chunks that carry them have logprobs: null.
  • bytes is null for special tokens such as end-of-turn, which can appear among top_logprobs. A token that holds only part of a UTF-8 character is named by its bytes, for example "bytes:\xe4\xbd".
  • On qwen3.8-mtp and qwen3.8-dflash2, tokens produced by speculative decoding take their log probabilities from the batched pass that checks the draft. That pass runs different kernels from one-token decoding, so the values can differ slightly from what the same model reports with speculation off.
  • top_logprobs without logprobs=True, or outside 0โ€“100, gets HTTP 400.

๐ŸŽฏ Example: Score the Next Token After a Partial Answer

Available from FLM v1.0.11.

To see how likely each answer is, end messages with the start of the assistantโ€™s reply and set continue_final_message (an extra_body field). The model continues that text instead of starting a new reply, and the first log probability entry is its distribution for the very next token:

from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:52625/v1", api_key="flm")

response = client.chat.completions.create(
    model="llama3.2:1b",
    messages=[
        {"role": "user", "content": "Is Paris the capital of France? Answer yes or no."},
        {"role": "assistant", "content": "The answer is:"},
    ],
    max_tokens=1,
    temperature=0,
    logprobs=True,
    top_logprobs=5,
    extra_body={"continue_final_message": True},
)

for alt in response.choices[0].logprobs.content[0].top_logprobs:
    print(f"{alt.token!r:10} {alt.logprob:8.4f}")
  • The prompt goes through the modelโ€™s own chat template, so the score is for the context the model is used to.
  • content is only the continuation, without your text, and keeps its whitespace as the model wrote it (' Yes', not 'Yes').
  • Use a model with thinking off (think: false, or a non-reasoning model). A model that always reasons first reads your text as its reasoning, so its first token is not the answer.
  • Continuing works on every chat model except single-turn (flash) models and gpt-oss. What else is refused, and how the prompt is cut: Continuing an Assistant Message.

๐Ÿ“ท Example: Multi-Image Input

Send multiple images together with a text prompt.
The model will analyze them, compare, and respond with reasoning.

import base64
from openai import OpenAI

# Paths to your local images
image_path_0 = r"C:\Users\info\OneDrive\Desktop\FLM\image_test\image0.jpg"
image_path_1 = r"C:\Users\info\OneDrive\Desktop\FLM\image_test\image1.png"

# Read and encode the images as Base64 strings (required for API input)
with open(image_path_0, "rb") as image_file:
    image_0 = base64.b64encode(image_file.read()).decode("utf-8")
with open(image_path_1, "rb") as image_file:
    image_1 = base64.b64encode(image_file.read()).decode("utf-8")

# Connect to your local FLM/OpenAI-compatible endpoint
client = OpenAI(base_url="http://127.0.0.1:52625/v1", api_key="dummykey")

# Create a chat completion request with text + two images
response = client.chat.completions.create(
    model="gemma3:4b",  # Vision-capable model
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "Describe the images. Which one you like better and why?"},
                
                {"type": "image_url", "image_url": {"url": f"data:image/jpg;base64,{image_0}"}}, # standard OpenAI-compatible API
                {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_1}"}}, 
            ],
        }
    ],
)

# Print the model's answer
print(response.choices[0].message.content)

# cleanup
del response, client
import gc
gc.collect()

๐ŸŽ™๏ธ Example: Multi-Modal Input

Send an image and an audio file together with a text prompt to the multimodal interface of Gemma 4.

import base64
import gc
import sys
from openai import OpenAI

audio_path = r"C:\Users\info\OneDrive\Desktop\FLM\audio_test\audio.wav"
image_path = r"C:\Users\info\OneDrive\Desktop\FLM\image_test\image.png"


# Read the audio and image files and encode them as Base64 for API input
with open(audio_path, "rb") as audio_file:
    audio = base64.b64encode(audio_file.read()).decode("utf-8")
with open(image_path, "rb") as image_file:
    image = base64.b64encode(image_file.read()).decode("utf-8")


client = OpenAI(base_url="http://127.0.0.1:52625/v1", api_key="dummykey")

stream = client.chat.completions.create(
    model="gemma4-it:e2b",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "Finish two tasks: 1. Summarize the audio. 2. Describe the image."},
                {
                    "type": "input_audio",  
                    "input_audio": {"data": audio},
                },
                {
                    "type": "image_url",
                    "image_url": {"url": f"data:image/png;base64,{image}"},
                },
            ],
        }
    ],
    stream=True
)

print("=== STREAMING BEGIN ===")
try:
    for chunk in stream:
        content = chunk.choices[0].delta.content
        if content:
            print(content, end="", flush=True)
    print("\n=== STREAMING END ===")
except Exception as e:
    sys.stderr.write(f"\n[STREAM ERROR] {e}\n")

# cleanup
del stream, client
gc.collect()

โšก Example: Flash Models (Single-Turn, Pinned System Prompt)

Flash models โ€” gemma4e-flash:e2b, gemma4e-flash:e4b, nemotron3.5-bolt-dflash-flash:e4b, qwen3vl-flash:4b, and hy-mt2-flash:1.8b โ€” trade context length for a much faster time-to-first-token. They are built for high-throughput, short, independent requests: classification, captioning, extraction, routing, translation.

They are single-turn: every request starts from a clean KV state, so earlier turns are not carried over. The system prompt is the one exception โ€” it is prefilled once and cached, so keep it fixed and vary only the user message (and, for vision models, the image).

Below, the same system prompt is pinned across requests that each caption a different image. The system-prompt KV is prefilled once on the first request; every request after that reuses it, so only the new image and text need to be prefilled โ€” expect noticeably lower latency from the second request onward.

import time
import base64
from openai import OpenAI

client = OpenAI(
    base_url="http://127.0.0.1:52625/v1",
    api_key="dummykey",  # FLM runs locally; the key is not checked
)

# Keep everything fixed in the system prompt โ€” it is prefilled once and
# reused across requests. Vary only the user message and image.
SYSTEM_PROMPT = "You are a concise assistant. Describe the image in one short sentence."

image_paths = [
    r"C:\Users\info\OneDrive\Desktop\FLM\image_test\image0.jpg",
    r"C:\Users\info\OneDrive\Desktop\FLM\image_test\image1.png",
]

for path in image_paths:
    with open(path, "rb") as image_file:
        image = base64.b64encode(image_file.read()).decode("utf-8")

    print(f"\n> {path}")
    start = time.time()
    stream = client.chat.completions.create(
        model="qwen3vl-flash:4b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},  # same every time โ€” its KV is cached after the first call
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": "Describe this image."},
                    {"type": "image_url", "image_url": {"url": f"data:image/jpg;base64,{image}"}},
                ],
            },
        ],
        stream=True,
    )
    for chunk in stream:
        delta = chunk.choices[0].delta.content
        if delta:
            print(delta, end="", flush=True)
    print(f"\n[time to first response: {time.time() - start:.2f}s]")
    # 1st image: pays the full system-prompt prefill cost.
    # 2nd image onward: system-prompt KV is reused from cache โ€” expect lower latency.

# cleanup
del stream, client
import gc
gc.collect()

โš ๏ธ Do not append the reply to messages and send it back, as in the multi-turn example โ€” the history is discarded, and it only wastes your 1k budget.
๐Ÿง  Changing the system prompt discards the pinned prefix and re-prefills it. Alternating between two different system prompts defeats the optimization entirely.
๐Ÿ“ Context is fixed at 1k tokens for every flash model; context-length overrides are ignored.
๐Ÿ”ง Tool calling is not supported โ€” any tools you pass are dropped.
๐ŸŒ hy-mt2-flash is text-only and has no default system prompt โ€” pin your own translation instruction as the system prompt, or put it in the user message.
๐Ÿ–ผ๏ธ gemma4e-flash and nemotron3.5-bolt-dflash-flash default to the smallest visual token budget (70) and keeps only the first 30 seconds of an audio clip. qwen3vl-flash always downscales images to a 256 px longer side.

For the full per-model details, see the gemma4e-flash:e2b, gemma4e-flash:e4b, nemotron3.5-bolt-dflash-flash:e4b, qwen3vl-flash:4b, and hy-mt2-flash:1.8b model cards. Performance numbers are on the Flash benchmarks page.