c#如何调用deepseek的api

接入与开发

给 C# 开发者的 DeepSeek 接口接入速查:HttpClient 写法、流式输出、超时并发与排错顺序,附可复用代码结构。

不少 .NET 同学拿到 Key 之后卡在第一步:HttpClient 怎么写、请求体长什么样、流式怎么读、超时和并发怎么设才不被限流。网上示例要么太老,要么只贴一半代码。这篇按「能跑起来」的顺序给一份 C# 落地方案,包含最小调用、流式解析、超时并发取舍和排错清单,代码结构可直接抄进项目。

拿到 Key 之后卡住的人,大多不是不懂 C#,而是不知道请求体长什么样、流式怎么读、超时并发怎么设才不会被限流。这篇按「先跑通、再优化」的顺序给一份能直接抄进项目的结构,从最小调用一路写到排错清单。

示例代码只保留必要字段,模型名、价格这类会变的东西不写死,需要时去站内页面核对当时的数字。整套思路围绕一个原则:先用最简单的方式确认链路通,再往上加流式、重试和日志。

最小可跑:一个异步方法把请求发出去

第一步别碰流式,先用普通 POST 拿到完整 JSON。请求体按 OpenAI 兼容结构组织,先只放 model、messages、stream 三个字段就够,多余参数等跑通再加。模型名不要凭记忆写,去站内模型资料库看当前可选值,换模型时改配置而不是改代码。

// 注册一次,全局复用
builder.Services.AddHttpClient("deepseek", c =>
{
    c.BaseAddress = new Uri("https://api.deepseek.com/");
    c.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("DEEPSEEK_KEY"));
});

var payload = new
{
    model = "<去站内模型页核对当前模型名>",
    messages = new[] { new { role = "user", content = "你好" } },
    stream = false
};

var resp = await http.PostAsJsonAsync("chat/completions", payload);
var body = await resp.Content.ReadAsStringAsync();
Console.WriteLine(body);
  • 用 IHttpClientFactory 或单例 HttpClient,别在方法体里 new HttpClient,反复创建会耗尽连接
  • 鉴权头固定是 Bearer 加 Key,Key 从环境变量或用户机密读,不要硬编码进仓库
  • Key 怎么申请、有没有免费额度,去站内教程页核对当时的说明
  • 非流式调通再加 stream,这一步能省掉后面大半的排查时间

流式输出:C# 里怎么按行解析 SSE

流式最容易踩的坑是缓冲区:如果在发送请求时没指定 ResponseHeadersRead,HttpClient 会把整段响应读完才交给你,前端看到的就是「卡一下然后全出来」。加上这个选项之后才能边收边读。

using var req = new HttpRequestMessage(HttpMethod.Post, "chat/completions")
{
    Content = JsonContent.Create(payload with { stream = true })
};
using var resp = await http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
resp.EnsureSuccessStatusCode();

using var stream = await resp.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);

while (!reader.EndOfStream)
{
    var line = await reader.ReadLineAsync();
    if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data:")) continue;
    var data = line.Substring(5).Trim();
    if (data == "[DONE]") break;

    var chunk = JsonSerializer.Deserialize<JsonElement>(data);
    var delta = chunk.GetProperty("choices")[0]
                     .GetProperty("delta");
    if (delta.TryGetProperty("content", out var c))
        Console.Write(c.GetString());
}

增量内容在 choices[0].delta.content 里,字段可能整个不存在,也可能值为 null,取到就跳过,别直接拼字符串。把这段解析抽成一个方法,控制台小工具和 ASP.NET Core 接口复用同一份,别写两遍。

超时、并发与重试:别把服务打死

连接超时和整体超时建议分开设。连接慢通常是网络问题,读取慢是因为模型在生成,长回答要放宽读取侧。默认的短超时套在流式上,会把正常生成误判成失败。

场景处理方式不适用情况
连接失败或 DNS 出错快速失败,指数退避重试少数几次Key 或路由错误时重试无意义
读取超时放宽读取侧,或改流式分段处理非流式且确实超时的长任务要另行处理
429 限流降并发、加退避,看响应头里的恢复提示并发上限按账号档位不同而不同,去站内核对
401 / 403不重试,直接查认证头或权限临时网络抖动导致的要看具体错误码

并发不是越大越好。按账号能承受的档位控制同时发出的请求数,超了就会遇到限流,触发原因和恢复节奏站内有专门一篇讲。重试只针对超时和 5xx,重试要带退避和次数上限,最好在项目里封一个统一入口,别让每个调用点自己写一套。

  1. 确认错误类型先看状态码,是网络层超时还是服务端返回错误码,两类处理完全不同。
  2. 判断能不能重试超时和 5xx 可以退避重试,认证类和参数类错误重试只是浪费调用次数。
  3. 限制重试次数给一个上限和递增等待,避免失败时把并发堆到更高。
  4. 回到日志复盘把每次请求的耗时和状态码记下来,事后才能看出是偶发还是系统性变慢。

日志与排错:出问题先看哪几个字段

第一次接入建议单独写个控制台小项目验证,跑通再往业务代码里搬。跟框架配置混在一起排查,很难分清是接口问题还是依赖注入、中间件的问题。

  • 每次请求至少记:时间、模型名、耗时、状态码、token 用量、错误原文
  • 400 先查请求体字段名和类型,多半是模型名写错或 messages 格式不对
  • 401 查 Key 和请求头格式,403 查权限和模型可用性,两者处理动作不一样
  • 超时先查网络和超时设置,别一上来就改重试次数
  • 多人共用一个 Key 时,转发层加请求标识,否则日志分不清是谁的调用

token 用量和费用换算会随价格变动,别在代码里写死换算比例,需要展示时去站内价格页核对当时的数字,或者干脆只展示 token 数。团队里想比较不同接入方式的实际体验,可以用站内的速度测试工具跑一轮,看延迟和成功率,而不是凭感觉。

先让最小调用跑通,再加流式、重试和日志,每一步都只引入一个新的不确定因素。
下一步把最小调用跑通后,去模型资料库确认可用模型名,再去价格页核对当时的计费口径,最后用速度测试工具对比一下你所在网络的真实延迟。

常见问题

C# 里用 HttpClient 调 DeepSeek,一直返回 401 怎么办?

先确认请求头是不是 Bearer 加空格再加 Key,很多人漏了空格或者写成了别的字段名。再确认 Key 没带多余引号或换行,尤其是从配置文件读出来时。如果头没问题,检查是不是把 Key 用在了没开通的模型上,那会表现成 403 而不是 401。两类的区分站内有一篇专门讲。

流式输出在 C# 里读不全,最后一段丢了怎么排查?

常见原因是没开 ResponseHeadersRead,或者读完一行就 break 导致末尾缓冲没冲出来。检查循环是否只在 [DONE] 时退出,而不是遇到空行就结束。另外别用 ReadToEndAsync,那是等全部内容回来才返回,流式就白做了。可以先在控制台打印原始行看结构再写解析。

并发一高就报错,C# 这边应该怎么控制?

用 SemaphoreSlim 在调用入口限个同时执行数,值按你的账号档位定,具体额度去站内官方 API 页面核对该模型当时的限制口径。超出的请求排队而不是直接抛异常,失败后用带退避的重试补上。别在多个线程里各自 new 一个 HttpClient 无限制发。

本文依据各平台公开文档与官方公告整理,只做中文归纳;具体做法、参数与限制以官方文档为准。