如何计算 AI 问答成本
目前大部分 AI 服务都采用 SSE(Server-Sent Events,服务器推送事件)进行回应,即逐字返回对于问题的响应。SSE 是一种基于 HTTP 协议的实时通信技术,常见于 LLM(大语言模型)接口功能的调试场景。
开发人员在调用 AI 应用的 API 时经常有着 SSE 事件的拼接场景,以及拼接后的 Tokens 换算及成本预估等需求。下文将以请求调试某个 AI 应用为例,演示如何在调试接口的过程中,自动将输入和输出的字符数换算为 Token 值,配合实时汇率接口,在单次调试 API 的过程中就能估算出大致成本。
前置准备
开始在调试过程中计算问答成本前需要了解大模型厂商的计费条件。以 OpenAI 定价策略为例,需要先统计每次问答过程中输入和输出所耗费的 Tokens 值,再将其换算为人民币。
因此想要在调试 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¤cies=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¤cies=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) + "元");
使得在调试接口的过程中就能够预估出本次请求的大致成本。