Streaming Prediction¶
Submit images and receive results as a real-time NDJSON stream (newline-delimited JSON). Each line is a JSON object pushed as processing progresses — ideal for progress indicators or large batches where you want partial results early.
Two versions are available:
- v1 — upload files directly as
multipart/form-data - v2 — pass image URLs in a JSON body
POST /inference/predict/stream (v1)¶
Request¶
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
api_key |
string |
Yes | Your API key |
query |
string |
No | Natural language query for LLM analysis |
files |
File[] |
Yes | One or more image files |
metadata |
string[] |
Yes | One JSON string per file controlling inference flags (see below) |
Each metadata entry is a JSON-stringified object that controls how the corresponding file is processed:
| Key | Type | Description |
|---|---|---|
is_yolo |
boolean |
Run YOLO object detection on this file |
is_llm |
boolean |
Run LLM-based analysis on this file |
Metadata ordering
metadata is a parallel array to files. Both arrays must have the same length.
Response¶
Content-Type: application/x-ndjson
Each line is a JSON object. Read lines incrementally as they arrive:
{"status": "processing", "filename": "photo.jpg"}
{"status": "done", "filename": "photo.jpg", "result": { ... }}
Code Examples¶
async function predictStream(apiKey, query, files, metadata, onChunk) {
const form = new FormData();
form.append("api_key", apiKey);
if (query) form.append("query", query);
for (const file of files) {
form.append("files", file);
}
// Each metadata entry must be a JSON string
for (const m of metadata) {
form.append("metadata", JSON.stringify(m));
}
const response = await fetch(
"https://sightapi.raku.so/api/v1/inference/predict/stream",
{ method: "POST", body: form }
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail?.message ?? "Request failed");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (line.trim()) onChunk(JSON.parse(line));
}
}
}
// Usage (browser)
const fileInput = document.querySelector("input[type=file]");
await predictStream(
"YOUR_API_KEY",
"find me the serial number from the images",
[...fileInput.files],
[
{ is_yolo: true, is_llm: false },
{ is_yolo: true, is_llm: true },
{ is_yolo: false, is_llm: true },
],
(chunk) => console.log("Received:", chunk)
);
interface FileMetadata {
is_yolo: boolean;
is_llm: boolean;
}
type ChunkHandler = (chunk: unknown) => void;
async function predictStream(
apiKey: string,
query: string | null,
files: File[],
metadata: FileMetadata[],
onChunk: ChunkHandler
): Promise<void> {
const form = new FormData();
form.append("api_key", apiKey);
if (query) form.append("query", query);
for (const file of files) {
form.append("files", file);
}
// Each metadata entry must be a JSON string
for (const m of metadata) {
form.append("metadata", JSON.stringify(m));
}
const response = await fetch(
"https://sightapi.raku.so/api/v1/inference/predict/stream",
{ method: "POST", body: form }
);
if (!response.ok) {
const error = await response.json();
throw new Error((error.detail as { message: string })?.message ?? "Request failed");
}
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
if (line.trim()) onChunk(JSON.parse(line));
}
}
}
import json
import httpx
from collections.abc import Generator
def predict_stream(
api_key: str,
query: str | None,
file_paths: list[str],
metadata: list[dict],
) -> Generator[dict, None, None]:
files = [("files", open(p, "rb")) for p in file_paths]
# metadata entries must be JSON strings
data = {
"api_key": api_key,
"metadata": [json.dumps(m) for m in metadata],
}
if query:
data["query"] = query
with httpx.Client() as client:
with client.stream(
"POST",
"https://sightapi.raku.so/api/v1/inference/predict/stream",
data=data,
files=files,
) as response:
response.raise_for_status()
for line in response.iter_lines():
if line:
yield json.loads(line)
# Usage
for chunk in predict_stream(
api_key="YOUR_API_KEY",
query="find me the serial number from the images",
file_paths=["./img1.jpg", "./img2.jpg", "./img3.jpg"],
metadata=[
{"is_yolo": True, "is_llm": False},
{"is_yolo": True, "is_llm": True },
{"is_yolo": False, "is_llm": True },
],
):
print("Received:", chunk)
using System.Net.Http;
using System.Text.Json;
using System.Runtime.CompilerServices;
public record FileMetadata(bool IsYolo, bool IsLlm);
public class RakuSightClient
{
private static readonly HttpClient Http = new();
public static async IAsyncEnumerable<JsonDocument> PredictStreamAsync(
string apiKey,
string? query,
IEnumerable<(string FileName, Stream Content)> files,
IEnumerable<FileMetadata> metadata,
[EnumeratorCancellation] CancellationToken ct = default)
{
using var form = new MultipartFormDataContent();
form.Add(new StringContent(apiKey), "api_key");
if (query is not null)
form.Add(new StringContent(query), "query");
foreach (var (fileName, content) in files)
form.Add(new StreamContent(content), "files", fileName);
// Each metadata entry must be a JSON string
foreach (var m in metadata)
{
var json = JsonSerializer.Serialize(new { is_yolo = m.IsYolo, is_llm = m.IsLlm });
form.Add(new StringContent(json), "metadata");
}
using var request = new HttpRequestMessage(HttpMethod.Post,
"https://sightapi.raku.so/api/v1/inference/predict/stream")
{
Content = form,
};
using var response = await Http.SendAsync(
request, HttpCompletionOption.ResponseHeadersRead, ct);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync(ct);
using var reader = new System.IO.StreamReader(stream);
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync(ct);
if (!string.IsNullOrWhiteSpace(line))
yield return JsonDocument.Parse(line);
}
}
}
// Usage
await using var s1 = File.OpenRead("img1.jpg");
await using var s2 = File.OpenRead("img2.jpg");
await using var s3 = File.OpenRead("img3.jpg");
await foreach (var chunk in RakuSightClient.PredictStreamAsync(
"YOUR_API_KEY",
"find me the serial number from the images",
[("img1.jpg", s1), ("img2.jpg", s2), ("img3.jpg", s3)],
[
new FileMetadata(IsYolo: true, IsLlm: false),
new FileMetadata(IsYolo: true, IsLlm: true),
new FileMetadata(IsYolo: false, IsLlm: true),
]))
{
Console.WriteLine(chunk.RootElement);
}
OkHttp recommended
Java's built-in HttpClient does not support streaming multipart responses easily. Use OkHttp:
import okhttp3.*;
import java.io.*;
OkHttpClient client = new OkHttpClient();
// Each metadata entry is a JSON string
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("api_key", "YOUR_API_KEY")
.addFormDataPart("query", "find me the serial number from the images")
.addFormDataPart("metadata", "{\"is_yolo\": true, \"is_llm\": false}")
.addFormDataPart("metadata", "{\"is_yolo\": true, \"is_llm\": true}")
.addFormDataPart("metadata", "{\"is_yolo\": false, \"is_llm\": true}")
.addFormDataPart("files", "img1.jpg",
RequestBody.create(new File("img1.jpg"), MediaType.parse("image/jpeg")))
.addFormDataPart("files", "img2.jpg",
RequestBody.create(new File("img2.jpg"), MediaType.parse("image/jpeg")))
.addFormDataPart("files", "img3.jpg",
RequestBody.create(new File("img3.jpg"), MediaType.parse("image/jpeg")))
.build();
Request request = new Request.Builder()
.url("https://sightapi.raku.so/api/v1/inference/predict/stream")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code: " + response);
BufferedReader reader = new BufferedReader(
new InputStreamReader(response.body().byteStream()));
String line;
while ((line = reader.readLine()) != null) {
if (!line.isBlank()) {
System.out.println("Received: " + line);
// parse with Jackson: objectMapper.readTree(line)
}
}
}
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
)
type FileMetadata struct {
IsYolo bool `json:"is_yolo"`
IsLlm bool `json:"is_llm"`
}
func predictStream(
apiKey, query string,
filePaths []string,
metadata []FileMetadata,
onChunk func(map[string]any),
) error {
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
_ = w.WriteField("api_key", apiKey)
if query != "" {
_ = w.WriteField("query", query)
}
// Each metadata entry must be a JSON string
for _, m := range metadata {
b, _ := json.Marshal(m)
_ = w.WriteField("metadata", string(b))
}
for _, path := range filePaths {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
part, _ := w.CreateFormFile("files", filepath.Base(path))
io.Copy(part, f)
}
w.Close()
resp, err := http.Post(
"https://sightapi.raku.so/api/v1/inference/predict/stream",
w.FormDataContentType(),
&buf,
)
if err != nil {
return err
}
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
var chunk map[string]any
json.Unmarshal([]byte(line), &chunk)
onChunk(chunk)
}
return scanner.Err()
}
func main() {
err := predictStream(
"YOUR_API_KEY",
"find me the serial number from the images",
[]string{"./img1.jpg", "./img2.jpg", "./img3.jpg"},
[]FileMetadata{
{IsYolo: true, IsLlm: false},
{IsYolo: true, IsLlm: true},
{IsYolo: false, IsLlm: true},
},
func(chunk map[string]any) {
fmt.Println("Received:", chunk)
},
)
if err != nil {
panic(err)
}
}
POST /inference/predict/stream/v2¶
Same streaming behavior as v1, but accepts image URLs in a JSON body instead of file uploads.
Request¶
Content-Type: application/json
{
"api_key": "YOUR_API_KEY",
"query": "What objects are in this image?",
"data": [
{
"image_url": "https://storage.example.com/images/photo.jpg",
"filename": "photo.jpg",
"is_yolo": true,
"is_llm": false
}
]
}
Response¶
Content-Type: application/x-ndjson — same streaming format as v1.
Code Examples¶
async function predictStreamV2(apiKey, query, data, onChunk) {
const response = await fetch(
"https://sightapi.raku.so/api/v1/inference/predict/stream/v2",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ api_key: apiKey, query, data }),
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail?.message ?? "Request failed");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (line.trim()) onChunk(JSON.parse(line));
}
}
}
// Usage
await predictStreamV2(
"YOUR_API_KEY",
"Describe the scene",
[{ image_url: "https://storage.example.com/photo.jpg", filename: "photo.jpg", is_yolo: true, is_llm: false }],
(chunk) => console.log("Received:", chunk)
);
interface DataItem {
image_url: string;
filename: string;
is_yolo: boolean;
is_llm: boolean;
}
async function predictStreamV2(
apiKey: string,
query: string,
data: DataItem[],
onChunk: (chunk: unknown) => void
): Promise<void> {
const response = await fetch(
"https://sightapi.raku.so/api/v1/inference/predict/stream/v2",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ api_key: apiKey, query, data }),
}
);
if (!response.ok) {
const error = await response.json();
throw new Error((error.detail as { message: string })?.message ?? "Request failed");
}
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
if (line.trim()) onChunk(JSON.parse(line));
}
}
}
import httpx
import json
from collections.abc import Generator
def predict_stream_v2(
api_key: str,
query: str,
data: list[dict],
) -> Generator[dict, None, None]:
payload = {"api_key": api_key, "query": query, "data": data}
with httpx.Client() as client:
with client.stream(
"POST",
"https://sightapi.raku.so/api/v1/inference/predict/stream/v2",
json=payload,
) as response:
response.raise_for_status()
for line in response.iter_lines():
if line:
yield json.loads(line)
# Usage
for chunk in predict_stream_v2(
api_key="YOUR_API_KEY",
query="Describe the scene",
data=[
{
"image_url": "https://storage.example.com/images/photo.jpg",
"filename": "photo.jpg",
"is_yolo": True,
"is_llm": False,
}
],
):
print("Received:", chunk)
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Runtime.CompilerServices;
public class RakuSightClient
{
private static readonly HttpClient Http = new();
public static async IAsyncEnumerable<JsonDocument> PredictStreamV2Async(
string apiKey, string query, IEnumerable<object> data,
[EnumeratorCancellation] CancellationToken ct = default)
{
var payload = new { api_key = apiKey, query, data };
using var request = new HttpRequestMessage(HttpMethod.Post,
"https://sightapi.raku.so/api/v1/inference/predict/stream/v2")
{
Content = JsonContent.Create(payload),
};
using var response = await Http.SendAsync(
request, HttpCompletionOption.ResponseHeadersRead, ct);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync(ct);
using var reader = new System.IO.StreamReader(stream);
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync(ct);
if (!string.IsNullOrWhiteSpace(line))
yield return JsonDocument.Parse(line);
}
}
}
import okhttp3.*;
import java.io.*;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
public class RakuSightClient {
private static final OkHttpClient CLIENT = new OkHttpClient();
private static final ObjectMapper MAPPER = new ObjectMapper();
public static void predictStreamV2(
String apiKey, String query, List<?> data) throws Exception {
String bodyJson = MAPPER.writeValueAsString(
new java.util.HashMap<String, Object>() {{
put("api_key", apiKey);
put("query", query);
put("data", data);
}}
);
RequestBody body = RequestBody.create(
bodyJson, MediaType.parse("application/json"));
Request request = new Request.Builder()
.url("https://sightapi.raku.so/api/v1/inference/predict/stream/v2")
.post(body)
.build();
try (Response response = CLIENT.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Failed: " + response);
BufferedReader reader = new BufferedReader(
new InputStreamReader(response.body().byteStream()));
String line;
while ((line = reader.readLine()) != null) {
if (!line.isBlank()) {
System.out.println("Received: " + line);
}
}
}
}
}
package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type DataItem struct {
ImageURL string `json:"image_url"`
Filename string `json:"filename"`
IsYolo bool `json:"is_yolo"`
IsLlm bool `json:"is_llm"`
}
type PredictRequest struct {
APIKey string `json:"api_key"`
Query string `json:"query"`
Data []DataItem `json:"data"`
}
func predictStreamV2(
apiKey, query string,
data []DataItem,
onChunk func(map[string]any),
) error {
payload, _ := json.Marshal(PredictRequest{APIKey: apiKey, Query: query, Data: data})
resp, err := http.Post(
"https://sightapi.raku.so/api/v1/inference/predict/stream/v2",
"application/json",
bytes.NewReader(payload),
)
if err != nil {
return err
}
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
var chunk map[string]any
json.Unmarshal([]byte(line), &chunk)
onChunk(chunk)
}
return scanner.Err()
}
func main() {
err := predictStreamV2(
"YOUR_API_KEY",
"Describe the scene",
[]DataItem{
{ImageURL: "https://storage.example.com/photo.jpg", Filename: "photo.jpg", IsYolo: true},
},
func(chunk map[string]any) {
fmt.Println("Received:", chunk)
},
)
if err != nil {
panic(err)
}
}