1. 项目概述与核心价值最近在折腾一个需要集成AI对话能力的.NET项目找了一圈开源库最终锁定了GitHub上这个叫ChatGPT.Net的项目。这可不是OpenAI官方那个Python库而是一个由社区开发者PawanOsman维护的、专门为.NET生态打造的客户端库。简单来说它让你能在C#、VB.NET、F#这些语言里用几行代码就能调用OpenAI的ChatGPT API无论是聊天、文本补全还是其他高级功能都变得异常简单。如果你正在开发一个桌面应用、Web API后端或者任何需要智能对话功能的.NET程序这个库能帮你省下大量处理HTTP请求、解析JSON、管理会话状态的底层代码时间。我最初接触它是因为要给一个内部客服系统加个智能问答助手。手动去调OpenAI的API虽然不难但涉及到流式响应、上下文管理、错误重试这些细节写起来还是挺繁琐的。ChatGPT.Net把这些脏活累活都封装好了提供了一个非常.NET风格的、强类型的API。用起来的感觉就像是在用HttpClient和直接用RestSharp或者Refit这种高级封装库的区别——后者让你更专注于业务逻辑。这个库的核心价值就是为.NET开发者提供了一个生产就绪的、功能全面且易于扩展的OpenAI API客户端大大降低了AI能力集成的门槛。2. 库的整体设计与架构解析2.1 核心设计哲学简洁与强类型ChatGPT.Net的设计思路非常清晰将OpenAI API的HTTP交互抽象成一组直观的、强类型的C#对象和方法。它没有试图去创造一个全新的AI编程模型而是忠实地映射了OpenAI的接口规范。比如API中的chat/completions端点在库里就对应一个ChatClient类发送的请求体被封装成ChatMessage和ChatRequest对象返回的响应则是ChatResponse。这种设计的好处是双重的。对于新手你几乎不需要去看OpenAI的API文档库的IntelliSense提示和对象属性名就能引导你完成调用。对于有经验的开发者这种强类型设计能在编译期就捕获很多错误比如错误地设置了不存在的模型名称或者给消息角色赋值了非法枚举值IDE会直接报错而不是等到运行时API返回400错误才知道。注意虽然库做了封装但了解OpenAI API的基本概念如模型、消息角色、温度参数仍然是必要的。库只是让调用变得更安全、更方便而不是替代你对API本身的理解。2.2 项目结构一览浏览一下项目的源代码结构能更好地理解它的组织方式。通常这类客户端库的核心部分会放在一个主要类库项目中。ChatGPT.Net/ ├── src/ │ └── ChatGPT.Net/ # 核心类库 │ ├── Clients/ # 客户端实现如ChatClient, CompletionClient │ ├── Models/ # 请求/响应模型如ChatRequest, ChatResponse │ ├── Enums/ # 枚举类型如Role, FinishReason │ ├── Services/ # 核心服务接口如IChatClient │ └── Extensions/ # 一些扩展方法 ├── tests/ # 单元测试项目 └── samples/ # 示例代码这种结构是典型的关注点分离。Clients目录下的类负责实际的HTTP通信Models目录下的类定义了数据的形状Enums让代码更可读Services中的接口则定义了契约便于进行单元测试和依赖注入。当你需要扩展功能或者排查问题时可以很轻松地定位到相关代码。2.3 依赖与兼容性作为一个现代.NET库ChatGPT.Net通常以NuGet包的形式分发。它的核心依赖是System.Text.Json用于JSON序列化/反序列化以及Microsoft.Extensions.Http如果使用了IHttpClientFactory的最佳实践。这意味着它天然支持.NET Standard 2.0/2.1以及.NET Core/5/6/7/8等现代框架。在项目文件中你可能会看到这样的目标框架声明TargetFrameworksnetstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0/TargetFrameworks这保证了广泛的兼容性无论是传统的.NET Framework项目通过.NET Standard 2.0兼容还是最新的.NET 8项目都可以无缝使用。3. 快速上手指南从零到第一次对话3.1 安装与基础配置第一步通过NuGet包管理器控制台或者图形界面安装包Install-Package ChatGPT.Net或者使用.NET CLIdotnet add package ChatGPT.Net安装完成后最基本的用法是直接实例化客户端。你需要一个OpenAI的API密钥可以在OpenAI官网获取。using ChatGPT.Net; // 最简单的方式直接提供API密钥和可选的基础URL如果你使用Azure OpenAI或代理 var client new ChatClient(你的-OpenAI-API-密钥); // 或者指定自定义端点例如使用第三方代理服务时 // var client new ChatClient(你的-API-密钥, https://your-proxy.com/v1);然而在生产环境中更推荐使用依赖注入DI来管理HttpClient的生命周期以避免套接字耗尽等问题。如果你的项目使用了.NET的通用主机或ASP.NET Core可以这样配置// 在Program.cs或Startup.cs中 services.AddHttpClient(); // 确保已添加HttpClient工厂 services.AddSingletonIChatClient(sp { var httpClientFactory sp.GetRequiredServiceIHttpClientFactory(); var httpClient httpClientFactory.CreateClient(); // 可以将API密钥放在配置中如IConfiguration var apiKey configuration[OpenAI:ApiKey]; var baseUrl configuration[OpenAI:BaseUrl]; // 可选 return new ChatClient(apiKey, baseUrl, httpClient); });这样你就可以在控制器或服务中通过构造函数注入IChatClient来使用了。3.2 发起你的第一次聊天请求配置好客户端后发起一次对话非常简单。核心是构建一个ChatRequest对象其中包含消息列表和模型等参数。// 1. 准备对话消息 var messages new ListChatMessage { // 系统消息用于设定助手的行为。这是一个可选但非常有用的角色。 new ChatMessage { Role Role.System, Content 你是一个乐于助人的助手回答要简洁明了。 }, // 用户消息是用户的输入 new ChatMessage { Role Role.User, Content 用C#写一个Hello World程序。 } }; // 2. 构建请求 var request new ChatRequest { Model gpt-3.5-turbo, // 指定模型如 gpt-3.5-turbo, gpt-4, gpt-4-turbo-preview 等 Messages messages, MaxTokens 500, // 限制回复的最大长度 Temperature 0.7 // 控制回复的随机性0-2值越高越有创意越低越确定 }; // 3. 发送请求并获取响应 try { var response await client.SendMessage(request); // 4. 处理响应 if (response.Choices?.Count 0) { var reply response.Choices[0].Message.Content; Console.WriteLine($助手回复{reply}); // 响应中还包含其他有用信息 var finishReason response.Choices[0].FinishReason; // 停止原因如 stop, length var totalTokens response.Usage.TotalTokens; // 本次请求消耗的总令牌数 Console.WriteLine($消耗令牌数{totalTokens}); } } catch (Exception ex) { // 处理可能发生的异常如网络错误、API密钥无效、额度不足等 Console.WriteLine($请求失败{ex.Message}); }运行这段代码你应该就能收到一个完整的C# Hello World程序代码。这就是最基本的同步请求模式。3.3 处理流式响应Streaming对于需要实时显示AI思考过程的应用比如仿ChatGPT网页的逐字输出效果同步等待整个回复生成完毕体验不佳。OpenAI API支持流式响应Server-Sent EventsChatGPT.Net也对此提供了优雅的支持。流式响应的核心是使用SendMessageStreamAsync方法它返回一个IAsyncEnumerableChatResponse你可以像遍历集合一样处理每一块返回的数据。var request new ChatRequest { Model gpt-3.5-turbo, Messages messages, Stream true // 必须设置为true以启用流式响应 }; Console.Write(助手); await foreach (var chunk in client.SendMessageStreamAsync(request)) { // 每个chunk是一个ChatResponse但通常只包含一个Choice的一小部分delta内容 if (chunk.Choices?.Count 0) { var deltaContent chunk.Choices[0].Delta?.Content; if (!string.IsNullOrEmpty(deltaContent)) { // 逐块输出内容实现打字机效果 Console.Write(deltaContent); } // 可以检查是否结束 if (chunk.Choices[0].FinishReason ! null) { Console.WriteLine(\n[回复结束]); break; } } }使用流式响应时有几个关键点需要注意请求参数必须在ChatRequest中将Stream属性设置为true。响应处理流式响应的每个“块”(chunk)结构与非流式响应略有不同。回复内容不在chunk.Choices[0].Message.Content中而是在chunk.Choices[0].Delta.Content里。Delta对象代表相对于之前内容的增量。结束判断当某个chunk的Choices[0].FinishReason不为空时例如值为stop表示流式传输结束。性能与体验流式响应能极大提升用户感知速度尤其生成长文本时。但它会保持一个长时间的HTTP连接在Web服务器环境下需要注意连接管理和超时设置。4. 核心功能深度解析与高级用法4.1 会话管理与上下文保持单次问答往往不够我们需要让AI记住之前的对话历史这就是上下文管理。ChatGPT.Net本身不内置会话状态管理这是应用层的职责但它通过Messages列表完美支持了这一点。实现上下文对话的关键在于在每次请求时将完整的历史消息列表发送给API。// 模拟一个简单的对话循环 ListChatMessage conversationHistory new ListChatMessage { new ChatMessage { Role Role.System, Content 你是一个编程专家。 } }; while (true) { Console.Write(你); var userInput Console.ReadLine(); if (userInput?.ToLower() exit) break; // 1. 将用户输入加入历史 conversationHistory.Add(new ChatMessage { Role Role.User, Content userInput }); // 2. 构建请求发送整个历史 var request new ChatRequest { Model gpt-3.5-turbo, Messages conversationHistory, // 关键发送全部历史 MaxTokens 300 }; var response await client.SendMessage(request); var assistantReply response.Choices[0].Message.Content; // 3. 将助手回复也加入历史以供下一轮使用 conversationHistory.Add(new ChatMessage { Role Role.Assistant, Content assistantReply }); Console.WriteLine($助手{assistantReply}); Console.WriteLine($本轮消耗令牌{response.Usage.TotalTokens} 历史总令牌数估算在增加...); }上下文长度与令牌限制这里有一个重要的陷阱。模型如gpt-3.5-turbo有上下文窗口限制例如4096个令牌。如果你无限制地追加历史消息很快就会超过这个限制导致API调用失败。因此生产系统必须实现历史消息裁剪策略。常见的策略有固定轮数只保留最近N轮对话。令牌数限制估算历史消息的总令牌数可以使用Tiktoken等库进行精确计算或按字符数/4粗略估算当超过阈值如3000令牌时从最旧的消息开始移除但尽量保留系统消息和最近几轮对话。总结压缩当历史过长时可以调用AI本身对之前的对话进行总结然后用总结文本替换掉旧的历史消息这是一种更高级但成本也更高的策略。4.2 参数调优控制AI的“性格”与输出ChatRequest对象提供了丰富的参数来控制生成行为理解这些参数是获得理想回复的关键。参数类型默认值作用与说明Modelstring必填指定使用的模型如gpt-3.5-turbo,gpt-4,gpt-4-turbo-preview。不同模型在能力、成本和上下文长度上差异巨大。MessagesListChatMessage必填对话消息列表是请求的核心。消息顺序很重要通常以系统消息开始。MaxTokensint?null回复的最大令牌数。必须设置否则AI可能生成极长的文本消耗大量令牌。需小于模型的上下文窗口。Temperaturedouble?1.0采样温度0-2。值越低如0.2输出越确定、一致值越高如1.2输出越随机、有创意。对于代码生成、事实问答建议较低0.2-0.7对于创意写作可以较高0.8-1.2。TopPdouble?1.0核采样0-1。与Temperature二选一即可通常不一起调整。它控制从累积概率超过TopP的最小词集中采样。Nint?1为每个输入消息生成多少个回复选项。会增加成本用于获取多个候选答案。Streambool?false是否使用流式输出。Stopstring或Liststringnull停止序列。当AI生成包含这些字符串的文本时会停止生成。例如设置Stop \n可以让AI在换行后停止。PresencePenaltydouble?0.0存在惩罚-2.0 到 2.0。正值惩罚已出现过的令牌鼓励谈论新话题避免重复。FrequencyPenaltydouble?0.0频率惩罚-2.0 到 2.0。正值惩罚频繁出现的令牌效果与PresencePenalty类似但更细粒度。LogitBiasDictionaryint, double?null对数偏置。可以精确地增加或减少特定令牌IDtoken ID被选中的概率。需要了解模型的令牌表属于高级用法。Userstring?null代表终端用户的标识符OpenAI可用于监控和检测滥用。实操心得Temperature与MaxTokens的搭配创意文案生成Temperature0.8~1.2,MaxTokens300。给予一定的随机性同时限制长度避免跑偏。代码生成/逻辑推理Temperature0.2~0.5,MaxTokens800。低温度保证代码结构正确、逻辑严谨根据任务复杂度设置足够的令牌数。翻译/摘要Temperature0.3~0.7,MaxTokens根据原文长度动态计算。保持准确性的同时允许少量措辞变化。4.3 函数调用Function Calling集成OpenAI的Chat Completions API支持函数调用功能允许你描述一些工具函数给AIAI可以根据用户需求决定是否调用以及传入什么参数。ChatGPT.Net也支持这一强大特性。假设我们有一个查询天气的函数// 1. 定义你的工具函数在实际项目中这可能是服务层的方法 public string GetWeather(string location, string unit celsius) { // 模拟查询逻辑 return $地点 {location} 的天气是晴朗的温度 25 {unit}。; }使用ChatGPT.Net进行函数调用的流程如下// 2. 定义工具描述告诉AI这个函数能做什么、需要什么参数 var tools new ListTool { new Tool { Type function, Function new FunctionDefinition { Name get_weather, Description 获取指定城市的当前天气信息, Parameters new { Type object, Properties new { Location new { Type string, Description 城市名称例如北京、San Francisco }, Unit new { Type string, Enum new[] { celsius, fahrenheit }, Description 温度单位 } }, Required new[] { location } } } } }; // 3. 构建包含工具描述的请求 var messages new ListChatMessage { new() { Role Role.User, Content 上海今天天气怎么样 } }; var request new ChatRequest { Model gpt-3.5-turbo-1106, // 建议使用支持函数调用的较新模型 Messages messages, Tools tools, // 关键将工具列表传入请求 ToolChoice auto // 让AI自动决定是否调用工具。也可指定为 none 或具体工具名。 }; // 4. 发送请求 var response await client.SendMessage(request); var firstChoice response.Choices[0]; // 5. 检查AI是否决定调用函数 if (firstChoice.FinishReason tool_calls firstChoice.Message.ToolCalls?.Count 0) { var toolCall firstChoice.Message.ToolCalls[0]; if (toolCall.Function.Name get_weather) { // 6. 解析AI提供的参数通常是JSON字符串 var arguments JsonSerializer.DeserializeWeatherArgs(toolCall.Function.Arguments); // 7. 执行本地函数 var weatherResult GetWeather(arguments.Location, arguments.Unit); // 8. 将函数执行结果作为新的“工具”角色消息再次发送给AI让它生成面向用户的回复 messages.Add(firstChoice.Message); // 添加AI要求调用工具的消息 messages.Add(new ChatMessage { Role Role.Tool, Content weatherResult, ToolCallId toolCall.Id // 必须关联对应的ToolCall ID }); // 9. 发送第二次请求让AI整合结果并回复用户 var secondRequest new ChatRequest { Model request.Model, Messages messages }; var secondResponse await client.SendMessage(secondRequest); Console.WriteLine($最终回复{secondResponse.Choices[0].Message.Content}); } } else { // AI没有调用工具直接回复 Console.WriteLine($直接回复{firstChoice.Message.Content}); } // 用于反序列化参数的辅助类 public class WeatherArgs { public string Location { get; set; } public string Unit { get; set; } celsius; }这个过程看似复杂但逻辑清晰描述工具 - AI决定调用 - 本地执行 - 返回结果给AI - AI生成最终回复。它实现了AI与外部系统/数据的联动是构建智能Agent的基础。4.4 使用其他模型与端点ChatGPT.Net通常以ChatClient为核心因为它对应最常用的Chat Completions API。但OpenAI还有其他端点如Completions旧版文本补全、Embeddings生成向量、Audio语音转录/合成等。一个完整的客户端库可能会提供对应的客户端类。例如使用Embeddings API将文本转换为向量// 假设库提供了EmbeddingClient var embeddingClient new EmbeddingClient(your-api-key); var request new EmbeddingRequest { Model text-embedding-3-small, Input The food was delicious and the waiter... }; var response await embeddingClient.CreateEmbedding(request); var embeddingVector response.Data[0].Embedding; // 浮点数列表代表文本的语义向量这个向量可以用于语义搜索、文本分类、聚类等任务。使用前需要确认你安装的ChatGPT.Net版本是否包含了这些客户端的实现或者查看项目文档。5. 生产环境实践错误处理、重试与性能5.1 健壮的错误处理机制网络请求和远程API调用充满了不确定性健壮的错误处理是生产代码的必备项。ChatGPT.Net在调用失败时会抛出异常我们需要捕获并妥善处理。try { var response await client.SendMessage(request); // 处理成功响应 } catch (HttpRequestException httpEx) { // 网络层错误连接失败、超时、DNS解析失败等 Console.WriteLine($网络请求失败: {httpEx.Message}); // 可以考虑重试 } catch (ApiException apiEx) // 假设库定义了自定义的ApiException包含状态码和错误信息 { // OpenAI API返回的错误状态码非2xx Console.WriteLine($API错误 ({apiEx.StatusCode}): {apiEx.Message}); // 根据状态码进行不同处理 switch (apiEx.StatusCode) { case 401: // 无效的API密钥 Console.WriteLine(请检查API密钥是否正确且未过期。); break; case 429: // 请求速率超限Rate Limit Console.WriteLine(请求过于频繁请稍后再试。); // 可以解析响应头中的 retry-after 信息 break; case 500: case 503: // OpenAI服务器内部错误 Console.WriteLine(服务暂时不可用请重试。); break; case 400: // 错误的请求通常是参数问题 Console.WriteLine($请求参数有误: {apiEx.Message}); break; default: Console.WriteLine($未知API错误: {apiEx.Message}); break; } } catch (Exception ex) { // 其他未预期的异常 Console.WriteLine($未预期的错误: {ex.Message}); }特别注意429错误Rate LimitOpenAI对免费账号和付费账号都有每分钟/每天的请求次数和令牌消耗限制。在并发请求或快速连续请求时很容易触发。处理策略包括指数退避重试遇到429错误后等待一段时间如2秒再重试如果继续失败等待时间加倍4秒、8秒...直到成功或达到最大重试次数。队列与限流在应用层实现一个请求队列控制发送到OpenAI API的请求速率。监控使用量定期检查响应头中的x-ratelimit-remaining-requests和x-ratelimit-remaining-tokens提前预警。5.2 实现带退避策略的自动重试对于瞬态故障网络抖动、服务器过载自动重试能显著提高成功率。我们可以结合Polly这样的弹性库来实现。首先安装Pollydotnet add package Polly然后为你的HTTP客户端配置重试策略using Polly; using Polly.Extensions.Http; // 1. 定义一个针对HttpRequestException和特定状态码的重试策略 var retryPolicy HttpPolicyExtensions .HandleTransientHttpError() // 处理5xx和408请求超时 .OrResult(msg msg.StatusCode System.Net.HttpStatusCode.TooManyRequests) // 处理429 .WaitAndRetryAsync( retryCount: 3, // 重试3次 sleepDurationProvider: (retryAttempt, response, context) { // 如果是429错误尝试从响应头读取Retry-After否则使用指数退避 var retryAfter response?.Result?.Headers.RetryAfter?.Delta; if (retryAfter.HasValue) { return retryAfter.Value; } // 指数退避2秒4秒8秒 return TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)); }, onRetryAsync: (outcome, timespan, retryAttempt, context) { Console.WriteLine($请求失败{timespan.TotalSeconds}秒后进行第{retryAttempt}次重试。原因{outcome.Exception?.Message ?? outcome.Result?.StatusCode.ToString()}); return Task.CompletedTask; }); // 2. 在依赖注入中配置使用此策略的HttpClient services.AddHttpClient(OpenAIClient) .AddPolicyHandler(retryPolicy); // 应用重试策略 // 3. 创建ChatClient时使用这个具名的HttpClient services.AddSingletonIChatClient(sp { var httpClientFactory sp.GetRequiredServiceIHttpClientFactory(); var httpClient httpClientFactory.CreateClient(OpenAIClient); // 使用具名客户端 return new ChatClient(apiKey, baseUrl, httpClient); });这样所有通过这个ChatClient发起的请求在遇到可重试的错误时都会自动按照策略进行重试无需在每个调用处写重复代码。5.3 性能考量与最佳实践HttpClient单例与工厂始终使用IHttpClientFactory来创建和管理HttpClient而不是手动new HttpClient()。工厂能管理连接池生命周期避免套接字耗尽问题并方便集成策略如重试、熔断。异步编程库的方法基本都是async的务必使用await进行异步调用避免阻塞线程池线程尤其是在Web服务器中。合理设置超时OpenAI的API响应时间受模型、输入长度、服务器负载影响。通过HttpClient.Timeout属性设置一个合理的超时时间例如60秒并配合重试策略。批量处理如果有多条独立的文本需要处理例如生成多个摘要考虑是否可以将它们合并到一个请求中如果API支持或者使用并行请求注意速率限制。令牌使用优化精简输入在保证清晰的前提下尽量减少系统提示词和用户输入的长度。设置合理的MaxTokens根据任务预估回复长度不要设置得过大。监控用量定期检查response.Usage中的令牌计数分析成本构成。缓存对于一些相对静态或可重复的查询例如将固定产品描述翻译成多种语言可以考虑在应用层对AI的回复进行缓存避免重复调用产生费用。6. 常见问题排查与调试技巧6.1 典型错误与解决方案速查表问题现象可能原因排查步骤与解决方案ApiException: 401 UnauthorizedAPI密钥无效、过期或格式错误。1. 检查密钥字符串是否正确复制前后有无空格。2. 登录OpenAI平台确认密钥是否被删除或重置。3. 确认使用的端点BaseUrl是否需要特定的密钥格式如Azure OpenAI。ApiException: 429 Rate limit exceeded超出速率限制。免费用户限制较严。1. 降低请求频率在代码中增加延迟。2. 实现指数退避重试逻辑见5.2节。3. 升级到付费计划以获得更高限额。ApiException: 400 Bad Request请求参数错误。1. 检查Model名称拼写是否正确如gpt-3.5-turbo。2. 检查Messages列表是否为空或格式错误。3.最常见MaxTokens设置过大超过了模型上下文窗口或与已有消息令牌数之和超过限制。尝试减小MaxTokens或裁剪历史消息。4. 检查Temperature等参数是否在有效范围内。HttpRequestException: Connection refused / Timeout网络连接问题。1. 检查本地网络是否通畅。2. 如果你在某些网络环境下确认是否能正常访问api.openai.com。3. 考虑使用HTTP代理需在创建HttpClientHandler时配置并传递给ChatClient构造函数。注意此处仅讨论企业内网代理等合规场景必须合法合规使用网络。流式响应中断或不完整网络不稳定或客户端读取流超时。1. 增加HttpClient的Timeout和ReadWriteTimeout。2. 在遍历IAsyncEnumerable时使用CancellationToken并设置合理的超时。3. 实现断线重连逻辑对于长对话场景较复杂。回复内容不相关或质量差提示词Prompt设计不佳或参数设置不当。1. 优化系统消息和用户消息给出更清晰、具体的指令。2. 调整Temperature参数对于确定性任务调低如0.2创意任务调高如0.8。3. 使用更强大的模型如从gpt-3.5-turbo切换到gpt-4。函数调用未被触发模型不支持或工具描述不清。1. 确认使用的模型支持函数调用如gpt-3.5-turbo-1106及以后版本gpt-4-turbo-preview等。2. 检查ToolChoice参数是否设置为auto或具体的函数名。3. 仔细检查函数描述的Name,Description,Parameters是否清晰准确。AI需要根据描述来判断是否调用。6.2 调试与日志记录在开发阶段详细的日志是快速定位问题的关键。启用请求/响应日志你可以在创建HttpClient时添加一个日志记录的DelegatingHandler。public class LoggingHandler : DelegatingHandler { private readonly ILoggerLoggingHandler _logger; public LoggingHandler(ILoggerLoggingHandler logger) _logger logger; protected override async TaskHttpResponseMessage SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // 记录请求URL和头信息注意不要记录包含API密钥的Authorization头 _logger.LogDebug(Request: {Method} {Url}, request.Method, request.RequestUri); // 如果需要可以记录请求体注意可能是敏感信息 // if (request.Content ! null) // { // var requestBody await request.Content.ReadAsStringAsync(); // _logger.LogDebug(Request Body: {Body}, requestBody); // } var response await base.SendAsync(request, cancellationToken); _logger.LogDebug(Response Status: {StatusCode}, response.StatusCode); // 记录响应体用于调试错误信息 if (!response.IsSuccessStatusCode) { var responseBody await response.Content.ReadAsStringAsync(); _logger.LogError(Response Body: {Body}, responseBody); } return response; } } // 注册到DI services.AddTransientLoggingHandler(); services.AddHttpClient(OpenAIClient) .AddHttpMessageHandlerLoggingHandler() // 添加日志处理器 .AddPolicyHandler(retryPolicy);安全警告在生产环境中记录日志时务必避免记录完整的请求和响应体尤其是它们可能包含敏感的API密钥在请求头中和用户隐私数据。上述示例仅记录了状态码和错误响应体相对安全。更安全的做法是只记录元数据或者对敏感信息进行脱敏处理。使用Fiddler/Charles等抓包工具对于复杂的交互问题如函数调用流程使用网络抓包工具直接查看原始的HTTP请求和响应数据是最直观的调试方式。这能帮你确认发送的数据格式是否正确以及OpenAI返回的原始数据是什么。6.3 成本监控与优化AI API调用是计费服务成本控制很重要。估算令牌数在发送请求前可以粗略估算输入令牌数英文约1 token4字符中文约1-2字符1 token。这有助于设置合理的MaxTokens和预测成本。分析Usage对象每次API响应都包含Usage对象详细列出了本次请求消耗的PromptTokens输入、CompletionTokens输出和TotalTokens。定期汇总这些数据。设置预算和告警在OpenAI平台后台可以为API密钥设置使用预算和软硬限制。当用量接近限制时会收到邮件告警。考虑缓存如前所述对可预测的、重复的查询结果进行缓存。模型选型gpt-3.5-turbo的成本远低于gpt-4。在满足需求的前提下优先使用更经济的模型。对于简单的分类、补全任务甚至可以评估更便宜的text-embedding或completion模型如果适用。