NevoFlux
无头部署(Docker)

OpenAI 与 Anthropic API

用任意 OpenAI 或 Anthropic SDK 驱动无头 NevoFlux 容器——Chat Completions、Responses API 与 Messages API,阻塞或流式均可——并把每个模型名路由到 agent 循环或某个 Python 脚本后端。

无头容器支持三种 LLM 线格式。把官方 SDK 指向它,提示词就会变成一个任务:由 agent (或你提供的脚本)驱动浏览器,它的回答作为 assistant 消息返回。

协议端点所在端口起始版本
OpenAI Chat CompletionsPOST /v1/chat/completions--http-addr 与 --openai-addrv0.3.10(流式自 v0.3.14)
OpenAI 模型列表GET /v1/models--http-addr 与 --openai-addrv0.3.14
OpenAI ResponsesPOST /v1/responses--http-addr 与 --openai-addrv0.3.16
Anthropic MessagesPOST /v1/messages--http-addr 与 --anthropic-addrv0.3.16

这四个端点都同时挂在主任务端口上,因此镜像默认命令 (--daemon --headless --http-addr 0.0.0.0:8080)已经提供它们。专用标志只是额外再开一个 监听端口。

这些端点都不校验 API 密钥。 Authorization: Bearer … 与 x-api-key 会被接受并忽略 ——SDK 坚持要发点什么,随便填个占位值即可。任何能访问该端口的人,都能用容器的服务商 密钥和浏览器运行任务。网络边界(私有网络、带认证的反向代理)是唯一的闸门。

用官方 SDK 快速上手

from openai import OpenAI
client = OpenAI(base_url="http://host:8080/v1", api_key="unused")

# Chat Completions
client.chat.completions.create(
    model="nevoflux",
    messages=[{"role": "user", "content": "open example.com, report the title"}])

# Responses API
client.responses.create(model="nevoflux", input="open example.com, report the title")
import anthropic
client = anthropic.Anthropic(base_url="http://host:8080", api_key="unused")
client.messages.create(
    model="nevoflux", max_tokens=1024,
    messages=[{"role": "user", "content": "open example.com, report the title"}])

注意 base URL 的差别:OpenAI SDK 要 …/v1,Anthropic SDK 要裸主机地址(它会自己拼上 /v1/messages)。

用 curl:

curl -s localhost:8080/v1/chat/completions -H 'content-type: application/json' \
  -d '{"model":"gpt-4","messages":[{"role":"user","content":"open example.com, report title"}]}'
# → {"object":"chat.completion","choices":[{"message":{"role":"assistant",
#      "content":"...Example Domain"},"finish_reason":"stop"}],"usage":{...}, ...}

请求如何变成任务

  • 提示词取最后一条非空的 user 消息——messages 中(Chat Completions、Messages)或 input 中(Responses;裸字符串也算)的最后一条 user。没有任何 user 文本的请求返回 400。
  • 其余一切来自环境变量。 这些请求不携带 mode、profile、policy 或各种上限,因此 使用容器的 NEVOFLUX_TASK_* / NEVOFLUX_POLICY_* / NEVOFLUX_WALL_CLOCK_SECS 默认值 (见任务 API → 环境变量默认值)。 客户端无法通过发送不同的 JSON 来放宽策略。
  • 采样参数不作用于 agent。 temperature、max_tokens、system / instructions 以及 之前的轮次会作为数据传给脚本后端;agent 循环会忽略它们——它用自己配置的 模型把提示词当作任务运行。
  • 任务共用一个队列。 每个请求都是与 POST /tasks 同一个串行队列里的一个任务,所以 一个慢请求会拖后下一个。

流式

设置 "stream": true,各协议都会以自身的事件形状流式返回,SDK 的流式辅助函数无需改动 即可使用:

协议帧
Chat Completionschat.completion.chunk 数据帧(先 role,再若干 content 增量,最后一个带 finish_reason 的块),以 data: [DONE] 结束
Responses有名事件——response.created → response.output_item.added → response.content_part.added → response.output_text.delta… → response.output_text.done → response.content_part.done → response.output_item.done → response.completed——每个都带单调递增的 sequence_number
Messagesmessage_start → content_block_start → content_block_delta… → content_block_stop → message_delta → message_stop

首帧与末帧之间到达什么,取决于后端:

  • agent 循环在任务结束时才产出答案,所以流中会看到开头几帧、任务运行期间的保活帧, 然后整段答案作为一个增量到达。流式仍有价值——长任务期间连接保持存活——但不是逐 token 输出。
  • 脚本后端会把它用 emit_text(…) 发出的内容流式推送;emit_progress(…) 则变成 SSE 注释帧,保持连接存活但不会显示在客户端里。

流开始后的错误以内容形式送达:在 Chat Completions 上,错误信息作为一个 content 增量 发送,并带 finish_reason: "error"——因为很多客户端会丢弃没有 choices 的数据帧,否则 用户只会看到一个空回答。Responses 与 Messages 使用各自协议的 error 事件。

客户端断开。 流式客户端离开时会置位一个取消标志。脚本后端会在下一个工具调用边界 停下;agent 循环不读取该标志,会把任务跑到自己结束为止。请设置 NEVOFLUX_WALL_CLOCK_SECS,以免被丢弃的请求无限期占用队列。

模型名与路由

GET /v1/models 列出本容器接受的模型名,请求中的 model 字段决定由哪个后端来运行。

单后端模式(默认——未设置 NEVOFLUX_OPENAI_MODELS):

  • 列出的名字是 NEVOFLUX_HEADLESS_MODEL(默认 nevoflux-script)。
  • 接受任意模型名并原样回显,所以写死了 "model": "gpt-4" 的客户端照常可用。
  • 每个请求都运行 agent 循环;若设置了 NEVOFLUX_HEADLESS_SCRIPT,则运行该脚本。

多后端模式——把名字映射到脚本:

NEVOFLUX_OPENAI_MODELS='google-search=/opt/nevoflux/google-search.py,agent='
  • 每个 name=path 条目就是一个模型;路径为空(agent=)表示"运行真正的 agent 循环", 而不是"回落到 NEVOFLUX_HEADLESS_SCRIPT"。
  • GET /v1/models 按顺序列出所有键。
  • 未知名字会被拒绝,返回 404 model_not_found——一旦名字有了含义,悄悄换成别的后端 比报错更糟。
  • model 为空时选第一个条目。
环境变量作用默认值
NEVOFLUX_OPENAI_MODELSname=/path/script.py,… 路由表;路径为空 = agent 循环未设置(单后端)
NEVOFLUX_HEADLESS_MODEL单后端模式下广播的模型名nevoflux-script
NEVOFLUX_HEADLESS_SCRIPT单后端模式下每个任务运行的脚本(见固定脚本模式)未设置(agent 循环)

脚本后端

脚本后端是一个 Python 文件,与固定脚本模式 运行在同一个沙箱解释器里,由它自己回答聊天请求——通常是在容器的浏览器里驱动某个网页聊天 界面,本身不发起任何 LLM 调用。请求被翻译成一次脚本调用,返回值再被翻译回协议的响应。

两个入口;服务端会在源码中查找 def chat( 并优先使用它:

def chat(request):      # 完整契约(推荐)
    emit_progress("opening the page")          # SSE 注释帧,保持流存活
    nav = browser_navigate(url="https://example.com")
    md = browser_get_markdown(tab_id=nav["tab_id"])["markdown"]
    emit_text(md[:200])                         # 一个流式 content 增量
    return {"content": md[:200]}

def run(task):           # 旧式:只收到提示词字符串
    ...

request 是一个 dict,包含(但不限于)protocol(openai、responses 或 anthropic)、model、messages(完整历史,含纯文本 content 与原始 content_parts)、task(最后一条 user 消息)、客户端的 tools / tool_choice、 stream、采样 params,以及 metadata.budget_secs——沙箱对本次调用实际执行的挂钟预算 (任务挂钟减去 10 秒余量)。

脚本只返回以下之一:

{"content": "the answer"}
{"tool_calls": [{"id": "call_1", "name": "read_file", "arguments": {"path": "/tmp/a"}}]}
{"error": {"message": "...", "type": "server_error", "code": "..."}}

外加可选的 finish_reason 与 usage。tool_calls 会作为真正的工具调用透传给客户端 (网关负责把 arguments 字符串化),因此一个没有原生工具通道的聊天页面,也能服务会用 工具的客户端。旧式 run(task) 的返回值原样透传——返回 dict 时,整个 dict 会以 JSON 形式 成为 assistant 消息。

失败如何映射到 HTTP(非流式):

情形HTTPtype / code
请求体格式错误、没有 user 消息400invalid_request_error
模型不在路由表中404model_not_found
脚本抛异常 / 沙箱失败502server_error / script_error
脚本返回 {"error": …}502脚本提供的 code
超时504timeout

完整契约——请求字段、浏览器工具的结果封装、带预算的轮询、在仅有聊天的页面上合成工具 调用——见 agent 仓库中的 docs/reference/script-backend-contract.md,示例位于 docs/reference/script-backends/。

脚本后端拿到的是浏览器,而不是策略。 它直接在沙箱里调用浏览器工具;限制 agent 的 shell 与文件写入的每任务 policy,并不会限制脚本对页面做什么。只部署你读过的脚本。

需要登录的后端。 驱动网页应用的脚本通常需要一个已登录的 base 配置。每个任务都在一次性 克隆上运行,所以请以任务的形式登录一次(不要用 /v1/chat/completions,它无法按请求 放宽挂钟)并设置 save_profile,之后用 NEVOFLUX_TASK_PROFILE 让后续请求指向保存好的 base。见任务 API → 按任务选择后端。

值得了解的协议差异

  • Anthropic 有自己的端口标志(--anthropic-addr),而不是挂在 --openai-addr 上:它的 请求头(x-api-key、anthropic-version)与错误封装({"type":"error","error":{…}}) 都是 Anthropic 自己的。
  • /v1/messages 接受但不执行 max_tokens——协议要求必填,但并没有据此采样。
  • 用量。 这些响应里的 usage 来自后端终帧携带的内容:脚本可以设置它,agent 循环下 则为零。agent 循环真正的逐轮 token 统计(包括提示缓存读取)在任务记录上—— GET /tasks/:id,见任务 API → 状态与结果。
  • 响应 id 带有任务 id:chatcmpl-task-7、resp_task-7、msg_task-7 都属于任务 task-7,可以接着用 GET /tasks/task-7 查看。

本页目录