当前位置: 首页 > article >正文

如何使用C++来实现OPENAI协议通过OLLAMA来与AI大模型通信

目前大模型在使用方面最大的长处就是聊天功能。在聊天中可以谈天说地。可以聊聊人生,可以聊聊历史,可以让它写一首七言绝句,也可作一篇洋洋洒洒的长篇报告。

如何才能与大模型进行沟通呢,一个通用的方法是openai协议,其实质就是用http来向大模型提问所获得回答。想用http,C++程序员第一时间想到的无疑就是curl库。现在github上有个项目叫openai-cpp,就是把curl用为作通信库,封装了主要的openai协议。ollama也支持openai协议,当然也可以用这个openai-cpp来对ollama进行通信,进而可与ollama支持的大模型进行通信。

openai-cpp的项目提供了各个功能的示例,其中,主要功能当然就是chat功能了。下面来谈谈openai-cpp的使用。openai-cpp的这个示例都是用cmake来管理和编译的,因我比较熟悉qt5的qmake,就用它来管理和编译示例项目。

拿openai-cpp的10-chat.cpp来说,它的代码非常简单如下所示

#include "openai.hpp"

#include <iostream>

int main() {
    openai::start();

    auto chat = openai::chat().create(R"(
    {
        "model": "gpt-3.5-turbo",
        "messages":[{"role":"user", "content":"blah"}],
        "max_tokens": 7,
        "temperature": 0
    }
    )"_json);
    std::cout << "Response is:\n" << chat.dump(2) << '\n'; 
}

如何在Qt5下运行这个代码呢。

首先,要把openai::start();替换为openai::start("ollama", "optional_organization",true,"http://127.0.0.1:11434/v1/");在这里,“ollama""optional_organization"两个参数可随便写。"http://127.0.0.1:11434/v1/",固定为本地IP。可根据需要把IP改为其它的IP进行远程操作。

其次,以大家熟悉的windows平台为例,需要在Qt5项目的pro文件中,加入
LIBS += -L$$PWD/curl/lib -lcurl 

并将从curl官网下的win64版的curl中的include,lib,拷贝到项目所在的目录下。

然后,在pro文件中加入

SOURCES += \
        main.cpp

INCLUDEPATH += \
        curl/include \
        openai/nlohmann \
        openai

最后,我们可用系统需要最小的qwen:7b大模型来测试。
将原代码中的
    auto chat = openai::chat().create(R"(
    {
        "model": "gpt-3.5-turbo",
        "messages":[{"role":"user", "content":"blah"}],
        "max_tokens": 7,
        "temperature": 0
    }
    )"_json);
改为
    auto chat = openai::chat().create(R"(
    {
        "model": "qwen:7b",
        "messages":[{"role":"user", "content":"hello"}],
        "max_tokens": 7,
        "temperature": 0
    }
    )"_json);

这样,就可以编译成功了。
在运行前,确认ollama已正确安装并已执行过ollama run qwen:7b命令,第一次执行这个命令,ollama会自动下载qwen:7b的大模型,可能时间较长,差不多4G大小,我的网络用了将近一小时。

试运行一下,有回答了。但回答显然是不全的。这时把"max_tokens": 7,一行改为"max_tokens": 1024,就可以回答全了。

来分析一下代码:
第一行代码,是包含openai-cpp的头文件,事实上,整个openai-cpp的项目,就一个头文件。
主要的代码就是auto chat = openai::chat().create(),括号里的内容是nlohmann的json库,这个库是基于std:string的库。同样,也就只是一个名为json.hpp头文件。代码中的写法是用宏定义的方法生成了一个json字串内容为    
{
        "model": "qwen:7b",
        "messages":[{"role":"user", "content":"hello"}],
        "max_tokens": 1024,
        "temperature": 0
}
的nlohmann的json对象。这种写法作为示例程序很简洁明了,但如果要写成实用的代码则不太方便了。在上述10-chat.cpp中的加入两行
#include <nlohmann/json.hpp>
using json = nlohmann::json;

然后,就可使用nlohmann的json库了。
我是这样写的

        json prompt;
        prompt["model"] = "qwen:7b";
        json msg_json;
        msg_json["role"] = "user";
        msg_json["content"] = "邓丽君是日本人吗?";

        prompt["messages"].push_back(msg_json);
        prompt["max_tokens"] = 1024;
        prompt["temperature"] = 1.5;

这时,那个chat代码就可改为openai::chat.create(prompt);

这时,prompt就可填入任何你想问的问题。你会发现,用英文提问,回答的是英文,是中文提问,则回答中文。
这个json对象的参数中,有两个重要的参数。“max_tokens”,可以认为就是指定回答的内容长度,长度不够,则会出现回答不全的情况。"temperature"参数,是指定回答的随机性。10-chat的代码中是0,你会发现每次回答的内容都一样。事实上,查到的资料表明,这个参数的取值是在0~2之间。如我取的是1.5,随机性就已经很强了,同一个问题,回答的实质内容基本一样,但表达的文字不一样。给人的感觉就是大模型像个人在回答了,而不是机械的给出一个始终不变的标准答案。

接下来,我们来试试让大模型更像个人。向人提问,“什么最好”,正常人在回答这个问题时肯定不知道你问的哪个方面的问题,不知如何回答,但如果之前向这个人问过,“米饭好吃吗”,那这个人就可据此认为你问的“什么最好”应该是指什么最好吃。这就是上下文。openai同样也支持这个上下文。“messages"这个参数就是可以把你的提问保存下来,如果你能把大模型对这个问题的问答也保存下来,大模型就更能理解你的上下文关系了。

下面我把最终的完整代码贴出来供大家参考。

#include "openai.hpp"

using namespace openai;

#include <iostream>

#include <vector>

using namespace std;

#include <nlohmann/json.hpp>
using json = nlohmann::json;

#include <QDebug>


int main() {

    auto& openai = openai::start("ollama", "optional_organization",true,"http://127.0.0.1:11434/v1/");

    json prompt;
    prompt["model"] = "qwen:7b";

    json msg_json;
    msg_json["role"] = "user";
    msg_json["content"] = "邓丽君是日本人吗?";

    prompt["messages"].push_back(msg_json);
    prompt["max_tokens"] = 1024;
    prompt["temperature"] = 1.5; //经试验,temperature取值在0~2之间有效

    qDebug()<<"Q:"<<QString::fromStdString(msg_json["content"]);

    auto chat = openai.chat.create(prompt);

    //qDebug()<< "Response is:\n"<<QString::fromStdString(chat.dump(2));

    string res = chat.dump(2);
    //取出返回的json字串中的content内容关输出
    json jr = json::parse(res);
    // 先获取choices数组中的第一个元素(示例中只有一个元素)
    json choice = jr["choices"][0];
    // 再从对应的message对象中获取content字段
    string role = choice["message"]["role"];
    //qDebug()<<"role:"<<QString::fromStdString(role);
    string content = choice["message"]["content"];
    qDebug()<<"A:"<<QString::fromStdString(content);

    //prompt["messages"].clear(); //实践证明,只是没有清除messages的内容,默认是上下文有效的。

    //加入回答到上文中,这个添加似乎对AI联系上下文回答效果就是让AI接下来的问答知道是指邓丽君。与上文的效果相同。在有上文的情况下,看不出明显区别
    msg_json["role"] = "assistant";
    msg_json["content"] = content;
    prompt["messages"].push_back(msg_json);


    msg_json["role"] = "user";
    msg_json["content"] = "她为什么总唱日本歌?";
    prompt["messages"].push_back(msg_json);

    qDebug()<<"Q:"<<QString::fromStdString(msg_json["content"]);

    auto chat_1 = openai.chat.create(prompt);

    res = chat_1.dump(2);

    //qDebug()<< "Response is:\n"<<QString::fromStdString(res);

    jr = json::parse(res);
    // 先获取choices数组中的第一个元素(示例中只有一个元素)
    choice = jr["choices"][0];

    content = choice["message"]["content"];
    qDebug()<<"A:"<< QString::fromStdString(content);

}

在代码中,使用了qDebug来在windows控制台上输出结果,这是因为这样做可以在windows的控制台上正确的输出utf-8编码的中文了。

为方便大家,我把完整的qt5下的项目上传以供大家正载

https://download.csdn.net/download/hugerat/90291612

该项目在curl/lib文件夹下,有一个名为libcurl-x64.dll的文件,要正常运示例,需将此文件拷贝到编译生成的exe文件的相同文件夹下,或者你的windows安装了libcurl并配置好了环境变量。由名字可知它是一个64位的文件,应此Qt5的项目在编译时,应选择64位的编译器,比如我用的是mingw 64-bit


http://www.kler.cn/a/512197.html

相关文章:

  • VSCode的配置与使用(C/C++)
  • Zabbix监控山特UPS电源:实现高效监控与告警
  • 光谱相机如何还原色彩
  • 团体程序设计天梯赛-练习集——L1-013 计算阶乘和
  • C链表的一些基础知识
  • Django简介与虚拟环境安装Django
  • 搭建一个基于Spring Boot的外贸平台
  • browser-use 的简单使用
  • [Datawheel学习]用Llama-index创建Agent、数据库对话Agent和RAG接入Agent
  • Python采集modBus协议数据
  • Linux网络IOv1.1介绍-纯PDF版
  • MySQL 中单独获取已知日期的年月日
  • 直驱式风电储能制氢仿真模型matlab/simulink
  • Type-C充电与智能家居的结合
  • 【王树森搜索引擎技术】概要01:搜索引擎的基本概念
  • MySQL 事务及MVCC机制详解
  • TypeScript - 利用GPT辅助学习
  • SparkSQL数据模型综合实践
  • 电路研究9——GPRS用的AT命令手册
  • Javascript IndexedDB 数据库
  • Golang学习笔记_28——工厂方法模式(实例)
  • 【开源免费】基于SpringBoot+Vue.JS夕阳红公寓管理系统(JAVA毕业设计)
  • 告别手动编辑:如何用Python快速创建Ansible hosts文件?
  • MyBatis与Hibernate的全面对比
  • 软件测试 —— Postman(断言)
  • Windows FileZila Server共享电脑文件夹 映射21端口外网连接