1. 项目概述:TiktokenSharp,一个高效的C#分词库
在开发基于OpenAI API的C#应用时,一个绕不开的核心环节就是Token计数。无论是为了控制API调用成本(毕竟GPT-4是按Token收费的),还是为了确保发送的提示词不超过模型的最大上下文限制,准确、高效地计算文本的Token数量都至关重要。OpenAI官方提供了Python的tiktoken库来解决这个问题,但对于.NET生态的开发者来说,直接使用并不方便。这就是TiktokenSharp诞生的背景。
TiktokenSharp是一个用C#实现的、与OpenAI官方tiktoken库兼容的分词(Tokenization)库。它的核心目标很简单:让你在C#项目中,能像在Python里使用tiktoken一样,轻松地将文本转换成Token ID序列,或者反过来将Token ID序列解码回文本,并且计算结果与官方完全一致。目前,它已经实现了o200k_base(用于GPT-4o、o1、o3系列)、cl100k_base(用于GPT-3.5-Turbo、GPT-4、文本嵌入模型)和p50k_base(用于早期的Codex、文本补全模型如text-davinci-003)这三种主流的编码算法。
这个库特别适合以下场景的.NET开发者:正在构建聊天机器人、AI助手、代码生成工具、文本分析流水线,或者任何需要与OpenAI模型进行深度交互的应用。你不再需要自己费劲去解析BPE(Byte Pair Encoding)文件,或者担心实现的算法与官方有偏差。TiktokenSharp帮你封装好了这一切,并且,根据其基准测试,它在性能和内存占用上表现相当出色。
2. 核心设计与实现思路解析
2.1 为什么需要独立的Token计算库?
很多开发者最初可能会想:“我直接调用API不就行了,为什么要在本地计算Token?” 这里有几个关键原因:
- 成本预估与预算控制:在发送一个冗长的提示词之前,如果能预先知道它会消耗多少Token,你就能更精确地预估API调用成本,避免意外的高额账单。这对于有严格预算限制或面向大量用户的服务至关重要。
- 上下文窗口管理:每个模型都有固定的上下文窗口上限(例如,GPT-4 Turbo是128K Tokens)。你需要确保你准备发送的“系统指令+用户消息+历史对话+模型回复”的总Token数不超过这个限制。本地计算允许你在组装请求时实时校验和截断。
- 优化提示词工程:在设计和迭代提示词(Prompt)时,频繁地计算Token数可以帮助你精简语言,用更少的Token表达更清晰的意图,这本身就是一种提升效果和降低成本的艺术。
- 离线或预处理需求:在某些数据预处理流水线中,你可能需要先对海量文本进行Token化分析或过滤,而不必实时连接OpenAI API。
TiktokenSharp的出现,正是为了满足C#/.NET开发者在上述场景中的需求,填补了生态中的一个重要工具空白。
2.2 架构与依赖设计:为何选择“按需下载”模式?
阅读TiktokenSharp的文档,你首先会注意到一个特点:它没有将核心的BPE(Byte Pair Encoding)词表文件直接打包进NuGet包。首次使用某个编码器时,库会从OpenAI的官方CDN下载对应的.tiktoken文件。这个设计选择背后有作者的深思熟虑:
首要考虑是包体积与灵活性。BPE文件虽然本质是文本文件,但包含了数万到数十万的合并规则(Merge Rules)。如果将其内嵌到程序集中,会导致NuGet包体积显著增大(可能从几十KB增加到几MB)。对于现代应用,几MB似乎不算什么,但对于那些追求极致轻量、或在CI/CD流水线中希望依赖包下载更快的场景,每一KB都值得考量。更重要的是,OpenAI的编码算法和词表可能会更新(尽管不频繁)。采用外部文件的方式,理论上可以在不更新TiktokenSharp库本身的情况下,通过替换文件来适应未来的变化,提供了更大的灵活性。
其次是为了与官方实现保持高度一致。OpenAI的Pythontiktoken库同样采用了运行时加载外部词表文件的方式。TiktokenSharp遵循这一模式,确保了行为上最大程度的可预测性和一致性。开发者可以相信,在C#中计算出的Token数,与在Python中计算出的结果将是完全相同的,这消除了跨语言协作时的潜在分歧。
当然,这个设计也带来了一个明显的“副作用”:首次运行时的网络依赖。为了解决这个问题,库提供了TikToken.PBEFileDirectory属性,允许你自定义文件的缓存目录。这对于部署在云函数(如Azure Functions)或容器等无状态、只读环境中的应用至关重要。你可以选择在构建/发布阶段,就预先将所需的.tiktoken文件下载好,并放置在这个自定义目录中,随应用一起打包部署,从而彻底消除运行时的网络依赖和延迟。
3. 快速上手指南与核心API详解
3.1 安装与基础使用
通过NuGet安装是唯一推荐的方式,这能确保你获得稳定的版本并管理好依赖。
# 使用 .NET CLI dotnet add package TiktokenSharp # 或者在Visual Studio的包管理器控制台中 Install-Package TiktokenSharp安装完成后,使用起来非常直观。库提供了两种主要方式来获取编码器实例。
方式一:通过模型名称获取(推荐)这是最方便的方式,你不需要记忆具体的编码名称,库内部维护了一个模型到编码的映射表。
using TiktokenSharp; // 获取适用于 gpt-3.5-turbo 模型的编码器 TikToken tikToken = TikToken.EncodingForModel("gpt-3.5-turbo"); // 编码:将文本转换为Token ID列表 List<int> tokenIds = tikToken.Encode("Hello, world! 你好,世界!"); Console.WriteLine($"Token IDs: [{string.Join(", ", tokenIds)}]"); // 输出可能类似于:[9906, 11, 1917, 0, 234, 163, 120, 234, 163, 123, 123] (具体值取决于词表) // 解码:将Token ID列表转换回文本 string originalText = tikToken.Decode(tokenIds); Console.WriteLine($"Decoded Text: {originalText}"); // 输出: Hello, world! 你好,世界!方式二:直接指定编码名称如果你明确知道要使用哪种编码,或者使用的模型尚未被EncodingForModel方法支持,可以直接使用编码名称。
using TiktokenSharp; // 直接使用 cl100k_base 编码(GPT-3.5/4系列通用) TikToken tikToken = TikToken.GetEncoding("cl100k_base"); List<int> tokenIds = tikToken.Encode("The quick brown fox jumps over the lazy dog."); // ... 后续操作注意:
EncodingForModel方法内部也是根据模型名称查找对应的编码名称,然后调用GetEncoding。对于常见模型(如gpt-4,gpt-4o,text-embedding-3-small等),库的映射表都是保持更新的。你可以查阅项目的ModelUtils.cs文件来查看完整的映射关系。
3.2 管理BPE文件与应对部署环境
如前所述,首次使用某个编码器会触发文件下载。下载的文件默认会存储在TikToken.PBEFileDirectory属性指向的目录。如果该属性未设置,则使用应用程序的基目录(AppDomain.CurrentDomain.BaseDirectory)。
自定义缓存目录:为了避免每次部署到新环境都下载,或者为了在Docker容器中固定文件位置,你可以在程序启动初期设置这个路径。
// 在Program.cs或应用初始化代码中设置 TikToken.PBEFileDirectory = Path.Combine(AppContext.BaseDirectory, "TiktokenCache"); // 之后再调用 EncodingForModel 或 GetEncoding var encoder = TikToken.EncodingForModel("gpt-4");预下载文件与离线部署:对于无法在运行时访问外网的生产环境(如某些严格的内网部署),预下载文件是必须的步骤。
- 手动下载:从项目README或OpenAI官方链接获取文件。
p50k_base.tiktoken: https://openaipublic.blob.core.windows.net/encodings/p50k_base.tiktokencl100k_base.tiktoken: https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktokeno200k_base.tiktoken: https://openaipublic.blob.core.windows.net/encodings/o200k_base.tiktoken
- 放置文件:将下载的
.tiktoken文件放入你设置的PBEFileDirectory目录中,或者直接放在应用程序的运行目录下。 - 验证:部署后,运行一个简单的编码测试,确保库能正确读取本地文件,而不会尝试联网。
处理只读环境(如Azure App Service的某些配置):在版本1.2.1中,库增强了对只读环境的兼容性。如果设置的PBEFileDirectory和应用程序基目录都不可写,库会优雅地回退到系统的临时目录(Path.GetTempPath())尝试创建缓存。如果连临时目录也不行,库还提供了内存加载的降级方案,以避免运行时直接崩溃。这体现了库在健壮性上的考量。
3.3 核心API与高级用法
除了基础的Encode和Decode,理解Token化过程中的一些细节对高级应用很有帮助。
计算Token数量:虽然Encode返回的是ID列表,你可以通过其Count属性得到Token数,但库通常也提供了更便捷的方法(如果查看源码,可能会有CountTokens这样的优化方法)。最直接的方式依然是:
TikToken encoder = TikToken.EncodingForModel("gpt-4o"); string myPrompt = "请将以下文字翻译成英文:..."; int tokenCount = encoder.Encode(myPrompt).Count; Console.WriteLine($"提示词消耗Token数: {tokenCount}");处理特殊Token:OpenAI的模型识别一些特殊Token,如<|endoftext|>。这些Token在词表中有对应的ID。TiktokenSharp的编码过程会正常处理这些标记。
var encoder = TikToken.GetEncoding("cl100k_base"); // 编码一个包含特殊标记的文本 var ids = encoder.Encode("故事开始<|endoftext|>故事结束"); // 解码后会原样输出特殊标记的文本形式 var text = encoder.Decode(ids);关于异步初始化:在1.0.6版本中,库将内部的下载客户端从WebClient升级为HttpClient,并增加了异步方法。虽然主要的EncodingForModel和GetEncoding是同步的,但其内部的首次下载逻辑在更新后应该是基于HttpClient的,这意味着在现代.NET应用中能更好地利用异步I/O。不过,公开API层面目前似乎没有显式的异步初始化方法,初始化过程的网络请求在同步方法内完成。对于不希望阻塞主线程的场景,可以考虑在应用启动时在后台线程提前初始化所需的编码器。
4. 性能深度剖析与基准测试解读
对于一个会被频繁调用的基础库(例如,处理每条用户消息前都可能需要计算Token),性能至关重要。TiktokenSharp的README中提供了一份与另一个流行库SharpToken的基准测试对比,数据很有说服力。
4.1 测试场景还原
测试代码模拟了一个高压场景:对一段长度适中的英文文本(莎士比亚《李尔王》的简介)反复进行编码-解码操作10,000次。这考验的是库的核心算法效率和内存管理能力。
// 基准测试核心循环(简化示意) private string _kLongText = "King Lear, one of Shakespeare's darkest..."; // 一段长文本 private TikToken _tikToken = TikToken.GetEncoding("cl100k_base"); [Benchmark] public int TiktokenSharp() { var sum = 0; for (var i = 0; i < 10000; i++) { var encoded = _tikToken.Encode(_kLongText); // 编码 var decoded = _tikToken.Decode(encoded); // 解码 sum += decoded.Length; // 避免循环被优化掉 } return sum; }4.2 数据解读与性能优势
我们重点看.NET 10.0下的数据:
| Method | Runtime | Mean | Allocated |
|---|---|---|---|
| SharpToken | .NET 10.0 | 58.24 ms | 22.13 MB |
| TiktokenSharp | .NET 10.0 | 40.85 ms | 13.28 MB |
- 执行时间(Mean):
TiktokenSharp平均耗时约40.85毫秒,比SharpToken的58.24毫秒快了约30%。这意味着在需要大量进行Token计算的场景下,TiktokenSharp能提供更快的响应速度,减少延迟。 - 内存分配(Allocated):
TiktokenSharp在整个10,000次循环中分配了约13.28 MB的内存,而SharpToken分配了22.13 MB。TiktokenSharp的内存分配量减少了约40%。更少的内存分配意味着更低的垃圾回收(GC)压力,这对于需要高吞吐、低延迟的服务器端应用(如Web API)来说是一个巨大的优势,有助于提高应用的总体稳定性和性能。
为什么TiktokenSharp更快、更省内存?虽然我们无法看到全部源码,但可以从版本更新日志和常见优化手段推断:
- 算法优化:版本
1.1.0和1.2.0的日志明确提到了“优化算法效率”和“优化内存分配和执行效率”。这可能包括使用更高效的数据结构(如Dictionary或Trie树的优化实现)来存储和查找BPE合并规则,优化字符串处理逻辑以减少子字符串创建等。 - 缓存策略:BPE词表文件在加载后,很可能被转换为内存中查找效率更高的形式(例如,将字符串规则哈希化)。良好的缓存设计可以避免重复计算。
- 零分配或池化技术:在核心的编码循环中,可能采用了
ArrayPool或Span等技术来复用数组,避免频繁的堆内存分配,这与基准测试中显示的低分配量结果是吻合的。
4.3 对实际项目的启示
这份性能数据告诉我们,在选择C#的Token计算库时,TiktokenSharp是一个在性能上具有明显优势的选择。特别是当你构建的服务需要实时处理大量用户查询(例如一个公开的AI聊天服务),每一毫秒的节省和每一次GC的减少,都能转化为更高的并发能力和更低的服务器成本。
5. 版本迭代与兼容性管理
TiktokenSharp保持着活跃的更新,主要围绕两个方面:支持新模型和优化性能/健壮性。
5.1 模型支持时间线
库的更新日志清晰地反映了OpenAI模型发布的节奏:
- 2023年初:支持了
gpt-3.5-turbo(cl100k_base) 和text-davinci-003(p50k_base)。 - 2023年4月:添加了对
gpt-4模型的支持。 - 2024年5月:紧随OpenAI发布会,添加了对
gpt-4o模型(使用o200k_base编码)的支持。 - 2024年9月:支持
o1系列模型。 - 2025年3月:支持
o3系列模型。 - 2025年11月:支持
gpt-5模型。
这提供了一个重要经验:当你开始使用一个新的OpenAI模型时,最好检查一下你所用的TiktokenSharp库版本是否已经支持该模型的编码。你可以通过查看项目的 GitHub Releases 页面或NuGet版本历史来确认。
5.2 重大变更与升级建议
从更新日志看,库的公共API(如TikToken.EncodingForModel和TikToken.GetEncoding)保持稳定,这降低了升级成本。主要的变更是内部优化和功能增强。
- 1.0.5版本:增加了对
.NET Standard 2.0的支持,这是一个重要的兼容性提升。这意味着你的项目可以面向更古老的.NET Framework 4.6.1+或.NET Core 2.0+,大大扩展了库的适用场景(例如,遗留的WinForms、WPF或某些企业级框架项目)。 - 1.0.6版本:将网络组件从
WebClient升级到HttpClient。这是一个现代化改进,HttpClient在现代.NET中性能更好,支持异步更完善。对于升级到此版本的用户,无需更改代码。 - 1.2.1版本:增强了在只读环境下的鲁棒性。如果你将应用部署到Serverless函数(如Azure Functions)或只读文件系统的容器中,强烈建议至少升级到此版本,以避免因缓存文件写入失败导致的运行时异常。
升级策略:对于生产项目,建议在测试环境中先行升级到最新版本,运行完整的测试套件(特别是涉及Token计算的功能测试),确保无误后再部署到生产环境。由于库的核心功能是计算,输入输出确定性很强,升级风险通常较低。
6. 实战集成:在ASP.NET Core Web API中的应用示例
让我们构建一个简单的场景:一个提供“文本智能摘要”服务的Web API。该服务接收一段长文本,使用OpenAI的Chat Completion API生成摘要,但我们需要在调用前检查Token数,确保不超过模型限制,并估算成本。
6.1 项目设置与依赖注入
首先,创建一个新的ASP.NET Core Web API项目,并添加必要的包。
dotnet new webapi -n TextSummarizationService cd TextSummarizationService dotnet add package TiktokenSharp dotnet add package OpenAI为了优雅地使用TiktokenSharp,我们可以将其封装为一个服务,并通过依赖注入(DI)容器管理其生命周期。由于TikToken实例内部缓存了已加载的词表,将其注册为单例(Singleton)是最佳实践,可以避免重复加载文件。
// Services/ITokenizationService.cs public interface ITokenizationService { int CountTokens(string text, string modelName = "gpt-4o"); bool IsWithinLimit(string text, string modelName, int maxTokens, out int currentTokens); } // Services/TokenizationService.cs using TiktokenSharp; public class TokenizationService : ITokenizationService { private readonly ILogger<TokenizationService> _logger; private readonly ConcurrentDictionary<string, TikToken> _encoderCache; public TokenizationService(ILogger<TokenizationService> logger) { _logger = logger; _encoderCache = new ConcurrentDictionary<string, TikToken>(); // 可选:预加载常用编码器,避免首次请求的延迟 PreloadCommonEncoders(); } private void PreloadCommonEncoders() { var commonModels = new[] { "gpt-4o", "gpt-4-turbo", "gpt-3.5-turbo" }; foreach (var model in commonModels) { try { GetEncoderForModel(model); _logger.LogInformation("Preloaded encoder for model: {Model}", model); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to preload encoder for model: {Model}", model); } } } private TikToken GetEncoderForModel(string modelName) { // 使用缓存,避免重复创建实例 return _encoderCache.GetOrAdd(modelName, (model) => { _logger.LogDebug("Creating new TikToken encoder for model: {Model}", model); return TikToken.EncodingForModel(model); }); } public int CountTokens(string text, string modelName = "gpt-4o") { if (string.IsNullOrEmpty(text)) return 0; try { var encoder = GetEncoderForModel(modelName); return encoder.Encode(text).Count; } catch (Exception ex) { _logger.LogError(ex, "Error counting tokens for model {Model}", modelName); // 降级策略:返回一个基于字符的粗略估计(通常1个Token约等于0.75个英文单词或2-3个中文字符) // 注意:这只是一个非常粗略的估计,仅用于应急。 return (int)(text.Length * 0.4); } } public bool IsWithinLimit(string text, string modelName, int maxTokens, out int currentTokens) { currentTokens = CountTokens(text, modelName); return currentTokens <= maxTokens; } }在Program.cs中注册服务:
// Program.cs builder.Services.AddSingleton<ITokenizationService, TokenizationService>(); // 同时注册OpenAI服务(假设使用OpenAI官方.NET SDK) builder.Services.AddOpenAIService(settings => settings.ApiKey = builder.Configuration["OpenAI:ApiKey"]);6.2 实现摘要端点与Token检查
接下来,创建一个API控制器,在调用OpenAI之前进行Token检查。
// Controllers/SummarizeController.cs using Microsoft.AspNetCore.Mvc; using OpenAI.Chat; [ApiController] [Route("api/[controller]")] public class SummarizeController : ControllerBase { private readonly ITokenizationService _tokenService; private readonly IOpenAIService _openAIService; private readonly ILogger<SummarizeController> _logger; public SummarizeController( ITokenizationService tokenService, IOpenAIService openAIService, ILogger<SummarizeController> logger) { _tokenService = tokenService; _openAIService = openAIService; _logger = logger; } [HttpPost] public async Task<IActionResult> SummarizeText([FromBody] SummarizeRequest request) { if (request == null || string.IsNullOrWhiteSpace(request.Text)) { return BadRequest("Text is required."); } // 1. 计算输入文本的Token数 int inputTokenCount = _tokenService.CountTokens(request.Text, request.Model); _logger.LogInformation("Input text token count: {TokenCount} for model {Model}", inputTokenCount, request.Model); // 2. 定义模型的最大上下文窗口和预留的回复空间 // 例如,gpt-4o的上下文窗口是128K,我们预留4K给回复,剩下的是输入上限。 int maxContextTokens = GetMaxContextTokens(request.Model); int reservedForCompletion = 4096; // 预留4K token给模型生成 int maxInputTokens = maxContextTokens - reservedForCompletion; if (inputTokenCount > maxInputTokens) { // 3. 如果超出限制,进行截断或返回错误 // 这里简单返回错误,实际生产环境可能需要更复杂的截断策略 return BadRequest(new { error = $"Input text too long. Token count ({inputTokenCount}) exceeds the maximum allowed ({maxInputTokens}) for model {request.Model} with {reservedForCompletion} tokens reserved for completion.", inputTokenCount, maxInputTokens }); } // 4. 估算本次API调用的成本(可选,用于日志或计费) // 假设输入+输出总Token数的一个粗略估算(这里假设输出2000个token) int estimatedTotalTokens = inputTokenCount + 2000; decimal estimatedCost = EstimateCost(request.Model, estimatedTotalTokens); _logger.LogInformation("Estimated API call cost: ${Cost}", estimatedCost.ToString("F6")); try { // 5. 调用OpenAI API var chatMessages = new List<Message> { new Message(Role.System, "你是一个专业的文本摘要助手。请为用户提供以下文本的简洁、准确的摘要。"), new Message(Role.User, request.Text) }; var chatRequest = new ChatRequest( messages: chatMessages, model: request.Model, maxTokens: reservedForCompletion // 使用预留的token数作为生成上限 ); var result = await _openAIService.ChatEndpoint.GetCompletionAsync(chatRequest); // 6. 计算实际使用的Token数(来自API响应) int usagePromptTokens = result.Usage.PromptTokens; int usageCompletionTokens = result.Usage.CompletionTokens; int usageTotalTokens = result.Usage.TotalTokens; _logger.LogInformation("API Usage - Prompt: {Prompt}, Completion: {Completion}, Total: {Total}", usagePromptTokens, usageCompletionTokens, usageTotalTokens); // 7. 返回结果 return Ok(new SummarizeResponse { Summary = result.FirstChoice.Message.Content, InputTokenCount = inputTokenCount, ActualPromptTokens = usagePromptTokens, ActualCompletionTokens = usageCompletionTokens, ActualTotalTokens = usageTotalTokens, EstimatedCost = estimatedCost }); } catch (Exception ex) { _logger.LogError(ex, "Error calling OpenAI API."); return StatusCode(500, "An error occurred while generating the summary."); } } private int GetMaxContextTokens(string model) { // 简化示例,实际应根据模型名称返回准确值 return model.Contains("gpt-4") ? 128000 : // GPT-4系列通常是128K model.Contains("gpt-3.5") ? 16384 : // GPT-3.5 Turbo 16K 4096; // 默认值 } private decimal EstimateCost(string model, int totalTokens) { // 简化成本估算,实际价格请查阅OpenAI官网 // 价格单位:美元 per 1K tokens decimal inputPricePer1K = model.Contains("gpt-4") ? 0.01m : 0.001m; decimal outputPricePer1K = model.Contains("gpt-4") ? 0.03m : 0.002m; // 这是一个非常粗略的估算,假设一半输入一半输出 decimal estimatedCost = (totalTokens / 1000.0m) * ((inputPricePer1K + outputPricePer1K) / 2); return estimatedCost; } } // Request and Response DTOs public class SummarizeRequest { public string Text { get; set; } public string Model { get; set; } = "gpt-4o"; } public class SummarizeResponse { public string Summary { get; set; } public int InputTokenCount { get; set; } public int ActualPromptTokens { get; set; } public int ActualCompletionTokens { get; set; } public int ActualTotalTokens { get; set; } public decimal EstimatedCost { get; set; } }6.3 部署注意事项与配置
将集成了TiktokenSharp的应用部署到生产环境时,需要关注以下几点:
BPE文件部署:这是最关键的一步。如果你不希望应用在启动时从网上下载文件(避免网络问题或延迟),必须在Dockerfile或发布脚本中加入预下载步骤。
# Dockerfile 示例片段 FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app # 创建缓存目录 RUN mkdir -p /app/TiktokenCache # 你可以选择在这里通过RUN curl/wget命令下载文件,或者更常见的做法是: # 在构建阶段(build stage)下载,然后复制到最终镜像。 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build # ... 构建你的应用 ... # 假设你在项目中创建了一个目录“TiktokenFiles”并预置了下载好的 .tiktoken 文件 COPY ["TiktokenFiles/", "/app/TiktokenCache/"] FROM base AS final WORKDIR /app COPY --from=build /app/publish . COPY --from=build /app/TiktokenCache ./TiktokenCache # 复制预下载的文件 ENTRYPOINT ["dotnet", "TextSummarizationService.dll"]然后在应用启动时(
Program.cs)设置路径:// 确保在依赖注入容器构建之前设置 string cachePath = Path.Combine(AppContext.BaseDirectory, "TiktokenCache"); if (Directory.Exists(cachePath)) { TikToken.PBEFileDirectory = cachePath; Console.WriteLine($"Tiktoken cache directory set to: {cachePath}"); }监控与日志:在你的
TokenizationService中已经添加了日志记录。密切关注首次加载编码器时的延迟,以及任何文件读取错误。这能帮助你快速定位部署问题。版本锁定:在生产环境中,务必在
.csproj文件中锁定TiktokenSharp的版本号,避免因自动升级到不兼容的新版本而引入意外问题。<PackageReference Include="TiktokenSharp" Version="1.2.1" />
通过以上步骤,你就将一个高效的本地Token计算能力无缝集成到了你的.NET应用中,实现了对OpenAI API调用前的重要校验和成本控制,使得整个服务更加健壮和可控。