Inference API#

Antfly inference is a local inference server for ONNX-based ML models.

What is Antfly inference#

Antfly inference provides local ML inference with an OpenAI-compatible API:

  • Embedding Generation: OpenAI-compatible text embedding endpoints, with dense or sparse embedding payloads
  • Text Chunking: Semantic chunking with ONNX models or fixed-size fallback
  • Reranking: Relevance re-scoring for search results
  • Extraction: Schema-driven entity, relation, classification, and structured extraction
  • Text Rewriting: Transform text using Seq2Seq models (question generation, query generation, etc.)

Download the latest release at https://antfly.io/docs/downloads

When to Use Antfly inference#

Antfly inference can run standalone or as part of an Antfly cluster:

  • Local ONNX model inference without external API dependencies
  • OpenAI-compatible embedding payloads served at /ai/v1/embed and /ai/v1/embeddings
  • Semantic text chunking for RAG pipelines
  • Relevance reranking for improved search quality
  • Centralized model serving across distributed nodes
  • Privacy-preserving ML inference (data never leaves your infrastructure)

Authentication#

The standalone inference listener speaks plaintext HTTP and does not provide built-in authentication. It refuses non-loopback binds by default. Keep the loopback default, or put it behind a trusted proxy that terminates TLS and authenticates every request; only then use --allow-insecure-public-bind to opt into a non-loopback listener. When authentication is enabled, the unified Antfly server requires a valid principal with inference/* read permission for /ai/v1 and /ml/v1. Global */* read or admin grants also satisfy this requirement through the existing wildcard policy.

Features#

Embedding Generation#

  • Models: ONNX models auto-discovered from {models_dir}/embedders/
  • API: OpenAI-compatible payloads served at /ai/v1/embed and /ai/v1/embeddings
  • Response Shape: Each data[i].embedding is either a dense float vector or a sparse {indices, values} object

Text Chunking#

  • Models: Fixed-size chunking (always available) + ONNX models
  • Model Discovery: Auto-discovers models from {models_dir}/chunkers/
  • Caching: 2-minute TTL memory cache
  • Fallback: Falls back to fixed chunking if model fails

Reranking#

  • Model Discovery: Auto-discovers ONNX models from {models_dir}/rerankers/
  • Quantization: Automatically uses quantized models if available
  • Input: Pre-rendered text prompts (client handles field extraction)

Create embeddings (alias of /embeddings)#

POST/embed

Alias of /ai/v1/embeddings.

Accepts the OpenAI embeddings request shape and returns the same OpenAI-compatible response envelope. For sparse-capable models, data[i].embedding is a sparse vector object instead of a dense float array. Dense image inputs are header-validated and admitted against the aggregate decoded-pixel budget before model loading. Remote URL byte potential is reserved before fetch; inline sources use their actual encoded size.

Request Body#

Example:

{
  "model": "string",
  "input": "string",
  "encoding_format": "float",
  "dimensions": 0,
  "task_type": "RETRIEVAL_QUERY",
  "input_type": "search_query",
  "error_policy": "fail_fast"
}

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/embed" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "string",
  "input": "string",
  "encoding_format": "float",
  "dimensions": 0,
  "task_type": "RETRIEVAL_QUERY",
  "input_type": "search_query",
  "error_policy": "fail_fast"
}'

Responses#

{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "embedding": [
        0
      ],
      "index": 0
    }
  ],
  "model": "string",
  "usage": {
    "prompt_tokens": 0,
    "total_tokens": 0
  },
  "errors": [
    {
      "index": 0,
      "code": "string",
      "message": "string",
      "stage": "parse",
      "retryable": true,
      "status": 0,
      "retry_after_ms": 0
    }
  ],
  "summary": {
    "total": 0,
    "succeeded": 0,
    "failed": 0
  }
}

Chunk text into smaller segments#

POST/chunk

Splits text into smaller chunks using semantic or fixed-size chunking models.

Models#

Fixed Chunking (always available)#

  • Simple token-based splitting with overlap
  • Use model="fixed"
  • Fast and deterministic

ONNX Models#

  • Semantic chunking based on content similarity
  • Models auto-discovered from models_dir/chunkers/
  • Falls back to fixed chunking if model fails

Caching#

Results are cached in memory for 2 minutes. Cache key includes both config and text content.

Request Body#

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/chunk"

Responses#

{
  "object": "list",
  "data": [
    {
      "object": "chunk",
      "index": 0,
      "id": 0,
      "text": "This is the first chunk...",
      "start_char": 0,
      "end_char": 100,
      "mime_type": "text/plain"
    },
    {
      "object": "chunk",
      "index": 1,
      "id": 1,
      "text": "This is the second chunk...",
      "start_char": 90,
      "end_char": 190,
      "mime_type": "text/plain"
    }
  ],
  "model": "fixed",
  "usage": {
    "prompt_tokens": 12,
    "completion_tokens": 0,
    "total_tokens": 12
  },
  "cache_hit": false
}

Rerank multimodal documents by relevance#

POST/rerank_multimodal

Re-scores multimodal documents based on relevance to a text query.

This endpoint accepts the same content-part image conventions as generation and embedding. Text-only requests can be served immediately. Image-bearing requests reserve the stable contract for native ColQwen-style late-interaction reranking as that encoder lands. Image-bearing requests already run native Zig image preprocessing and grid preparation. Remote URL byte potential is reserved before fetch, and image headers plus aggregate decoded pixels are admitted before model loading.

Request Body#

Example:

{
  "model": "vidore/colqwen2-v1.0",
  "query": "invoice total due date",
  "documents": [
    {
      "id": "string",
      "content": null
    }
  ]
}

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/rerank_multimodal" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "vidore/colqwen2-v1.0",
  "query": "invoice total due date",
  "documents": [
    {
      "id": "string",
      "content": null
    }
  ]
}'

Responses#

{
  "object": "list",
  "data": [
    {
      "object": "rerank.score",
      "index": 0,
      "score": 0
    }
  ],
  "model": "string",
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "total_tokens": 0,
    "cached_prompt_tokens": 0
  }
}

Rerank prompts by relevance#

POST/rerank

Re-scores pre-rendered text prompts based on relevance to a query using native or ONNX reranking models.

Client Responsibilities#

The client must:

  1. Extract relevant fields from documents
  2. Render any templates
  3. Send pre-rendered text strings as prompts

This design keeps inference stateless and allows clients to customize rendering logic.

Models#

  • Models are auto-discovered from models_dir/rerankers/
  • Cross-encoder rerankers are supported through the existing text scorer
  • Late-interaction text rerankers such as ColBERT can opt in with model_manifest.json capability late_interaction or colbert
  • Supports quantized models (model_quantized.onnx)
  • Automatically prefers quantized variants if available

This endpoint is still text-only. Real ColQwen-style multimodal reranking requires a future request shape that carries page images or image-derived embeddings.

For document-based reranking with field extraction, use the client-side lib/reranking package which handles rendering before calling this endpoint.

Request Body#

Example:

{
  "model": "BAAI/bge-reranker-v2-m3",
  "query": "machine learning applications",
  "prompts": [
    "Introduction to machine learning...",
    "Deep learning fundamentals..."
  ]
}

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/rerank" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "BAAI/bge-reranker-v2-m3",
  "query": "machine learning applications",
  "prompts": [
    "Introduction to machine learning...",
    "Deep learning fundamentals..."
  ]
}'

Responses#

{
  "object": "list",
  "data": [
    {
      "object": "rerank.score",
      "index": 0,
      "score": 0
    }
  ],
  "model": "string",
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "total_tokens": 0,
    "cached_prompt_tokens": 0
  }
}

Generate text using LLM (OpenAI-compatible)#

POST/generate

Generates text using local LLM models (e.g., Gemma 3). Fully compatible with the OpenAI Chat Completions API.

Models#

Models are auto-discovered from models_dir/generators/ at startup. Use the /ai/v1/models endpoint to list available models.

Streaming#

Set stream: true to receive Server-Sent Events (SSE) with incremental token deltas. Each event contains a ChatCompletionChunk object. A successful stream ends with data: [DONE]. If generation or stream writing fails after the HTTP response starts, the server instead emits an event: error frame whose data is a plain-text error message, then closes the stream without a [DONE] frame.

Input Format#

Uses OpenAI-compatible chat format with messages array containing role (system, user, assistant) and content. Set stream: true for streaming responses.

Downloaded and inline encoded media is limited cumulatively across the request to the lower of 100 MiB, configured max_download_size_bytes, and—when admission.inference.max_concurrent_requests is positive—16 MiB times that capacity. A zero configured download limit disables nonempty media. Remote URL byte potential is reserved before fetch; inline sources reserve their actual encoded size without adding it to the existing request-body reservation. Accepted image headers are then validated and decoded source pixels are admitted at a conservative 16 bytes per pixel against the lower of 512 MiB or 16 MiB times a positive admission.inference.max_concurrent_requests; a zero concurrency setting still uses the finite 512 MiB ceiling. max_image_dimension limits each source edge. Malformed images return 400, while dimension or aggregate excess returns 413 before model loading. Initial capacity admission occurs before media fetch, so an overloaded server returns 503 without fetching content.

Request Body#

Example:

{
  "model": "google/gemma-3-1b-it",
  "messages": [
    {
      "role": null,
      "content": null,
      "tool_calls": [
        null
      ],
      "tool_call_id": "string"
    }
  ],
  "max_tokens": 256,
  "temperature": 0.7,
  "top_p": 0,
  "top_k": 0,
  "stream": true,
  "stream_options": {
    "include_usage": true
  },
  "chat_template_kwargs": {
    "enable_thinking": true
  },
  "ignore_eos": true,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get the current weather in a location",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "The city and state, e.g. San Francisco, CA"
            }
          },
          "required": [
            "location"
          ]
        },
        "strict": true
      }
    }
  ],
  "min_p": 0,
  "repetition_penalty": 0,
  "frequency_penalty": -2,
  "presence_penalty": -2,
  "response_format": {
    "type": "text",
    "json_schema": {
      "name": "string",
      "strict": true,
      "schema": {}
    }
  },
  "grammar": "string",
  "draft_model": "string",
  "speculative_k": 1,
  "speculation_policy": "auto",
  "speculation_calibration": "none",
  "cache_dtype": "f16",
  "cache_compaction_ratio": 0,
  "prompt_cache_key": "string",
  "prompt_cache": true,
  "backend": "auto",
  "mode": "eager",
  "compiled_target": "partitioned",
  "tool_choice": "auto"
}

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/generate" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "google/gemma-3-1b-it",
  "messages": [
    {
      "role": null,
      "content": null,
      "tool_calls": [
        null
      ],
      "tool_call_id": "string"
    }
  ],
  "max_tokens": 256,
  "temperature": 0.7,
  "top_p": 0,
  "top_k": 0,
  "stream": true,
  "stream_options": {
    "include_usage": true
  },
  "chat_template_kwargs": {
    "enable_thinking": true
  },
  "ignore_eos": true,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get the current weather in a location",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "The city and state, e.g. San Francisco, CA"
            }
          },
          "required": [
            "location"
          ]
        },
        "strict": true
      }
    }
  ],
  "min_p": 0,
  "repetition_penalty": 0,
  "frequency_penalty": -2,
  "presence_penalty": -2,
  "response_format": {
    "type": "text",
    "json_schema": {
      "name": "string",
      "strict": true,
      "schema": {}
    }
  },
  "grammar": "string",
  "draft_model": "string",
  "speculative_k": 1,
  "speculation_policy": "auto",
  "speculation_calibration": "none",
  "cache_dtype": "f16",
  "cache_compaction_ratio": 0,
  "prompt_cache_key": "string",
  "prompt_cache": true,
  "backend": "auto",
  "mode": "eager",
  "compiled_target": "partitioned",
  "tool_choice": "auto"
}'

Responses#

{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1704123456,
  "model": "string",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": null,
        "content": "string",
        "reasoning_content": "string",
        "tool_calls": [
          null
        ]
      },
      "finish_reason": "stop",
      "logprobs": {}
    }
  ],
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "total_tokens": 0,
    "cached_prompt_tokens": 0
  },
  "speculation": {
    "policy": "string",
    "calibration": "string",
    "decision": "string",
    "disabled_reason": "string"
  }
}

Generate text for a synchronous batch of requests#

POST/generate/batch

Runs multiple non-streaming generation requests as one synchronous batch. Compatible native requests for the same model are executed through the native batched KV decoder; unsupported per-item options are returned as per-item errors without failing sibling requests.

A syntactically valid batch envelope returns HTTP 200 even when some or all items fail. Clients must inspect each item's mutually exclusive response and error fields, plus summary, rather than treating HTTP 200 as success for every item. Fatal service or envelope failures still use non-2xx responses.

This endpoint implements the synchronous form only. Future async durable batching will use the same request item shape with mode: async.

Batch generation is text-only. Image or other multimodal content parts are rejected per item as UNSUPPORTED_MULTIMODAL before media fetch or model loading; other item failures remain independently reported.

Request Body#

Example:

{
  "mode": "sync",
  "requests": [
    {
      "custom_id": "string",
      "body": {
        "model": "google/gemma-3-1b-it",
        "messages": [
          {
            "role": null,
            "content": null,
            "tool_calls": [
              null
            ],
            "tool_call_id": "string"
          }
        ],
        "max_tokens": 256,
        "temperature": 0.7,
        "top_p": 0,
        "top_k": 0,
        "stream": true,
        "stream_options": {
          "include_usage": true
        },
        "chat_template_kwargs": {
          "enable_thinking": true
        },
        "ignore_eos": true,
        "tools": [
          {
            "type": "function",
            "function": {
              "name": "get_weather",
              "description": "Get the current weather in a location",
              "parameters": {
                "type": "object",
                "properties": {
                  "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA"
                  }
                },
                "required": [
                  "location"
                ]
              },
              "strict": true
            }
          }
        ],
        "min_p": 0,
        "repetition_penalty": 0,
        "frequency_penalty": -2,
        "presence_penalty": -2,
        "response_format": {
          "type": "text",
          "json_schema": {
            "name": "string",
            "strict": true,
            "schema": {}
          }
        },
        "grammar": "string",
        "draft_model": "string",
        "speculative_k": 1,
        "speculation_policy": "auto",
        "speculation_calibration": "none",
        "cache_dtype": "f16",
        "cache_compaction_ratio": 0,
        "prompt_cache_key": "string",
        "prompt_cache": true,
        "backend": "auto",
        "mode": "eager",
        "compiled_target": "partitioned",
        "tool_choice": "auto"
      }
    }
  ]
}

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/generate/batch" \
  -H "Content-Type: application/json" \
  -d '{
  "mode": "sync",
  "requests": [
    {
      "custom_id": "string",
      "body": {
        "model": "google/gemma-3-1b-it",
        "messages": [
          {
            "role": null,
            "content": null,
            "tool_calls": [
              null
            ],
            "tool_call_id": "string"
          }
        ],
        "max_tokens": 256,
        "temperature": 0.7,
        "top_p": 0,
        "top_k": 0,
        "stream": true,
        "stream_options": {
          "include_usage": true
        },
        "chat_template_kwargs": {
          "enable_thinking": true
        },
        "ignore_eos": true,
        "tools": [
          {
            "type": "function",
            "function": {
              "name": "get_weather",
              "description": "Get the current weather in a location",
              "parameters": {
                "type": "object",
                "properties": {
                  "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA"
                  }
                },
                "required": [
                  "location"
                ]
              },
              "strict": true
            }
          }
        ],
        "min_p": 0,
        "repetition_penalty": 0,
        "frequency_penalty": -2,
        "presence_penalty": -2,
        "response_format": {
          "type": "text",
          "json_schema": {
            "name": "string",
            "strict": true,
            "schema": {}
          }
        },
        "grammar": "string",
        "draft_model": "string",
        "speculative_k": 1,
        "speculation_policy": "auto",
        "speculation_calibration": "none",
        "cache_dtype": "f16",
        "cache_compaction_ratio": 0,
        "prompt_cache_key": "string",
        "prompt_cache": true,
        "backend": "auto",
        "mode": "eager",
        "compiled_target": "partitioned",
        "tool_choice": "auto"
      }
    }
  ]
}'

Responses#

{
  "object": "generate.batch",
  "data": [
    {
      "custom_id": "string",
      "index": 0,
      "response": {
        "id": "chatcmpl-abc123",
        "object": "chat.completion",
        "created": 1704123456,
        "model": "string",
        "choices": [
          {
            "index": 0,
            "message": {
              "role": null,
              "content": "string",
              "reasoning_content": "string",
              "tool_calls": [
                null
              ]
            },
            "finish_reason": "stop",
            "logprobs": {}
          }
        ],
        "usage": {
          "prompt_tokens": 0,
          "completion_tokens": 0,
          "total_tokens": 0,
          "cached_prompt_tokens": 0
        },
        "speculation": {
          "policy": "string",
          "calibration": "string",
          "decision": "string",
          "disabled_reason": "string"
        }
      },
      "error": {
        "code": "string",
        "message": "string",
        "retryable": true,
        "retry_after_ms": 1
      }
    }
  ],
  "summary": {
    "total": 0,
    "succeeded": 0,
    "failed": 0
  }
}

OpenAI Chat Completions endpoint#

POST/chat/completions

OpenAI-compatible chat completions path for SDKs that call /chat/completions relative to the configured base URL.

Request Body#

Example:

{
  "model": "google/gemma-3-1b-it",
  "messages": [
    {
      "role": null,
      "content": null,
      "tool_calls": [
        null
      ],
      "tool_call_id": "string"
    }
  ],
  "max_tokens": 256,
  "temperature": 0.7,
  "top_p": 0,
  "top_k": 0,
  "stream": true,
  "stream_options": {
    "include_usage": true
  },
  "chat_template_kwargs": {
    "enable_thinking": true
  },
  "ignore_eos": true,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get the current weather in a location",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "The city and state, e.g. San Francisco, CA"
            }
          },
          "required": [
            "location"
          ]
        },
        "strict": true
      }
    }
  ],
  "min_p": 0,
  "repetition_penalty": 0,
  "frequency_penalty": -2,
  "presence_penalty": -2,
  "response_format": {
    "type": "text",
    "json_schema": {
      "name": "string",
      "strict": true,
      "schema": {}
    }
  },
  "grammar": "string",
  "draft_model": "string",
  "speculative_k": 1,
  "speculation_policy": "auto",
  "speculation_calibration": "none",
  "cache_dtype": "f16",
  "cache_compaction_ratio": 0,
  "prompt_cache_key": "string",
  "prompt_cache": true,
  "backend": "auto",
  "mode": "eager",
  "compiled_target": "partitioned",
  "tool_choice": "auto"
}

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "google/gemma-3-1b-it",
  "messages": [
    {
      "role": null,
      "content": null,
      "tool_calls": [
        null
      ],
      "tool_call_id": "string"
    }
  ],
  "max_tokens": 256,
  "temperature": 0.7,
  "top_p": 0,
  "top_k": 0,
  "stream": true,
  "stream_options": {
    "include_usage": true
  },
  "chat_template_kwargs": {
    "enable_thinking": true
  },
  "ignore_eos": true,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get the current weather in a location",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "The city and state, e.g. San Francisco, CA"
            }
          },
          "required": [
            "location"
          ]
        },
        "strict": true
      }
    }
  ],
  "min_p": 0,
  "repetition_penalty": 0,
  "frequency_penalty": -2,
  "presence_penalty": -2,
  "response_format": {
    "type": "text",
    "json_schema": {
      "name": "string",
      "strict": true,
      "schema": {}
    }
  },
  "grammar": "string",
  "draft_model": "string",
  "speculative_k": 1,
  "speculation_policy": "auto",
  "speculation_calibration": "none",
  "cache_dtype": "f16",
  "cache_compaction_ratio": 0,
  "prompt_cache_key": "string",
  "prompt_cache": true,
  "backend": "auto",
  "mode": "eager",
  "compiled_target": "partitioned",
  "tool_choice": "auto"
}'

Responses#

{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1704123456,
  "model": "string",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": null,
        "content": "string",
        "reasoning_content": "string",
        "tool_calls": [
          null
        ]
      },
      "finish_reason": "stop",
      "logprobs": {}
    }
  ],
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "total_tokens": 0,
    "cached_prompt_tokens": 0
  },
  "speculation": {
    "policy": "string",
    "calibration": "string",
    "decision": "string",
    "disabled_reason": "string"
  }
}

Rewrite text using Seq2Seq models#

POST/rewrite

Rewrite/transform text using Seq2Seq models (T5, FLAN-T5, BART, etc.).

Models#

  • Models are auto-discovered from models_dir/rewriters/
  • Seq2Seq models have encoder.onnx, decoder-init.onnx, and decoder.onnx files
  • Compatible with LMQG question generation models

Use Cases#

  • Question Generation: Generate questions from answer-context pairs
  • Query Generation: Generate search queries from documents
  • Paraphrasing: Rewrite text in different words
  • Translation: Translate text between languages

Request Body#

Example:

{
  "model": "lmqg/flan-t5-small-squad-qg",
  "inputs": [
    "Translate to German: Hello, how are you?"
  ]
}

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/rewrite" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "lmqg/flan-t5-small-squad-qg",
  "inputs": [
    "Translate to German: Hello, how are you?"
  ]
}'

Responses#

{
  "object": "list",
  "data": [
    {
      "object": "rewrite",
      "index": 0,
      "texts": [
        "string"
      ]
    }
  ],
  "model": "string",
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "total_tokens": 0,
    "cached_prompt_tokens": 0
  }
}

Read text from images (OCR/document understanding)#

POST/read

Extracts text from images using Vision2Seq models like TrOCR, Donut, Florence-2, or Pix2Struct.

Models#

Models are auto-discovered from models_dir/readers/ at startup. Use the /ai/v1/models endpoint to list available models.

  • TrOCR: Pure OCR for printed/handwritten text
  • Donut: Document understanding with structured output (receipts, forms)
  • Florence-2: Multi-task vision model (OCR, captioning, VQA)
  • Pix2Struct: Visual question answering with natural-language prompts
  • Moondream: Decoder-only vision-language reader that can return text plus optional flattened fields

Task Prompts#

Some models support task prompts for different extraction modes:

  • Donut CORD: <s_cord-v2> for receipt parsing
  • Donut DocVQA: <s_docvqa><s_question>...</s_question><s_answer> for visual QA
  • Florence-2 OCR: <OCR> for text extraction
  • Florence-2 Caption: <CAPTION> for image description
  • Pix2Struct: natural-language questions like What type of document is this?
  • Moondream: natural-language prompts like Describe this image.

Image admission reserves the effective downloaded-byte ceiling at 16 MiB per weighted capacity unit and at least one unit per two declared images. The effective ceiling is the minimum of the configured per-image limit times image count, the read-batch limit (256 MiB by default), and—when admission is bounded—16 MiB times admission.inference.max_concurrent_requests. Admission happens before model resolution or download.

After download, image headers are validated before model loading. Decoded source pixels are admitted at a conservative 16 bytes per pixel against the lower of 512 MiB or 16 MiB times a positive admission.inference.max_concurrent_requests; a zero concurrency setting still uses the finite 512 MiB ceiling. The reservation grows atomically from downloaded-byte admission before inference. max_image_dimension limits each source edge; malformed images return 400 and dimension or aggregate excess returns 413. The same decoded-pixel policy covers generate/chat, dense embedding, multimodal reranking, image /extract, and the embedded read, extract, and dense embedding APIs. Batch generation rejects multimodal content before media fetch.

Request Body#

Example:

{
  "model": "microsoft/trocr-base-printed",
  "images": [
    {
      "url": "data:image/png;base64,iVBORw0KGgo..."
    }
  ],
  "prompt": "What type of document is this?",
  "max_tokens": 256
}

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/read" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "microsoft/trocr-base-printed",
  "images": [
    {
      "url": "data:image/png;base64,iVBORw0KGgo..."
    }
  ],
  "prompt": "What type of document is this?",
  "max_tokens": 256
}'

Responses#

{
  "object": "list",
  "data": [
    {
      "text": "Invoice Total: $123.45",
      "fields": {
        "menu.nm": "Coffee",
        "menu.price": "$3.50",
        "total": "$123.45"
      },
      "regions": [
        {
          "text": "string",
          "bbox": [
            0
          ],
          "confidence": 0,
          "label": "string"
        }
      ]
    }
  ],
  "model": "string",
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "total_tokens": 0,
    "cached_prompt_tokens": 0
  }
}

Transcribe audio to text (speech-to-text)#

POST/transcribe

Transcribes audio to text using Speech2Seq models like Whisper, Wav2Vec2, or HuBERT.

Models#

Models are auto-discovered from models_dir/transcribers/ at startup. Use the /api/models endpoint to list available models.

  • Whisper: OpenAI's Whisper models (multilingual, automatic language detection)
  • Wav2Vec2: Facebook's Wav2Vec 2.0 models (English-focused)
  • HuBERT: Facebook's HuBERT models (self-supervised)

Audio Input#

Audio data should be base64-encoded. Supported formats depend on the model:

  • WAV (recommended - raw PCM)
  • MP3
  • FLAC
  • M4A/AAC

Request Body#

Example:

{
  "model": "openai/whisper-tiny",
  "audio": "string",
  "language": "en"
}

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/transcribe" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "openai/whisper-tiny",
  "audio": "string",
  "language": "en"
}'

Responses#

{
  "object": "list",
  "data": [
    {
      "object": "transcription",
      "index": 0,
      "text": "Hello, how are you today?",
      "language": "en"
    }
  ],
  "model": "string",
  "usage": {
    "prompt_tokens": 0,
    "completion_tokens": 0,
    "total_tokens": 0,
    "cached_prompt_tokens": 0
  }
}

Extract entities, relations, classifications, and structures#

POST/extract

Schema-driven extraction over shared AI content parts. This is the canonical public API for named entity recognition, relation extraction, text/document classification, token classification, and structured document extraction.

Image-backed extraction uses the same byte-reserving and image-count-weighted admission policy as /read, before model resolution or download. Text-only extraction consumes one admission unit.

Request Body#

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/extract"

Responses#

No response body

List available models#

GET/models

Returns lists of available embedding, chunking, reranking, generator, extractor, rewriter, reader, and transcriber models.

Embedders#

  • ONNX models from models_dir/embedders/
  • Quantized variants have -i8 suffix

Chunkers#

  • Always includes "fixed" (built-in)
  • Plus any ONNX models from models_dir/chunkers/

Rerankers#

  • Native or ONNX rerankers from models_dir/rerankers/
  • model_manifest.json capabilities can mark late-interaction text rerankers (late_interaction, colbert)
  • Empty if no models configured

Generators#

  • LLM models from models_dir/generators/
  • Empty if no models configured

Extractors#

  • Extraction-capable models from the managed model registry
  • Includes GLiNER models for zero-shot entity and relation extraction

Rewriters#

  • Seq2Seq models from models_dir/rewriters/
  • T5, FLAN-T5, BART, and LMQG question generation models

Readers#

  • Vision2Seq models from models_dir/readers/
  • TrOCR, Donut, Florence-2 for OCR and document understanding

Transcribers#

  • Speech2Seq models from models_dir/transcribers/
  • Whisper, Wav2Vec2, HuBERT for speech-to-text

Models are discovered at service startup and cached.

Code Examples#

curl -X GET "http://localhost:8080/ai/v1/models"

Responses#

{
  "object": "list",
  "data": [
    {}
  ],
  "allow_downloads": true,
  "backends": {
    "native": true,
    "onnx": false,
    "metal": true,
    "cuda": false,
    "xla": false,
    "wasm": false
  },
  "chunkers": {},
  "rerankers": {},
  "classifiers": {},
  "embedders": {},
  "extractors": {},
  "generators": {},
  "rewriters": {},
  "readers": {},
  "transcribers": {}
}

Create embeddings (OpenAI-compatible)#

POST/embeddings

OpenAI-compatible embeddings endpoint. Accepts the same request format as OpenAI's /v1/embeddings API, served here under /ai/v1/embeddings. For sparse-capable models, each data item still uses the embedding field, but its value is a sparse vector object instead of a dense float array. Dense image inputs are header-validated and admitted against the aggregate decoded-pixel budget before model loading. Remote URL byte potential is reserved before fetch; inline sources use their actual encoded size. Use this endpoint for drop-in compatibility with OpenAI SDKs.

Request Body#

Example:

{
  "model": "string",
  "input": "string",
  "encoding_format": "float",
  "dimensions": 0,
  "task_type": "RETRIEVAL_QUERY",
  "input_type": "search_query",
  "error_policy": "fail_fast"
}

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/embeddings" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "string",
  "input": "string",
  "encoding_format": "float",
  "dimensions": 0,
  "task_type": "RETRIEVAL_QUERY",
  "input_type": "search_query",
  "error_policy": "fail_fast"
}'

Responses#

{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "embedding": [
        0
      ],
      "index": 0
    }
  ],
  "model": "string",
  "usage": {
    "prompt_tokens": 0,
    "total_tokens": 0
  },
  "errors": [
    {
      "index": 0,
      "code": "string",
      "message": "string",
      "stage": "parse",
      "retryable": true,
      "status": 0,
      "retry_after_ms": 0
    }
  ],
  "summary": {
    "total": 0,
    "succeeded": 0,
    "failed": 0
  }
}

List Traditional ML predictors#

GET/predictors

Returns the Traditional ML predictor catalog for /ml/v1/predict. Predictors are loaded from <ml_dir>/<name>/ and exposed separately from the AI model catalog.

Code Examples#

curl -X GET "http://localhost:8080/ai/v1/predictors"

Responses#

{
  "object": "list",
  "predictors": {}
}

Run a traditional ML predictor#

POST/predict

Run a tabular predictor (tree ensemble, linear, or SVM) on a batch of feature vectors. Models are loaded from <ml_dir>/<name>/ and identified by name. Use /ml/v1/models for the list of available predictors and their feature schemas.

Request Body#

Example:

{
  "model": "string",
  "input": [
    [
      0
    ]
  ]
}

Code Examples#

curl -X POST "http://localhost:8080/ai/v1/predict" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "string",
  "input": [
    [
      0
    ]
  ]
}'

Responses#

{
  "model": "string",
  "task": "regression",
  "predictions": [
    [
      0
    ]
  ]
}