Hoshimi

OpenAI SDK 与 curl

用 OpenAI 的 Python、JavaScript SDK 和 curl 调用 Hoshimi 的 Chat Completions 与 Responses:非流式与流式、推理内容、推理强度、工具调用、图片和错误处理。

OpenAI 的官方 SDK 只要换掉 Base URL 就能直接调用 Hoshimi。本页的示例都在本机实测过;把 <model-id> 换成 GET /v1/models 返回的某个 id(见接入客户端)。

连接参数

参数值
Base URLhttp://127.0.0.1:10600/v1
API Key网关的模型 API 密钥;没有设置时填任意非空字符串
模型/v1/models 中的 id,或 渠道/模型

示例从环境变量读取密钥:

export HOSHIMI_API_KEY="你的模型 API 密钥"   # 没有设置模型 API 密钥时随便填

Python

pip install openai

非流式

import os
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:10600/v1", api_key=os.environ["HOSHIMI_API_KEY"])

reply = client.chat.completions.create(
    model="<model-id>",
    messages=[{"role": "user", "content": "用一句话介绍你自己"}],
    stream=False,
)
print(reply.choices[0].message.content)

一定要写 stream=False

Hoshimi 在请求没有 stream 字段时默认流式返回。SDK 在不传 stream 时不会发送这个字段,create() 会返回一段 SSE 原文(str)而不是对象。Responses 的 client.responses.create() 也一样。

流式

stream = client.chat.completions.create(
    model="<model-id>",
    messages=[{"role": "user", "content": "数到五"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

流中有一个 choices 为空、只带 usage 的块(上游报告了用量时),不需要设置 stream_options;所以循环里要先判断 chunk.choices。

推理内容与推理强度

推理模型的思考过程放在 reasoning_content 字段,SDK 把它作为附加属性保留:

reply = client.chat.completions.create(
    model="<model-id>",
    reasoning_effort="high",
    messages=[{"role": "user", "content": "91 是质数吗?一句话回答。"}],
    stream=False,
)
message = reply.choices[0].message
print("思考:", getattr(message, "reasoning_content", None))
print("回答:", message.content)

流式时从 chunk.choices[0].delta 上用 getattr(delta, "reasoning_content", None) 读取。reasoning_effort 可以写 minimal、low、medium、high、xhigh、max,也可以写模型自己的级别名;各模型的级别见 模型 页,映射规则见 OpenAI 兼容接口。

工具调用

import json

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询城市当前天气",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

def get_weather(city: str) -> str:
    return json.dumps({"city": city, "sky": "sunny", "celsius": 21})

messages = [{"role": "user", "content": "巴黎现在天气怎么样?"}]
while True:
    reply = client.chat.completions.create(model="<model-id>", messages=messages, tools=tools, stream=False)
    message = reply.choices[0].message
    if not message.tool_calls:
        print(message.content)
        break
    messages.append(message.model_dump(exclude_none=True))
    for call in message.tool_calls:
        result = get_weather(**json.loads(call.function.arguments))
        messages.append({"role": "tool", "tool_call_id": call.id, "content": result})

tool_choice 可以是 auto、none、required 或指定某个函数。每一轮都要把完整的 messages 发回去,Hoshimi 不保存历史。

图片

图片作为 image_url 内容块发送,可以是 data: URL:

import base64

data = base64.b64encode(open("photo.png", "rb").read()).decode()
reply = client.chat.completions.create(
    model="<model-id>",
    messages=[{"role": "user", "content": [
        {"type": "text", "text": "图里有什么?"},
        {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{data}"}},
    ]}],
    stream=False,
)

只有能看图的模型会理解图片,模型 页的 视觉 标记说明了这一点。

Responses

response = client.responses.create(
    model="<model-id>",
    instructions="回答尽量简短。",
    input="91 是质数吗?",
    reasoning={"effort": "low"},
    stream=False,
)
print(response.output_text)

流式时用 stream=True 逐个读取事件:

events = client.responses.create(model="<model-id>", input="数到五", stream=True)
for event in events:
    if event.type == "response.output_text.delta":
        print(event.delta, end="", flush=True)

不要用 responses.stream()

Hoshimi 的 Responses 事件流不发送 response.content_part.added 等内容分段事件,Python SDK 的 client.responses.stream() 辅助方法依赖这些事件,会抛出 IndexError。请改用上面的 responses.create(..., stream=True)。

Responses 的工具调用循环:把上一轮的全部 output 项和 function_call_output 一起放回 input:

tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "查询城市当前天气",
    "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
}]

items = [{"role": "user", "content": "巴黎现在天气怎么样?"}]
while True:
    response = client.responses.create(model="<model-id>", input=items, tools=tools, stream=False)
    calls = [item for item in response.output if item.type == "function_call"]
    if not calls:
        print(response.output_text)
        break
    items += [item.model_dump(exclude_none=True) for item in response.output]
    for call in calls:
        result = get_weather(**json.loads(call.arguments))
        items.append({"type": "function_call_output", "call_id": call.call_id, "output": result})

Responses 是无状态的:previous_response_id 和结构化输出 text.format 会被拒绝(400),只接受 type: "function" 的工具。完整的字段说明见 OpenAI 兼容接口。

JavaScript / TypeScript

npm install openai
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "http://127.0.0.1:10600/v1",
  apiKey: process.env.HOSHIMI_API_KEY,
});

// 非流式:同样必须写 stream: false
const reply = await client.chat.completions.create({
  model: "<model-id>",
  messages: [{ role: "user", content: "用一句话介绍你自己" }],
  stream: false,
});
console.log(reply.choices[0].message.content);

// 流式
const stream = await client.chat.completions.create({
  model: "<model-id>",
  messages: [{ role: "user", content: "数到五" }],
  stream: true,
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}

和 Python 一样,不写 stream 时 create() 返回的是 SSE 原文字符串。

curl

非流式:

curl http://127.0.0.1:10600/v1/chat/completions \
  -H "Authorization: Bearer $HOSHIMI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"<model-id>","messages":[{"role":"user","content":"你好"}],"stream":false}'

流式(-N 关闭 curl 的缓冲):

curl -N http://127.0.0.1:10600/v1/chat/completions \
  -H "Authorization: Bearer $HOSHIMI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"<model-id>","messages":[{"role":"user","content":"数到五"}]}'

每个事件是一行 data: {...},最后是 data: [DONE]。

Responses:

curl http://127.0.0.1:10600/v1/responses \
  -H "Authorization: Bearer $HOSHIMI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"<model-id>","input":"你好","reasoning":{"effort":"low"},"stream":false}'

没有设置模型 API 密钥时可以省略 Authorization 头,x-api-key: <密钥> 也可以代替它。

在 Playground 里,回复的 更多操作 菜单中的 复制为 API 请求 会把这条回复背后的 Chat Completions 请求复制成一条 curl 命令,密钥从 $HOSHIMI_API_KEY 读取,可以直接作为起点。

错误处理

/v1 的错误使用 OpenAI 的格式,SDK 会抛出对应的异常(如 openai.NotFoundError、openai.BadRequestError):

{"error": {"message": "Model 'nope' not found. Use `provider/model` or see /v1/models.", "type": "invalid_request_error", "code": "model_not_found"}}

流式响应开始之后,上游的失败不能再改变 HTTP 状态码:Chat Completions 会发出一个 data: {"error": {...}} 事件并结束(没有 data: [DONE]),Responses 发出 response.failed 事件或同样的 error 事件。失败的调用会记录在 用量 页,可以在那里查看错误信息。错误码的完整列表见 API 概览。

本页目录