콘텐츠로 이동

REST API 레퍼런스

mlxcel-server는 생성, 오디오, 임베딩, 재순위화, 토큰화, 관측성 API를 제공합니다. 생성 경로는 비스트리밍 JSON과 Server-Sent Events(SSE) 스트리밍을 모두 지원하고 임베딩과 재순위화 경로는 일반 JSON을 반환합니다.

목차


인증

서버를 --api-key <key> 옵션으로 시작하면 GET /health와 GET /를 제외한 모든 요청에 해당 키를 포함해야 합니다. 키는 Authorization 헤더로 전달합니다:

Authorization: Bearer <key>

Bearer 접두사를 생략하고 원시 토큰만 Authorization 헤더에 넣는 방식도 호환 목적으로 허용됩니다. 쿼리 파라미터 인증은 지원하지 않습니다.

키가 누락되거나 올바르지 않은 요청은 HTTP 401을 반환합니다.

서버 시작 시 --api-key를 지정하지 않으면, 인증 없이 모든 요청을 허용합니다.


요청 우선순위

Chat completions, text completions, Responses, Anthropic Messages, native /completion 생성 엔드포인트는 프리필 큐 순서를 제어하는 선택적 X-Priority 헤더를 허용합니다:

값 의미
high 최고 우선순위; 프리필 큐의 맨 앞에 배치
normal (기본값) 표준 우선순위
low 최저 우선순위; 일반 요청 이후에 처리
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "X-Priority: high" \
  -d '{"model": "...", "messages": [...]}'

임베딩 및 재순위화 API

mlxcel은 OpenAI 호환 POST /v1/embeddings, llama-server 고유 POST /embedding과 /embeddings, 그리고 POST /v1/rerank, /v1/reranking, /rerank, /reranking 재순위화 경로를 제공합니다. 고유 임베딩 경로는 OpenAI 경로와 응답 모양이 다르며 별칭이 아닙니다. 재순위화는 documents를 보내면 Jina/Cohere 형식, texts를 보내면 TEI 호환 배열을 반환합니다.

정확한 요청·응답 스키마, 모델 로딩 방식, 옵션, 오류, 클라이언트 예시는 임베딩 및 재순위화 API를 참고하십시오.


POST /v1/chat/completions

OpenAI 호환 채팅 완성 엔드포인트입니다. 비스트리밍과 스트리밍(SSE) 응답을 모두 지원합니다. VLM 또는 오디오 입력을 지원하는 모델에서는 콘텐츠 파트 배열을 사용해 텍스트, 이미지, 비디오, 오디오 입력을 전달할 수 있습니다.

요청 본문

{
  "model": "string",
  "messages": [
    {
      "role": "system | user | assistant | tool",
      "content": "string or array of content parts",
      "name": "string (optional)"
    }
  ],
  "stream": false,
  "stream_options": {
    "include_usage": true
  },
  "max_tokens": 512,
  "temperature": 0.7,
  "top_p": 0.9,
  "top_k": 40,
  "min_p": 0.05,
  "repetition_penalty": 1.1,
  "repetition_context_size": 64,
  "seed": 42,
  "stop": ["string"],
  "frequency_penalty": 0.0,
  "presence_penalty": 0.0,
  "dry_multiplier": 0.0,
  "dry_base": 1.75,
  "dry_allowed_length": 2,
  "dry_penalty_last_n": 0,
  "dry_sequence_breakers": [1, 2, 3],
  "xtc_probability": 0.0,
  "xtc_threshold": 0.1,
  "max_pattern_size": 0,
  "min_pattern_size": 0,
  "min_count": 0
}

스트림 옵션

파라미터 타입 설명
stream_options object 스트리밍 응답 옵션. stream이 true일 때만 사용
stream_options.include_usage boolean true로 설정하면 [DONE] 마커 전에 토큰 사용량 통계가 포함된 최종 청크를 전송합니다

도구와 tool_choice

tools는 요청에 도구가 하나 이상 있을 때만 채팅 템플릿에 렌더링됩니다. tools가 없거나 "tools": []인 요청은 템플릿의 tools 변수를 정의되지 않은 상태로 두며, 이는 transformers와 llama-server의 동작과 같습니다. tool_choice는 그대로 되돌려주는 값이 아니라 강제됩니다.

tool_choice 렌더링되는 도구 동작
없음 또는 "auto" 전부 모델이 도구 호출 여부를 결정합니다
"none" 없음 도구를 렌더링하지 않습니다
"required" 전부 선언된 함수 중 하나 이상을 호출하라는 지시를 프롬프트에 덧붙입니다. tools가 없거나 비어 있으면 400으로 거부합니다
{"type": "function", "function": {"name": "f"}} f만 모델에 f를 호출하라고 지시합니다. f가 선언되지 않았거나, type이 function이 아니거나, 이름이 비어 있으면 400으로 거부합니다

지시문은 첫 system 메시지에, 없으면 마지막 user 메시지에, 그것도 없으면 새 선두 system 메시지로 들어가며 요청 객체 자체는 바뀌지 않습니다. 도구 호출 wire 형식이 고정 래퍼 안의 JSON 객체인 템플릿(Hermes와 Qwen의 <tool_call>, Mistral Nemo의 [TOOL_CALLS], Llama 3의 <|python_tag|>)에서는 강제 선택이 도구 스키마로 만든 구조화 출력 문법으로도 제약되므로, required는 선언된 함수 호출을, 이름 지정 형식은 그 함수 호출을 finish_reason: "tool_calls"와 함께 만들어 냅니다. JSON wire 형식이 없는 포맷, 문법 엔진이 표현하지 못하는 도구 스키마, 분리형 라우터는 지시문과 좁혀진 도구 목록만 받으며, 호출 없이 끝난 강제 선택은 warn으로 기록됩니다. 강제 선택과 제약형 response_format을 함께 보내면 400입니다. 같은 규칙이 Responses API와 Anthropic Messages의 {"type": "any"}, {"type": "tool"} 매핑에도 적용됩니다.

투기적 디코딩 timings

드래프터가 그 요청에서 검증 라운드를 한 번 이상 실행했으면, 비스트리밍 응답과 finish_reason을 싣는 스트리밍 청크에 최상위 timings 객체가 포함됩니다. llama-server의 필드에 draft_n, draft_n_accepted, draft_rounds, draft_kind(mtp, dflash, prompt-lookup)가 더해집니다. prompt lookup에서는 이 카운터가 해당 요청에 대한 드래프터의 누적값입니다. 일반 디코딩과 프리필 안에서 끝난 요청에는 이 객체가 없고, 스트림 중간 청크에는 드래프트 카운터가 실리지 않습니다. 네이티브 /completion과 /completions 경로는 기존 timings 객체에 같은 네 필드를 넣습니다. 투기적 디코딩을 참고하십시오.

샘플링 파라미터

모든 샘플링 파라미터는 선택 사항입니다. 생략하면 서버 기본값이 사용됩니다(시작 시 CLI 플래그로 설정 가능).

파라미터 타입 설명
max_tokens integer 생성할 최대 토큰 수
temperature float 샘플링 온도. 0.0 = 그리디 디코딩, 높을수록 무작위성 증가
top_p float 핵 샘플링 임계값 (0.0-1.0)
top_k integer top-k 샘플링. 0 = 비활성화. llama-server와 마찬가지로 어휘 크기 이상의 값도 모든 토큰을 남깁니다
min_p float min-p 임계값. 0.0 = 비활성화
p_less boolean 하이퍼파라미터가 없는 p-less truncation. temperature를 적용한 분포에서 확률이 sum(p^2) 이상인 토큰을 모두 남깁니다. argmax 토큰은 항상 남고 greedy 요청에서는 적용하지 않습니다. 기본값 false이며 서버 전역 기본값을 정하는 플래그는 없습니다. 샘플링 참고
repetition_penalty float 반복 페널티. 1.0 = 페널티 없음
repetition_context_size integer 반복 페널티 적용 컨텍스트 창
seed integer 재현성을 위한 난수 시드
cache_prompt boolean llama-server 프롬프트 캐시 스위치. 생략하면 서버 전역 설정을 따르고, false면 이 요청을 캐시 없이 프리필하며 캐시에 아무것도 기여하지 않습니다. true는 기본값을 명시할 뿐이라 서버 전역에서 끈 캐시를 다시 켜지 못합니다. 요청 루트, 평탄화된 OpenAI SDK extra_body, 중첩된 extra_body 어디에 넣어도 됩니다. 재현 가능한 greedy 출력 참고
stop array of strings 정지 시퀀스; 일치 시 생성 중단
frequency_penalty float OpenAI 호환 빈도 페널티
presence_penalty float OpenAI 호환 존재 페널티
dry_multiplier float DRY 페널티 배율. 0.0 = 비활성화. 일반적인 값: 0.8-1.3
dry_base float DRY 지수 페널티 스케일링의 밑. 일반적인 값: 1.75
dry_allowed_length integer DRY 적용 전 최소 시퀀스 길이. 일반적인 값: 2
dry_penalty_last_n integer DRY 룩백 창. 0 = 전체 컨텍스트
dry_sequence_breakers array of integers DRY 매칭을 초기화하는 토큰 ID
xtc_probability float XTC (Exclude Top Choices) 샘플링 확률
xtc_threshold float XTC 확률 임계값
max_pattern_size integer tail N-gram 반복("loop") 감지에서 스캔할 최대 패턴 크기입니다. 기본값 0은 서버 측 정책이 정한 값을 그대로 둡니다(아래 참조). 명시적인 값을 주면 이 요청에서만 해당 정책을 override합니다. 필드 이름은 vLLM SamplingParams와 동일해서 같은 JSON을 두 엔진 모두에 쓸 수 있습니다.
min_pattern_size integer 스캔할 최소 패턴 크기입니다. 0은 1로 처리됩니다. max_pattern_size와 함께 쓸 때만 의미가 있습니다.
min_count integer tail에서 패턴이 연속으로 이만큼 반복되면 생성을 조기 종료합니다. 2 이상이어야 하며 더 작은 값은 감지를 비활성화합니다.

루프 감지는 생성 스트림 끝에서 짧은 토큰 패턴이 반복될 때 생성을 조기 종료합니다. 요청에서 세 필드를 모두 생략하면 서버 정책이 적용됩니다. MLXCEL_LOOP_DETECTION 전역 override가 설정되어 있으면 그 값을, 아니면 계열별 자동 활성화 정책(Gemma 4 계열은 기본적으로 켜져 있으며 threshold는 min=1, max=20, count=4)을 따릅니다. 요청에서 세 필드 중 하나라도 설정하면(강제 비활성화용 max_pattern_size: 0 포함) 전역 override와 계열별 기본값보다 항상 우선합니다. 운영자 수준 override는 MLXCEL 환경 변수를 참조하십시오.

재현 가능한 greedy 출력

프롬프트 캐시 히트는 프리필을 나눠 실행합니다. 캐시된 prefix는 이전에 한 번에 계산됐고 나머지는 따로 계산되므로, 첫 토큰의 logit이 캐시 없이 실행한 결과와 마지막 몇 비트에서 달라질 수 있고 근소한 동점에서는 greedy 토큰이 바뀔 수 있습니다. 출력이 이전 요청이 캐시에 남긴 내용에 좌우되면 안 될 때는 "cache_prompt": false를 보내십시오. 네이티브 /completion 경로도 같은 필드를 따르며, 서버 전체에 적용하려면 --no-cache-prompt를 씁니다.

curl http://localhost:8080/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"m","messages":[{"role":"user","content":"Hi"}],"temperature":0,"cache_prompt":false}'

채팅 템플릿 kwargs

Qwen3.6의 preserve_thinking 같은 템플릿 레벨 기능 스위치가 있는 대화형 모델을 위해, 요청별 kwarg를 세 가지 형태로 전달할 수 있습니다. 먼저 일치하는 형태가 적용됩니다.

우선순위 형태 출처
1 최상위 chat_template_kwargs llama.cpp
2 extra_body.chat_template_kwargs vLLM / OpenAI Python SDK
3 extra_body.preserve_thinking (평면 형태) DashScope (preserve_thinking 키만 인식)

chat_template_kwargs에 포함된 키는 알려지지 않은 키여도 그대로 Jinja 템플릿에 전달되므로, 새로운 kwarg를 이해하는 템플릿은 서버 측 허용 목록 변경 없이 해당 kwarg를 사용할 수 있습니다.

요청별 값은 --chat-template-kwargs / LLAMA_ARG_CHAT_TEMPLATE_KWARGS로 지정된 서버 기본값과 병합됩니다. 충돌 시에는 요청별 키가 우선하지만 요청에 없는 서버 기본 키는 병합 과정에서 유지됩니다.

기본 형태 (llama.cpp):

{
  "model": "qwen3.6",
  "messages": [...],
  "chat_template_kwargs": {"preserve_thinking": true}
}

vLLM / OpenAI SDK 형태:

{
  "model": "qwen3.6",
  "messages": [...],
  "extra_body": {
    "chat_template_kwargs": {"preserve_thinking": true}
  }
}
# Python OpenAI SDK
client.chat.completions.create(
    model="qwen3.6",
    messages=[...],
    extra_body={"chat_template_kwargs": {"preserve_thinking": True}},
)

DashScope 형태 (보조, preserve_thinking만 해당):

{
  "model": "qwen3.6",
  "messages": [...],
  "extra_body": {"preserve_thinking": true}
}
preserve_thinking

true인 경우 이전 모든 assistant 턴의 <think>...</think> 블록이 그대로 모델에 전달되어 Qwen3.6의 다중 턴 추론 보존이 활성화됩니다. false(기본값)인 경우 서버는 Qwen3/Qwen3.5 rolling-checkpoint 규칙을 적용합니다. 메시지 목록을 역순으로 훑어 최근 비-tool user 턴을 찾고 그 이전 모든 assistant 응답에서 <think> 블록을 제거합니다.

추론 기능이 없는 모델(Qwen2, Llama)은 이 플래그를 조용히 무시합니다. Qwen3/Qwen3.5도 플래그를 받아들이지만 보존 모드의 품질 이점은 Qwen3.6에서만 검증되었습니다. 이전 Qwen3 계열은 rolling-checkpoint 규약으로 학습되었기 때문입니다.

구조화 출력 (response_format)

response_format을 type: "json_schema"로 설정하면 서버가 디코딩을 제약하므로 생성되는 모든 토큰이 지정된 스키마를 준수합니다. 스키마를 위반하는 토큰은 샘플링 전에 -inf로 마스킹되므로 비준수 출력이 발생하지 않습니다.

요청 형태 (Chat Completions API):

{
  "model": "qwen3-8b",
  "messages": [{"role": "user", "content": "동물 한 종류와 서식지를 알려주세요."}],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "AnimalResult",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "animal": {"type": "string"},
          "habitat": {
            "type": "string",
            "enum": ["forest", "desert", "ocean", "urban", "unknown"]
          }
        },
        "required": ["animal", "habitat"],
        "additionalProperties": false
      }
    }
  }
}

json_schema 래퍼는 name과 strict 필드도 허용합니다. OpenAI SDK 호환성을 위한 것이며 서버 동작에는 영향을 주지 않고 schema 필드만이 제약 디코딩을 구동합니다.

response_format.type 동작
text 또는 미설정 제약 없는 일반 생성
json_schema 제공된 JSON 스키마로 제약
json_object 이 MVP에서는 HTTP 400으로 거부

스키마 한도는 문법 컴파일 전에 적용되어 DoS를 방지합니다:

한도 값
직렬화된 스키마 크기 64 KiB
중첩 깊이 32단계
$ref 개수 64개

한도를 초과하는 스키마는 짧은 설명 메시지와 함께 HTTP 400으로 거부됩니다. 스키마 내용은 오류 응답에 포함되지 않습니다.

토크나이저 요구 사항: 제약 디코딩에는 HuggingFace BPE 토크나이저(tokenizer.json)가 필요합니다. SentencePiece 또는 Tiktoken 토크나이저로 로드된 모델은 response_format 설정 시 UnsupportedTokenizer 메시지와 함께 HTTP 400을 반환합니다.

curl 예시:

curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3-8b",
    "messages": [{"role": "user", "content": "동물 한 종류와 서식지를 알려주세요."}],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "result",
        "schema": {
          "type": "object",
          "properties": {
            "animal": {"type": "string"},
            "habitat": {"type": "string", "enum": ["forest", "desert", "ocean"]}
          },
          "required": ["animal", "habitat"],
          "additionalProperties": false
        }
      }
    }
  }'

멀티모달 콘텐츠

VLM 또는 오디오 입력을 지원하는 모델의 경우, 메시지의 content 필드는 일반 문자열 대신 콘텐츠 파트 배열로 지정할 수 있습니다:

{
  "role": "user",
  "content": [
    {
      "type": "text",
      "text": "What is in this image?"
    },
    {
      "type": "image_url",
      "image_url": {
        "url": "data:image/jpeg;base64,/9j/4AAQSkZJRgAB..."
      }
    }
  ]
}

지원되는 콘텐츠 파트는 다음과 같습니다.

타입 형태 비고
text {"type":"text","text":"..."} 모든 모델에서 사용 가능
image_url {"type":"image_url","image_url":{"url":"..."}} VLM 모델용 이미지 입력
video_url {"type":"video_url","video_url":{"url":"...","fps":2.0}} 고유 비디오 경로가 있는 계열은 클립을 직접 소비하고, 비전 타워가 있는 그 밖의 체크포인트는 샘플링한 프레임을 최대 --video-max-frames개까지 순서가 있는 이미지로 받습니다. ffmpeg가 PATH에 있어야 합니다.
input_audio {"type":"input_audio","input_audio":{"data":"data:audio/wav;base64,...","format":"wav"}} 현재 연결된 오디오 경로는 WAV 페이로드를 기대합니다.

원격·로컬 media access

Image/audio 입력 획득은 client가 제공한 HTTP(S) URL, file:// URL, bare local path를 허용하므로, API key는 caller를 인증할 뿐 그 fetch를 sandbox하지 않습니다. 두 정책이 범위를 좁힙니다. 공인 주소가 아닌 곳으로 resolve되는 원격 URL은 연결 전·중·후에 거부하며, MLXCEL_ALLOW_PRIVATE_MEDIA_URLS=1이 이를 해제합니다. 로컬 파일은 설정한 --media-path 루트 아래에서만 읽습니다. 둘 다 hostname allowlist도 HTTPS-only policy도 아니므로, caller를 제한하고 mlxcel 외부에서 egress/filesystem policy를 강제하는 일은 그대로 필요합니다. Local video path는 별도로 MLXCEL_VIDEO_DIR_ALLOWLIST 아래에서 fail-closed입니다. 미디어 입력을 참조하십시오.

이미지 url 필드는 다음을 허용합니다:

  • Base64 데이터 URI: data:image/jpeg;base64,...
  • 로컬 파일 경로: file:///path/to/image.jpg 또는 절대 경로
  • HTTP/HTTPS URL: https://example.com/image.jpg

비디오 url도 data URI, HTTP/HTTPS URL, 로컬 파일 경로를 받을 수 있습니다. 로컬 비디오 경로는 기본적으로 거부되며 서버가 읽을 수 있는 디렉터리를 MLXCEL_VIDEO_DIR_ALLOWLIST에 쉼표로 구분해 지정해야 합니다. 서버는 MLXCEL_VIDEO_DIR_ALLOWLIST를 시작할 때 한 번만 읽으며, 네이티브 비디오 계열과 프레임 샘플링 fallback 모두 이 값을 씁니다. 변수를 바꾸거나 목록에 있는 디렉터리를 새로 만든 뒤에는 서버를 다시 시작하십시오. Data-video URI와 HTTP(S) URL은 비디오 디코더가 읽기 전에 권한 0700인 전용 디렉터리 안에 새로 만든 0600 파일로 저장됩니다.

한 요청의 비디오와 오디오는 Gemma 4 Unified만 받으며, 다른 계열은 Combined video and audio inputs are not supported와 함께 400을 반환합니다. 이미지+비디오, 이미지+오디오 조합은 로드된 모델이 해당 경로를 구현한 경우 지원됩니다. 고유 비디오 계열과 프레임 대체 경로는 비디오 및 오디오 입력을 참조하십시오.

비스트리밍 응답

{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1715000000,
  "model": "qwen3-8b",
  "system_fingerprint": null,
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Hello! How can I help you today?"
      },
      "finish_reason": "stop",
      "logprobs": null
    }
  ],
  "usage": {
    "prompt_tokens": 12,
    "completion_tokens": 9,
    "total_tokens": 21
  }
}

finish_reason 필드의 값:

값 의미
stop EOS 토큰이 생성되었거나 정지 시퀀스가 일치
length max_tokens 한도에 도달
error 생성 중 내부 오류 발생

스트리밍 응답 (SSE)

"stream": true로 설정하면 응답은 Server-Sent Events 스트림으로 전달됩니다. 각 이벤트는 JSON 청크를 담은 data: 필드를 포함합니다. 스트림은 data: [DONE]으로 종료됩니다.

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1715000000,"model":"qwen3-8b","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null,"logprobs":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1715000000,"model":"qwen3-8b","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null,"logprobs":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1715000000,"model":"qwen3-8b","system_fingerprint":null,"choices":[{"index":0,"delta":{},"finish_reason":"stop","logprobs":null}]}

data: [DONE]

stream_options.include_usage가 true로 설정되면, 완료 청크 이후 [DONE] 마커 전에 사용량 청크가 추가로 전송됩니다:

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1715000000,"model":"qwen3-8b","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":12,"completion_tokens":9,"total_tokens":21}}

data: [DONE]

툴 콜 스트리밍

툴 콜을 지원하는 모델이 스트리밍 중 툴 콜을 생성하면, 서버의 StreamFilter가 delta.content에서 툴 콜 마크업과 JSON 페이로드를 자동으로 제거합니다. 클라이언트는 툴 콜 이전의 일반 텍스트만 delta.content로 수신하고 마지막 청크에서 finish_reason: "tool_calls"를 받습니다. 모델 내부 마커는 delta.content에 노출되지 않습니다. 토큰 경계에서 구분자와 부분적으로만 일치하는 문자열은 구분자인지 확정될 때까지 보류하고, 구분자가 아니면 일반 콘텐츠로 내보냅니다.

Hermes/Qwen JSON, Mistral([TOOL_CALLS]NAME[ARGS]{json}과 JSON 목록 형식), Llama 3 JSON, Qwen3-Coder XML 형식에서는 툴 콜의 이름과 인자가 생성되는 동안 스트리밍됩니다. 호출의 첫 청크에는 index, id, type: "function", function.name이 들어 있고, 이후 청크에는 index와 function.arguments 조각이 들어 있습니다. 조각을 이어 붙이면 호출의 최종 arguments 문자열이 됩니다. 그 밖의 형식과 스트리머가 추적하지 못한 호출은 생성이 끝날 때 시작 청크 하나와 인자 청크 하나로 보냅니다. 생성 종료 시점의 파싱 결과가 기준이므로 스트리밍된 호출은 id를 유지하고 아직 보내지 않은 나머지 인자를 받으며, 스트림과 파싱 결과가 어긋나면 요청 id와 함께 warn으로 기록합니다. 이 형식들에서 객체 값 arguments는 키 순서, 공백, 숫자 표기를 유지한 모델의 JSON 텍스트 그대로이며 스트리밍 여부와 무관합니다. 문자열 값 arguments는 그대로입니다.

스트리밍 중 인식 및 필터링되는 툴 콜 형식은 아래와 같습니다. GLM-4.7, LongCat-Flash, MiniMax M3처럼 본문이 JSON이 아닌 형식은 요청의 tool schema에 맞춰 인자 값을 변환합니다. string 타입 파라미터는 원문을 그대로 두고, 나머지는 JSON이나 느슨한 리터럴로 파싱합니다.

형식 모델 구분자
Hermes/Qwen/DeepSeek Qwen3, Qwen2.5, DeepSeek V3/R1 등 <tool_call> / </tool_call>
Qwen3-Coder Qwen3-Coder와 같은 chat template을 쓰는 Qwen3.5/3.6 체크포인트 <tool_call> 내부의 <function=name> + <parameter=key> 본문
Mistral Nemo Mistral Nemo / Ministral [TOOL_CALLS]
MiniMax M2 MiniMax M2 <invoke name="..."> / </invoke>
Functionary v3.1 Meetkai Functionary v3.1 <function=name> / </function>
Functionary v3.2 Meetkai Functionary v3.2 >>>name\n
Command R Cohere Command R7B Action: / Action Input:
Granite 3.3 IBM Granite 3.3 <response><tool_call> / </tool_call></response>
Gemma 4 Google Gemma 4 <\|tool_call>call:name{...}<tool_call\|>
Llama 3.x Meta Llama 3.x {"name": ..., "parameters": ...}
Generic JSON 기타 {"name": ..., "arguments": ...}
Function-calling Gemma function-call template을 쓰는 Gemma 체크포인트 <start_function_call>call:name{...}<end_function_call>
Mistral (bracketed) Ministral 2410 이후, Mistral Small 3, Magistral, Devstral [TOOL_CALLS]NAME[ARGS]{json}
Kimi K2 Moonshot Kimi K2 <\|tool_calls_section_begin\|>으로 감싼 functions.NAME:INDEX<\|tool_call_argument_begin\|>{json}
MiniMax M3 MiniMax M3 네임스페이스 XML. 모든 태그 앞에 ]<]minimax[>[ 접두사
Pythonic Llama 계열 pythonic tool use, 일부 Nemotron template <\|tool_call_start\|> / <\|tool_call_end\|> 안의 [get_weather(city="Paris", days=2)]
GLM-4.7 glm4_moe, glm4_moe_lite, glm_moe_dsa, glm4v_moe <tool_call> 블록 안의 NAME<arg_key>KEY</arg_key><arg_value>VALUE</arg_value> 쌍
LongCat-Flash longcat_flash, longcat_flash_ngram <longcat_tool_call>과 <longcat_arg_key> / <longcat_arg_value> 쌍
Harmony GPT-OSS <\|channel\|>commentary to=functions.NAME ... <\|call\|>

logprobs를 요청하는 경우, 억제된 구분자 토큰마다 빈 delta.content ("")와 채워진 logprobs 필드를 가진 SSE 청크가 추가로 전송됩니다. 이 플레이스홀더 청크는 생성된 토큰과 SSE 위치 간의 일대일 대응을 유지하는데 모델이 병렬 툴 콜을 순차 생성하는 시나리오에서 logprobs 항목을 SSE 위치로 인덱싱할 때 정확한 정렬을 보장합니다.

curl 예시

비스트리밍:

curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3-8b",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "What is 2 + 2?"}
    ],
    "max_tokens": 100,
    "temperature": 0.7
  }'

스트리밍:

curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  --no-buffer \
  -d '{
    "model": "qwen3-8b",
    "messages": [{"role": "user", "content": "Count to 5."}],
    "stream": true
  }'

이미지 포함 (VLM):

curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen2.5-vl-7b",
    "messages": [
      {
        "role": "user",
        "content": [
          {"type": "text", "text": "Describe this image."},
          {"type": "image_url", "image_url": {"url": "file:///tmp/photo.jpg"}}
        ]
      }
    ]
  }'

mlxcel 확장 필드

mlxcel은 응답 본문에 OpenAI에 없는 필드를 몇 개 더합니다. 각각은 선택 필드이고 로드된 모델이 만들어 내지 않으면 생략되므로, 표준 클라이언트는 달라진 wire shape를 볼 일이 없습니다.

필드 위치 만드는 주체
choices[].message.reasoning_content 비스트리밍 chat completions 별도 reasoning 채널을 방출하는 모델. vLLM과 같은 규약입니다
choices[].message.florence2_result 비스트리밍 chat completions Florence-2 전용
choices[].message.reasoning_only 비스트리밍 chat completions 생성한 토큰이 모두 추론 채널에 남아 content가 비었을 때 true. 예를 들어 thinking 블록이 max_tokens를 다 쓴 경우입니다
choices[].delta.reasoning_only finish_reason을 담은 스트리밍 청크 콘텐츠를 하나도 내보내지 않은 스트림에서 같은 조건일 때

florence2_result는 Florence-2 태스크 답변의 구조화된 형태를 담아, 클라이언트가 message.content의 포맷된 문자열을 다시 파싱하지 않아도 되게 합니다. 객체는 {"task": "<OD>", "kind": "bboxes" | "text" | "quad_boxes" | "polygons" | "bboxes_or_polygons", ...} 형태이고 좌표 배열은 upstream mlx-vlm 키 이름(bboxes, quad_boxes, polygons, labels, bboxes_labels, polygons_labels)을 씁니다. 좌표는 원본 이미지 크기 기준의 픽셀입니다. 스트리밍 chat 응답과 /v1/responses는 렌더링된 텍스트만 반환합니다.

reasoning_only는 콘텐츠가 있는 응답, 도구 호출로 끝난 응답(이때 빈 content는 정상), 아무것도 생성하지 않은 응답에서는 생략됩니다. 새 finish_reason 값이 아니라 별도 불리언이므로 finish_reason을 열거형으로 검증하는 클라이언트에 영향을 주지 않으며, finish_reason은 length 또는 stop으로 유지됩니다.


POST /v1/completions

레거시 텍스트 완성 엔드포인트입니다. 메시지 배열 대신 원시 프롬프트 문자열을 받습니다. 채팅 템플릿 없이 명령 수행에 적합합니다.

요청 본문

{
  "model": "string",
  "prompt": "string",
  "stream": false,
  "stream_options": {
    "include_usage": true
  },
  "max_tokens": 512,
  "temperature": 0.7,
  "top_p": 0.9,
  "top_k": 40,
  "min_p": 0.05,
  "repetition_penalty": 1.1,
  "seed": null,
  "stop": ["string"],
  "frequency_penalty": 0.0,
  "presence_penalty": 0.0,
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "result",
      "schema": {"type": "object", "properties": {"value": {"type": "string"}}, "required": ["value"]}
    }
  }
}

샘플링 파라미터는 /v1/chat/completions와 동일합니다. response_format 필드는 위 구조화 출력 섹션과 동일한 형태를 따릅니다. type: "json_schema"는 제약 디코딩을 활성화하고 type: "text" 또는 미설정은 일반 생성을 의미하며 type: "json_object"는 HTTP 400으로 거부됩니다.

비스트리밍 응답

{
  "id": "cmpl-abc123",
  "object": "text_completion",
  "created": 1715000000,
  "model": "qwen3-8b",
  "system_fingerprint": null,
  "choices": [
    {
      "index": 0,
      "text": "The capital of France is Paris.",
      "finish_reason": "stop",
      "logprobs": null
    }
  ],
  "usage": {
    "prompt_tokens": 8,
    "completion_tokens": 7,
    "total_tokens": 15
  }
}

스트리밍 응답

/v1/chat/completions와 동일한 SSE 형식입니다. delta 필드에는 "content" 대신 "text"가 포함됩니다.

curl 예시

curl http://localhost:8080/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3-8b",
    "prompt": "The capital of France is",
    "max_tokens": 20,
    "temperature": 0
  }'

OpenAI Responses API (/v1/responses)

mlxcel은 OpenAI Responses API를 1급 엔드포인트로 노출하며 OpenAI Python SDK의 client.responses.create(...) 호출이 chat-completions 어댑터 없이 mlxcel-server에 그대로 동작합니다.

엔드포인트

메서드 경로 설명
POST /v1/responses 응답 생성 (동기 또는 SSE 스트리밍).
GET /v1/responses/{id} 저장된 응답 조회.
DELETE /v1/responses/{id} 저장된 응답 삭제.
POST /v1/responses/{id}/cancel 응답을 'cancelled' 상태로 표시.
POST /v1/responses/compact 이전 턴을 요약해 compaction 출력 item 하나로 만듭니다.
POST /v1/responses/input_tokens 요청 본문을 렌더링한 입력의 토큰 수를 셉니다.

/v1 접두사 없는 별칭(/responses, /responses/{id}, /responses/{id}/cancel, /responses/compact, /responses/input_tokens)도 함께 마운트되어 있어서 기존 /chat/completions 별칭 정책과 동일하게 동작합니다.

빠른 예제

curl http://127.0.0.1:8080/v1/responses \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "qwen3-0.6b-4bit",
    "input": "Reply with: hello",
    "max_output_tokens": 64,
    "thinking_budget_tokens": 0
  }'
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="sk-no-auth")
resp = client.responses.create(
    model="qwen3-0.6b-4bit",
    input="Reply with: hello",
    max_output_tokens=64,
)
print(resp.output_text)

지원 범위

/v1/responses 엔드포인트는 텍스트 입력(문자열 또는 메시지 item 배열), chat-completions와 동일한 멀티모달 메시지 콘텐츠 파트, function 도구, text.format을 통한 JSON 스키마 구조화 출력, previous_response_id 체이닝, 문자열 형태의 conversation 필드, 그리고 단조 증가 sequence_number를 동반한 형식화된 SSE 스트리밍 이벤트를 지원합니다.

빌트인 도구 타입(web_search, file_search, computer_use_preview, code_interpreter, image_generation, mcp, custom, apply_patch, function_shell)은 400 invalid_request_error로 거부됩니다. 메시지의 input_image 콘텐츠 파트는 받지만 input_file 파트, "disabled"가 아닌 truncation, background: true는 400을 반환합니다.

type 없이 role을 가진 입력 item은 message로 읽고, assistant 메시지는 output_text와 refusal 콘텐츠 파트를 받으므로 클라이언트가 이전 response.output을 그대로 입력으로 다시 보낼 수 있습니다. 생성 프롬프트가 이미 추론 블록을 연 상태로 끝나는 템플릿(<think>\n으로 끝남)에서는 추론이 메시지보다 앞선 reasoning item 안에서 response.reasoning_text.delta 이벤트로 스트리밍됩니다.

압축

POST /v1/responses/compact는 input에 담긴 전체 이력을 받아 {"id", "object": "response.compaction", "created_at", "model", "output", "usage"}를 반환합니다. output에는 유지한 system·developer 메시지, 보존한 item, 그리고 compaction item 하나가 들어 있으며 이 item의 encrypted_content는 base64url 캡슐입니다(암호화되지 않았고 요약과 보존 item을 담습니다). 아무것도 저장하지 않습니다. 보존 대상은 마지막 user 메시지와 keep_tokens 안에 들어가는 최신 완결 턴이며(기본값은 컨텍스트 창의 1/16, 최대 8192), 분할 지점은 user 턴 앞이나 완료된 도구 호출 묶음 뒤로 한정되므로 function call이 그 출력과 떨어지지 않습니다. 요약은 비스트리밍 생성 한 번이며 max_output_tokens로 제한합니다(기본값 1024, 1에서 16384). stream, previous_response_id, conversation은 거부하고 keep_tokens는 0보다 커야 합니다. 이력이 컨텍스트 창 하나보다 길거나, 요약이 들어가지 않거나, 결과가 입력보다 짧아지지 않으면 코드 context_length_exceeded로 400을 반환하고 아무것도 바꾸지 않습니다.

/v1/responses와 /v1/responses/input_tokens는 compaction 입력 item을 받아 재생하며 previous_response_id 체인에서도 마찬가지입니다.

/v1/responses는 context_management: [{"type": "compaction", "compact_threshold": N}](N은 1에서 100000000)도 받습니다. /v1/responses/input_tokens와 같은 방식으로 센 렌더링 입력이 N에 도달하거나 출력 공간이 남지 않으면 생성 전에 위와 같이 이력을 압축하고 output은 compaction item으로 시작합니다. 스트리밍 요청은 response.in_progress 뒤에 mlx.compaction.started와 mlx.compaction.completed(input_tokens_before, input_tokens_after 포함)를 보냅니다. context_management가 있는 요청은 생성 길이가 프롬프트 뒤에 남은 컨텍스트로 제한되고, length로 멈추면 status: "incomplete"와 이유 max_output_tokens를 반환합니다.

CLI 플래그

플래그 기본값 환경 변수
--responses-store-max-entries 1024 LLAMA_ARG_RESPONSES_STORE_MAX_ENTRIES
--responses-store-ttl-secs 3600 LLAMA_ARG_RESPONSES_STORE_TTL_SECS
--conversation-store-max-entries 256 LLAMA_ARG_CONVERSATION_STORE_MAX_ENTRIES
--conversation-store-ttl-secs 3600 LLAMA_ARG_CONVERSATION_STORE_TTL_SECS

저장소 최대 크기를 0으로 두면 응답 저장 기능이 비활성화되고 GET/DELETE/cancel과 previous_response_id를 사용하는 요청은 모두 400을 반환합니다.

요청·응답 필드 레퍼런스 전체와 스트리밍 이벤트 목록, /v1/chat/completions에서의 마이그레이션 가이드는 OpenAI Responses API를 참조하십시오.


Anthropic Messages API (/v1/messages)

mlxcel은 Anthropic Messages API를 제공하므로, 공식 Anthropic SDK(client.messages.create(...))를 chat-completions 변환 계층 없이 mlxcel-server에 그대로 사용할 수 있습니다.

엔드포인트

메서드 경로 설명
POST /v1/messages 메시지 생성 (동기 또는 SSE 스트리밍).
POST /v1/messages/count_tokens 요청이 소비할 프롬프트 토큰 수 계산.

/v1 접두사가 없는 별칭(/messages, /messages/count_tokens)도 함께 마운트됩니다.

빠른 예시

curl http://127.0.0.1:8080/v1/messages \
  -H 'Content-Type: application/json' \
  -H 'anthropic-version: 2023-06-01' \
  -d '{
    "model": "qwen3-0.6b-4bit",
    "max_tokens": 64,
    "system": "Be terse.",
    "messages": [{"role": "user", "content": "Reply with: hello"}]
  }'
from anthropic import Anthropic

client = Anthropic(base_url="http://127.0.0.1:8080", api_key="sk-no-auth")
msg = client.messages.create(
    model="qwen3-0.6b-4bit",
    max_tokens=64,
    messages=[{"role": "user", "content": "Reply with: hello"}],
)
print(msg.content[0].text)

지원 범위

최상위 system(문자열 또는 text 블록 배열), messages, max_tokens, stop_sequences, temperature/top_p/top_k, function tools 및 tool_choice, thinking을 지원합니다. 콘텐츠 블록은 text, image(base64 및 url 소스), tool_use, tool_result을 지원하며 tool_result 블록 내부의 이미지 콘텐츠도 포함하므로 비전 모델이 도구가 생성한 이미지를 소비할 수 있습니다. 스트리밍은 Anthropic SSE 이벤트 시퀀스(message_start, content_block_start/delta/stop, message_delta, message_stop)를 방출하며 usage 토큰 수는 비스트리밍 응답과 스트리밍 message_delta 양쪽에 채워집니다.

Anthropic 서버 도구(web_search, code_execution 등 input_schema가 없는 도구)는 수락되지만 무시됩니다. 로컬 모델 서버는 이를 실행할 수 없기 때문입니다. 알 수 없는 최상위 필드(metadata, container, service_tier 등)는 수락되고 무시됩니다.

extended thinking을 켠 경우, 생성 프롬프트가 이미 추론 블록을 연 상태로 끝나는 템플릿은 그 추론을 thinking 콘텐츠 블록으로 반환합니다.

요청·응답 필드 레퍼런스 전체, 콘텐츠 블록 매핑(tool_result 내부 이미지 포함), 스트리밍 이벤트 목록, 토큰 카운트 엔드포인트는 Anthropic Messages API를 참조하십시오.


POST /completion

llama-server 호환 native completion입니다. 응답에서 content, stop, tokens_predicted, tokens_evaluated, timings를 기대하는 llama.cpp client를 마이그레이션할 때 사용합니다.

curl http://localhost:8080/completion \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"The capital of France is","n_predict":32,"temperature":0,"stream":false}'

요청은 prompt, n_predict, stream, temperature, top_k, top_p, min_p, repeat_penalty, repeat_last_n, stop, seed, frequency/presence penalty, DRY 설정, p_less, cache_prompt, 세 가지 thinking-budget alias를 받습니다. Structured response_format은 no-op {"type":"text"} 형식을 제외하면 400을 반환합니다. JSON-schema constrained decoding에는 /v1/chat/completions를 사용하십시오.


OpenAI 오디오 API (/v1/audio)

서버는 POST /v1/audio/speech, /v1/audio/transcriptions, /v1/audio/translations와 /v1 없는 alias를 mount합니다. Whisper와 Kokoro는 audio-only server process에서 실행합니다. 체크포인트 설정, multipart/JSON schema, WAV 형식, 25 MiB upload 제한, queue 제어, 오류는 OpenAI 오디오 API를 참조하십시오.


Realtime VoiceChat WebSocket (/v1/realtime)

서버가 Nemotron VoiceChat 체크포인트를 로드하면 전이중 음성용 WebSocket 업그레이드 경로로 GET /v1/realtime을 마운트하고, chat과 completion 경로에는 501로 응답합니다. 세션 프로토콜, 이벤트, 제한은 Realtime VoiceChat API를 참고하십시오.


OpenAI 이미지 API (/v1/images)

서버를 --image-model(또는 MLXCEL_IMAGE_MODEL)로 시작하면 POST /v1/images/generations와 POST /v1/images/edits, 그리고 별칭 /images/generations와 /images/edits를 마운트합니다. 이미지 워커는 임베딩 워커처럼 시작 시 로드되고 한 번에 요청 하나를 처리하며, 큐가 가득 차면 503, 요청별 타임아웃이면 504로 응답합니다. 플래그가 없으면 두 경로 모두 501로 응답하며 그 플래그를 안내합니다. 본문은 JSON만 받으며 multipart 업로드는 제공하지 않습니다. response_format은 b64_json만, output_format은 png만 받습니다. url, path, output_path, output_dir은 서버가 클라이언트 대신 파일을 쓰지 않으므로 400을 반환합니다. 성공 응답은 {"created", "data": [{"b64_json", "mime_type", "width", "height", "seed", "revised_prompt"}], "output_format", "size"}입니다. 체크포인트 패밀리, 요청 필드, 오류 구분은 이미지 생성을 참조하십시오.


POST /tokenize

로드된 모델의 토크나이저를 사용하여 텍스트 문자열을 토큰화합니다. 생성 요청을 제출하기 전에 토큰 수를 세는 데 유용합니다.

요청 본문

{
  "content": "Hello, world!",
  "add_special": false
}
필드 타입 설명
content string 토큰화할 텍스트
add_special boolean BOS 토큰 앞에 추가 / EOS 토큰 뒤에 추가 여부. 기본값: false

응답

{
  "tokens": [9906, 11, 1917, 0]
}

tokens 배열에는 부호 있는 32비트 정수 토큰 ID가 포함됩니다.

curl 예시

curl http://localhost:8080/tokenize \
  -H "Content-Type: application/json" \
  -d '{"content": "Hello, world!", "add_special": false}'

POST /detokenize

토큰 ID 시퀀스를 텍스트 문자열로 변환합니다.

요청 본문

{
  "tokens": [9906, 11, 1917, 0]
}
필드 타입 설명
tokens array of integers 디코딩할 토큰 ID. 64비트 정수로 읽으므로 32비트 범위를 넘는 id도 어휘 범위 검사까지 도달합니다

응답

{
  "content": "Hello, world!"
}

curl 예시

curl http://localhost:8080/detokenize \
  -H "Content-Type: application/json" \
  -d '{"tokens": [9906, 11, 1917, 0]}'

음수 id나 어휘 범위를 벗어난 id가 있으면 500, type server_error와 함께 token id {id} is out of vocabulary range (vocab_size {n}) 메시지를 반환하며, 메시지에는 요청 순서상 첫 번째로 잘못된 id가 들어갑니다. 디코딩 전에 검사하므로 일부만 디코딩한 텍스트는 반환하지 않습니다. 상태 코드와 오류 타입은 llama-server와 같습니다.


GET /v1/models

이 서버 인스턴스에서 사용 가능한 모델을 나열합니다. mlxcel은 시작 시 단일 모델을 로드하므로, 이 엔드포인트는 항상 하나의 항목이 포함된 목록을 반환합니다.

응답

{
  "object": "list",
  "data": [
    {
      "id": "qwen3-8b",
      "object": "model",
      "created": 1715000000,
      "owned_by": "user"
    }
  ]
}

curl 예시

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

GET /health

헬스 체크 엔드포인트입니다. GET /도 같은 handler를 사용하는 unauthenticated alias이며 API key를 설정해도 두 path는 접근할 수 있습니다. 서버 활성 상태, 모델 로딩 상태, 현재 배치 지표, 컨텍스트 창 크기, 툴 콜 파서 가용 여부를 보고합니다.

응답

모델이 로드되고 용량이 있는 경우 (HTTP 200):

{
  "status": "ok",
  "model": "qwen3-8b",
  "batch": {
    "active_sequences": 2,
    "queue_depth": 0,
    "max_batch_size": 4
  },
  "observability": {
    "sequences_started": 42,
    "sequences_completed": 40,
    "total_prefill_tokens": 8192,
    "total_decode_tokens": 4096,
    "decode_steps_processed": 256,
    "prefill_chunks_processed": 42,
    "current_batch_size": 2,
    "cache_pool_active": 2
  },
  "context_size": 4096,
  "tool_call_parser": "mlxcel"
}

모든 슬롯이 사용 중인 경우 (HTTP 503):

{
  "status": "no slot available",
  "model": "qwen3-8b",
  "batch": { ... },
  "observability": { ... },
  "context_size": 4096,
  "tool_call_parser": "mlxcel"
}

모델이 아직 로딩 중인 경우 (HTTP 503):

{
  "status": "loading model",
  "tool_call_parser": null
}

응답 필드

필드 타입 설명
status string "ok", "no slot available", 또는 "loading model"
model string 로드된 모델의 표시 이름. 로딩 중에는 표시되지 않음.
batch object 배치 큐와 슬롯 지표. 로딩 중에는 표시되지 않음.
observability object 누적 생성 카운터. 로딩 중에는 표시되지 않음.
context_size integer 슬롯별 실제 컨텍스트 윈도우. 명시적 --ctx-size와 여러 active slot을 함께 쓰면 나뉜 슬롯별 값입니다. 0은 모델 기본값 적용을 의미합니다. 로딩 중에는 표시되지 않음.
tool_call_parser string|null 채팅 템플릿이 툴 콜을 지원하면 "mlxcel", 지원하지 않으면 null. 모델 로드 후 항상 표시됨.

curl 예시

curl -i http://localhost:8080/health

프롬프트 캐시 엔드포인트

프롬프트 캐시 관측 및 리셋 엔드포인트는 항상 마운트됩니다. 프롬프트 캐시가 비활성화되어 있으면 안정적인 disabled payload를 반환하므로, 모니터링 클라이언트가 조건부 라우팅을 할 필요가 없습니다.

GET /v1/cache/stats

현재 프롬프트 캐시 스냅샷을 반환합니다:

{
  "enabled": true,
  "apc_enabled": false,
  "block_size": 16,
  "hash": "sha256",
  "entries": 12,
  "bytes": 104857600,
  "capacity_bytes": 2147483648,
  "max_entries": 1024,
  "hits": 42,
  "lookups": 60,
  "hit_rate": 0.7,
  "inserts": 18,
  "evictions_lru": 0,
  "evictions_ttl": 0,
  "rejections_oversized": 0,
  "total_blocks_stored": 0,
  "unique_block_hashes": 0,
  "apc_active_entries": 0
}

POST /v1/cache/reset

실시간 프롬프트 캐시 항목을 모두 지우고 해제한 스냅샷을 반환합니다. 이 호출은 멱등적입니다.

{
  "cleared": true,
  "freed_bytes": 104857600,
  "freed_entries": 12
}

GET /metrics

텍스트 형식의 Prometheus 호환 지표입니다. --metrics 또는 --metrics-port로 서버를 시작하면 활성화됩니다. 그렇지 않으면 /metrics 라우트가 마운트되지 않습니다.

mlxcel_requests_total은 완료된 생성 요청을 셉니다. 채팅 완성은 스트리밍 여부와 관계없이 한 번 세며, 스트리밍 요청은 생성이 끝날 때 기록하고 클라이언트가 취소한 채팅 완성 스트림은 제외합니다. mlxcel_completion_tokens_total은 그렇게 센 요청의 완성 토큰을 합산합니다. 스트리밍 /v1/completions와 네이티브 /completion 요청은 기록되지 않습니다.

응답

Content-Type: text/plain; version=0.0.4; charset=utf-8
# HELP mlxcel_requests_total Total number of completed generation requests; chat completion streams cancelled by the client are excluded
# TYPE mlxcel_requests_total counter
mlxcel_requests_total 42

# HELP mlxcel_prompt_tokens_total Total prompt tokens processed
# TYPE mlxcel_prompt_tokens_total counter
mlxcel_prompt_tokens_total 8192

# HELP mlxcel_completion_tokens_total Total completion tokens of the requests counted in mlxcel_requests_total
# TYPE mlxcel_completion_tokens_total counter
mlxcel_completion_tokens_total 4096

# HELP mlxcel_generation_time_seconds_total Total generation time in seconds
# TYPE mlxcel_generation_time_seconds_total counter
mlxcel_generation_time_seconds_total 12.345

# HELP mlxcel_slots_total Total number of parallel slots
# TYPE mlxcel_slots_total gauge
mlxcel_slots_total 4

# HELP mlxcel_slots_available Available parallel slots
# TYPE mlxcel_slots_available gauge
mlxcel_slots_available 2

# HELP mlxcel_queue_depth Current prefill queue depth
# TYPE mlxcel_queue_depth gauge
mlxcel_queue_depth 0

# HELP mlxcel_batch_sequences_started Total sequences that entered prefill
# TYPE mlxcel_batch_sequences_started counter
mlxcel_batch_sequences_started 42

# HELP mlxcel_batch_sequences_completed Total sequences that completed generation
# TYPE mlxcel_batch_sequences_completed counter
mlxcel_batch_sequences_completed 40

# HELP mlxcel_batch_prefill_tokens_total Cumulative prefill tokens processed
# TYPE mlxcel_batch_prefill_tokens_total counter
mlxcel_batch_prefill_tokens_total 8192

# HELP mlxcel_batch_decode_tokens_total Cumulative decode tokens generated
# TYPE mlxcel_batch_decode_tokens_total counter
mlxcel_batch_decode_tokens_total 4096

# HELP mlxcel_batch_current_size Current active batch size
# TYPE mlxcel_batch_current_size gauge
mlxcel_batch_current_size 2

# HELP mlxcel_cache_pool_active Active cache entries
# TYPE mlxcel_cache_pool_active gauge
mlxcel_cache_pool_active 2

curl 예시

curl http://localhost:8080/metrics

GET /slots

슬롯별 상태 엔드포인트입니다. --slots로 서버를 시작하면 활성화됩니다(명시적으로 비활성화하지 않는 한). 각 디코드 슬롯의 상태와 대기 중인 요청을 보고합니다.

응답

[
  {
    "id": 0,
    "state": "decoding",
    "model": "qwen3-8b",
    "context_size": 8192,
    "is_processing": true
  },
  {
    "id": 1,
    "state": "idle",
    "model": "qwen3-8b",
    "context_size": 8192,
    "is_processing": false
  },
  {
    "id": 2,
    "state": "queued",
    "model": "qwen3-8b",
    "context_size": 8192,
    "is_processing": false
  }
]

context_size는 /health가 보고하는 값과 같은 슬롯별 실제 컨텍스트 윈도우입니다.

슬롯 상태:

상태 의미
decoding 슬롯이 활발하게 토큰을 생성 중
idle 슬롯이 새 시퀀스를 위해 대기 중
queued 요청이 프리필 큐에서 대기 중

curl 예시

curl http://localhost:8080/slots

GET /props

서버 속성 엔드포인트입니다. 현재 기본 생성 설정과 전체 슬롯 수를 반환합니다. llama-server의 /props 형식과 호환됩니다.

응답

{
  "default_generation_settings": {
    "n_predict": 512,
    "temperature": 0.7,
    "top_k": 40,
    "top_p": 0.9,
    "min_p": 0.05,
    "repeat_penalty": 1.1,
    "repeat_last_n": 64,
    "seed": 18446744073709551615,
    "frequency_penalty": 0.0,
    "presence_penalty": 0.0,
    "dry_multiplier": 0.0,
    "dry_base": 1.75,
    "dry_allowed_length": 2,
    "dry_penalty_last_n": 0
  },
  "total_slots": 4
}

seed 필드는 시드가 설정되지 않으면 u64::MAX(18446744073709551615)로 기본 설정되며 이는 비결정적 샘플링을 의미합니다.

curl 예시

curl http://localhost:8080/props

오류 코드

모든 오류 응답은 OpenAI 오류 형식을 따릅니다:

{
  "error": {
    "message": "human-readable description",
    "type": "error_type_string",
    "code": null
  }
}
HTTP 상태 오류 타입 발생 조건
400 Bad Request invalid_request_error 잘못된 JSON, 필수 필드 누락, 토큰화 오류
401 Unauthorized missing_api_key / invalid_api_key API 키 누락 또는 올바르지 않음
501 Not Implemented not_implemented 해당 경로를 처리할 모델이 서버에 없음. 예: Nemotron VoiceChat 체크포인트를 로드한 서버의 chat·completion 경로(/v1/realtime으로만 서비스). 메시지에 서버가 처리하는 경로가 나옴
501 Not Implemented not_supported_error 로드한 모델이 해당 기능을 가질 수 없음. 예: fill-in-the-middle 토큰이 없는 체크포인트에 대한 POST /infill
503 Service Unavailable server_busy 프리필 큐가 가득 찼거나 모델 로딩 중이거나 헬스 체크에서 사용 가능한 슬롯이 없음
503 Service Unavailable request_memory_budget_exceeded Metal 텍스트 전용 Nemotron-H 요청의 추정 메모리가 요청 메모리 예산을 넘음(아래 참고)
500 Internal Server Error server_error 내부 생성 오류, 또는 /detokenize에 어휘 범위를 벗어난 id

503 응답 예시:

{
  "error": {
    "message": "All slots are busy. Please try again later.",
    "type": "server_busy",
    "code": null
  }
}

수용 제어

mlxcel은 모든 생성 엔드포인트에 큐 깊이 수용 제어를 적용합니다. SSE 스트림을 열거나 생성을 실행하기 전에 서버는 프리필 큐에 용량이 있는지 확인합니다. 용량이 없으면 연결을 수락하고 대기하는 대신 즉시 HTTP 503을 반환합니다.

따라서 클라이언트는 503 응답을 백프레셔 신호로 처리하고 지수 백오프를 적용한 재시도 로직을 구현해야 합니다.

요청 메모리 수용 제어 (Metal Nemotron-H)

Metal에서 텍스트 전용 Nemotron-H 체크포인트는 활성 슬롯 하나로 실행되며, 요청마다 메모리 사용량을 보수적으로 추정해 수용 여부를 정합니다. 추정치가 예산을 넘는 요청은 503을 받고, type과 code는 request_memory_budget_exceeded이며 estimated_bytes와 budget_bytes 필드가 추가됩니다. 스트리밍 요청도 SSE 헤더를 보내기 전에 같은 방식으로 거부됩니다. 예산은 장치 권장 working set, 설정된 wired 메모리 상한, MLX allocator 상한 중 가장 작은 값입니다. MLXCEL_REQUEST_MEMORY_BUDGET(예: 12G 같은 바이트 크기)으로는 예산을 낮추기만 할 수 있고, 0이나 none 같은 잘못된 값을 주면 서버가 시작되지 않습니다.

{
  "error": {
    "message": "request_memory_budget_exceeded: estimated_bytes=..., budget_bytes=...; shorten the prompt/output or lower retained cache usage",
    "type": "request_memory_budget_exceeded",
    "code": "request_memory_budget_exceeded",
    "estimated_bytes": 2048,
    "budget_bytes": 1024
  }
}