【AI大模型接入SDK】 —— Ollama本地接入Deepseek

🌈欢迎来到实战项目专栏 ~~ 从零实现AI大模型接入SDK

AI大模型接入SDK

🌏为什么需要本地接入大模型

各大模型厂商已经提供了网页版的大模型使用服务,比如DeepSeek、ChatGPT等,用户直接在网页上提问,就能得到需要的答案,为什么还要本地接入大模型呢?

使用云端大模型的优点

  • 效果强:云端算力足、模型大,输出质量通过高于本地模型
  • 即开即用:无需下载和配置,注册后即可使用
  • 自动升级:官方会不断更新和优化模型
  • 插件生态:ChatGPTplus、GeminiAdvanced等往往自带额外功能

因此对于普通用户和非敏感任务,直接使用官网的云端服务是最简单、最经济的选择

但对于企业、有隐私或特殊需求的用户,就需要本地部署大模型

本地接入大模型步骤:

下面介绍使用Ollama本地接入:deepseek

🌏Ollama介绍

Getup and running with large language models

🔥Ubuntu下Ollama的使用

ubuntu下Ollama下载安装

cpp 复制代码
curl -fsSL https://ollama.com/install.sh | sh

Ollama常用指令

命令 描述
ollama serve 启动 Ollama
ollama show 显示模型信息
ollama run 运行模型
ollama stop 停止正在运行的模型
ollama pull 从 Ollama 官方维护的模型库中拉取模型
ollama list 列出所有可用模型
ollama ps 列出正在运行的模型
ollama rm 删除模型
ollama help 显示任意命令的帮助信息
标志 描述
-h--help 显示 Ollama 的帮助信息
-v--version 显示版本信息

注意: ollama serve 启动的是一个前台进程 ,终端关闭进程也就关闭了。在生产环境中,推荐使用 systemctl 来管理 Ollama 服务,该种方式下 Ollama 服务在后台运行,即使终端关闭服务仍会继续运行

cpp 复制代码
sudo systemctl start ollama    # 启动服务
sudo systemctl stop ollama     # 停⽌服务
sudo systemctl restart ollama  # 重启服务
sudo systemctl status ollama   # 查看服务状态

Ollama 服务启动之后,查看ollama服务运行情况:

可以看到,Ollama监听localhost11434端口,因此在模型接入时endpoint可设置为:

bash 复制代码
http://127.0.0.1:11434
c 复制代码
ollama run deepseek-r1:1.5b

ollama 会自动下载 deepseek-r1:1.5b模型,大概1.1GB

下载好,我们查看一下:

cpp 复制代码
/bye         //退出与模型聊天

blobs/目录下存储模型的实际权重数据文件,文件名是 sha256-<哈希值> ,用来保证唯一性和去重

manifests/ 目录下存储模型的清单信息,包括模型的metadata(名称、版本、描述等信息)、依赖关系和模型运行的参数配置

当在终端运行 ollama run deepseek-r1:1.5b 命令时,Ollama首先查看 manifests/ 目录,找到 deepseek-r1:1.5b 的清单文件,根据清单文件中的哈希值,找到 blobs/ 目录中对应的模型文件,加载并运行模型

🔥Windows下Ollama的使用

在终端下打开

或者在浏览器中输⼊: http://localhost:11434 ,看到 Ollama is running 时说明ollama服务已经启动了

win10中,ollama安装好之后会带一个图形化界面:

设置界面

环境变量:

参数 标识与配置
OLLAMA_MODELS 表示模型文件的存放目录 ,默认目录为当前用户目录 ,即 C:\Users\%username%\.ollama\models。建议放在其他盘,例如 D:\ApplyTool\ProgramTool\ollama\models
OLLAMA_HOST 表示 Ollama 服务监听的网络地址 ,默认为 127.0.0.1。如果想允许其他电脑访问 Ollama(如局域网中的其他电脑),建议设置为 0.0.0.0
OLLAMA_PORT 表示 Ollama 服务监听的默认端口 ,默认为 11434。如果端口有冲突,可以修改为其他端口,例如 8080
OLLAMA_ORIGINS 表示 HTTP 客户端的请求来源 ,使用半角逗号分隔列表。如果本地使用不受限制,可以设置成星号 *
OLLAMA_KEEP_ALIVE 表示大模型加载到内存后的存活时间 ,默认为 5m,即 5 分钟。例如纯数字 300 代表 300 秒;0 代表处理请求响应后立即卸载模型;任何负数表示一直存活。建议设置成 24h,即模型在内存中保持 24 小时,提高访问速度。
OLLAMA_NUM_PARALLEL 表示请求处理的并发数量 ,默认为 1(即单并发串行处理请求)。建议按照实际需求进行调整。
OLLAMA_MAX_QUEUE 表示请求队列长度 ,默认值为 512。建议按照实际需求进行调整,超过队列长度的请求会被抛弃。
OLLAMA_DEBUG 表示输出 Debug 日志 ,应用开发阶段可以设置为 1(即输出详细日志信息,便于排查问题)。
OLLAMA_MAX_LOADED_MODELS 表示最多同时加载到内存中模型的数量 ,默认为 1(即只能有 1 个模型在内存中)。

🌏Ollama模型初始化

由于现在是通过Ollama本地接入某个大模型,Ollama实际是在本地搭建了一个服务器,用户可以通过Ollama下载需要接入的模型,Ollma会替用户管理模型,并真正和大模型对接,用户通过Ollama提供的HTTP接口访问 。用户向大模型发的消息实际是,先发给Ollama服务器,Ollama服务器将消息发给大模型,大模型响应之后,Ollama再将消息返回给用户,用户不直接和大模型交互,因此初始化时不需要设置apikey进行身份认证

注意:本地部署可以在自己的本地机器上,也可以在企业自己的局域网或云服务器上

Ollama接入本地模型:下载安装好Ollama,下载对应的模型 --- deepseek-r1 ,通过代码方式接入本地模型时,实现一个接入程序:LLMManager

Ollama可以接入许多大模型,具体接入那个大模型看用户选择,因此需加⼊_model_name_model_desc来保存接入的大模型的名称和描述信息

OllamaProvider.h的实现

cpp 复制代码
#pragma once
#include "LLMProvider.h"

namespace ai_chat_sdk{
    class OllamaProvider : public LLMProvider{
    public:
        //初始化模型
        bool initModel(const std::map<std::string, std::string>& config) override;
        //检查模型是否可用
        bool isAvailable() const override;
        //获取模型名称
        std::string getModelName() const override;
        //获取模型描述
        std::string getModelDesc() const override;
        //发送消息 ------ 全量返回
        std::string sendMessage(const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam) override;
        //发送消息 ------ 增量返回, 流式返回
        std::string sendMessageStream(const std::vector<Message>& messages, 
                                       const std::map<std::string, std::string>& requestParam,
                                       std::function<void(const std::string&, bool)> callback) override;

    protected:
        std::string _model_name; // 模型名称     因为Ollama中会存在多个模型,所以需要指定模型名称
        std::string _model_desc; // 模型描述
    };
}

OllamaProvider.cpp的实现

cpp 复制代码
#pragma once
#include "../include/util/OllamaProvider.h"
#include "../include/util/myLog.h"
#include "jsoncpp/json/json.h"
#include "../include/util/httplib.h"
#include <jsoncpp/json/writer.h>
#include <sstream>

namespace ai_chat_sdk{
    //初始化模型
    bool OllamaProvider::initModel(const std::map<std::string, std::string>& config){
        //初始化模型名称
        auto it = config.find("_model_name");
        if(it == config.end()){
            ERR("OllamaProvider initModel: _model_name not found in config");
            return false;
        }
        model_name = it->second;

        //初始化模型描述信息
        it = config.find("model_desc");
        if(it == config.end()){
            ERR("OllamaProvider initModel: model_desc not found in config");
            return false;
        }
        model_desc = it->second;

        //初始化endpoint
        it = config.find("_endpoint");
        if(it == config.end()){
            ERR("OllamaProvider initModel: _endpoint not found in config");
            return false;
        }
        _endpoint = it->second;
        
        _is_available = true;
        return true;
    }

    //检测模型是否可用
    bool OllamaProvider::isAvailable() const{
        return _is_available;
    }
    //获取模型名称
    std::string OllamaProvider::getModelName() const{
        return model_name;
    }
    //获取模型描述
    std::string OllamaProvider::getModelDesc() const{
        return model_desc;
    }
        //发送消息 ------ 全量返回
    std::string OllamaProvider::sendMessage(const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam)
    {
        return "";
    };

    //发送消息 ------ 增量返回, 流式返回
    std::string OllamaProvider::sendMessageStream(const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam, std::function<void(const std::string&, bool)> callback)
    {
        return "";
    }
}

接下来重点去实现全量返回和流式返回 以及二者的测试

🌏发送消息 --- 全量消息

发送全量消息接口

URL/api/chat

启动ollama之后,在终端中使用bash给ollama发送请求

cpp 复制代码
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}}'

看看是否能收到模型的响应信息?此处有可能会失败:

  • 尝试将代理关闭 ,不运行clash工具 + ~bashrc文件中添加的环境变量屏蔽掉
  • 如果还是不行,检查curl工具是否还在走代理,将代理给屏蔽掉

成功后可以拿到模型的响应信息:

后续进行反序列化的时候,也是要在响应信息里找到有效的内容信息进行返回,和以往的实现方式差不多

只是在请求体上有变化:注意温度值和最大token数要放在一个options对象中

cpp 复制代码
//发送消息 ------ 全量返回
std::string OllamaProvider::sendMessage(const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam)
{
    //1.模型是否有效
    if(!_is_available){
        ERR("OllamaProvider sendMessage: model is not available");
        return "";
    }

    //2.构建请求参数:温度值和最大token数
    float temperature = 0.7;
    int max_tokens = 1024;
    if(requestParam.find("temperature") != requestParam.end()){
        temperature = std::stof(requestParam.at("temperature"));
    }
    if(requestParam.find("max_tokens") != requestParam.end()){
        max_tokens = std::stoi(requestParam.at("max_tokens"));
    }

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

    //4.构造请求体,这里注意温度值和最大token数要放在一个options对象中
    Json::Value options;
    options["temperature"] = temperature;
    options["num_ctx"] = max_tokens;

    Json::Value requestBody;
    requestBody["model"] = model_name;
    requestBody["messages"] = messageArray;
    requestBody["options"] = options;
    requestBody["stream"] = false;

    //5.对requestBody进行序列化
    Json::StreamWriterBuilder writerbuilder;
    writerbuilder["indentation"] = "";
    std::string requestBodyStr = Json::writeString(writerbuilder, requestBody);

    //6.创建客户端
    httplib::Client client(_endpoint);
    client.set_connection_timeout(30, 0);                    // 30秒连接超时
    client.set_read_timeout(60, 0);                          // 60秒读取超时

    //7.设置请求头
    httplib::Headers headers = {
        {"Content-Type", "application/json"}
    };

    //8.发送请求
    auto result = client.Post("/v1/chat/completions", headers, requestBodyStr, "application/json");
    if(!result)  // 请求失败
    {
        ERR("GeminiProvider sendMessage: request failed, status code: {}", to_string(result.error()));
        return "";
    }

    //模型响应不成功
    if(result->status != 200)
    {
        ERR("GeminiProvider sendMessage: request failed, status code: {}", result->status);
        return "";
    }

    INFO("GeminiProvider sendMessage: HTTP响应成功, status: {}", result->status);
    INFO("GeminiProvider sendMessage: HTTP响应体: {}", result->body);

    //9.反序列化 模型返回的响应结果 
    Json::Value responseBody;
    Json::CharReaderBuilder readerBuilder;
    std::istringstream responsestream(result->body);
    std::string errors;
    if (!Json::parseFromStream(readerBuilder, responsestream, &responseBody, &errors)) {
        ERR("GeminiProvider sendMessage: JSON解析失败: {}", errors);
        return "";
    }

    //解析成功 ------ 提取信息内容
    if(responseBody.isMember("message") && responseBody["message"].isObject() && responseBody["message"].isMember("content"))
    {
        std::string content = responseBody["message"]["content"].asString();
        INFO("OllamaProvider sendMessage: 响应内容: {}", content);
        return content;
    };

    //解析失败说明responseBody结构有问题
    ERR("OllamaProvider sendMessage: response body structure is wrong");
    return "";
}

🌏发送消息-全量消息测试

cpp 复制代码
//Ollama -- 全量返回测试
TEST(OllamaProvider, sendMessage) 
{
    //实例化OllamaProvider的对象
    auto provider = std::make_shared<ai_chat_sdk::OllamaProvider>();
    ASSERT_TRUE(provider != nullptr); //不报错就是成功

    std::map<std::string, std::string> modelParams;
    modelParams["model_name"] = "deepseek-r1:1.5b";
    modelParams["model_desc"] = "本地部署的模型:deepseek-r1:1.5b,专注于深度理解与推理";
    modelParams["_endpoint"] = "http://127.0.0.1:11434";

    //初始化对象
    provider->initModel(modelParams);
    ASSERT_TRUE(provider->isAvailable()); //

    //因为调用sendMessage方法需要传入参数:requestParam、message
    const std::map<std::string, std::string> requestParam = {
        {"temperature", "0.7"}, 
        {"max_tokens", "2048"}
    };

    //调用sendMessage方法,发送消息
    std::vector<ai_chat_sdk::Message> messages;
    messages.push_back(ai_chat_sdk::Message("user", "你是谁?"));
    std::string fulldata = provider->sendMessage(messages, requestParam);

    ASSERT_FALSE(fulldata.empty()); //期待响应是false,如果fulldata是有数据的,没问题;反之数据为空会导致测试失败,报错
    INFO("response: {}", fulldata);
}

接着在CMakelist中添加对应OllamaProvider.cpp

接下来我们看看测试的效果

🌏发送消息 --- 流式响应

URL/api/chat

首先还是启动ollama之后,在终端中使用bash给ollama发送请求试一下

同样的要找出message对象里的content字段的信息,如果"done"字段是false的话,说明流式的增量数据还没有发送完毕!为true说明发送完毕了

要注意的是:Ollama已经对模型返回来的信息是做过处理了的,数据块之间只有单个\n 进行分隔,并且已经把SSE返回的数据进行了二次加工变成了下图的样子

  • 所以我们对响应数据进行解析就直接对下图的返回数据来进行解析即可

注意:

  • 收到所有完整的事件,事件和事件之间以\n分隔
  • 在解析数据时候,先处理done = "false"的结尾事件
cpp 复制代码
chunkJson.get("done", false).asBool()

"done" 字段就取它的值,没有就返回 false。最终转化成bool值进行返回

cpp 复制代码
//发送消息 ------ 增量返回, 流式返回
std::string OllamaProvider::sendMessageStream(const std::vector<Message>& messages, const std::map<std::string, std::string>& requestParam, std::function<void(const std::string&, bool)> callback)
{
    //1.模型是否有效
    if(!_is_available){
        ERR("OllamaProvider sendMessageStream: model is not available");
        return "";
    }

    //2.构建请求参数:温度值和最大token数
    float temperature = 0.7;
    int max_tokens = 1024;
    if(requestParam.find("temperature") != requestParam.end()){
        temperature = std::stof(requestParam.at("temperature"));
    }
    if(requestParam.find("max_tokens") != requestParam.end()){
        max_tokens = std::stoi(requestParam.at("max_tokens"));
    }

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

    //4.构造请求体,这里注意温度值和最大token数要放在一个options对象中
    Json::Value options;
    options["temperature"] = temperature;
    options["num_ctx"] = max_tokens;

    Json::Value requestBody;
    requestBody["model"] = model_name;
    requestBody["messages"] = messageArray;
    requestBody["options"] = options;
    requestBody["stream"] = true;

    //5.对requestBody进行序列化
    Json::StreamWriterBuilder writerbuilder;
    writerbuilder["indentation"] = "";
    std::string requestBodyStr = Json::writeString(writerbuilder, requestBody);

    //6.创建客户端
    httplib::Client client(_endpoint);
    client.set_connection_timeout(30, 0);                    // 30秒连接超时
    client.set_read_timeout(300, 0);                          // 60秒读取超时

    //7.设置请求头
    httplib::Headers headers = {
        {"Content-Type", "application/json"}
    };

    //流式响应变量
    std::string buffer;             // 流式数据接收区
    bool gotError = false;          // 是否发生错误
    std::string ErrorMsg;           // 错误信息
    int statusCode = 0;             // HTTP状态码
    bool isfinished = false;        // 流式响应是否结束
    std::string fulldata;           // 完整响应内容

    //创建请求对象
    httplib::Request request;
    request.method = "POST";
    request.path = "/api/chat";
    request.headers = headers;
    request.body = requestBodyStr;

    //响应头处理器
    request.response_handler = [&](const httplib::Response& request) {
        statusCode = request.status;
        if (statusCode != 200) {
            gotError = true;
            ErrorMsg = "OllamaProvider sendMessageStream: request failed, status code: " + std::to_string(statusCode);
            return false;
        }
    };

    //内容接收处理器
    request.content_receiver = [&](const char* data, size_t datalen, size_t offset, size_t totallen)->bool {
        if (gotError) 
            return false;

        //将数据追加到缓冲区
        buffer.append(data, datalen);
        INFO("OllamaProvider sendMessageStream: received data:{} ", buffer);
        
        //数据块之间是以\n分隔
        //此处接收到的数据块并不是模型返回的SSE格式数据,而是经过ollama处理过的数据: 字节流
        size_t pos = 0;
        while((pos = buffer.find("\n")) != std::string::npos)
        {
            std::string chunk = buffer.substr(0, pos);
            buffer.erase(0, pos + 1);

            if (chunk.empty()) 
                continue;

            //对字节流进行反序列化
            Json::Value chunkJson;
            Json::CharReaderBuilder readerBuilder;
            std::istringstream iss(chunk);
            std::string errors;
            //解析JSON数据
            if (!Json::parseFromStream(readerBuilder, iss, &chunkJson, &errors)) {
                ERR("OllamaProvider sendMessageStream: JSON解析失败: {}", errors);
                return false;
            }

            //get done字段: 如果为true,则表示响应结束
            if (chunkJson.get("done", false).asBool()) {
                isfinished = true;
                callback("", true);
                return true;
            }

            //解析成功 ------ 提取信息内容
            if (chunkJson.isMember("message") && chunkJson["message"].isObject() && chunkJson["message"].isMember("content")) {
                std::string delta = chunkJson["message"]["content"].asString();
                fulldata += delta;
                callback(delta, false);
            }
        }
        return true;
    };

    //给ollama发送请求
    auto result = client.send(request);
    if (!result) {
        ERR("OllamaProvider sendMessageStream: request failed, error: {}", to_string(result.error()));
        return "";
    }

    if(!isfinished)
    {
        WARN("OllamaProvider sendMessageStream: response is not finished");
        callback("", true); 
    }

    return "";
}

🌏发送消息 --- 流式响应测试

cpp 复制代码
//Ollama -- 流式返回测试
TEST(OllamaProvider, sendMessage) 
{
    //实例化OllamaProvider的对象
    auto provider = std::make_shared<ai_chat_sdk::OllamaProvider>();
    ASSERT_TRUE(provider != nullptr); //不报错就是成功

    std::map<std::string, std::string> modelParams;
    modelParams["model_name"] = "deepseek-r1:1.5b";
    modelParams["model_desc"] = "本地部署deepseek-r1:1.5b模型, 采⽤专家混合架构,专注于深度理解与推理";
    modelParams["_endpoint"] = "http://127.0.0.1:11434";

    //初始化对象
    provider->initModel(modelParams);
    ASSERT_TRUE(provider->isAvailable()); //

    //因为调用sendMessage方法需要传入参数:requestParam、message
    const std::map<std::string, std::string> requestParam = {
        {"temperature", "0.7"}, 
        {"max_tokens", "2048"}
    };

    //调用sendMessage方法,发送消息
    std::vector<ai_chat_sdk::Message> messages;
    messages.push_back(ai_chat_sdk::Message("user", "你是谁?"));

    //流式响应
    auto writechunk = [&](const std::string& chunk, bool last) {
        INFO("chunk: {}", chunk);
        if (last) {
            INFO("[Done]");
        }
    };
    std::string fulldata = provider->sendMessageStream(messages, requestParam, writechunk);

    ASSERT_FALSE(fulldata.empty()); //期待响应是false,如果fulldata是有数据的,没问题;反之数据为空会导致测试失败,报错
    INFO("response: {}", fulldata);
}

📢写在最后

接下来登场的是 模型管理

相关推荐
美林数据Tempodata1 小时前
从“开得起来“到“开得下去“:高校AI通识课建设的系统性解法与4E/4S双轨框架拆解
人工智能·产教融合·ai教育·教育改革·通识教育
谢亮_vipxieliang1 小时前
ValidX与Maven/Gradle集成配置指南
java·spring boot·spring·maven·hibernate
比兔代理1 小时前
路由器静态IP怎么设置?光猫、WAN口与局域网配置教程
网络·智能路由器
Nuanyt1 小时前
JUC常见核心知识梳理01 线程 并发 JMM volatile 管程 锁 synchronized
java·开发语言·网络·jvm
m0_614523551 小时前
平面跟踪为什么中途漂移:按首次异常帧建立排查记录
人工智能·平面·视频编辑
深度学习lover1 小时前
<数据集>番茄叶片病害识别<目标检测>
人工智能·yolo·目标检测·计算机视觉·数据集·番茄叶片病害识别
hfywmsj1 小时前
广州餐饮铺位招租数据建模:基于多维评估体系的选址算法设计与实现
人工智能·算法·广州餐饮铺位招租
驱动小百科1 小时前
英伟达GeForce Game Ready 616.92驱动发布:支持《007 First Light》等游戏
人工智能·游戏·英伟达616.92驱动·geforce game·nvidia显卡驱动
Geek-Chow1 小时前
DeepSeek Harness 深入指南 / DeepSeek Harness Series
人工智能