【AI】基于OllamaSharp与.NET Core API的高效LLM查询实现:把Base URL改到TaoToken
发布时间:2026/10/2 6:13:41 锦皓数字建站

1. 从本地 Ollama 到统一入口.NET Core API 接入 LLM 的真实痛点在 .NET Core API 项目里用 OllamaSharp 调本地大模型起步其实很顺dotnet add package OllamaSharp写个OllamaClient指向http://localhost:11434跑通一个/query/{input}接口半天就能出活。但只要项目从「我自己电脑上跑个 demo」变成「团队内网服务、多人共用、要换模型、要控成本」问题就集中爆发了。我自己踩过的第一个坑是地址散落。appsettings.json里写一份appsettings.Development.json里又写一份Docker 环境变量再覆盖一次最后线上到底连的是哪台机器得翻三个文件才能确认。第二个坑是模型名硬编码想从codellama换成别的模型得改代码重新发布。第三个坑最要命本地 Ollama 服务一旦没启动API 返回的是一坨底层连接异常前端拿到 500 却不知道是模型服务挂了还是网络不通。这些问题的本质是调用入口没有统一。OllamaSharp 本身是个很干净的客户端库它不负责帮你管理「连哪里、用什么模型、怎么鉴权」。当你的 .NET Core API 需要面向内网多个调用方、需要灵活切换后端模型服务时就得在配置层做一层抽象。这篇要解决的就是这件事把 OllamaSharp 的 Base URL 从写死的本地地址改成一个统一的模型服务入口同时保留appsettings.json动态配置的能力。适合正在做 .NET Core 后端、需要给内网提供 LLM 查询接口的开发者。核心检索词就三个OllamaSharp、.NET Core API、LLM 查询接入。下面从项目结构、配置片段、请求验证到报错排查一步步给可复制的做法。2. TaoToken 作为统一入口的前置准备Base URL、Key 与模型 ID先说清楚这一层是干什么的。OllamaSharp 默认连的是 Ollama 原生服务地址形如http://host:11434走的是 Ollama 自己的 API 协议。而我们要做的是把 Base URL 指向一个兼容 OpenAI 协议的统一模型服务入口这样 .NET Core API 侧不用关心后端到底是哪家模型、部署在哪台机器只认一个地址和一个 Key。TaoToken 在这里扮演的就是这个统一入口的角色。它的 API 地址是https://taotoken.net/api注意这个地址不带任何查询参数是纯粹的接口根路径。官网入口在https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content需要注册和拿 Key 的话从这里进。前置准备其实就三样东西我把它叫「三件套」缺一不可配置项作用示例值Base URL请求发往哪里https://taotoken.net/apiAPI Key身份鉴权sk-开头的一串字符Model ID用哪个模型如gpt-4o-mini、claude-3-5-sonnet等这里有个关键认知OllamaSharp 的OllamaClient构造函数接收的是 Ollama 原生地址如果你直接把https://taotoken.net/api塞进去协议是对不上的。所以正确的做法有两条路一是用 OllamaSharp 里支持自定义HttpClient的重载把请求导向兼容端点二是干脆在 .NET Core API 里用标准HttpClient直接调 OpenAI 兼容的/v1/chat/completions。考虑到标题聚焦 OllamaSharp我下面会以「保留 OllamaSharp 的调用风格 自定义 HttpClient 指向统一入口」为主线同时给出纯 HttpClient 的对照写法你可以按项目实际情况选。拿 Key 的路径进官网后到控制台在 API Keys 页面创建一个新 Key。地址是https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite。创建后立刻复制保存页面刷新后就不再完整显示。如果你只是想先验证模型通不通不想写代码可以用模型对话页面直接试https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite。长期做编码和 Agent 场景的话Coding Plan 更划算入口在https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite。注意Base URL 一定不要带 UTM 参数或任何尾部斜杠https://taotoken.net/api就是最终形态。带参数的地址在 HttpClient 拼接时容易出双斜杠或路径错乱这是后面 404 报错的常见来源。3. 可复制配置appsettings.json 与 HttpClient 注入片段这一节是全文的核心给的都是能直接粘进项目的片段。先建项目如果你已经有 .NET Core API 项目跳过前两步dotnet new webapi -n OllamaLLMAPI cd OllamaLLMAPI dotnet add package OllamaSharp然后是配置文件。我建议把模型服务的三件套单独放一个节点不要和数据库连接串混在一起。appsettings.json这样写{ LlmGateway: { BaseUrl: https://taotoken.net/api, ApiKey: sk-你的Key粘贴在这里, ModelId: gpt-4o-mini, TimeoutSeconds: 60 }, Logging: { LogLevel: { Default: Information, Microsoft.AspNetCore: Warning } }, AllowedHosts: * }对应的强类型配置类namespace OllamaLLMAPI; public class LlmGatewayOptions { public const string SectionName LlmGateway; public string BaseUrl { get; set; } string.Empty; public string ApiKey { get; set; } string.Empty; public string ModelId { get; set; } string.Empty; public int TimeoutSeconds { get; set; } 60; }接下来是Program.cs里的依赖注入。这里的关键是注册一个带鉴权头的HttpClient并把它交给后续的调用逻辑。注意BaseAddress的写法末尾要带斜杠否则相对路径拼接会丢掉/api这一段using OllamaLLMAPI; var builder WebApplication.CreateBuilder(args); builder.Services.ConfigureLlmGatewayOptions( builder.Configuration.GetSection(LlmGatewayOptions.SectionName)); builder.Services.AddHttpClient(llm, (sp, client) { var opt sp.GetRequiredServiceIOptionsLlmGatewayOptions().Value; client.BaseAddress new Uri(opt.BaseUrl.TrimEnd(/) /); client.DefaultRequestHeaders.Authorization new System.Net.Http.Headers.AuthenticationHeaderValue(Bearer, opt.ApiKey); client.Timeout TimeSpan.FromSeconds(opt.TimeoutSeconds); }); builder.Services.AddControllers(); var app builder.Build(); app.MapControllers(); app.Run();如果你坚持用 OllamaSharp 的OllamaClient风格可以这样构造把自定义HttpClient传进去using OllamaSharp; public class LlmQueryService { private readonly OllamaApiClient _client; private readonly LlmGatewayOptions _opt; public LlmQueryService(IHttpClientFactory factory, IOptionsLlmGatewayOptions options) { _opt options.Value; var http factory.CreateClient(llm); _client new OllamaApiClient(http); _client.SelectedModel _opt.ModelId; } public async Taskstring AskAsync(string input, CancellationToken ct default) { var sb new System.Text.StringBuilder(); await foreach (var token in _client.GenerateAsync(input, ct)) { sb.Append(token?.Response); } return sb.ToString(); } }控制器保持极简只做参数校验和异常兜底[ApiController] [Route([controller])] public class QueryController : ControllerBase { private readonly LlmQueryService _svc; public QueryController(LlmQueryService svc) _svc svc; [HttpGet(query/{input})] public async TaskIActionResult Get(string input, CancellationToken ct) { if (string.IsNullOrWhiteSpace(input)) return BadRequest(new { error input 不能为空 }); try { var result await _svc.AskAsync(input, ct); return Ok(new { model _svc.ModelId, answer result }); } catch (HttpRequestException ex) { return StatusCode(502, new { error 上游模型服务不可达, detail ex.Message }); } } }这套配置的好处是换模型只改appsettings.json的ModelId换入口只改BaseUrl代码一行不动。生产环境用环境变量覆盖即可比如LlmGateway__ApiKey双下划线对应 JSON 层级。4. 验证请求状态码、日志与一次真实对话配置写完先别急着上生产用最小成本验证通道。启动项目dotnet run控制台会打印监听地址通常是http://localhost:5xxx。另开一个终端用 curl 打一发curl -i http://localhost:5000/query/query/用一句话解释什么是依赖注入注意路由是[controller]/query/{input}所以路径里会出现两个query这是原示例的路由设计我保留它以便对照。返回正常的话你会看到类似HTTP/1.1 200 OK Content-Type: application/json; charsetutf-8 {model:gpt-4o-mini,answer:依赖注入是一种设计模式它把对象的创建和使用分离……}状态码 200 说明整条链路通了.NET Core API 收到请求 → HttpClient 带 Bearer Key 发往https://taotoken.net/api→ 上游返回 → 序列化回前端。如果返回 502说明上游没通看detail字段里的异常信息。日志这块建议开Information级别在LlmQueryService里加一行耗时记录方便定位是网络慢还是模型慢var sw System.Diagnostics.Stopwatch.StartNew(); var result await _svc.AskAsync(input, ct); sw.Stop(); _logger.LogInformation(LLM 调用完成 model{Model} 耗时{Ms}ms, _svc.ModelId, sw.ElapsedMilliseconds);实测下来一次普通对话请求在 1 到 3 秒之间取决于模型和输入长度。如果你看到耗时稳定在 60 秒整那基本是TimeoutSeconds触发了说明请求根本没到上游检查 Base URL 和 Key。想更直观地验证模型本身通不通可以先用模型对话页面发一条同样的 prompt对比返回内容是否一致。页面入口https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite。如果页面能出结果而 API 不行问题一定在 .NET 侧的配置或网络。提示验证阶段把appsettings.Development.json里的 Key 单独放不要提交到 Git。生产用环境变量或密钥管理服务注入。5. 常见报错排查401、local proxy failed 与 reading choices这一节按真实报错来对都是我或身边同事实际撞过的。401 Unauthorized。最常见九成是 Key 的问题。检查三处appsettings.json里 Key 有没有多余空格Authorization头是不是Bearer加空格再加 Key少个空格也会 401Key 是不是在控制台被删了或过期了。重新去https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite生成一个再试。local proxy failed / connection refused。这个报错说明请求压根没发出去或者发到了一个不存在的本地地址。典型原因是BaseUrl还留着http://localhost:11434没改或者改成了https://taotoken.net但漏了/api。记住完整地址是https://taotoken.net/api。另外检查公司内网有没有对出站 HTTPS 做限制如果有需要走内网允许的出口。reading choices 相关报错。这类错误通常出现在解析响应体时比如Cannot read property choices of undefined或反序列化失败。根因是上游返回的不是标准 OpenAI 格式可能是错误 JSON也可能是空响应。排查方法在HttpClient上加一个DelegatingHandler把原始响应体打出来或者临时用 curl 直接打上游看返回结构。如果返回里带error字段先解决那个错误choices自然就有了。OAuth / 鉴权方式不匹配。有些项目里混用了 OAuth token 和 API Key导致请求头格式不对。TaoToken 这里用的是标准 Bearer API Key不需要 OAuth 流程。如果你在Program.cs里同时注册了别的鉴权中间件确认它没有覆盖掉llm这个命名 HttpClient 的请求头。模型 ID 不存在。返回 404 或 400提示 model not found。检查ModelId拼写大小写敏感。不确定有哪些模型可用去文档页查https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite。排查顺序建议固定成先 curl 直连上游确认 Key 和地址 → 再 curl 打本地 API 确认路由 → 最后看日志里的异常堆栈。这样能快速定位是配置层、网络层还是代码层的问题。6. 把入口固定下来后续接入与长期使用建议走到这里你的 .NET Core API 已经能通过统一入口稳定调用 LLM 了。回头看真正让项目可维护的不是某一行代码而是把「地址、Key、模型」这三样东西从代码里抽出来放进配置。这样测试环境和生产环境可以指向不同入口换模型不用重新编译团队新人拉下代码填个 Key 就能跑。如果你后面要接 Claude Code 这类编码工具或者做更复杂的 Agent 编排思路是一样的认准 Base URL、Key、Model ID 三件套。Claude Code 的接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有具体的环境变量写法。长期高频调用的话Coding Plan 的入口在https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite比按量付费更适合日常开发。最后给一个实用技巧在LlmQueryService里加一层简单的内存缓存对相同 prompt 的重复请求直接返回上次结果能省下不少调用量。缓存 key 用ModelId input的哈希过期时间设个 5 分钟就够。这个改动不到二十行但在内网多人共用的场景下命中率往往不低。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。