C# 调用 OpenAI API 实战:一位老程序员的踩坑与经验分享 开篇引子二十年的技术变革从当年的 Delphi、VB 到后来的 Java、C#从微软的 .NET Framework 1.0 一路用到现在的 .NET 10当年为了一个 HTTP 请求还要自己写 socket现在呢一个HttpClient搞定一切。我以为这辈子已经没什么新技术能让我激动了。结果呢AI 来了。说实话2022 年底 ChatGPT 刚出来的时候我跟很多人一样觉得这就是个更智能一点的搜索引擎嘛能有多少花样结果用了之后发现这玩意儿是真的能听懂人话啊让它写代码它能给你写出像模像样的代码让它解释 Bug它能给你分析得头头是道。作为一个老 C# 程序员我当然想这能不能跟我的 .NET 项目结合起来能不能让我的程序也能调用 AI答案是当然可以而且比想象中简单得多。这篇文章呢就是我这几年来踩过的坑、总结的经验手把手教你怎么用 C# 调用 OpenAI API。甭管你是刚入行的小年轻还是跟我一样写了十几年代码的老油条保准你能看懂、能上手。磨刀不误砍柴工环境准备那些事儿API Key 怎么搞首先你得有个 OpenAI 的 API Key。这玩意儿怎么获取呢打开 https://platform.openai.com/ 注册账号进入 API 密钥管理页面点击 “Create new secret key”特别注意这个 Key 只显示一次一定要保存好刷新页面就看不到了。我当年第一次就忘了保存只能重新创建浪费了好几分钟。保存好之后有两种方式配置到你的项目中方式一环境变量推荐# Linux/macOSexportOPENAI_API_KEYsk-xxx# Windows PowerShell$env:OPENAI_API_KEYsk-xxx方式二User SecretsASP.NET Core 项目dotnet user-secretssetOPENAI_API_KEYsk-xxx反正记住一点千万别把 API Key 硬编码到代码里我知道有些人为了图方便直接在代码里写apiKey: sk-xxx这等于把你的信用卡密码贴在脑门上——分分钟被人搬空余额。NuGet 包选哪个现在 C# 调用 OpenAI 有两个主流选择官方 SDK包名OpenAI版本2.11.0dotnetaddpackage OpenAI这是 OpenAI 官方出的基于 OpenAPI 规范自动生成品质有保障。最新版本支持 .NET Standard 2.0也就是说你用 .NET Framework 4.6.1、.NET Core 2.0、.NET 5 都可以。社区经典包名Betalgo.Ranul.OpenAI版本9.2.6dotnetaddpackage Betalgo.Ranul.OpenAI这个是社区老牌库了原名叫Betalgo.OpenAI后来因为商标问题改的名。功能很全文档也丰富很多老项目都在用。我的建议是新项目直接用官方 SDK。毕竟官方出品兼容性更好有什么 API 变更也会第一时间跟进。老项目如果已经用习惯了也不是不能继续用。.NET 版本选择官方示例用的是 .NET 10但你用 .NET 8 也完全没问题。.NET 6 我都测过能跑。第一次亲密接触Hello World 级别的调用环境准备好了接下来就是见证奇迹的时刻。我们先来实现最基础的功能调用 AI让它回答一个问题。同步调用usingOpenAI.Chat;usingSystem;ChatClientclientnew(model:gpt-4o,apiKey:Environment.GetEnvironmentVariable(OPENAI_API_KEY));ChatCompletioncompletionclient.CompleteChat(Say this is a test.);Console.WriteLine($[ASSISTANT]:{completion.Content[0].Text});就这么几行代码AI 就回答了让我解释一下这几个关键点ChatClient这是 SDK 的核心类相当于你跟 OpenAI 服务器之间的接线员model指定用哪个模型。目前主流是gpt-4o旗舰多模态模型上下文窗口 128K如果想省钱可以用gpt-4o-mini如果想用最新的可以用gpt-5.5CompleteChat同步方法会一直等待服务器返回结果才继续执行异步调用实际项目中我更推荐用异步usingOpenAI.Chat;usingSystem;usingSystem.Threading.Tasks;ChatClientclientnew(model:gpt-4o,apiKey:Environment.GetEnvironmentVariable(OPENAI_API_KEY));ChatCompletioncompletionawaitclient.CompleteChatAsync(Say this is a test.);Console.WriteLine($[ASSISTANT]:{completion.Content[0].Text});就多了个await但这在 ASP.NET Core 或者桌面应用中区别可大了去了——界面不会卡死服务器不会阻塞线程池不会被占满。传递对话历史上面那个例子每次都像在问一个陌生人没有上下文。如果你想要有上下文的对话比如多轮对话你需要维护一个消息列表ListChatMessagemessagesnew(){newSystemChatMessage(你是一个乐于助人的助手。),newUserChatMessage(什么是 C#),newAssistantChatMessage(C# 是一种由微软开发的现代、面向对象的编程语言...),newUserChatMessage(它跟 Java 有什么区别)};ChatCompletioncompletionawaitclient.CompleteChatAsync(messages);注意这里的消息类型SystemChatMessage系统提示告诉 AI 它的角色定位UserChatMessage用户的问题AssistantChatMessageAI 的回答这样 AI 就知道之前聊过什么回答会更加连贯。让 AI 学会说话流式响应实战为什么要流式不知道你们有没有这种感觉等 AI 生成一段长文本的时候那个加载圈转啊转的等待的那几秒钟简直就是煎熬。特别是做一个聊天机器人用户打出一句话然后看着光标愣愣地等着 AI 一点一点地把字打出来——这体验简直了所以后来有了流式响应Streaming这个玩意儿。AI 生成一段就返回一段像打字机一样一个字一个字地蹦出来。用户能看到 AI 正在思考等待感大大降低。流式调用实现官方 SDK 已经封装好了流式接口我们来看看怎么用usingOpenAI.Chat;usingSystem;usingSystem.ClientModel;ChatClientclientnew(model:gpt-4o,apiKey:Environment.GetEnvironmentVariable(OPENAI_API_KEY));CollectionResultStreamingChatCompletionUpdatecompletionUpdatesclient.CompleteChatStreaming(Say this is a test.);Console.Write([ASSISTANT]: );foreach(StreamingChatCompletionUpdateupdateincompletionUpdates){if(update.ContentUpdate.Count0){Console.Write(update.ContentUpdate[0].Text);}}看到了吗核心区别就是同步方法CompleteChat→ 返回ChatCompletion流式方法CompleteChatStreaming→ 返回StreamingChatCompletionUpdate的集合每个update包含的是增量内容也就是新生成的那几个字。遍历这个集合一个字一个字地打印出来打字机效果就出来了。异步流式同样支持异步版本usingOpenAI.Chat;usingSystem;usingSystem.ClientModel;usingSystem.Threading.Tasks;ChatClientclientnew(model:gpt-4o,apiKey:Environment.GetEnvironmentVariable(OPENAI_API_KEY));AsyncCollectionResultStreamingChatCompletionUpdatecompletionUpdatesclient.CompleteChatStreamingAsync(Say this is a test.);Console.Write([ASSISTANT]: );awaitforeach(StreamingChatCompletionUpdateupdateincompletionUpdates){if(update.ContentUpdate.Count0){Console.Write(update.ContentUpdate[0].Text);}}实际项目中Web API 返回流式响应会用到Server-Sent Events (SSE)不过那是另一个话题了。原理是一样的AI 生成一点前端就显示一点。Function Calling 实战让 AI 调用你的 C# 方法好了现在 AI 能回答问题了也能流式打字了。但这些都还是纸上谈兵——AI 只能从它的训练数据里获取信息无法访问实时数据也无法执行具体操作。Function Calling函数调用就是为了解决这个问题而生的。它让 AI 能够识别什么时候需要调用外部工具提取用户请求中的参数把参数传给你的 C# 方法把方法的返回值告诉 AIAI 根据返回值生成最终回答说白了这就是让 AI 变成了你程序的大脑能够指挥你的代码去干活。天气查询实战我们来做个完整的例子让 AI 帮你查天气。首先定义两个 C# 方法privatestaticstringGetCurrentLocation(){returnSan Francisco;}privatestaticstringGetCurrentWeather(stringlocation,stringunitcelsius){return$31{unit};}这只是模拟方法实际上你可以调用天气 API 获取真实数据。然后定义工具ToolprivatestaticreadonlyChatToolgetCurrentWeatherToolChatTool.CreateFunctionTool(functionName:nameof(GetCurrentWeather),functionDescription:Get the current weather in a given location,functionParameters:BinaryData.FromBytes({type:object,properties:{location:{type:string,description:The city and state, e.g. Boston, MA},unit:{type:string,enum:[celsius,fahrenheit],description:The temperature unit to use.}},required:[location]}u8.ToArray()));privatestaticreadonlyChatToolgetCurrentLocationToolChatTool.CreateFunctionTool(functionName:nameof(GetCurrentLocation),functionDescription:Get the users current location);这里的 JSON Schema 就是告诉 AI这两个函数接受什么参数、参数的类型是什么、哪些是必填的。接下来是调用逻辑的核心部分ListChatMessagemessages[newUserChatMessage(Whats the weather like today?)];ChatCompletionOptionsoptionsnew(){Tools{getCurrentLocationTool,getCurrentWeatherTool}};boolrequiresAction;do{requiresActionfalse;ChatCompletioncompletionclient.CompleteChat(messages,options);switch(completion.FinishReason){caseChatFinishReason.Stop:// AI 正常回答不需要调用工具messages.Add(newAssistantChatMessage(completion));break;caseChatFinishReason.ToolCalls:// AI 要求调用工具messages.Add(newAssistantChatMessage(completion));foreach(ChatToolCalltoolCallincompletion.ToolCalls){switch(toolCall.FunctionName){casenameof(GetCurrentWeather):usingJsonDocumentargsJsonJsonDocument.Parse(toolCall.FunctionArguments);boolhasLocationargsJson.RootElement.TryGetProperty(location,outJsonElementlocation);boolhasUnitargsJson.RootElement.TryGetProperty(unit,outJsonElementunit);stringresulthasUnit?GetCurrentWeather(location.GetString(),unit.GetString()):GetCurrentWeather(location.GetString());messages.Add(newToolChatMessage(toolCall.Id,result));break;casenameof(GetCurrentLocation):stringlocationResultGetCurrentLocation();messages.Add(newToolChatMessage(toolCall.Id,locationResult));break;}}requiresActiontrue;// 继续循环让 AI 根据工具返回值生成回答break;}}while(requiresAction);这段代码看起来有点长但逻辑其实很清楚发送用户问题和工具定义给 AIAI 如果说我需要查天气FinishReason会是ToolCalls解析 AI 传来的参数调用你的 C# 方法把方法的返回值包装成ToolChatMessage发送回去AI 根据返回值生成最终的人话回答循环直到 AI 回答完毕不需要再调用工具这就是 AI Agent 的基本原理是不是挺神奇的流式 Function Calling如果你想要流式效果处理逻辑稍微复杂一点需要用StreamingChatToolCallsBuilder来累加工具调用增量StreamingChatToolCallsBuildertoolCallsBuildernew();foreach(StreamingChatCompletionUpdateupdateinclient.CompleteChatStreaming(messages,options)){foreach(StreamingChatToolCallUpdatetoolCallUpdateinupdate.ToolCallUpdates){toolCallsBuilder.Append(toolCallUpdate);}if(update.FinishReasonChatFinishReason.ToolCalls){IReadOnlyListChatToolCalltoolCallstoolCallsBuilder.Build();// 同样处理工具调用...}}原理是一样的就是把增量信息攒起来攒成一个完整的工具调用再处理。那些年踩过的坑血泪经验总结写代码二十年踩过的坑比吃过的盐还多。调用 OpenAI API 这几年我总结了几条血泪经验你们可得记好了。Token 限制别让 AI 撑坏了OpenAI 的模型有上下文窗口限制比如gpt-4o是 128K tokensgpt-5已经到了 400K。听起来很多是不是但你要是聊着聊着把整个对话历史都塞进去很快就会超过限制。经验之谈中文大约 1 token ≈ 1.5-2 个汉字英文大约 1 token ≈ 0.75 个单词用 OpenAI Tokenizer 在线计算怎么解决两种思路截断只保留最近 N 轮对话旧的丢掉摘要用另一个 AI 把历史对话压缩成摘要错误处理别让程序崩了API 调用失败的场景太多了网络抖动、服务器繁忙、API Key 过期、模型不存在……你永远不知道什么时候会出问题。我的建议是一定要加 try-catch并且对不同错误码做不同处理try{varcompletionawaitclient.CompleteChatAsync(messages);}catch(ClientResultExceptionex){switch(ex.StatusCode){case401:// API Key 无效Console.WriteLine(检查一下你的 API Key 是不是过期了);break;case429:// 速率超限// 等待一段时间后重试awaitTask.Delay(5000);break;case500:// 服务器错误Console.WriteLine(OpenAI 服务器抽风了等会儿再试);break;default:Console.WriteLine($出错了:{ex.Message});break;}}官方 SDK 的异常继承自ClientResultException里面包含状态码和错误信息好好利用。速率限制别被封号OpenAI 和 Azure OpenAI 都有速率限制Rate Limits。以 GPT-4o GlobalStandard 为例每分钟请求数RPM限制是 300每分钟 Token 数TPM限制是 300,000。超过限制怎么办服务器会返回429 Too Many Requests。处理策略官方 SDK 内置自动重试机制会自动处理自己实现的话用指数退避等 1 秒、2 秒、4 秒……慢慢重试看看响应头里的Retry-After那是服务器建议你等待的时间成本控制别让余额归零这个必须重点强调OpenAI 是按 Token 收费的最新的 GPT-5.5 是 $5.00/M 输入 tokens、$30.00/M 输出 tokens。我见过太多人写demo的时候用gpt-4o跑都没跑就直接跑了十几个小时等发现的时候账单已经几百美元了。省钱建议正式项目用gpt-4o-mini便宜量大管饱减少不必要的max_tokens设置别让 AI 生成的太长开启缓存CacheGating可以节省成本定期查看 Usage 页面了解消耗情况代码考古时间手动 HTTP vs 官方 SDK有些老派程序员不喜欢用 SDK觉得封装得太厚看不到底层原理。那我们来看看如果不用 SDK怎么直接调 HTTP 接口。手动 HTTP 调用usingSystem.Net.Http;usingSystem.Text;usingSystem.Text.Json;varclientnewHttpClient();client.DefaultRequestHeaders.Add(Authorization,$Bearer{apiKey});varrequestBodynew{modelgpt-4o,messagesnew[]{new{roleuser,contentHello!}},streamfalse};varresponseawaitclient.PostAsync(https://api.openai.com/v1/chat/completions,newStringContent(JsonSerializer.Serialize(requestBody),Encoding.UTF8,application/json));varresponseBodyawaitresponse.Content.ReadAsStringAsync();Console.WriteLine(responseBody);这代码看起来也不复杂是不是但是你需要自己处理 JSON 序列化流式响应要自己解析 SSE错误处理要自己写Function Calling 的工具调用要自己解析 JSON Schema每次 API 升级你可能都要改代码而官方 SDK 呢一个CompleteChat()搞定一切。我的观点是除非有特殊原因否则能用 SDK 就用 SDK。省心省力代码还更干净。人家微软和 OpenAI 合作写的 SDK性能和兼容性都经过测试比自己造的轮子强多了。当然如果你用的是一些非标准的 OpenAI 兼容服务比如本地部署的 LLM那可能还是得自己写 HTTP 调用。收工寄语AI 开发的未来写到这里也该收工了。回想这二十年从当年的 ASP、JSP到后来的 ASP.NET MVC、Entity Framework再到现在的 AI 集成不得不说咱们这行变化是真快。但不管技术怎么变核心逻辑是一样的用代码解决实际问题。AI API 不是什么玄学就是一种新的工具。学会了用它你的程序就能听懂人话能帮你查资料、能帮你写代码、能帮你处理各种繁琐的任务。这篇文章从环境配置讲到流式响应再到 Function Calling基本涵盖了 C# 调用 OpenAI API 的主要场景。希望能帮到你们。至于未来会怎样说实话我也看不清。AI 的发展速度已经超出我的预期了——去年还在用 GPT-4今年 GPT-5 都出来了还有什么 o1、o3 推理模型以后会变成什么样谁知道呢但有一点是肯定的持续学习别让自己落伍。