# Chat Completions (https://hypit.ai/zh/api-reference/chat/)

> 可直接替换的 OpenAI 端点——同步、流式、视觉、工具调用、厂商透传。

`POST /v1/chat/completions` 是整套 API 里兼容性最好的一条。请求基本逐字节转发给上游，响应也基本
原样回来，所以只要是 OpenAI 兼容客户端能发的东西，在这里都能发。

```bash
curl https://hypit.ai/v1/chat/completions \
  -H "Authorization: Bearer $HYPIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-3.1-pro",
    "messages": [
      {"role": "system", "content": "用一句话回答。"},
      {"role": "user", "content": "Hypit 是什么？"}
    ]
  }'
```

## 配合 OpenAI SDK [#配合-openai-sdk]

```python
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["HYPIT_API_KEY"],
    base_url=os.environ.get("HYPIT_BASE_URL", "https://hypit.ai/v1"),
)

completion = client.chat.completions.create(
    model="gemini-3.1-pro",
    messages=[{"role": "user", "content": "Hypit 是什么？"}],
    max_tokens=200,
)
print(completion.choices[0].message.content)
print(completion.usage)
```

```js
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.HYPIT_API_KEY,
  baseURL: process.env.HYPIT_BASE_URL ?? "https://hypit.ai/v1",
});

const completion = await client.chat.completions.create({
  model: "gemini-3.1-pro",
  messages: [{ role: "user", content: "Hypit 是什么？" }],
  max_tokens: 200,
});
console.log(completion.choices[0].message.content);
```

## 我们读什么，转发什么 [#我们读什么转发什么]

只有两个字段是必填的，被网关真正读取的也只有这几个：

| 字段               | 我们为什么读它                           |
| ---------------- | --------------------------------- |
| `model`          | **必填**——决定路由                      |
| `messages`       | **必填**且非空——遍历它找图片分片               |
| `stream`         | 选择 SSE 路径                         |
| `stream_options` | 见下                                |
| `modalities`     | `["text","image"]` 让支持出图的对话模型返回像素 |

其余一切——`max_tokens`、`max_completion_tokens`、`temperature`、`top_p`、`stop`、`tools`、
`tool_choice`、`response_format`、`reasoning_effort`，以及任何我们从没听说过的厂商私有字段——都会
原封不动地送到上游。请求体上限 **32 MiB**。

响应体是上游的原文，只有两处例外：

1. 厂商的账单字段会被剥掉（`credits`、`cost`、`cost_usd`、`balance`、`billing` 及其同类，顶层和
   `usage` 内部都剥），免得别人家的账本泄漏进你的。你真实的花费以额度计，不在那个字段里。
2. 如果我们把你的模型名映射成了另一个上游模型名，顶层 `"model"` 字符串会被改写回你请求的那个名字。
   没有映射时，厂商自己的细化名（比如 `gpt-4o-2024-08-06`）会保留。

`usage` 本身不做改动——上游报什么你看到什么。我们内部会把它归一化用于计费，但这从不改变线上的字节。

## 流式 [#流式]

设置 `"stream": true`。响应是 `text/event-stream`，每个 chunk 是上游帧的原文，流以
`data: [DONE]` 结束——上游没发这个哨兵时由我们补上。

```python
stream = client.chat.completions.create(
    model="gemini-3.1-pro",
    messages=[{"role": "user", "content": "写一首关于纸船的俳句。"}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)
```

```js
const stream = await client.chat.completions.create({
  model: "gemini-3.1-pro",
  messages: [{ role: "user", content: "写一首关于纸船的俳句。" }],
  stream: true,
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
```

```bash
curl -N https://hypit.ai/v1/chat/completions \
  -H "Authorization: Bearer $HYPIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gemini-3.1-pro","stream":true,
       "messages":[{"role":"user","content":"写一首关于纸船的俳句。"}]}'
```

### 流式下的 usage [#流式下的-usage]

如果你完全没有传 `stream_options`，我们会替你补上 `{"include_usage": true}`，这样最后一帧会带
`usage`。只要你自己传了 `stream_options`——包括 `{"include_usage": false}`——我们就原样照办，不动它。

<Callout type="warn" title="流一旦开始，既不会故障转移，也不会改状态码">
  第一个字节上线之后，HTTP 状态码就永远是 `200` 了。之后发生的失败会以一个 SSE 帧的形式送达，帧里
  是标准错误信封，随后是 `data: [DONE]`：

  ```text
  data: {"error":{"message":"…","type":"upstream_error","code":"stream_broken","param":null}}

  data: [DONE]
  ```

  请对**每一帧**检查 `error`，而不只是看 HTTP 状态码。另外注意：流式过程中断开连接不会让这次请求
  免于计费——token 已经生成了。
</Callout>

## 视觉 [#视觉]

图片分片的写法与 OpenAI 一致：

```json
{
  "model": "gemini-3.1-pro",
  "messages": [
    {
      "role": "user",
      "content": [
        { "type": "text", "text": "这张图里有什么？" },
        { "type": "image_url", "image_url": { "url": "https://example.com/boat.png" } }
      ]
    }
  ]
}
```

`http(s)` 图片 URL 会**由网关下载**，在选择上游之前就地改写成 `data:` URL，这样只接受内联图片的
模型也能用。最多四张图并发抓取，总预算 20 秒，超时返回 `400 image_fetch_timeout`。`data:` URL
原样透传，而且总是更快的那条路。

只允许 `http`、`https` 和 `data:` 三种 scheme，抓取过程逐跳拒绝内网与链路本地地址。

## 错误 [#错误]

| 状态    | `code`                                               | 含义                    |
| ----- | ---------------------------------------------------- | --------------------- |
| `400` | `empty_body` / `invalid_request_body`                | 请求体缺失或不是 JSON 对象      |
| `400` | `missing_model` / `missing_messages`                 | 两者都必填                 |
| `400` | `invalid_message_content`                            | 某个 `content` 无法解码     |
| `400` | `invalid_media_url` / `unsupported_media_url_scheme` | `image_url.url` 未通过策略 |
| `400` | `image_fetch_timeout` / `image_fetch_failed`         | 引用的图片抓不到              |
| `413` | `request_too_large`                                  | 超过 32 MiB             |
| `404` | `model_not_found` / `no_capable_provider`            | 这把 Key 下该模型不可用于对话     |
| `502` | `empty_response` / `empty_stream`                    | 上游什么都没返回              |
| `502` | `stream_broken`                                      | 流提前中断——以 SSE 帧形式送达    |
| `503` | `no_available_provider`                              | 所有候选上游都在冷却中           |
| `504` | `request_timeout`                                    |                       |

上游失败会被分类，而不是原样透出：`upstream_error`（502）、`upstream_rate_limited`（429）、
`upstream_quota_exhausted`（402）、`upstream_unauthorized`（401）和 `upstream_rejected_request`
（厂商自己的 4xx，消息脱敏后转发）。我们返回的任何内容里都不会出现上游名称、base URL、上游模型名
或凭据。

公共错误码见[错误与限流](/zh/api-reference/errors)。