跳到主要内容

如何计算 AI 问答成本

目前大部分 AI 服务都采用 SSE(Server-Sent Events,服务器推送事件)进行回应,即逐字返回对于问题的响应。SSE 是一种基于 HTTP 协议的实时通信技术,常见于 LLM(大语言模型)接口功能的调试场景。

开发人员在调用 AI 应用的 API 时经常有着 SSE 事件的拼接场景,以及拼接后的 Tokens 换算及成本预估等需求。下文将以请求调试某个 AI 应用为例,演示如何在调试接口的过程中,自动将输入和输出的字符数换算为 Token 值,配合实时汇率接口,在单次调试 API 的过程中就能估算出大致成本。

前置准备

开始在调试过程中计算问答成本前需要了解大模型厂商的计费条件。以 OpenAI 定价策略为例,需要先统计每次问答过程中输入和输出所耗费的 Tokens 值,再将其换算为人民币。

GPT 价格

因此想要在调试 API 过程中计算出问答成本分为以下两步: 1. 计算输入和输出 Tokens 值。 2. 按照实时汇率换算为人民币。

Tokens 值换算库

将内容准确换算为 Tokens 值需引用第三方 Token 换算库。下面将以 OpenAI GPT Token Counter 库为例,使得能够在调试接口的过程中将输入/输出数据换算为 Tokens 数。

Node.js 示例代码:

const openaiTokenCounter = require('openai-gpt-token-counter');

const text = process.argv[2]; // 获取命令行参数中的测试内容
const model = "gpt-4"; // 替换为你想要使用的 OpenAI 模型

const tokenCount = openaiTokenCounter.text(text, model);
const characterCount = text.length; // 计算字符数

console.log(`${tokenCount}`);

将该 Node.js 脚本重命名为 gpt-tokens-counter.js 后放置于 Apifox 的外部程序目录下以供调用。

安装 Token 换算库

在脚本的所在目录中执行以下命令,初始化脚本的运行环境。

npm install openai-gpt-token-counter

实时汇率接口

得到输入和输出所耗费的 Tokens 值后,还需要通过实时汇率接口预估所消耗的成本(人民币)。本文将调用 Currencylayer API 获取实时汇率,点击此处注册账号并获取 API Key。

输入成本

换算 Tokens 输入值

输入值可以理解为用户在询问 AI 应用时所填写的问题和 Prompt,因此需要在前置操作添加自定义脚本提取位于请求参数 Body 中的 query 参数,方便换算为 Tokens 值。

在「前置操作」中添加上文的 Tokens 值换算脚本,参考以下示例代码:

try {
var jsonData = JSON.parse(pm.request.body.raw);
// 注意将 ./gpt-tokens/gpt-tokens-counter.js 替换为脚本实际存放的路径。
var result_input_tokens_js = pm.execute('./gpt-tokens/gpt-tokens-counter.js',[jsonData.query])
console.log(jsonData.query); // 控制台打印参整个 json 数据
pm.environment.set("RESULT_INPUT_TOKENS", result_input_tokens_js);
console.log("Input Tokens count: " + pm.environment.get("RESULT_INPUT_TOKENS"));
} catch (e) {
console.log(e);
}

点击“运行”按钮后可以在控制台中看到已统计的输入值。

换算为实际成本(人民币)

得到输入所耗费的 Tokens 值后,还需请求实时汇率接口得到一个换算乘数,再将其与 Tokens 值相乘得到实际成本(人民币)。在前置操作中添加以下脚本:

pm.sendRequest("http://apilayer.net/api/live?access_key=YOUR-API-KEY&currencies=CNY&source=USD&format=1", (err, res) => {
if (err) {
console.log(err);
} else {
const quotes = res.json().quotes;
const rate = parseFloat(quotes.USDCNY).toFixed(3);
pm.environment.set("USDCNY_RATE", rate);
var USDCNY_RATE = pm.environment.get("USDCNY_RATE");
// 取上个前置脚本中的 RESULT_INPUT_TOKENS 变量
var RESULT_INPUT_TOKENS = pm.environment.get("RESULT_INPUT_TOKENS");

// 计算 tokens 汇率值
const tokensExchangeRate = 0.03; // 每 1000 tokens 的美元价格(以 GPT-4-8k context 输入定价为参考)

// 计算人民币预估价格
const CNYPrice = ((RESULT_INPUT_TOKENS / 1000) * tokensExchangeRate * USDCNY_RATE).toFixed(2);

pm.environment.set("INPUT_PRICE", CNYPrice);

console.log("输入值预估成本(人民币): " + CNYPrice + "元");
}
});

输出成本

拼接返回响应

当接口返回响应中的 Content-Type 包含 text/event-stream 参数时,Apifox 会自动将返回的数据解析为 SSE 事件。通常情况下 SSE 事件中的每个返回中仅包含片段字符(通常是 1 个字),此时需要将所有返回内容重新拼接为完整语句。

前往接口定义中的后置操作,添加自定义脚本提取响应内容并完成拼接。

拼接响应示例代码:

// 获取响应的文本
const text = pm.response.text()
// 将文本分割成行
var lines = text.split('\n');
// 创建一个空数组来存储 "answer" 参数
var answers = [];
// 遍历每一行
for (var i = 0; i < lines.length; i++) {
const line = lines[i];
// 跳过不以 data 开头的行
if (!line.startsWith('data:')) {
continue;
}
// 尝试解析 JSON 数据
try {
var data = JSON.parse(line.substring(5).trim()); // 去掉前面的 "data: "
// 获取 "answer" 参数,并将其添加到数组中
answers.push(data.answer);
} catch (e) {
// 如果当前行不是有效的 JSON 数据,就忽略它
}
}
// 使用 join() 方法拼接 "answer" 参数
var result = answers.join('');
// 将结果显示在 body 的“可视化”标签页
pm.visualizer.set(result);
// 打印结果到控制台
console.log(result);

发起请求后可以在控制台中得到完整的响应内容。

换算 Tokens 输出值

得到完整的响应内容后,还需要通过第三方库将其换算为 Tokens 值。在后置操作中添加以下自定义脚本,使得 Apifox 能够调用外部 gpt-tokens-counter.js 脚本(脚本具体代码请参考前置准备:Tokens 值换算库)得出 Tokens 值。

// 获取响应的文本
const text = pm.response.text()
// 将文本分割成行
var lines = text.split('\n');
// 创建一个空数组来存储 "answer" 参数
var answers = [];
// 遍历每一行
for (var i = 0; i < lines.length; i++) {
const line = lines[i];
// 跳过不以 data 开头的行
if (!line.startsWith('data:')) {
continue;
}
// 尝试解析 JSON 数据
try {
var data = JSON.parse(line.substring(5).trim()); // 去掉前面的 "data: "
// 获取 "answer" 参数,并将其添加到数组中
answers.push(data.answer);
} catch (e) {
// 如果当前行不是有效的 JSON 数据,就忽略它
}
}
// 使用 join() 方法拼接 "answer" 参数
var result = answers.join('');
// 将结果显示在 body 的“可视化”标签页
pm.visualizer.set(result);
// 打印结果到控制台
console.log(result);

// 计算输出的 tokens 数量。
var RESULT_OUTPUT_TOKENS = pm.execute('./gpt-tokens/gpt-tokens-counter.js',[result])
pm.environment.set("RESULT_OUTPUT_TOKENS", RESULT_OUTPUT_TOKENS);

console.log("Output Tokens count: " + pm.environment.get("RESULT_OUTPUT_TOKENS"));

实际成本(人民币)

与前文中的成本计算方案类似,得到输出所耗费的 Tokens 值后,将其与汇率相乘得到实际成本(人民币)。

在后置操作中添加以下脚本:

pm.sendRequest("http://apilayer.net/api/live?access_key=YOUR-API-KEY&currencies=CNY&source=USD&format=1", (err, res) => {
if (err) {
console.log(err);
} else {
const quotes = res.json().quotes;
const rate = parseFloat(quotes.USDCNY).toFixed(3);
pm.environment.set("USDCNY_RATE", rate);
var USDCNY_RATE = pm.environment.get("USDCNY_RATE");
// 取上个后置脚本中的 RESULT_OUTPUT_TOKENS 变量
var RESULT_OUTPUT_TOKENS = pm.environment.get("RESULT_OUTPUT_TOKENS");

// 计算 tokens 汇率值
const tokensExchangeRate = 0.06; // 每 1000 tokens 的美元价格(以 GPT-4-8k context 输入定价为参考)

// 计算人民币预估价格
const CNYPrice = ((RESULT_OUTPUT_TOKENS / 1000) * tokensExchangeRate * USDCNY_RATE).toFixed(2);

pm.environment.set("OUTPUT_PRICE", CNYPrice);

console.log("输出成本(人民币): " + CNYPrice + "元");
}
});

预估总成本

最后在后置操作中添加一个可以自动计算输入 + 输出总成本的自定义脚本。

// 输入输出成本加总

const INPUTPrice = Number(pm.environment.get("INPUT_PRICE"));

const OUTPUTPrice = Number(pm.environment.get("OUTPUT_PRICE"));

console.log("总成本:" + (INPUTPrice + OUTPUTPrice) + "元");

使得在调试接口的过程中就能够预估出本次请求的大致成本。