冰冰冰-v1 文档
hyy 开发的中文多模态对话大模型的接入说明。基座 Qwen3-VL-8B-Instruct, LoRA 微调,支持文本、识图与工具调用,提供 OpenAI 与 Anthropic 两种兼容协议。
快速开始
想聊天,不需要密钥;想接程序,直接跳到 鉴权。
方式一:网页对话
- 打开 https://chat.bingbingbing.top,不需要注册,也不需要填任何密钥。
- 直接发消息、传图片。长文档可以整篇粘贴,窗口有 131072 token。
- 要切思考档位的话,打开右上角 设置 → 思考档位:
low:快速作答,适合闲聊、改写、格式转换。medium:均衡,日常问答与写代码的默认选择。high:深入推理,适合数学、复杂逻辑与多步规划。- 关闭:不加任何思考指令,模型直接回答。
档位指令由服务端按所选档位注入,前端只负责告诉它档位。所以同一段对话换档位,行为会立刻变化,不需要重开会话。
方式二:调用 API
# 1) 用主密钥换一把 10 分钟有效的临时 key
curl -s https://api.bingbingbing.top/v1/keys \
-H "Authorization: Bearer $BING_MASTER_KEY"
# 2) 拿返回的 key 直接对话
curl -s https://api.bingbingbing.top/v1/chat/completions \
-H "Authorization: Bearer $TEMP_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"bingbingbing-v1","messages":[{"role":"user","content":"冰冰冰?"}]}'
模型信息
| 模型 ID | bingbingbing-v1 |
|---|---|
| 基座 | Qwen3-VL-8B-Instruct |
| 微调方式 | LoRA |
| 参数量 | 8.19B |
| 上下文长度 | 131072 token(128K) |
| 单次输出上限 | 8192 token |
| 能力 | 文本 / 识图 / 工具调用 |
| 思考档位 | low / medium / high |
8.19B 是权重里的实际参数量,不是「8B 级别」的约数;上下文与输出上限就是服务端 实际接受的值,超出会被截断或直接返回错误,不要按更大的数字做预算。
API 概览
所有接口都挂在同一个基址下:https://api.bingbingbing.top。
同一把 key 既能调 OpenAI 协议,也能调 Anthropic 协议,不需要分别申请。
| 方法 | 路径 | 用途 | 鉴权头 |
|---|---|---|---|
POST | /v1/keys | 用主密钥换临时 key | Authorization: Bearer <主密钥> |
POST | /v1/chat/completions | OpenAI 协议对话 | Authorization: Bearer <key> |
POST | /v1/messages | Anthropic 协议对话 | x-api-key: <key> |
GET | /v1/models | 列出可用模型 | Authorization: Bearer <key> |
网页端还有一条自己的通道。 chat.bingbingbing.top 走的是
/api/chat,不在这份文档的 /v1/* 范围内,也不需要你提供 key。
第三方客户端一律接 /v1/*。
鉴权:两段式
不要把主密钥塞进浏览器、前端项目或公开的 CI 变量里。正确做法是先换一把 临时 key,再用它调接口。
第一步:换临时 key
curl -s -X POST https://api.bingbingbing.top/v1/keys \
-H "Authorization: Bearer $BING_MASTER_KEY"
返回:
{
"key": "bbk_9f3c...d21",
"expires_at": "2026-01-01T00:10:00Z",
"ttl_seconds": 600,
"concurrency": 1,
"reused": false
}
| 字段 | 类型 | 说明 |
|---|---|---|
key | string | 临时 key,直接当 Bearer token 用。 |
expires_at | string | 过期时间(UTC,ISO 8601)。 |
ttl_seconds | number | 剩余有效秒数,正常是 600。 |
concurrency | number | 这把 key 的并发上限,临时 key 固定为 1。 |
reused | boolean | 为 true 表示这把是复用已有的,没有新签发。 |
三条必须记住的规则
- 有效期 10 分钟。
ttl_seconds到点即失效,继续用会拿到401。客户端应当在缓存里存住 key,在快过期时重新申请,而不是每个请求都申请一次。 - 同一个 IP 只会拿到同一把。 重复申请返回
reused: true,服务端不重复签发。所以同一台机器上多进程共享一把 key 是正常现象,不是 bug。 - 临时 key 并发上限是 1。 同一把 key 同时发起第二个请求,会直接得到
429,而不是排队。需要并发就换长期 key。
长期 key
服务端通过环境变量 BING_API_KEYS 配置长期 key(多个用分隔符隔开),
它不过期、并发上限更高,适合放在自建后端的服务端环境里。用法与临时 key 完全一样,
直接放在 Authorization: Bearer 或 x-api-key 里即可。
长期 key 一旦泄露,等于把模型对外的额度永久交出去。前端(浏览器 / 小程序 / 桌面客户端) 只应该用临时 key,并且由你自己的后端去换,主密钥永远不出服务端。
OpenAI 协议
curl -s https://api.bingbingbing.top/v1/chat/completions \
-H "Authorization: Bearer $TEMP_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "bingbingbing-v1",
"messages": [
{"role": "user", "content": "你敢冰我?"}
],
"reasoning_effort": "medium",
"max_tokens": 512,
"stream": false
}'
from openai import OpenAI
client = OpenAI(
base_url="https://api.bingbingbing.top/v1",
api_key=temp_key, # 临时 key 或长期 key
)
resp = client.chat.completions.create(
model="bingbingbing-v1",
messages=[{"role": "user", "content": "你敢冰我?"}],
max_tokens=512,
extra_body={"reasoning_effort": "medium"}, # SDK 尚未认识的参数放这里
)
msg = resp.choices[0].message
print(getattr(msg, "reasoning_content", None)) # 思考内容,与正文分开
print(msg.content) # 正文
返回结构与 OpenAI 官方一致:choices[0].message.content 是正文,
思考内容在 choices[0].message.reasoning_content。流式(stream: true)
时两者都走 SSE 的 delta,字段名相同。
Anthropic 协议
这个协议必须带 anthropic-version: 2023-06-01,
且 key 放在 x-api-key 而不是 Authorization —— 少任何一个都会被判成鉴权失败。
curl -s https://api.bingbingbing.top/v1/messages \
-H "x-api-key: $TEMP_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "bingbingbing-v1",
"max_tokens": 512,
"thinking": {"type": "enabled", "budget_tokens": 1024},
"messages": [
{"role": "user", "content": "你敢冰我?"}
]
}'
import anthropic
client = anthropic.Anthropic(
base_url="https://api.bingbingbing.top", # 注意:SDK 自己会补 /v1
api_key=temp_key,
)
msg = client.messages.create(
model="bingbingbing-v1",
max_tokens=512,
thinking={"type": "enabled", "budget_tokens": 1024},
messages=[{"role": "user", "content": "你敢冰我?"}],
)
for block in msg.content:
if block.type == "thinking":
print(block.thinking)
elif block.type == "text":
print(block.text)
Anthropic 协议把内容拆成块:思考在 type: "thinking" 的块里(字段是 thinking),
正文在 type: "text" 的块里。两者永远不会混在同一个块里。
列出模型
curl -s https://api.bingbingbing.top/v1/models \
-H "Authorization: Bearer $TEMP_KEY"
返回只有一个模型 bingbingbing-v1。这个接口常被用来做连通性探活:
它不触发模型推理,因此不会因为实例缩容到零而等冷启动。
思考档位
三档的区别只有一件事:给模型多少思考预算。预算越大,复杂任务越稳,简单任务越浪费。
| 档位 | 思考预算 | 适合 |
|---|---|---|
low | 192 token | 闲聊、改写、翻译、格式转换 |
medium | 256 token | 日常问答、写代码、读图(默认) |
high | 1024 token | 数学、复杂逻辑、多步规划 |
三种传参写法
同一个档位,三条路都能到;用你手上 SDK 顺手的那条。
1. OpenAI 协议的 reasoning_effort
{
"model": "bingbingbing-v1",
"reasoning_effort": "high",
"messages": [{ "role": "user", "content": "证明 √2 是无理数" }]
}
2. reasoning_level(同一套档位名的别名)
{
"model": "bingbingbing-v1",
"reasoning_level": "low",
"messages": [{ "role": "user", "content": "把这段改得更口语" }]
}
3. Anthropic 协议的 thinking.budget_tokens
{
"model": "bingbingbing-v1",
"max_tokens": 2048,
"thinking": { "type": "enabled", "budget_tokens": 1024 },
"messages": [{ "role": "user", "content": "这道题错在哪" }]
}
思考内容怎么取
- OpenAI 协议:
choices[0].message.reasoning_content(流式在delta.reasoning_content)。 - Anthropic 协议:
content数组里type: "thinking"的块,字段名thinking。 - 两者都不会混进正文:
content/text里只有答案。所以可以放心把正文直接渲染给用户,把思考过程丢进折叠块或干脆丢掉。
预算不是「至少会想这么多」,而是上限。模型判断问题简单时会提前结束思考, 实际消耗往往小于预算值。
图片输入
基座是 VL 模型,图片和文字可以放在同一条消息里。OpenAI 协议用
image_url 块,Anthropic 协议用 image 块(base64)。
import base64, pathlib
from openai import OpenAI
client = OpenAI(base_url="https://api.bingbingbing.top/v1", api_key=temp_key)
b64 = base64.b64encode(pathlib.Path("shot.png").read_bytes()).decode()
resp = client.chat.completions.create(
model="bingbingbing-v1",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "这张图里的报错是什么原因?"},
{"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{b64}"}},
],
}],
)
print(resp.choices[0].message.content)
ToolCall
/v1/* 不会替你执行任何工具。 服务端只回传模型决定的
tool_calls;真正去查数据库、调天气、读文件的是调用方。
执行完把结果作为一条 role: "tool" 的消息发回去,模型才会继续。
一次完整往返
- 请求里带上
tools定义。 - 模型这次不回答,返回
finish_reason: "tool_calls"与message.tool_calls。 - 你自己执行这些调用。
- 把助手消息原样、再加
role: "tool"的结果,一起发第二次请求。 - 模型基于工具结果给出最终回答。
完整示例:请求 → tool_calls → 执行 → role: tool 发回
第 1 步:带上工具定义请求
POST /v1/chat/completions
{
"model": "bingbingbing-v1",
"messages": [
{ "role": "user", "content": "杭州现在多少度?" }
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询某个城市的当前气温",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名" }
},
"required": ["city"]
}
}
}
]
}
第 2 步:拿到 tool_calls(模型还没有回答)
{
"choices": [{
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_01H...",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"杭州\"}" }
}]
}
}]
}
第 3 步:自己执行,再把结果发回第二次请求
POST /v1/chat/completions
{
"model": "bingbingbing-v1",
"messages": [
{ "role": "user", "content": "杭州现在多少度?" },
// 助手那条必须原样带回,tool_calls 里的 id 是关联凭据
{ "role": "assistant", "content": null,
"tool_calls": [{
"id": "call_01H...", "type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"杭州\"}" }
}]},
// 这一步就是你执行工具后的产物
{ "role": "tool", "tool_call_id": "call_01H...", "content": "{\"temp_c\": 21, \"sky\": \"多云\"}" }
]
}
Python:把往返写成循环
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.bingbingbing.top/v1", api_key=temp_key)
# 工具表:名字 → 真正干活的函数。模型只会报名字,执行权在你手里。
HANDLERS = {"get_weather": lambda city: {"temp_c": 21, "sky": "多云"}}
messages = [{"role": "user", "content": "杭州现在多少度?"}]
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询某个城市的当前气温",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
for _ in range(5): # 设个上限,避免模型来回调工具停不下来
resp = client.chat.completions.create(
model="bingbingbing-v1", messages=messages, tools=tools,
)
msg = resp.choices[0].message
if not msg.tool_calls:
print(msg.content)
break
messages.append(msg) # 助手消息原样入列,保留 tool_call_id
for call in msg.tool_calls:
args = json.loads(call.function.arguments or "{}")
result = HANDLERS[call.function.name](**args) # ← 你在执行
messages.append({
"role": "tool",
"tool_call_id": call.id, # 必须对上,否则模型认不出这是哪次调用的结果
"content": json.dumps(result, ensure_ascii=False),
})
Anthropic 协议的写法差别
- 工具定义是
tools: [{ name, description, input_schema }],没有function这层包装。 - 模型返回
stop_reason: "tool_use",内容块type: "tool_use",字段是id/name/input(已经是对象,不用再json.loads)。 - 结果回填成一条 user 消息,内容块为
{ type: "tool_result", tool_use_id, content }。
错误码
| 状态码 | 含义 | 你该怎么做 |
|---|---|---|
| 401 | key 无效或已过期(临时 key 超过 10 分钟就会走到这里) | 重新调 POST /v1/keys 换一把;长期 key 则检查是否配到 BING_API_KEYS 里。 |
| 429 | 并发超出上限(临时 key 为 1),或触发限流 | 串行化请求,或退避后重试;确实需要并发就改用长期 key。 |
| 503 | 服务端未配置密钥 | 这是部署方的问题,不是你的请求有问题。联系服务提供者。 |
| 502 | 上游不可达 | 指数退避重试。若持续出现,多半是上游实例正在拉起或已经挂掉。 |
| 504 | 上游超时(冷启动时最容易遇到) | 把客户端超时放宽到 180 秒以上再重试,见下一节。 |
401 和 429 是客户端可以自己处理的;502 / 503 / 504 是服务端与上游的状态,重试策略比改请求参数更有用。
冷启动
实例在空闲时会缩容到零。这意味着一段时间没人用之后的第一个请求会明显变慢, 通常需要 1–3 分钟把模型加载起来。
- 网页端会显示「服务器正在启动 bingbingbing-v1 模型…」,这段时间不用刷新页面。
- API 调用请把客户端超时设到 180 秒以上。默认的 30 秒或 60 秒几乎必然在冷启动时超时,然后你会看到一个
504。 - 冷启动期间不要并发重试:同一把临时 key 并发上限是 1,第二个请求会拿到
429,反而更难判断真实状态。 - 想探活又不想触发加载,用
GET /v1/models,它不进入推理。
from openai import OpenAI
client = OpenAI(
base_url="https://api.bingbingbing.top/v1",
api_key=temp_key,
timeout=240.0, # 冷启动留够 1–3 分钟 + 推理时间
max_retries=1, # 别在冷启动时打出一串并发请求
)
限额
网页端(chat.bingbingbing.top)有两道每分钟的限流,用于保护公共实例:
| 范围 | 限制 | 命中后 |
|---|---|---|
网页端对话 /api/chat | 每分钟 12 次 | 被拒绝,稍后重试 |
| 网页端工具调用 | 每分钟 60 次 | 被拒绝,稍后重试 |
这两条限流只作用于网页端那条通道。/v1/* 不受它们约束,
而是受 key 自身的并发上限约束(临时 key 为 1,超出返回 429)。
限额是每分钟重置的滑动窗口,不是整数分钟边界。被拒后等一小会儿再试,比立刻重试更容易成功。