Ollama API 全量响应模式接入指南:从接口规范到 C++ 实现

0 阅读

Ollama API 基础认知

Ollama 是一个用于在本地运行大语言模型的服务,它通过统一的 REST API 屏蔽了不同模型的底层差异。对于聊天场景,核心接口是 /api/chat,该接口支持两种响应模式:流式(逐块返回)和全量(一次性返回完整结果)。本文聚焦于后者,即设置 stream: false 的使用方式。

文章配图

模型命名与时长单位

调用时需指定模型名称,格式为 模型名:标签,例如 deepseek-r1:1.5b。所有时间相关的字段(如加载耗时、推理耗时)均以纳秒为单位返回,这在做性能分析时需要注意单位换算。

/api/chat 全量返回接口规范

接口基本信息

  • 方法: POST
  • 路径: /api/chat
  • 地址: 默认为 http://127.0.0.1:11434
  • Content-Type: application/json

请求参数

请求体是一个 JSON 对象,包含必选和可选参数。

必选参数

  • model: 字符串,指定要使用的模型。
  • messages: 数组,包含对话历史。每条消息有 rolesystem/user/assistant/tool)和 content 字段。

关键可选参数

  • stream: 设为 false 以获取全量响应。
  • options: 一个对象,用于传递模型超参数。
    • temperature: 控制输出随机性,范围 0~1。
    • num_ctx: 上下文窗口大小(注意,Ollama 用的是 num_ctx,不是常见的 max_tokens)。
  • format: 可设为 json 或一个 JSON Schema,强制模型输出结构化数据。

响应结构

全量模式下,服务器会返回一个完整的 JSON 对象,而非多个数据块。其核心结构如下:

{
  "model": "deepseek-r1:1.5b",
  "created_at": "2026-08-28T04:56:59.195988466Z",
  "message": {
    "role": "assistant",
    "content": "..."
  },
  "done": true,
  "done_reason": "stop",
  "total_duration": 39751163910,
  "load_duration": 1337544202,
  "prompt_eval_count": 6,
  "prompt_eval_duration": 1795779000,
  "eval_count": 40,
  "eval_duration": 35587041000
}

其中,message.content 就是我们需要的模型回复文本。其他字段如 *_duration*_count 对于监控和调试非常有用,分别记录了各阶段的耗时(纳秒)和处理的 token 数量。

环境验证与常见排障

在编写代码前,最好先用 curl 命令行工具验证本地 Ollama 服务是否正常工作。

curl -s -X POST "http://127.0.0.1:11434/api/chat" \
-H "Content-Type: application/json" \
-d '{
  "model": "deepseek-r1:1.5b",
  "stream": false,
  "messages": [{"role": "user", "content": "你是谁?"}],
  "options": {"temperature": 0.7, "num_ctx": 2048}
}'

执行这条命令,如果能立刻看到一个完整的 JSON 响应,说明环境是通的。

常见问题

代理冲突:如果你的系统配置了 HTTP 代理,curl 可能会尝试通过代理连接 127.0.0.1,导致请求失败或超时。解决方法是临时取消代理设置,或者在 curl 命令中显式指定不使用代理(--noproxy "*")。

首次请求慢:第一次调用某个模型时,Ollama 需要从磁盘将模型加载到内存,这个过程可能很慢。这是正常现象,后续请求会快很多。可以通过 keep_alive 参数让模型在内存中多驻留一段时间。

C++ 全量返回实现流程

下面是一个基于 C++ 的完整实现思路,使用了 httplib 作为 HTTP 客户端,JsonCpp 处理 JSON 数据。

1. 构造请求体

首先,将内部的消息列表和超参数转换成 Ollama 要求的 JSON 格式。

// 构建历史消息数组
Json::Value messageArray(Json::arrayValue);
for (const auto& message : messages) {
    Json::Value msg;
    msg["role"] = message._role;
    msg["content"] = message._content;
    messageArray.append(msg);
}

// 构建options
Json::Value options;
options["temperature"] = temperature; // 从配置中读取
options["num_ctx"] = numCtx;         // 注意字段名是 num_ctx

// 构建完整请求体
Json::Value requestBody;
requestBody["model"] = _modelName;
requestBody["messages"] = messageArray;
requestBody["options"] = options;
requestBody["stream"] = false; // 关键!关闭流式

// 序列化为字符串
Json::StreamWriterBuilder builder;
std::string requestBodyStr = Json::writeString(builder, requestBody);

这里的关键点是确保 stream 字段为 false,并且上下文窗口参数使用 num_ctx 而非 max_tokens

在这里插入图片描述

2. 发送 HTTP 请求

在这里插入图片描述

使用 httplib::Client 创建客户端,配置合理的超时时间(模型推理可能较慢),然后发送 POST 请求。

httplib::Client client(_endpoint.c_str());
client.set_connection_timeout(30, 0); // 30秒连接超时
client.set_read_timeout(60, 0);       // 60秒读取超时

httplib::Headers headers = {{"Content-Type", "application/json"}};
auto response = client.Post("/api/chat", headers, requestBodyStr, "application/json");

if (!response || response->status != 200) {
    // 处理网络错误或HTTP错误
    return "";
}

3. 解析响应

收到响应后,将其反序列化为 JSON 对象,并从中提取 message.content 字段。

Json::Value responseBody;
Json::CharReaderBuilder reader;
std::string errors;
std::istringstream stream(response->body);

if (!Json::parseFromStream(reader, stream, &responseBody, &errors)) {
    // JSON解析失败
    return "";
}

// 提取回复内容
if (responseBody.isMember("message") &&
    responseBody["message"].isObject() &&
    responseBody["message"].isMember("content")) {
    return responseBody["message"]["content"].asString();
} else {
    // 响应格式不符合预期
    return "";
}

整个流程的核心在于正确地构造请求和稳健地处理响应,尤其是对各种可能的错误(网络、HTTP状态码、JSON格式)进行妥善处理。

单元测试与工程配置

为了保证代码质量,编写单元测试是必不可少的。可以使用 Google Test 框架来模拟调用过程。

TEST(OllamaLLMProviderTest, sendMessage) {
    auto provider = std::make_shared<OllamaLLMProvider>();
    // ... 初始化模型参数 ...
    provider->initModel(modelParam);
    ASSERT_TRUE(provider->isAvailable());

std::vector<Message> messages = {{"user", "你是谁?"}};
    std::map<std::string, std::string> requestParam = {
        {"temperature", "0.7"},
        {"max_tokens", "2048"}
    };

std::string response = provider->sendMessage(messages, requestParam);
    ASSERT_FALSE(response.empty());
    // 可以进一步断言response是否包含预期关键词
}

对应的 CMakeLists.txt 需要链接必要的库,如 jsoncpphttplib(通常作为头文件库)、OpenSSL(用于 HTTPS)以及 gtest

# ... 其他配置 ...
target_link_libraries(testLLM 
    jsoncpp 
    fmt
    spdlog 
    gtest 
    OpenSSL::SSL 
    OpenSSL::Crypto
)

扩展知识点

推理思考字段

像 DeepSeek-R1 这样的推理模型,有时会在回复中包含类似 `` 的标记,里面是模型的“思维链”。SDK 通常直接返回原始内容,是否解析和展示这部分内容,应由上层应用决定。

结构化输出

如果业务需要模型返回固定格式的数据(比如一个 JSON 对象),可以在请求中加入 format 字段。设为 "json" 可以让模型尽量输出合法 JSON;更进一步,传入一个完整的 JSON Schema,则能约束输出的具体结构,这对于自动化处理非常有用。