LangChain.js 实战系列:如何统计大模型使用的 token 使用量和花费
'# LangChain.js 实战系列:如何统计大模型使用的 token 使用量和花费
一、背景与问题
在大模型应用开发中,token 使用量和费用统计是核心指标。以通义千问、GPT-4等模型为例,单次调用的token消耗可能高达数千,而单token价格可达0.0001美元。若不进行精细统计,一个简单的对话系统可能在短时间内产生数万美元的费用。
传统方案存在三个核心痛点:
- 模型差异:不同模型的token价格差异可达300%
- 计算复杂度:需同时统计输入和输出token
- 性能瓶颈:高并发场景下统计效率要求
在LangChain.js中,我们需要通过自定义中间件和费用计算逻辑,构建一个完整的统计体系。这涉及多个技术点:模型调用链路拦截、token计数算法、费用计算模型以及性能优化方案。
二、基本原理
1. Token统计原理
每个模型调用的token消耗包含两部分:
- 输入token:用户输入内容经过tokenize后的数量
- 输出token:模型生成内容经过tokenize后的数量
计算公式:
total_tokens = input_tokens + output_tokens2. 费用计算模型
费用计算需考虑:
- 模型价格(如Qwen-7B的价格为$0.0001/token)
- 价格单位(部分模型按输入输出分别计费)
- 累计统计(需要记录历史数据)
3. 中间件设计
通过LangChain的LLMChain和StreamingAgent,我们可以在调用链路中插入统计逻辑:
const { LLMChain, PromptTemplate } = require("langchain");
const { Qwen } = require("langchain/llms");
const chain = new LLMChain({
llm: new Qwen(),
prompt: PromptTemplate.fromTemplate("问:{question}\n答:"),
// 插入统计中间件
});三、环境准备
# 安装依赖
npm install langchain
npm install @types/langchain -D四、核心实现
1. 基础Token统计器
// token-counter.ts
export class TokenCounter {
private modelPrices: Record<string, number> = {
"qwen:7b": 0.0001, // 0.1美分/token
"gpt-4": 0.001, // 1美分/token
"llama-3": 0.00005, // 0.05美分/token
};
async countTokens(input: string, model: string): Promise<number> {
// 实际应用中需调用模型的tokenize方法
const inputTokens = await this.tokenize(input, model);
const outputTokens = await this.estimateOutputTokens(input, model);
return inputTokens + outputTokens;
}
private async tokenize(text: string, model: string): Promise<number> {
// 模拟tokenize过程
return Math.floor(text.length * 0.5); // 2个字符约1个token
}
private async estimateOutputTokens(input: string, model: string): Promise<number> {
// 简单估计:输入长度的1/3
return Math.floor(input.length * 0.3);
}
}关键点解释:
- 模拟了tokenize过程,实际应调用模型的tokenize方法
- 采用简单的长度估算,实际应用中需使用模型的tokenize方法
- 支持不同模型的价格配置
2. 费用计算中间件
// cost-middleware.ts
import { BaseMiddleware } from "langchain";
export class CostMiddleware extends BaseMiddleware {
constructor(private tokenCounter: TokenCounter) {}
async run(input: any, model: any) {
const { question } = input;
const modelType = model.modelName;
const totalTokens = await this.tokenCounter.countTokens(question, modelType);
const pricePerToken = this.tokenCounter.modelPrices[modelType];
const cost = totalTokens * pricePerToken;
console.log(`Token usage: ${totalTokens}, Cost: $${cost.toFixed(6)}`);
// 继续执行后续处理
return await super.run(input, model);
}
}关键点解释:
- 通过
BaseMiddleware实现中间件功能 - 在调用模型前统计token和计算费用
- 支持不同模型的费用计算
3. 综合使用示例
// main.ts
import { LLMChain, PromptTemplate } from "langchain";
import { Qwen } from "langchain/llms";
import { TokenCounter, CostMiddleware } from "./cost-middleware";
const tokenCounter = new TokenCounter();
const middleware = new CostMiddleware(tokenCounter);
const chain = new LLMChain({
llm: new Qwen(),
prompt: PromptTemplate.fromTemplate("问:{question}\n答:"),
middleware: middleware,
});
async function test() {
const result = await chain.call({ question: "如何统计token使用量?" });
console.log("Answer:", result);
}
test();关键点解释:
- 将中间件集成到LLMChain中
- 完整的调用链路包含统计和费用计算
- 可扩展为更复杂的统计体系
五、完整案例
1. 完整的聊天系统实现
// chat-system.ts
import { LLMChain, PromptTemplate } from "langchain";
import { Qwen } from "langchain/llms";
import { TokenCounter, CostMiddleware } from "./cost-middleware";
const tokenCounter = new TokenCounter();
const middleware = new CostMiddleware(tokenCounter);
const chatChain = new LLMChain({
llm: new Qwen(),
prompt: PromptTemplate.fromTemplate("用户问:{question}\n助手答:"),
middleware: middleware,
});
async function chatLoop() {
const questions = [
"如何统计token使用量?",
"如何计算模型费用?",
"什么是token?",
"模型价格如何计算?"
];
for (const question of questions) {
console.log(`\nQuestion: ${question}`);
const result = await chatChain.call({ question });
console.log("Answer:", result);
}
}
chatLoop();2. 前端展示页面
<template>
<div>
<h2>Token统计系统</h2>
<div v-if="results.length">
<p>历史记录:</p>
<ul>
<li v-for="(record, index) in results" :key="index">
<strong>{{ record.question }}</strong><br>
Token使用: {{ record.tokens }}<br>
费用: ${{ record.cost.toFixed(6) }}
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
data() {
return {
results: []
};
},
async mounted() {
// 模拟获取历史记录
const history = await this.getHistory();
this.results = history;
},
methods: {
async getHistory() {
// 实际应用中应从数据库获取
return [
{ question: "如何统计token使用量?", tokens: 123, cost: 0.0123 },
{ question: "如何计算模型费用?", tokens: 456, cost: 0.0456 }
];
}
}
};
</script>六、源码解析
1. TokenCounter类分析
class TokenCounter {
private modelPrices: Record<string, number> = {
"qwen:7b": 0.0001, // 0.1美分/token
"gpt-4": 0.001, // 1美分/token
"llama-3": 0.00005, // 0.05美分/token
};
async countTokens(input: string, model: string): Promise<number> {
const inputTokens = await this.tokenize(input, model);
const outputTokens = await this.estimateOutputTokens(input, model);
return inputTokens + outputTokens;
}
private async tokenize(text: string, model: string): Promise<number> {
// 模拟tokenize过程
return Math.floor(text.length * 0.5); // 2个字符约1个token
}
private async estimateOutputTokens(input: string, model: string): Promise<number> {
return Math.floor(input.length * 0.3);
}
}关键点:
- 使用对象存储模型价格,支持动态配置
- tokenize方法应调用模型的tokenize方法
- 估计输出token时考虑输入长度
2. CostMiddleware类分析
class CostMiddleware extends BaseMiddleware {
constructor(private tokenCounter: TokenCounter) {}
async run(input: any, model: any) {
const { question } = input;
const modelType = model.modelName;
const totalTokens = await this.tokenCounter.countTokens(question, modelType);
const pricePerToken = this.tokenCounter.modelPrices[modelType];
const cost = totalTokens * pricePerToken;
console.log(`Token usage: ${totalTokens}, Cost: $${cost.toFixed(6)}`);
// 继续执行后续处理
return await super.run(input, model);
}
}关键点:
- 通过
super.run()实现中间件链式调用 - 支持不同模型的费用计算
- 可扩展为支持多模型的统计系统
七、进阶使用
1. 支持多模型统计
// multi-model-counter.ts
export class MultiModelCounter {
private modelPrices: Record<string, Record<string, number>> = {
"qwen:7b": {
input: 0.0001,
output: 0.0002
},
"gpt-4": {
input: 0.001,
output: 0.0015
}
};
async countCost(input: string, model: string, isOutput: boolean): Promise<number> {
const price = this.modelPrices[model];
if (!price) throw new Error(`Unsupported model: ${model}`);
const tokens = await this.tokenize(input, model);
return tokens * (isOutput ? price.output : price.input);
}
}2. 历史记录存储
// history-storage.ts
import { MongoClient } from "mongodb";
export class HistoryStorage {
private client: MongoClient;
constructor(private uri: string, private dbName: string) {
this.client = new MongoClient(uri);
}
async saveRecord(record: { question: string, tokens: number, cost: number }) {
const db = this.client.db(this.dbName);
const collection = db.collection("token_usage");
await collection.insertOne({
timestamp: new Date(),
question: record.question,
tokens: record.tokens,
cost: record.cost
});
}
}八、性能与工程实践
1. 性能优化方案
| 优化点 | 方法 | 效果 |
|---|---|---|
| 缓存模型价格 | 使用Redis缓存 | 减少配置加载时间 |
| 异步处理 | 使用Promise池 | 提高并发处理效率 |
| 限流控制 | 使用Redis的计数器 | 防止恶意调用 |
2. 异常处理机制
// error-handling.ts
export class TokenCounter {
async countTokens(input: string, model: string): Promise<number> {
try {
const inputTokens = await this.tokenize(input, model);
const outputTokens = await this.estimateOutputTokens(input, model);
return inputTokens + outputTokens;
} catch (error) {
console.error("Token counting error:", error);
throw new Error("Token counting failed");
}
}
}3. 安全考虑
- 价格信息安全:防止恶意用户篡改价格配置
- 输入验证:防止注入攻击
- 访问控制:限制对统计数据的访问权限
九、常见问题与踩坑
1. 常见错误及解决方案
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 价格未配置 | 显示"Unsupported model" | 检查modelPrices配置 |
| token计算错误 | 统计结果与实际不符 | 检查tokenize方法实现 |
| 费用计算错误 | 费用计算不准确 | 检查价格单位和计算方式 |
2. 典型陷阱
- 模型价格混淆:输入和输出价格混淆
- token计数不准确:未使用模型的tokenize方法
- 并发问题:未处理多线程下的统计问题
3. 调试技巧
- 使用
console.log输出关键变量 - 使用单元测试验证token计数逻辑
- 使用性能分析工具定位瓶颈
十、最佳实践
- 模型价格配置:使用配置文件管理不同模型的价格
- 统计中间件:在调用链路中插入统计逻辑
- 历史记录:保存历史记录用于审计和分析
- 性能优化:采用缓存和异步处理提升性能
- 安全措施:实施访问控制和输入验证
十一、总结
在LangChain.js中实现token和费用统计,需要结合中间件、token计数和费用计算三个核心模块。通过合理的设计,可以构建一个完整的统计系统。实际应用中需要注意:
- 模型价格差异带来的计算复杂度
- 高并发场景下的性能优化
- 安全性和数据准确性保障
建议在以下场景使用本方案:
- 需要精确统计token和费用的生产环境
- 有复杂的费用计算需求
- 需要历史记录审计的系统
不建议在以下场景使用:
- 对性能要求极高的实时系统
- 无需费用统计的简单应用
- 无法获取模型价格信息的场景
通过本文的深入分析,开发者可以构建一个既符合业务需求又具备扩展性的token统计系统,为大模型应用的可持续发展提供重要支持。
评论已关闭