HTTP API (Ollama / OpenAI)

TensorSharp.Server 暴露聚焦的 Ollama / OpenAI 兼容子集以及 Web UI 协议,全部位于 http://localhost:5000。OpenAI Chat Completions 客户端可使用 /v1 基址;Ollama 聊天客户端必须改用非标准路径 /api/chat/ollama

风格端点
兼容 Ollama/api/generate/api/chat/ollama/api/tags/api/show
兼容 OpenAI/v1/chat/completions/v1/models/v1/videos/generations/v1/skills
Web UI (SSE)/api/chat/api/sessions/api/models/api/models/load/api/upload/api/image-edit/api/image-edit/stream/api/video-generate/api/video-generate/stream/api/skills/api/code/artifacts
工具端点/health/api/version/api/queue/status
⚠️

Ollama 聊天路径:兼容 Ollama 的聊天端点是 POST /api/chat/ollama不是 /api/chat;后者是 Web UI 的 SSE 端点。/api/generate/api/tags/api/show/api/version 使用标准 Ollama 路径。

📌

启动时必须传入 --model;视觉 / 音频需要投影器时还要显式传入 --mmproj。请求必须指明该启动 GGUF 文件或其基名。/api/models/load 只能重新加载同一启动模型 / 投影器,不能加载任意路径,也不能给无模型进程添加模型。Ollama / OpenAI 兼容端点省略 max_tokens / num_predict 时默认生成 200 token(Web UI 使用 --max-tokens,默认 20000)。见可复制粘贴的服务器快速开始

🔒

服务没有 API Key 身份验证或内置 TLS,并监听 0.0.0.0:5000。只应在可信网络中使用,或在前方部署带身份验证与 TLS 的反向代理。

服务启动后的快速调用

服务器快速开始托管 gemma-4-E4B-it-Q8_0.gguf。在第二个终端中粘贴:

curl -s http://localhost:5000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"gemma-4-E4B-it-Q8_0.gguf","messages":[{"role":"user","content":"Reply with one short hello."}],"max_tokens":32}'

浏览器 UI 位于 http://localhost:5000/:带有 wwwroot 内容时,GET / 直接提供 index.htmlGET /health 是稳定的纯文本存活检查;只有不带 UI 的无界面部署才会让 / 回退到同一响应。

1 · 兼容 Ollama 的 API

列出与查看模型

curl http://localhost:5000/api/tags

curl -X POST http://localhost:5000/api/show \
  -H "Content-Type: application/json" \
  -d '{"model": "Qwen3.5-9B-Q8_0.gguf"}'

生成(非流式)

curl -X POST http://localhost:5000/api/generate \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen3.5-9B-Q8_0.gguf",
    "prompt": "What is 1+1?",
    "stream": false,
    "options": { "num_predict": 50, "temperature": 0.7, "top_p": 0.9 }
  }'
{
  "model": "Qwen3.5-9B-Q8_0.gguf",
  "response": "1+1 equals 2.",
  "done": true,
  "done_reason": "stop",
  "prompt_eval_count": 15,
  "eval_count": 10,
  "prompt_cache_hit_tokens": 0,
  "prompt_cache_hit_ratio": 0.0
}

prompt_cache_hit_tokens 报告有多少提示 token 直接由上一轮的 KV 缓存提供。/api/generate 总会重置会话,因此它始终为 0;当提示前缀与之前某轮匹配时,/api/chat/ollama 上它会非零。

生成(流式)

curl -X POST http://localhost:5000/api/generate \
  -H "Content-Type: application/json" \
  -d '{ "model": "Qwen3.5-9B-Q8_0.gguf", "prompt": "Tell me a joke.", "stream": true, "options": {"num_predict": 100} }'

每行是一个 JSON 对象(换行分隔的 JSON);最后的 "done": true 块携带计时与缓存字段。

聊天(多轮)

curl -X POST http://localhost:5000/api/chat/ollama \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen3.5-9B-Q8_0.gguf",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "What is the capital of France?"}
    ],
    "stream": false,
    "options": {"num_predict": 100}
  }'

带图像生成(多模态)

图像以 base64 编码字节放在 images 数组中发送:

IMG_B64=$(base64 < photo.png)
curl -X POST http://localhost:5000/api/generate \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"gemma-4-E4B-it-Q8_0.gguf\",
    \"prompt\": \"What is in this image?\",
    \"images\": [\"$IMG_B64\"],
    \"stream\": false,
    \"options\": {\"num_predict\": 200}
  }"

带思考模式的聊天

具备思考能力的模型接受 "think": true,并把思维链拆分到 message.thinking

curl -X POST http://localhost:5000/api/chat/ollama \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen3.5-9B-Q8_0.gguf",
    "messages": [{"role": "user", "content": "Solve 17 * 23 step by step."}],
    "think": true, "stream": false, "options": {"num_predict": 200}
  }'
{
  "message": {
    "role": "assistant",
    "content": "17 * 23 = 391.",
    "thinking": "17 * 20 = 340. 17 * 3 = 51. 340 + 51 = 391."
  },
  "done": true, "done_reason": "stop"
}

2 · 兼容 OpenAI 的 API

Chat Completions(非流式)

curl -X POST http://localhost:5000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen3.5-9B-Q8_0.gguf",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "What is 2+3?"}
    ],
    "max_tokens": 50, "temperature": 0.7
  }'
{
  "id": "chatcmpl-abc123...",
  "object": "chat.completion",
  "model": "Qwen3.5-9B-Q8_0.gguf",
  "choices": [{
    "index": 0,
    "message": {"role": "assistant", "content": "2 + 3 = 5."},
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 20, "completion_tokens": 8, "total_tokens": 28,
    "prompt_tokens_details": { "cached_tokens": 0 }
  }
}

usage.prompt_tokens_details.cached_tokens 遵循 OpenAI 的 KV 缓存命中扩展 —— 在共享前缀的后续轮次中它会逼近 prompt_tokens

Chat Completions(流式)

curl -X POST http://localhost:5000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{ "model": "Qwen3.5-9B-Q8_0.gguf", "messages": [{"role": "user", "content": "Hello!"}], "max_tokens": 50, "stream": true }'

每个块是一个 object: "chat.completion.chunk" 的 SSE data: 帧;流以 data: [DONE] 结束。

图像输入(OpenAI 格式)

IMG_B64=$(base64 < photo.png)
curl -X POST http://localhost:5000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"gemma-4-E4B-it-Q8_0.gguf\",
    \"messages\": [{
      \"role\": \"user\",
      \"content\": [
        {\"type\": \"text\", \"text\": \"What is in this image?\"},
        {\"type\": \"image_url\", \"image_url\": {\"url\": \"data:image/png;base64,$IMG_B64\"}}
      ]
    }],
    \"max_tokens\": 200
  }"

3 · 通过 HTTP 进行工具调用

发送一个 tools 数组;服务器会检测该架构的线格式并返回结构化的 tool_calls

curl -X POST http://localhost:5000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen3.5-9B-Q8_0.gguf",
    "messages": [{"role": "user", "content": "What is the weather in Paris?"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get current weather for a city.",
        "parameters": {
          "type": "object",
          "properties": {
            "city":  {"type": "string"},
            "units": {"type": "string", "enum": ["c", "f"]}
          },
          "required": ["city"]
        }
      }
    }],
    "max_tokens": 200
  }'
{
  "choices": [{
    "message": {
      "role": "assistant", "content": null,
      "tool_calls": [{
        "id": "call_abc123", "type": "function",
        "function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\",\"units\":\"c\"}" }
      }]
    },
    "finish_reason": "tool_calls"
  }]
}

续接循环:把助手的 tool_calls 与一条携带函数结果的 {"role": "tool", "tool_call_id": "...", "content": "..."} 消息追加进去,再次调用该端点。(Ollama 端点使用同样的流程,配以一条 role: "tool" 消息。)

4 · 通过 HTTP 使用 Agent Skills

Agent Skill(智能体技能)是一个面向模型的说明文件夹——一份 SKILL.md,外加它引用的脚本、参考文档与素材——服务端只在任务需要时才加载它。所有聊天接口 —— /v1/chat/completions/v1/responses、兼容 Ollama 的 /api/chat/ollama,以及 Web UI 的 /api/chat —— 都接受同样的两个可选字段:

"skills": ["pdf", "xlsx"],
"skills_discovery": true

skills_discovery 默认为 true:本次请求没有点名的技能,其名称与描述也会展示给模型,好让它自己发现你没想到要点名的那个。设为 false 则把本次请求限制在它列出的技能上。点名一个服务端没有的技能会返回 400 —— 用 GET /api/skills 查看已注册了哪些。

对能调用工具的家族,初始提示词只携带元数据,显式选中的技能也一样;模型激活匹配技能时再用 skills_read 读取 SKILL.md。若一个家族无法同时渲染并解析完整工具往返,则改为内联选中技能正文,并丢弃发现目录;当前 qwen4exp 与 Mistral 3 都走这条路径。

# 兼容 OpenAI
curl -X POST http://localhost:5000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemma-4-E4B-it-Q8_0.gguf",
    "messages": [{"role": "user", "content": "把这份对账单里的合计表格提取出来。"}],
    "skills": ["pdf"],
    "max_tokens": 600
  }'

# 兼容 Ollama
curl -X POST http://localhost:5000/api/chat/ollama \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemma-4-E4B-it-Q8_0.gguf",
    "messages": [{"role": "user", "content": "帮我做一张预算表。"}],
    "skills": ["xlsx"],
    "skills_discovery": false,
    "stream": false
  }'

# Web UI SSE
curl -N -X POST http://localhost:5000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "总结这份 PDF。"}], "skills": ["pdf"], "maxTokens": 400}'

返回的是一条普通回复。模型通过内置的 skills_list / skills_read 工具取用技能的其余内容,而这两个工具由服务端自己在进程内、紧挨着权重应答,因此客户端永远不会收到一个它无法执行的工具调用,现有的 OpenAI SDK 也无需改动(把这两个字段放进 extra_body 即可)。技能还能与你自己的 tools 共存:技能工具会并入你发来的工具列表,服务端只应答属于它自己的那几个,而对你的工具的调用照常回传——此时模型从技能里读到的内容已经在对话中了。

📌

每次内置读取或代码操作都可能引出一次额外生成,而每一轮都以流式运行。服务端吞掉自己的工具标记,同时转发解码中的正文 / 推理,因此客户端收到的是完成后的回答,而不是内部调用。--skills-max-rounds 限制循环:默认 8,提供代码执行时自动变为 24;运维显式设置的值始终原样保留。

Web UI 的流式响应会为每一次已执行的技能工具调用推送一帧,随完成即时送出:

data: {"skill_step":"skills_read","skill":"pdf","detail":"references/forms.md","ok":true,"round":1,"files":null}

skill_step 是执行过的内置工具,skill/detail 标识目标,ok 表示成功与否,round 从 1 起算;files{name, bytes, url} 携带生成产物。→ Web UI SSE 协议

智能体代码执行

代码执行是服务端启动能力,不是逐请求开关。带上 --code-exec 后,每个能调用工具的聊天接口都可以使用五个服务端自有工具:read_fileedit_filewrite_fileshellapply_patch。它们与技能共用同一个有界循环,绝不会转发客户端;调用方定义的工具仍由调用方处理。这里没有逐命令审批 API。→ 参数、沙箱与信任模型

每个 OpenAI 或 Ollama 请求都得到唯一的私有工作区,在内部轮次之间共享,并在完成、取消或失败后删除。具名 Web UI 会话则跨聊天轮次保留工作区;技能脚本与代码工具共用它。保留的产物可通过 GET /api/code/artifacts/{runId} 列出,并从 GET /api/code/artifacts/{runId}/{*path} 下载;下载始终带 attachment 与 nosniff

🔒

脚本与命令执行都默认关闭,OS 沙箱默认 required(要么隔离,要么拒绝)。macOS Seatbelt 与 Linux bubblewrap 0.12+ 会约束写入、主目录读取与默认关闭的网络,但 macOS 无法保证清理故意脱离的子进程。Windows job object 只约束进程树,不能限制文件或网络,因此代码需要显式 --code-exec-unconfined,技能脚本需要 --skills-sandbox preferred--code-exec-allow-network 赋予生成命令不受限的宿主 IP 网络访问(含 LAN / loopback);它与宿主代安装、--skills-allow-network 彼此独立。

管理技能注册表

同一份数据有两种形态:只读、偏 OpenAI 风格的 /v1/skills,以及 Web UI 的 /api/skills——后者额外提供加载错误、单文件读取、上传与删除。

端点用途
GET /v1/skills全部已注册技能,包装为 {"object": "list", "data": [...]}
GET /v1/skills/{name}单个技能,并把 SKILL.md 正文放在 instructions 字段里返回。
GET /api/skills同样的对象,形如 {"enabled": …, "installable": …, "skills": [ … ], "errors": [{"path", "message"}]} —— 管理界面因此还能列出那些看起来像技能却加载失败的目录,并给出原因。
GET /api/skills/{name}单个技能,含 instructions
GET /api/skills/{name}/files/{*path}技能随包的某个文件,走的是与模型自身读取相同的那道路径关卡,并且始终以 text/plainnosniff 返回:技能里可能带 .html.js 文件,按真实类型返回等于在服务端自己的源上执行上传来的内容。
POST /api/skills以 multipart 上传 .zip 安装技能(file=@pdf.zip),可选 overwrite=true 覆盖同名的已安装技能。成功返回 201 与安装后的技能对象。
POST /api/skills/rescan不重启即可让磁盘上的改动生效;返回体与 GET /api/skills 相同。
DELETE /api/skills/{name}删除一个已安装的技能 —— {"removed":true}。只有从这里上传的技能("origin": "installed")可以删除;在运维配置的目录中扫描到的技能属于对方自己的文件树,会被拒绝。
curl http://localhost:5000/v1/skills
curl http://localhost:5000/v1/skills/pdf

curl http://localhost:5000/api/skills
curl http://localhost:5000/api/skills/pdf
curl http://localhost:5000/api/skills/pdf/files/references/forms.md

# 上传技能文件夹的 .zip(pdf/SKILL.md),或其内容的 .zip(SKILL.md 位于压缩包根部)
curl -X POST http://localhost:5000/api/skills -F "file=@pdf.zip" -F "overwrite=true"

curl -X POST http://localhost:5000/api/skills/rescan
curl -X DELETE http://localhost:5000/api/skills/pdf   # {"removed":true}

技能对象:

{
  "id": "pdf",
  "object": "skill",
  "name": "pdf",
  "description": "Extract text and tables from PDF files, fill in PDF forms, and merge or split documents...",
  "license": "Apache-2.0",
  "compatibility": "Requires python3 with pypdf installed.",
  "files": [
    {"path": "scripts/extract_tables.py", "bytes": 4021, "kind": "script", "text": true},
    {"path": "references/forms.md", "bytes": 18233, "kind": "reference", "text": true}
  ],
  "bytes": 41288,
  "origin": "installed",
  "warnings": [],
  "modified": "2026-08-29T12:00:00Z"
}

kind 取值为 script / reference / asset / manifest / otherorigindiscovered 表示是扫描配置目录发现的,installed 表示是从这里上传的;warnings 收录那些不完全合规但仍然加载成功的问题(name 与目录名不一致、描述超过 1024 字符上限等)。GET /api/models 会以 "skills": {"enabled": true, "installable": true, "count": 7} 报告该功能本身,服务端关闭技能时该字段为 null —— Web UI 正是据此决定要不要显示相关控件。错误遵循服务端按前缀区分的惯例:/v1/* 返回 {"error": {"message": "…", "type": "invalid_request_error"}}/api/* 返回 {"error": "…"}

🔒

上传的技能属于不受信任的内容,因此落盘前会校验:ZIP 条目使用与模型读取相同的路径关卡,按解压后大小计数,并限制 4096 个文件、单文件 64 MB、总计 256 MB 与 200× 压缩比。除非服务端带 --skills-allow-exec 启动,否则不会提供 skills_run;在默认 required 策略下,它要么进入合格 OS 沙箱,要么拒绝。这仍是在授权模型选择任意代码,只应放在用户与技能都可信的宿主上。→ 技能相关参数

5 · 结构化输出(JSON schema)

OpenAI 的 response_format 接受 textjson_object 与经过校验的 json_schema。服务器会注入严格的 JSON 指令,并在返回前校验输出。JSON 请求还会把首个采样 token 约束为以 { 开头的候选,使爱闲聊的模型无法在 JSON 对象前输出散文,流式首 token 时延因此反映 prefill 延迟(TS_JSON_FORCE_OPEN=0 可关闭)。

curl -X POST http://localhost:5000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen3.5-9B-Q8_0.gguf",
    "messages": [
      {"role": "system", "content": "You are a concise extraction assistant."},
      {"role": "user", "content": "Extract the city and country from: Paris, France."}
    ],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "location_extraction", "strict": true,
        "schema": {
          "type": "object",
          "properties": {
            "city": { "type": "string" },
            "country": { "type": "string" },
            "confidence": { "type": ["string", "null"] }
          },
          "required": ["city", "country", "confidence"],
          "additionalProperties": false
        }
      }
    },
    "max_tokens": 120
  }'
⚠️

json_schema 不能与调用方的 toolsthink 同时使用。它可以与 skills 组合,但选中技能正文会被内联,而且该请求不会提供任何内置技能 / 代码工具,因为 schema 约束下无法生成调用标记。无效 schema 返回 HTTP 400;校验失败的输出返回 HTTP 422

6 · Web UI SSE 协议(/api/chat

这是内置聊天 UI 所用的协议;外部 UI 也可接入同一端点。每个事件都是一个 JSON 对象,以单个 data: … SSE 帧传递。

# 创建 / 销毁会话(仅 Web UI 流程)
curl -X POST http://localhost:5000/api/sessions          # {"sessionId":"a3b1c2..."}
curl -X DELETE http://localhost:5000/api/sessions/a3b1c2...

# 流式聊天
curl -N -X POST http://localhost:5000/api/chat \
  -H "Content-Type: application/json" \
  -d '{ "messages": [{"role": "user", "content": "Hi"}], "maxTokens": 50, "sessionId": null, "newChat": false, "think": false, "tools": [] }'

复用相同的 sessionId 会在下一轮把之前的助手 token 拼接进 KV 缓存前缀。终止帧会报告复用情况:

data: {"done":true,"tokenCount":187,"elapsed":2.143,"tokPerSec":87.23,"promptTokens":512,"kvReusedTokens":420,"kvReusePercent":82.0}

事件字段包括 token(内容)、thinking(推理块)、调用方的 tool_callsreplace/diffusionStep(DiffusionGemma 预览)、skill_step(已完成内置工具以及 round/files)、tool_progress(临时的 writing / running / finished 活动,含 tooltextsecondsdetail),以及终止的 done 摘要。浏览器完成后会清除活动状态,不构造工具历史记录,但会保留产物下载标签。

data: {"tool_progress":"writing","tool":"shell","text":"Writing code…","seconds":0,"detail":""}
data: {"tool_progress":"running","tool":"shell","text":"Running…","seconds":2.0,"detail":"python · 2.1 KB"}
data: {"tool_progress":"finished","tool":"shell","text":"Finished","seconds":2.4,"detail":"exit 0"}

文件上传(/api/upload

POST /api/upload 接收 multipart 表单(使用第一个文件),返回存储路径 / URL 与该类型的元数据;随后由 /api/chat 消息引用返回的服务端路径(imagePathsaudioPaths 等)。接受的类型包括:图像.png .jpg .jpeg .gif .webp .bmp .heic .heif)、视频.mp4 .mov .avi .mkv .webm,按 VIDEO_SAMPLE_FPS 抽帧)、音频.mp3 .wav .ogg .flac .m4a)、PDF文本 / 代码文件(以 textContent 完整返回)。数字版 PDF 返回完整提取的 textContent;扫描版 PDF 在托管视觉模型时像视频一样返回页面图像,否则给出 needsVision 警告。TS_PDF_MAX_PAGES 限制读取页数(默认全部)。

图像编辑(/api/image-edit · /api/image-edit/stream

托管模型为 qwen_image DiT 时可用。POST /api/image-edit 执行一次编辑:可提交 multipart(image 文件 + prompt,以及可选 stepscfgseed),也可提交 JSON {"imagePath": "...", "prompt": "...", "steps": 0, "cfg": 0, "seed": 0};其中 imagePath 必须指向上传目录中的文件,steps / cfg0 表示自动。响应为 { ok, url, width, height, elapsedSeconds },PNG 从 /uploads/ 提供。POST /api/image-edit/stream 接收相同 JSON,但以 SSE 流式返回去噪进度:每步帧为 {"imageEdit": true, "step": i, "total": N, "image": "data:image/png;base64,..."}(最多 8 张均匀预览),最后返回 {"done": true, "url": "...", "width": ..., "height": ..., "elapsedSeconds": ...}。整个进程中的编辑请求串行执行。

视频生成(/api/video-generate · /api/video-generate/stream · /v1/videos/generations

当托管的模型能生成视频时可用 —— MiniMax-H3,或只生成视频的 Wan。判定依据是 IVideoGenerationModel 接缝而不是架构字符串,因此其他模型会返回 400The loaded model is not a video-generation model.。模型自己拒绝的请求 —— 用 Ref2VA 的输入去跑 FL2VA 权重、在同一个请求体里同时给关键帧与具名参考、模式缺少必需输入 —— 在 /api/video-generate 上以 400 返回模型自己的报错信息,而不是笼统的 500;在流式接口上则以终止帧 {"done": true, "error": …} 返回同一条信息。三个接口共用同一个解析器,因此字段完全一致;视频生成在进程内串行执行。

# MiniMax-H3:提示词 -> MP4,外加一同生成的 32 kHz 立体声 WAV
curl -s http://localhost:5000/api/video-generate \
  -H "Content-Type: application/json" \
  -d '{
        "prompt": "a red fox trotting through falling snow, cinematic",
        "width": 640, "height": 384, "frames": 22, "fps": 24,
        "steps": 8, "cfg": 1.0, "seed": 42,
        "videoMode": "t2v", "generateAudio": true
      }'
{ "ok": true, "url": "/uploads/video-8f3c….mp4", "audioUrl": "/uploads/video-8f3c….wav",
  "width": 640, "height": 384, "frames": 22, "fps": 24,
  "seed": 42, "codec": "h264", "elapsedSeconds": 63.1 }

请求字段(camelCase):prompt(必填)、widthheightframesstepscfgcfg2seedfpsflowShiftnegativePromptsamplercfgCacheStridevideoModet2v / i2v / fl2v / ref)、generateAudioimagePathimageendImagereferenceImagesreferenceVideosreferenceAudiosreferenceVideoAudios。其中为音视频联合生成与参考条件新增的七个字段同时接受 snake_case —— video_modegenerate_audioend_imagereference_imagesreference_videosreference_audiosreference_video_audios —— 两种写法同时出现时 camelCase 优先;其余字段只接受 camelCase。

image 是内联 base64 形式(允许带 data:…;base64, 前缀)。imagePathendImage 以及每一个 reference* 条目都必须指向 /api/upload 之前返回过的文件,并被限制在上传目录之内 —— 目录之外的路径会被拒绝,报错形如 referenceImages entries must reference previously uploaded files.referenceVideoAudiosreferenceVideos 按下标配对:第 i 项就是第 i 段片段的音轨。当模型没有产出音轨时 audioUrlnull —— 音频以旁挂 WAV 写出,而不是混流进 MP4。

POST /api/video-generate/stream 接收相同请求体,并以 SSE 流式返回:每个心跳是 {"videoGen": true, "step": i, "total": N, "phase": …, "detail": …, "elapsedSeconds": …, "etaSeconds": …},最后是终止帧 {"done": true, "url": …, "audioUrl": …, "width": …, "height": …, "frames": …, "fps": …, "seed": …, "codec": …, "elapsedSeconds": …} —— 或者 {"done": true, "error": …}

POST /v1/videos/generations 是同一任务的 OpenAI images 风格封装。在上述字段之外,它还接受 "size": "832x480"(会拆成 width/height)、negative_prompt,以及取值 "url""b64_json"response_format,返回 { created, data: [{ url, b64_json }], audio_url, width, height, frames, fps, seed, codec, elapsed_seconds },MP4 由 /uploads/ 提供。

GET /api/models 是客户端在构造请求之前了解托管视频模型能接受什么的入口:除模型列表外,它还会返回一个 video 对象,包含 family"minimax-h3""wan")、supportsAudiosupportsImageConditioningsupportsEndImageConditioningsupportsReferenceConditioningmaxReferenceImages(MiniMax-H3 Ref2VA 上为 9)—— 对所有非视频模型则为 null。内置网页界面读的正是这一块,用来决定该提供哪些附件控件 —— 首帧、尾帧,或数量被 maxReferenceImages 限住的参考图 —— 而不是去匹配架构名字符串;完全拿不到这一块时(较旧的服务端,或不上报能力的模型),它会退回到一直以来那种单张图像的请求形式。

7 · 采样参数

Ollama 风格(在 options 对象内)

下表默认值用于请求省略字段时,可通过服务端参数 / TENSORSHARP_* 环境变量配置(见服务端参数)。

参数类型默认说明
num_predictint200最大生成 token 数
temperaturefloat0.8采样温度(0 = 贪心)
top_kint40Top-K 过滤(0 = 禁用)
top_pfloat0.9核采样阈值(1.0 = 禁用)
min_pfloat0最小概率过滤
repeat_penaltyfloat1.1重复惩罚(1.0 = 无)
presence_penalty / frequency_penaltyfloat0存在 / 频率惩罚
seedint-1随机种子(-1 = 随机)
stoparraynull停止序列

OpenAI 风格(顶层)

max_tokens(省略时默认 200)、temperaturetop_ppresence_penaltyfrequency_penaltyseedstop(字符串或数组),以及 response_formattext / json_object / json_schema)。注意:OpenAI 接口不解析 top_kmin_prepetition_penalty;这些字段使用服务端默认值。

8 · Python 客户端示例

使用 requests(Ollama 风格)

import requests

resp = requests.post("http://localhost:5000/api/generate", json={
    "model": "Qwen3.5-9B-Q8_0.gguf",
    "prompt": "What is machine learning?",
    "stream": False,
    "options": {"num_predict": 100, "temperature": 0.7}
})
print(resp.json()["response"])

使用 openai SDK

from openai import OpenAI

client = OpenAI(base_url="http://localhost:5000/v1", api_key="not-needed")

response = client.chat.completions.create(
    model="Qwen3.5-9B-Q8_0.gguf",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "What is 2+3?"}
    ],
    max_tokens=50, temperature=0.7
)
print(response.choices[0].message.content)

openai SDK 做流式

stream = client.chat.completions.create(
    model="Qwen3.5-9B-Q8_0.gguf",
    messages=[{"role": "user", "content": "Tell me about Python."}],
    max_tokens=200, stream=True
)
for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
print()