530 lines
13 KiB
C++
530 lines
13 KiB
C++
#pragma once
|
||
|
||
#include <string>
|
||
#include <vector>
|
||
#include <map>
|
||
|
||
#define CURL_STATICLIB
|
||
|
||
#include "mg_curl_export.h"
|
||
|
||
#define RAPIDJSON_HAS_STDSTRING 1
|
||
#include "rapidjson/document.h"
|
||
#include "rapidjson/encodings.h"
|
||
#include "rapidjson/stringbuffer.h"
|
||
#include "rapidjson/filereadstream.h"
|
||
#include "rapidjson/filewritestream.h"
|
||
#include "rapidjson/encodedstream.h"
|
||
#include "rapidjson/prettywriter.h"
|
||
|
||
#pragma comment (lib,"wldap32.lib")
|
||
#pragma comment (lib,"ws2_32.lib")
|
||
#pragma comment (lib,"Crypt32.lib")
|
||
#pragma comment (lib,"Normaliz.lib")
|
||
|
||
namespace AIJSON
|
||
{
|
||
std::string w2utf(LPCTSTR s, DWORD cp = CP_UTF8);
|
||
|
||
std::string w2utf(const std::wstring &s, DWORD cp = CP_UTF8);
|
||
|
||
std::wstring utf2w(LPCSTR s, DWORD cp = CP_UTF8);
|
||
|
||
std::wstring utf2w(const std::string &s, DWORD cp = CP_UTF8);
|
||
|
||
using namespace rapidjson;
|
||
|
||
typedef GenericDocument<UTF8<> > Document8;
|
||
typedef GenericValue<UTF8<> > Value8;
|
||
typedef const GenericValue<UTF8<> > CValue8;
|
||
typedef GenericArray<false, Value8> Arr8;
|
||
typedef GenericArray<true, Value8> CArr8;
|
||
typedef Value8::ConstMemberIterator CMIt8;
|
||
typedef Value8::MemberIterator MIt8;
|
||
typedef Value8::ConstValueIterator CVIt8;
|
||
typedef Value8::ValueIterator VIt8;
|
||
typedef GenericStringBuffer<UTF8<> > StringBuffer8;
|
||
typedef PrettyWriter<StringBuffer8, UTF8<>, UTF8<>> PrettyWriter8;
|
||
typedef Writer<StringBuffer8, UTF8<>, UTF8<>> Writer8;
|
||
|
||
Document8::AllocatorType &getJsonAllocator();
|
||
Value8& addMember(Value8 &v, LPCSTR name, Value8 &value);
|
||
Value8& addMember(Value8 &v, LPCSTR name, LPCSTR value);
|
||
Value8& addMember(Value8 &v, LPCSTR name, const int &value);
|
||
Value8& addMember(Value8 &v, LPCSTR name, const bool &value);
|
||
Value8& addMember(Value8 &v, LPCSTR name, const float &value);
|
||
|
||
Value8& pushBack(Value8 &arr, Value8 &value);
|
||
Value8& pushBack(Value8 &arr, LPCSTR value);
|
||
Value8& pushBack(Value8 &arr, const int &value);
|
||
Value8& pushBack(Value8 &arr, const bool &value);
|
||
Value8& pushBack(Value8 &arr, const float &value);
|
||
std::string write2Json(const Value8 &v, bool pretty);
|
||
bool json2File(LPCTSTR path, const Value8 &v, bool pretty = false);
|
||
bool file2Json(LPCTSTR pszJson, Document8 &doc);
|
||
|
||
template <typename T>
|
||
bool parseValue(CValue8 &v, LPCSTR key, T &t)
|
||
{
|
||
if (!key) return false;
|
||
CMIt8 it = v.FindMember(key);
|
||
if (it != v.MemberEnd() && it->value.Is<T>())
|
||
{
|
||
if (it->value.IsNull()) return false;
|
||
t = it->value.Get<T>();
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
|
||
namespace SHAI
|
||
{
|
||
|
||
class initCurl
|
||
{
|
||
public:
|
||
initCurl() { mg_curl_httpGlobalInit(); }
|
||
~initCurl() { mg_curl_httpGlobalCleanup(); }
|
||
};
|
||
|
||
class Response;
|
||
class Request
|
||
{
|
||
public:
|
||
|
||
enum roleType
|
||
{
|
||
RT_SYSTEM,
|
||
RT_USER,
|
||
RT_ASSISTANT,
|
||
RT_TOOL
|
||
};
|
||
|
||
struct message
|
||
{
|
||
roleType role;
|
||
std::string content;
|
||
};
|
||
|
||
public:
|
||
|
||
Request(LPCSTR apikey
|
||
, LPCSTR model
|
||
, LPCSTR url
|
||
, const bool &stream
|
||
);
|
||
|
||
Request();
|
||
|
||
void setapikey(LPCSTR apikey) { _apiKey = apikey; }
|
||
|
||
void setmodel(LPCSTR model) { _model = model; }
|
||
|
||
void seturl(LPCSTR url) { _apiEndpoint = url; }
|
||
|
||
void setstream(const bool &stream) { _stream = stream; if (_stream) _stream_options_include_usage = true; }
|
||
|
||
int send(Response &response) const;
|
||
|
||
std::string requestBody() const;
|
||
|
||
void addUserMessage(LPCSTR content);
|
||
|
||
void addSystemMessage(LPCSTR content);
|
||
|
||
void addAssistantMessage(LPCSTR content);
|
||
|
||
void clearMessages();
|
||
|
||
void addHeaders(LPCSTR key, LPCSTR value);
|
||
|
||
std::string apiEndPoint() const;
|
||
|
||
void set_frequency_penalty(const float &f){ _frequency_penalty = f;}
|
||
|
||
void set_top_p(const float &f) { _top_p = f;}
|
||
|
||
void set_temperature(const float &f) { _temperature = f;}
|
||
|
||
void set_max_tokens(const int &d) { _max_tokens = d;}
|
||
|
||
void set_presence_penalty(const float &f) { _presence_penalty = f;}
|
||
|
||
void set_top_k(const int &n){ _top_k = n;}
|
||
|
||
// 清除最后一个消息
|
||
void popbackMessage();
|
||
|
||
private:
|
||
|
||
void addMessage(const roleType &rt, LPCSTR content);
|
||
|
||
std::string getRole(const roleType &rt) const;
|
||
|
||
private:
|
||
std::string _apiKey;
|
||
|
||
std::string _apiEndpoint;
|
||
|
||
std::map<std::string, std::string> _headers;
|
||
|
||
std::string _model;
|
||
|
||
/*对话的消息列表。*/
|
||
std::vector<message> _messages;
|
||
|
||
/*
|
||
如果设置为 True,将会以 SSE(server-sent events)的形式以流式发送消息增量。
|
||
消息流以 data: [DONE] 结尾。
|
||
*/
|
||
bool _stream;
|
||
|
||
/*
|
||
流式输出相关选项。只有在 stream 参数为 true 时,才可设置此参数。
|
||
如果设置为 true,在流式消息最后的 data: [DONE] 之前将会传输一个额外的块。
|
||
此块上的 usage 字段显示整个请求的 token 使用统计信息,而 choices 字段将始终是一个空数组。
|
||
所有其他块也将包含一个 usage 字段,但其值为 null。
|
||
*/
|
||
bool _stream_options_include_usage;
|
||
|
||
/*
|
||
介于 -2.0 和 2.0 之间的数字。
|
||
如果该值为正,那么新 token 会根据其在已有文本中的出现频率受到相应的惩罚,降低模型重复相同内容的可能性。
|
||
default : 0
|
||
*/
|
||
float _frequency_penalty;
|
||
|
||
/*
|
||
介于 1 到 8192 间的整数,限制一次请求中模型生成 completion 的最大 token 数。
|
||
输入 token 和输出 token 的总长度受模型的上下文长度的限制。
|
||
如未指定 max_tokens参数,默认使用 4096。
|
||
*/
|
||
int _max_tokens;
|
||
|
||
/*
|
||
介于 -2.0 和 2.0 之间的数字。
|
||
如果该值为正,那么新 token 会根据其是否已在已有文本中出现受到相应的惩罚,从而增加模型谈论新主题的可能性。
|
||
default : 0
|
||
*/
|
||
float _presence_penalty;
|
||
|
||
/*
|
||
一个 object,指定模型必须输出的格式。
|
||
可以设置为 { "type": "plain_text" } 以启用纯文本模式,该模式保证模型生成的消息是有效的纯文本。
|
||
设置为 { "type": "json_object" } 以启用 JSON 模式,该模式保证模型生成的消息是有效的 JSON。
|
||
注意: 使用 JSON 模式时,你还必须通过系统或用户消息指示模型生成 JSON。
|
||
否则,模型可能会生成不断的空白字符,直到生成达到令牌限制,从而导致请求长时间运行并显得“卡住”。
|
||
此外,如果 finish_reason="length",这表示生成超过了 max_tokens 或对话超过了最大上下文长度,消息内容可能会被部分截断。
|
||
|
||
可能的值: "text" , "json_object"
|
||
default: "text"
|
||
*/
|
||
std::string _response_format_type;
|
||
|
||
/*
|
||
一个 string 或最多包含 16 个 string 的 list,在遇到这些词时,API 将停止生成更多的 token。
|
||
*/
|
||
std::vector<std::string> _stop;
|
||
|
||
/*
|
||
采样温度,介于 0 和 2 之间。
|
||
更高的值,如 0.8,会使输出更随机,而更低的值,如 0.2,会使其更加集中和确定。
|
||
我们通常建议可以更改这个值或者更改 top_p,但不建议同时对两者进行修改。
|
||
default:1
|
||
*/
|
||
float _temperature;
|
||
|
||
/*
|
||
作为调节采样温度的替代方案,模型会考虑前 top_p 概率的 token 的结果。
|
||
所以 0.1 就意味着只有包括在最高 10% 概率中的 token 会被考虑。
|
||
我们通常建议修改这个值或者更改 temperature,但不建议同时对两者进行修改。
|
||
*/
|
||
float _top_p;
|
||
|
||
/*
|
||
是否返回所输出 token 的对数概率。如果为 true,则在 message 的 content 中返回每个输出 token 的对数概率。
|
||
*/
|
||
bool _logprobs;
|
||
|
||
/*
|
||
一个介于 0 到 20 之间的整数 N,指定每个输出位置返回输出概率 top N 的 token,且返回这些 token 的对数概率。
|
||
指定此参数时,logprobs 必须为 true。
|
||
*/
|
||
int _top_logprobs;
|
||
|
||
/**/
|
||
int _top_k;
|
||
};
|
||
|
||
class Response
|
||
{
|
||
public:
|
||
Response();
|
||
|
||
public:
|
||
void readResponse(const std::string &s);
|
||
|
||
bool isUsageTokens() const { return _bUsageTokens; }
|
||
|
||
const std::string& content() const { return _content; }
|
||
|
||
const std::string& reasoningContent() const { return _reasoning_content; }
|
||
|
||
const std::string& allreasoningContent() const { return _all_reasoning_content; }
|
||
|
||
const std::string& allContent() const { return _all_content; }
|
||
|
||
int promptTokens() const { return _prompt_tokens; }
|
||
|
||
int completionTokens() const { return _completion_tokens; }
|
||
|
||
int totalTokens() const { return _total_tokens; }
|
||
|
||
void clear();
|
||
|
||
bool showThinkingContent() { return _bShowThinkingContent; }
|
||
|
||
void setShowThinkingContent(const bool &bShow) { _bShowThinkingContent = bShow; }
|
||
|
||
void appendOriginalResponse(const std::string &s) { _allOriginalResponse.push_back(s); }
|
||
|
||
const std::vector<std::string> &originalResponses() { return _allOriginalResponse; }
|
||
|
||
void setRecordContext(const bool &b) { _bRecordContext = b; }
|
||
|
||
bool needRecordContext() { return _bRecordContext; }
|
||
|
||
void set_finish_reason(LPCSTR s) { _finish_reason = s;}
|
||
|
||
const std::string& finish_reason() { return _finish_reason; }
|
||
|
||
private:
|
||
void parseChoices(AIJSON::CValue8 &v);
|
||
private:
|
||
|
||
// tokens
|
||
bool _bUsageTokens;
|
||
|
||
// finish_reason 类型
|
||
std::string _finish_reason;
|
||
|
||
// 单次思考内容
|
||
std::string _reasoning_content;
|
||
|
||
// 单次回答内容
|
||
std::string _content;
|
||
|
||
// 全部思考内容
|
||
std::string _all_reasoning_content;
|
||
|
||
// 全部回答内容
|
||
std::string _all_content;
|
||
|
||
// 提问的 token 数
|
||
int _prompt_tokens;
|
||
|
||
// 完成的 token 数
|
||
int _completion_tokens;
|
||
|
||
// 总 token 数
|
||
int _total_tokens;
|
||
|
||
// 是否显示思考内容
|
||
bool _bShowThinkingContent;
|
||
|
||
// 是否记录上下文
|
||
bool _bRecordContext;
|
||
|
||
// 全部返回
|
||
std::vector<std::string> _allOriginalResponse;
|
||
};
|
||
|
||
#pragma region AICloud
|
||
// 智能云提供商
|
||
class AICloud
|
||
{
|
||
protected:
|
||
// 智能云提供商名称
|
||
std::string _cloudName;
|
||
|
||
// apiKey
|
||
std::string _apiKey;
|
||
|
||
// api endpoint
|
||
std::string _endpoint;
|
||
|
||
// 模型列表
|
||
std::vector<std::string> _models;
|
||
|
||
// 当前模型
|
||
std::string _current_model;
|
||
|
||
// 当前使用云
|
||
bool _current_select;
|
||
|
||
public:
|
||
// 构造函数
|
||
AICloud() { }
|
||
~AICloud() { }
|
||
|
||
AICloud(LPCSTR name, LPCSTR key, LPCSTR endpoint)
|
||
: _apiKey(key)
|
||
, _endpoint(endpoint)
|
||
, _cloudName(name)
|
||
, _current_select(false)
|
||
{ }
|
||
|
||
std::string cloudName()const {return _cloudName;}
|
||
void setCloudName(LPCSTR name) { _cloudName = name; }
|
||
|
||
// 设置和获取 apiKey
|
||
void setApiKey(LPCSTR key) { _apiKey = key; }
|
||
std::string apiKey() const { return _apiKey; }
|
||
|
||
// 设置和获取 endpoint
|
||
void setEndpoint(LPCSTR endpoint) { this->_endpoint = endpoint; }
|
||
std::string endpoint() const { return _endpoint; }
|
||
|
||
// 设置获取 当前模型
|
||
void setCurrentModel(const int &idx);
|
||
void setCurrentModel(LPCSTR model){ _current_model = model;}
|
||
std::string currentModel() const { return _current_model; }
|
||
|
||
void setModels(const std::vector<std::string>& models) { _models = models;}
|
||
void insertModel(LPCSTR model) { _models.push_back(model); }
|
||
const std::vector<std::string>& models() const { return _models; }
|
||
|
||
void setCurrentSelect(const bool &b) {_current_select = b;}
|
||
bool currentSelect() const { return _current_select; }
|
||
|
||
// 解析服务器返回的模型列表json
|
||
// 支持硅基和deepseek模型
|
||
static void parseModelsJson(LPCSTR js, std::vector<std::string> &models);
|
||
|
||
virtual void getModels() {}
|
||
|
||
struct IS_EQUAL
|
||
{
|
||
IS_EQUAL(AICloud *p): _p(p) {}
|
||
AICloud *_p;
|
||
bool operator () (AICloud *p) const
|
||
{
|
||
return p->_cloudName == _p->_cloudName;
|
||
}
|
||
};
|
||
|
||
static AICloud* createCloud(LPCSTR name);
|
||
static bool IsCustomCloud(AICloud *p);
|
||
};
|
||
|
||
class AliyunBailian : public AICloud
|
||
{
|
||
public:
|
||
AliyunBailian() { }
|
||
~AliyunBailian() { }
|
||
|
||
AliyunBailian(LPCSTR key)
|
||
: AICloud(AIJSON::w2utf(_T("阿里云百炼")).c_str()
|
||
, key
|
||
, "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions")
|
||
{ }
|
||
|
||
virtual void getModels();
|
||
};
|
||
|
||
class SiliconFlow : public AICloud
|
||
{
|
||
public:
|
||
SiliconFlow() { }
|
||
~SiliconFlow() { }
|
||
|
||
SiliconFlow(LPCSTR key)
|
||
: AICloud(AIJSON::w2utf(_T("硅基流动")).c_str()
|
||
, key
|
||
, "https://api.siliconflow.cn/v1/chat/completions")
|
||
{ }
|
||
|
||
virtual void getModels();
|
||
};
|
||
|
||
class DeepSeek : public AICloud
|
||
{
|
||
public:
|
||
DeepSeek() { }
|
||
~DeepSeek() { }
|
||
|
||
DeepSeek(LPCSTR key)
|
||
: AICloud("DeepSeek", key, "https://api.deepseek.com/chat/completions")
|
||
{ }
|
||
|
||
virtual void getModels();
|
||
};
|
||
|
||
class Hyperbolic : public AICloud
|
||
{
|
||
public:
|
||
Hyperbolic() { }
|
||
~Hyperbolic() { }
|
||
|
||
Hyperbolic(LPCSTR key)
|
||
: AICloud("Hyperbolic", key, "https://api.hyperbolic.xyz/v1/chat/completions")
|
||
{ }
|
||
|
||
virtual void getModels();
|
||
};
|
||
|
||
class CustomCloud : public AICloud
|
||
{
|
||
public:
|
||
CustomCloud() { }
|
||
~CustomCloud() { }
|
||
|
||
CustomCloud(LPCSTR name, LPCSTR key, LPCSTR endpoint)
|
||
: AICloud(name, key, endpoint)
|
||
{ }
|
||
|
||
virtual void getModels() {};
|
||
};
|
||
#pragma endregion
|
||
|
||
class ChatConfig
|
||
{
|
||
private:
|
||
std::string _system_prompt;
|
||
int _max_tokens;
|
||
float _top_p;
|
||
float _temperature;
|
||
int _top_k;
|
||
float _frequency_penalty;
|
||
public:
|
||
ChatConfig()
|
||
: _system_prompt("")
|
||
, _max_tokens(4096)
|
||
, _top_p(.7f)
|
||
, _temperature(.6f)
|
||
, _top_k(50)
|
||
, _frequency_penalty(0.0f) { }
|
||
|
||
void set_system_prompt(LPCTSTR psz) { _system_prompt = AIJSON::w2utf(psz); }
|
||
void set_system_prompt(LPCSTR psz) { _system_prompt = psz; }
|
||
std::wstring system_prompt_w()const { return AIJSON::utf2w(_system_prompt); };
|
||
std::string system_prompt()const { return _system_prompt; }
|
||
|
||
void set_max_tokens(const int &i) { _max_tokens = i; }
|
||
int max_tokens()const { return _max_tokens;}
|
||
|
||
void set_top_k(const int &i){ _top_k = i;}
|
||
int top_k()const { return _top_k;}
|
||
|
||
void set_top_p(const float &f){ _top_p = f;}
|
||
float top_p()const { return _top_p;}
|
||
|
||
void set_temperature(const float &f){ _temperature = f;}
|
||
float temperature()const { return _temperature;}
|
||
|
||
void set_frequency_penalty(const float &f){ _frequency_penalty = f;}
|
||
float frequency_penalty()const { return _frequency_penalty;}
|
||
};
|
||
}
|