Semantic Kernel连接本地Ollama PHI3模型404错误? 一招解决
2025-04-19 01:53:48
搞定 Semantic Kernel 调用本地 Ollama PHI3 模型的 404 错误
写 C# 代码,想用 Semantic Kernel 框架连接你在本地跑的 Ollama 服务,调用像 phi3:3.8b
这样的小语言模型,结果一运行到 aiChatService.GetStreamingChatMessageContentsAsync(chatHistory)
这行,程序“砰”一下抛出 System.ClientModel.ClientResultException: Service request failed. Status: 404 (Not Found)
?别挠头,这事儿不罕见。代码瞅着没啥大问题,模型名字也没错,Ollama 服务可能也确认在跑,那到底是哪里卡壳了呢?
咱们来捋一捋,看看这 404 到底是怎么回事,又该怎么解决它。
404 Not Found:问题在哪?
“404 Not Found” 这个 HTTP 状态码,大白话就是“找不到”。在你这个场景下,具体意思是:你的 C# 程序(通过 Semantic Kernel)尝试去访问 Ollama 服务上的某个网络地址(API Endpoint),但 Ollama 服务器说:“嘿,你找的这个地址,我这儿没有!”
这通常指向以下几个可能的原因:
- Ollama 服务没跑起来,或者监听的地址不对。 这是最基础的,服务都没启动,自然谁也连不上。
- Semantic Kernel 配置的 Ollama 地址不对。 可能是 IP、端口号写错了。
- 关键:API Endpoint 路径错了。 这是最常见的原因!Semantic Kernel 的
AddOpenAIChatCompletion
这个扩展方法,设计初衷是用来连接 OpenAI 或者兼容 OpenAI API 格式的服务的。它会按照 OpenAI 的 API 规范去拼接请求路径。而 Ollama 虽然提供了一个兼容 OpenAI 的接口,但这个兼容接口的“门牌号”(路径)跟你代码里HttpClient
设置的基础地址 (http://localhost:11434
) 可能对不上。Ollama 的兼容接口通常需要一个特定的前缀,比如/v1
。 - 模型名字不对或者模型没下载。 虽然你的代码里
phi3:3.8b
看起来是对的,但最好还是确认下 Ollama 里确实有这个模型,并且名字完全一致。 - 本地防火墙捣乱。 防火墙可能阻止了你的 C# 程序访问
localhost
的指定端口。
重装 Ollama 服务和 AI 模型通常解决不了根本的配置或路径问题,所以我们得从上面这些点入手。
解决步骤
下面我们一步步来排查和解决。
第一步:确认 Ollama 服务状态和模型
这是最基础的检查,不能跳过。
-
检查 Ollama 服务是否在运行:
打开你的终端或命令行工具,尝试访问 Ollama 的基础 URL。如果 Ollama 跑在默认的http://localhost:11434
,你可以用curl
命令(Linux/macOS/Windows PowerShell)或者直接在浏览器里访问:curl http://localhost:11434
如果服务正常,它应该会返回类似
Ollama is running
的信息。如果返回“连接拒绝”或超时,说明 Ollama 服务没启动,或者监听的地址/端口不是11434
。你需要先启动 Ollama 服务。通常的启动命令是ollama serve
,但具体看你的安装方式。 -
确认模型已下载且名称正确:
在终端执行以下命令,列出 Ollama 已经下载的模型:ollama list
检查列表里是不是确实有一个叫做
phi3:3.8b
(或者你实际使用的精确模型名) 的模型。如果没有,需要先拉取模型:ollama pull phi3:3.8b
确保模型名字在你的 C# 代码 (
modelId: "phi3:3.8b"
) 和ollama list
输出里完全一致,大小写敏感。
如果这两步都没问题,服务在跑,模型也在,那我们接着往下看。
第二步:修正 API Endpoint 地址 (最可能的解决方案)
这个大概率是症结所在。Semantic Kernel 的 AddOpenAIChatCompletion
默认认为它连接的是一个遵循 OpenAI API 规范的服务端点。Ollama 为了兼容性,确实提供了一个这样的接口,但它通常不在根路径 /
下,而是在 /v1/
路径下。
你的代码里是这样配置 HttpClient
的:
using var httpClient = new HttpClient()
{
BaseAddress = new Uri("http://localhost:11434"),
};
然后 AddOpenAIChatCompletion
会使用这个 httpClient
。当它尝试调用聊天补全接口时,它可能会在 BaseAddress
后面拼接上 OpenAI 标准的相对路径,比如 /chat/completions
。但它缺少了关键的 /v1
部分。
解决方案: 修改 HttpClient
的 BaseAddress
,让它指向 Ollama 的 OpenAI 兼容 API 的基础路径 。
怎么改?
把 BaseAddress
改成 http://localhost:11434/v1/
。注意末尾的斜杠 /
不能少,这有助于 HttpClient
正确处理相对路径的拼接。
// 原来的 HttpClient
// using var httpClient = new HttpClient()
// {
// BaseAddress = new Uri("http://localhost:11434"),
// };
// --- 修改后 ---
using var httpClient = new HttpClient()
{
// 注意这里加上了 /v1/
BaseAddress = new Uri("http://localhost:11434/v1/"),
};
// Kernel 创建部分通常不需要改动
// (注意:apiKey 对于本地 Ollama 通常不需要,但 Semantic Kernel 的这个方法可能要求传一个非空值,
// 可以随便填一个字符串,比如 "ollama" 或 "none")
#pragma warning disable SKEXP0010 // 抑制实验性 API 警告
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: "phi3:3.8b", // 确认模型名称和 ollama list 中一致
httpClient: httpClient, // 使用配置了正确 BaseAddress 的 httpClient
apiKey: "not-needed") // 提供一个占位 API Key
.Build();
var aiChatService = kernel.GetRequiredService<IChatCompletionService>();
var chatHistory = new ChatHistory([new ChatMessageContent(AuthorRole.System, "Sos un asistente de programación de C#")]); ; // 原来的西班牙语 System Prompt
while (true)
{
Console.WriteLine("Your prompt:");
var userPrompt = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userPrompt)) break; // 添加退出循环的方式
chatHistory.Add(new ChatMessageContent(AuthorRole.User, userPrompt));
Console.WriteLine("AI Response:");
var response = "";
try // 增加异常处理
{
await foreach (var item in aiChatService.GetStreamingChatMessageContentsAsync(chatHistory))
{
if (item.Content is not null)
{
Console.Write(item.Content);
response += item.Content;
}
}
chatHistory.Add(new ChatMessageContent(AuthorRole.Assistant, response));
Console.WriteLine();
}
catch (Exception ex)
{
// 捕获并打印调用 API 时可能发生的异常
Console.WriteLine(// 原来的 HttpClient
// using var httpClient = new HttpClient()
// {
// BaseAddress = new Uri("http://localhost:11434"),
// };
// --- 修改后 ---
using var httpClient = new HttpClient()
{
// 注意这里加上了 /v1/
BaseAddress = new Uri("http://localhost:11434/v1/"),
};
// Kernel 创建部分通常不需要改动
// (注意:apiKey 对于本地 Ollama 通常不需要,但 Semantic Kernel 的这个方法可能要求传一个非空值,
// 可以随便填一个字符串,比如 "ollama" 或 "none")
#pragma warning disable SKEXP0010 // 抑制实验性 API 警告
Kernel kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: "phi3:3.8b", // 确认模型名称和 ollama list 中一致
httpClient: httpClient, // 使用配置了正确 BaseAddress 的 httpClient
apiKey: "not-needed") // 提供一个占位 API Key
.Build();
var aiChatService = kernel.GetRequiredService<IChatCompletionService>();
var chatHistory = new ChatHistory([new ChatMessageContent(AuthorRole.System, "Sos un asistente de programación de C#")]); ; // 原来的西班牙语 System Prompt
while (true)
{
Console.WriteLine("Your prompt:");
var userPrompt = Console.ReadLine();
if (string.IsNullOrWhiteSpace(userPrompt)) break; // 添加退出循环的方式
chatHistory.Add(new ChatMessageContent(AuthorRole.User, userPrompt));
Console.WriteLine("AI Response:");
var response = "";
try // 增加异常处理
{
await foreach (var item in aiChatService.GetStreamingChatMessageContentsAsync(chatHistory))
{
if (item.Content is not null)
{
Console.Write(item.Content);
response += item.Content;
}
}
chatHistory.Add(new ChatMessageContent(AuthorRole.Assistant, response));
Console.WriteLine();
}
catch (Exception ex)
{
// 捕获并打印调用 API 时可能发生的异常
Console.WriteLine($"\n[Error calling AI service]: {ex.Message}");
// 可以选择在这里 break 或者让用户继续尝试
// break;
}
}
#pragma warning restore SKEXP0010
quot;\n[Error calling AI service]: {ex.Message}");
// 可以选择在这里 break 或者让用户继续尝试
// break;
}
}
#pragma warning restore SKEXP0010
原理:
通过将 BaseAddress
设置为 http://localhost:11434/v1/
,你告诉了 HttpClient
(以及随后使用它的 Semantic Kernel 服务)所有后续的相对路径请求(比如 /chat/completions
)都应该基于这个 /v1/
路径进行。这样,最终发出的请求就会是 http://localhost:11434/v1/chat/completions
,这正好是 Ollama 提供的 OpenAI 兼容聊天接口的地址。
注意那个 apiKey
: AddOpenAIChatCompletion
方法签名通常需要一个 apiKey
参数。对于本地运行、不需要鉴权的 Ollama 实例,这个 key 其实没用。但方法可能不允许传入 null
或空字符串。所以,提供一个像 "not-needed"
或者 "ollama"
这样的占位符字符串通常就可以。
修改完之后,重新运行你的 C# 程序,看看 404 错误是不是消失了。
进阶技巧:环境变量配置 Endpoint
硬编码 http://localhost:11434/v1/
不太灵活。更好的做法是把这个地址放到配置文件(比如 appsettings.json
)或者环境变量里,然后在代码里读取。
// 示例:从环境变量读取 Ollama Endpoint
var ollamaEndpoint = Environment.GetEnvironmentVariable("OLLAMA_ENDPOINT_URL");
if (string.IsNullOrEmpty(ollamaEndpoint))
{
ollamaEndpoint = "http://localhost:11434/v1/"; // 提供一个默认值
Console.WriteLine("Warning: OLLAMA_ENDPOINT_URL environment variable not set. Using default: " + ollamaEndpoint);
}
using var httpClient = new HttpClient()
{
BaseAddress = new Uri(ollamaEndpoint),
};
// ... 后续代码使用这个 httpClient ...
这样,如果以后 Ollama 的地址或者端口变了,只需要修改环境变量或配置文件,不用改代码。
第三步:检查防火墙设置
虽然是本地调用 (localhost
),理论上不应该经过复杂的网络路由,但有些防火墙或安全软件策略比较严格,可能会干预进程间的本地网络通信。
- 临时禁用防火墙测试: 为了快速判断是不是防火墙的问题,可以临时 禁用你的防火墙(比如 Windows Defender 防火墙,或者其他第三方安全软件),然后再次运行你的 C# 程序。如果能成功连接,那就说明确实是防火墙规则挡住了。
- 警告: 禁用防火墙会降低系统安全性,测试完毕后务必重新启用!
- 添加防火墙规则: 如果确定是防火墙问题,不要一直禁用它。更好的方法是添加一条出站规则(outbound rule),允许你的 C# 应用程序(或者所有应用程序,如果你不确定具体是哪个进程)访问
localhost
(或127.0.0.1
) 的11434
TCP 端口。具体怎么添加规则,取决于你用的操作系统和防火墙软件。
第四步:考虑 Semantic Kernel 版本
极少数情况下,可能是 Semantic Kernel 的某个特定版本与 Ollama 的 OpenAI 兼容层之间存在不匹配。可以尝试更新 Semantic Kernel 相关的 NuGet 包到最新稳定版,或者查阅 Semantic Kernel 的 GitHub Issues 看有没有其他用户报告类似问题和特定版本的解决方案。
# 在你的项目目录下,使用 .NET CLI 更新包
dotnet add package Microsoft.SemanticKernel --version <最新版本号>
dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI --version <最新版本号>
# 其他相关的 SK 包也一并检查更新
安全建议
- 本地访问为主: 在开发和测试阶段,让 Ollama 只监听本地地址 (
localhost
或127.0.0.1
) 是最安全的。Ollama 默认通常就是这样。 - 谨慎暴露到网络: 如果你需要让局域网内的其他机器访问你的 Ollama 服务(比如把
ollama serve
绑定到0.0.0.0
),务必确保你的网络环境是受信任的。考虑使用防火墙规则,只允许特定 IP 地址访问 Ollama 的端口。目前 Ollama 本身对 API 访问的认证支持有限,所以网络层面的保护很重要。不要直接把没有安全防护的 Ollama 服务暴露到公网上。
总结一下
遇到 Semantic Kernel 调用本地 Ollama 报 404,别慌,按下面的顺序排查:
- 基础检查:
curl http://localhost:11434
确认服务运行,ollama list
确认模型存在且名字对得上。 - 修正 Endpoint: 大概率是这里的问题。修改 C# 代码里
HttpClient
的BaseAddress
,在原来的地址后面加上/v1/
,变成http://localhost:11434/v1/
。记得给apiKey
提供一个占位符。 - 防火墙: 临时关掉防火墙试试,如果管用,就去给应用程序添加访问
localhost:11434
的允许规则。 - 版本更新: 检查 Semantic Kernel 包是否是最新稳定版。
通常,修正了 API Endpoint 地址(第二步)就能解决这个 404 问题。试试看吧!
相关资源
- Ollama Documentation
- Ollama OpenAI Compatibility (这个文档对于理解
/v1
endpoint 很重要) - Microsoft Semantic Kernel Documentation