做了一个利用Qt实现调用文字大模型的API 小软件 AI.xyz。
想通过api直接访问国产语言大模型的调用情况,翻了半天 豆包、通义、文心 的官方文档。最后只找到百度提供通过api读取访问的功能。
一开始只看到 python
的sdk,试了试还可以,用 Qt
实现了下并集成进了AI.xyz。
python
resp = resources.Service.V2.describe_preset_services(
service_ids=['svcp-7d6044e91474', 'svcp-7940ab471306']
)
print(json.dumps(resp.body, indent=4))
resp = resources.Service.V2.service_metric(
"2024-07-04T15:51:00Z", "2024-08-04T15:51:00Z",service_id=[],app_id= ["94470723"] )
print(json.dumps(resp.body, indent=4))
print(json.dumps(resp.headers, indent=4))
软件内简单做了个页面显示。
希望豆包、通义赶紧增加对应接口,要不然只能统计本地调用的tokens。
最后写完发现官方也提供了SDK,没去编译,照着官方的又改了下自己写的。
百度智能云API鉴权
鉴权的主要目的是用于校验调用者的身份信息。调用千帆平台功能OpenAPI需使用基于安全认证AK/SK进行签名计算鉴权。
百度智能云提供的在线生成签名工具。
1 使用 QT 计算鉴权Authorization
cpp
#ifndef ModelApiMetrics_H
#define ModelApiMetrics_H
#include <QJsonObject>
#include <QCryptographicHash>
#include <QMessageAuthenticationCode>
#include <QString>
#include <QByteArray>
#include "hv/HttpClient.h"
// 大模型服务度量指标
// 百度鉴权 https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Sly8bm96d
// 百度签名计算工具 https://cloud.baidu.com/signature/index.html
class ModelApiMetrics {
public:
// 生成百度服务度量指标
static QMap<QString, QList<int> >GenerateBaiDuServiceMetric(
const QString& ak, const QString& sk, const QString& appId);
private:
// 生成百度服务的认证签名
static QString GenerateBaiDuAuth(HttpRequestPtr& req, QString ak, QString sk, int expireSeconds = 300);
// 使用 HMAC-SHA256 算法生成十六进制签名。
static QString HmacSha256Hex(const QString& key, const QString& message);
// 对字符串进行 URL 编码。
static QString UrlEncode(const QString& src, bool encodeSlash = false);
// 去掉字符串首尾指定的字符。
static QString Trim(const QString& s, const QString& chars = QStringLiteral(" \t\n\r\f\v"));
};
#endif // ifndef ModelApiMetrics_H
cpp
QString ModelApiMetrics::GenerateBaiDuAuth(HttpRequestPtr& req, QString ak, QString sk, int expireSeconds)
{
QString authStr;
QString signTime = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
authStr = QString("bce-auth-v1/%1/%2/%3")
.arg(ak)
.arg(signTime)
.arg(expireSeconds);
QString canonicalReq;
// method
req->method = HTTP_POST;
switch (req->method) {
case HTTP_GET:
canonicalReq += QString("GET\n");
break;
case HTTP_POST:
canonicalReq += QString("POST\n");
break;
case HTTP_PUT:
canonicalReq += QString("PUT\n");
break;
default:
break;
}
// url
canonicalReq += QString("%1\n").arg(UrlEncode(QString::fromStdString(req->url)));
// query string
hv::QueryParams params = req->query_params;
if (0 != params.size()) {
QStringList paramList;
for (auto& pair : params) {
QString encodedKey = UrlEncode(Trim(QString::fromStdString(pair.first)));
QString encodedValue = UrlEncode(Trim(QString::fromStdString(pair.second)));
paramList.append(QString("%1=%2").arg(encodedKey, encodedValue));
}
std::sort(paramList.begin(), paramList.end());
canonicalReq += QString("%1\n").arg(paramList.join("&"));
} else {
canonicalReq += '\n';
}
// header
canonicalReq += QString("host:%1").arg(UrlEncode(QString::fromStdString(req->headers["Host"])));
QString signKey = HmacSha256Hex(sk, authStr);
QString signature = HmacSha256Hex(signKey, canonicalReq);
authStr += QString("/host/%1").arg(signature);
return authStr;
}
QString ModelApiMetrics::HmacSha256Hex(const QString& key, const QString& message)
{
QByteArray keyBytes = key.toUtf8();
QByteArray messageBytes = message.toUtf8();
QByteArray hmac = QMessageAuthenticationCode::hash(messageBytes, keyBytes, QCryptographicHash::Sha256);
return QString(hmac.toHex());
}
QString ModelApiMetrics::UrlEncode(const QString& src, bool encodeSlash)
{
QByteArray encodedBytes;
for (QChar c : src) {
if ((c.isLetterOrNumber() || (c == '_') || (c == '-') || (c == '~') || (c == '.')) ||
((c == '/') && !encodeSlash)) {
encodedBytes.append(c.toLatin1());
} else {
encodedBytes.append('%');
QByteArray hex = QByteArray::number(c.unicode(), 16).toUpper();
if (hex.size() == 1) {
encodedBytes.append('0');
}
encodedBytes.append(hex);
}
}
return QString::fromUtf8(encodedBytes);
}
QString ModelApiMetrics::Trim(const QString& s, const QString& chars)
{
int start = 0;
int end = s.size() - 1;
while (start <= end && chars.contains(s[start])) {
++start;
}
while (end >= start && chars.contains(s[end])) {
--end;
}
return s.mid(start, end - start + 1);
}
2 通过 Libhv 查询 文心一言 服务调用情况
cpp
QMap<QString, QList<int> >ModelApiMetrics::GenerateBaiDuServiceMetric(
const QString& ak, const QString& sk, const QString& appId)
{
QMap<QString, QList<int> > resultMap;
HttpRequestPtr req(new HttpRequest);
req->url = "/v2/service";
req->method = HTTP_POST;
req->headers["Host"] = "qianfan.baidubce.com";
req->headers["Content-Type"] = "application/json";
req->query_params["Action"] = "DescribeServiceMetric";
req->headers["Authorization"] = GenerateBaiDuAuth(req, ak, sk).toLocal8Bit();
req->url = "https://qianfan.baidubce.com/v2/service";
QDateTime currentDateTimeUtc = QDateTime::currentDateTimeUtc();
QDateTime dateTime20DaysAgo = currentDateTimeUtc.addDays(-20);
QJsonObject jsonObject;
jsonObject["startTime"] = dateTime20DaysAgo.toString(Qt::ISODate);
jsonObject["endTime"] = currentDateTimeUtc.toString(Qt::ISODate);
jsonObject["appId"] = QJsonArray{ appId };
QJsonDocument jsonDocument(jsonObject);
QByteArray jsonData = jsonDocument.toJson(QJsonDocument::Compact);
req->body = jsonData;
hv::HttpClient client;
HttpResponse resp;
int ret = client.send(req.get(), &resp);
if (ret != 0) {
return resultMap;
}
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(resp.body.c_str(), &jsonError);
if (jsonDoc.isNull() || (jsonError.error != QJsonParseError::NoError)) {
return resultMap;
}
QJsonObject resultObject = jsonDoc.object();
QJsonObject result = resultObject["result"].toObject();
QJsonArray serviceList = result["serviceList"].toArray();
for (const QJsonValue& serviceVal : serviceList) {
QJsonObject serviceObj = serviceVal.toObject();
QString serviceName = serviceObj["serviceName"].toString();
QJsonArray appList = serviceObj["appList"].toArray();
for (const QJsonValue& appVal : appList) {
QJsonObject appObj = appVal.toObject();
QJsonObject metric = appObj["metric"].toObject();
QList<int> metricsList;
metricsList.append(metric["inputTokensTotal"].toInt());
metricsList.append(metric["outputTokensTotal"].toInt());
metricsList.append(metric["tokensTotal"].toInt());
metricsList.append(metric["succeedCallTotal"].toInt());
metricsList.append(metric["failureCallTotal"].toInt());
metricsList.append(metric["callTotal"].toInt());
resultMap[serviceName] = metricsList;
}
}
return resultMap;
}
3 使用返回结果
cpp
void SettingsPanelWid::ServiceMetric()
{
ConfigManager::GetInstance().WriteValue("ernie_ak", ui->ernie_ak->text());
ConfigManager::GetInstance().WriteValue("ernie_sk", ui->ernie_sk->text());
ConfigManager::GetInstance().WriteValue("ernie_appId", ui->ernie_appId->text());
QMap<QString, QList<int> > resMap = ModelApiMetrics::GenerateBaiDuServiceMetric(
ui->ernie_ak->text(), ui->ernie_sk->text(), ui->ernie_appId->text());
QString labName = QString("lab_transfer_%1_%2");
foreach(auto& name, resMap.keys())
{
int model = static_cast<int>(ModelInfoManager::GetModel(name));
for (int i = 0; i < resMap[name].size(); i++) {
QString labObjName = labName.arg(model).arg(i + 1);
QLabel* lab = ui->tab_4->findChild<QLabel *>(labObjName);
if (lab) {
lab->setText(QString::number(resMap[name][i]));
}
}
}
}