Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

5.2. OpenAI-Compatible API

Pass --api-port N to any local or cluster invocation to start an OpenAI wire-compatible REST server alongside the REPL. No changes are required to GenerationLoop, the scheduler, or any node code; the API layer is a pure translation shim above RequestScheduler. Any client that speaks the OpenAI Chat Completions wire format works against Juno with only a base-URL change, no prompt reformatting, no adapter library, no glue code.

Supported endpoints:

MethodPathDescription
POST/v1/chat/completionsBlocking or SSE streaming completion
GET/v1/modelsList loaded models
GET/v1/models/{model}Retrieve a single model

Request flow

Quick verification:

# Start local mode with API
./juno local --model-path /path/to/model.gguf --api-port 8080

# Blocking completion
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf",
    "messages": [{"role": "user", "content": "What is Java?"}]
  }'

# Streaming completion
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf",
    "messages": [{"role": "user", "content": "Tell me a joke."}],
    "stream": true
  }'

# List models
curl http://localhost:8080/v1/models

Request field mapping:

OpenAI fieldJuno internalNotes
modelmodelIdFirst loaded model if omitted
messages[].roleChatMessage.rolesystem / user / assistant
messages[].contentChatMessage.contentText only; image content not supported
temperatureSamplingParams.temperature0.0–2.0; default 0.7
top_pSamplingParams.topP0.0–1.0; default 0.9
max_completion_tokensSamplingParams.maxTokens1–32768; default 200
max_tokensSamplingParams.maxTokensDeprecated alias; max_completion_tokens takes precedence
frequency_penaltySamplingParams.repetitionPenaltyMapped: 1 + max(0, fp/2)
streamroute selectionfalse -> blocking JSON; true -> SSE
nN/AOnly 1 accepted; other values -> HTTP 400
stop, presence_penalty, logit_bias, user, seedN/ASilently ignored for client compatibility

Juno request extensions (namespaced under x_juno_* to avoid OpenAI field conflicts):

FieldTypeDefaultDescription
x_juno_prioritystringNORMALScheduler priority: HIGH / NORMAL / LOW
x_juno_session_idstringnoneStable session ID; enables KV-cache reuse across turns
x_juno_top_kinteger50Top-K sampling cutoff (0 = disabled)
x_juno_disclosurebooleantrueEU AI Act Article 50 opt-out. true includes x_juno_ai_disclosure in the response; false omits it. Set false only for API-to-API integrations with no human end-user present. See Chapter 9.7

Multi-turn conversation with KV-cache reuse:

SESSION_ID = "sess-my-conversation-001"

def chat(messages):
    return client.chat.completions.create(
        model="tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf",
        messages=messages,
        extra_body={"x_juno_session_id": SESSION_ID},
    ).choices[0].message.content

history = []
for user_input in ["My name is Alice.", "What is my name?"]:
    history.append({"role": "user", "content": user_input})
    reply = chat(history)
    history.append({"role": "assistant", "content": reply})
    print(reply)

Response extension: every response (blocking, and the first SSE chunk of a streaming response) includes x_juno_ai_disclosure, a short text notice, unless the request set x_juno_disclosure to false. This satisfies the EU AI Act Article 50 transparency obligation: natural persons must be notified they are interacting with an AI system. See Chapter 9.7 -- EU AI Act Compliance for the full analysis.

See also


<- 5.1 Juno Native API  |  Table of Contents  |  5.3 Error Handling ->