AI agent 编排工具 Goose Desktop 持续出现 404 错误的长期未解决 Issue

本文最后更新于 2026年2月22日 晚上

背景描述

这几天我在 Windows(以及 Linux)上用 Goose Desktop GUI 配 Azure OpenAI,撞上了一个很离谱的现象:GUI 里填的 LLM 地址/配置看起来完全正确,但一用就报 404

1
2
3
Could not contact provider
Failed to fetch models for azure_openai: Provider error: Request failed: Resource not found (404): Resource not found
Check your provider configuration in Settings → Providers

更离谱的是——同样的 Azure OpenAI 服务:

  • curl 直接调用没问题
  • 用 VScode 直接连也没问题
  • 装了 Goose CLI 之后,Desktop 反而突然好了,还能看到 CLI 刚跑出来的 session
  • 换多台机器,不同操作系统验证过

这整件事看起来是:Goose Desktop 本地的 goosed 后端无法正确的加载

相关 Issue(同类症状):block/goose#3571


关键事实:Desktop 不是“GUI 直连云端”,而是 “GUI → 本地 goosed → 云端”

Goose Desktop 运行时会在本机拉起一个 Rust 后端进程 goosed,GUI 只是通过 127.0.0.1:<port> 调它的 API,再由 goosed 去请求供应商。

所以“看起来像 LLM 404”的错误,至少要拆成三类看:

  • 本地 goosed API 返回的 4xx/5xx(例如鉴权、路由、版本不匹配)
  • goosed 转发到供应商后,供应商返回的 4xx/5xx
  • goosed 在后台做“拉模型列表/探活”失败(跟真正发 chat/completions 不是一回事)

试验过程

1)Desktop 里“看起来任何 provider 都 404”

GUI 提示经常是:

1
Request failed: Request failed with status: 404 Not Found

日志里则会看到类似(注意这个位置经常出现在 Desktop 的 main.log 里,因为它会把 goosed 的 stderr 转存进去):

1
WARN goose::providers::openai_compatible: Provider request failed with status: 404 Not Found ... "Resource not found"

这跟 issue block/goose#3571 的描述非常像:同一套 key/模型用 curl 能通,但 Desktop 里持续 404。


2)curl / PowerShell 直连 Azure:服务是好的

以 Azure OpenAI 为例,我这边 endpoint 大概长这样:

1
https://<portal>.azure.com

可以直接 200 并返回回复。

结论:Azure chat/completions 本身是通的


3) 省略其中 AI 带着我逛了至少 4h 的完全方向都错误的过程

根本原因

不知道是不是像我这样用自带 LLM 的人太少,半年都没人定位到根本原因(Root Cause);

三天前维护者还说拖太久了,直接把 issue 关了,实在离谱。

https://github.com/block/goose/issues/3571#issuecomment-3871651227

上面是我找到的绕过办法。本质原因是:Desktop 在写入 config.yml 时比 CLI 少写了 GOOSE_PROVIDERGOOSE_MODEL 两个字段,导致程序找不到 MODEL;

而选择 MODEL 时又必须先验证成功才能把配置写回文件——于是形成死循环,永远连不上。

因此只有先安装 CLI 并在其中配好,才能正常用。

更新:官方在 v1.24.0(2026-02-12)中修复了该问题。具体改动见 PR fix(providers): Azure OpenAI model listing 404 during configure #7034,Release 说明见 v1.24.0 · block/goose。升级到 v1.24.0 或更高版本后,Desktop 配置 Azure 时不应再出现“拉模型列表 404”导致的死循环。


开盖研究

这一章开始“开盖”——不再盯着 GUI 的报错,而是把 goosed 从源码编出来,直接用 API 证明它到底有没有成功连到 Azure

1)在 Windows 上把后端编出来(release)

由于从来也没用过 Rust,且有近 20年 没在 Windows 上运行编译型语言,以下路径也均为 AI建议 先走试试。

我用的路径是“纯 Windows + pwsh + MSVC toolchain”,核心是两件事:

  • Rust 工具链(rustup/cargo)
  • VS Build Tools(提供 link.exe

然后编后端:

1
cargo build --release -p goose-server

产物默认在:

1
target\release\goosed.exe

2)启动 goosed,并固定一个 X-Secret-Key

goosed 除 /status 外的大多数 API 都需要 X-Secret-Key。本地跑建议固定一个:

1
2
$env:GOOSE_SERVER__SECRET_KEY = "dev-secret"
.\target\release\goosed.exe agent

验证 health:

1
irm http://127.0.0.1:3000/status

3)验证:goosed 是否确实读取了 config.yaml 里的 Azure 配置

用 goosed 的 /config 直接读(需要 X-Secret-Key):

1
2
3
4
5
$h=@{ "X-Secret-Key"="dev-secret" }
$cfg = irm http://127.0.0.1:3000/config -Headers $h
$cfg.config.AZURE_OPENAI_ENDPOINT
$cfg.config.AZURE_OPENAI_API_VERSION
$cfg.config.AZURE_OPENAI_DEPLOYMENT_NAME

这一步能确认:配置确实被 goosed 读取到了

4)最关键:验证 goosed 能否真实调用 Azure(不是 /status 那种空壳)

我用的是一条“完整链路”:

  1. POST /agent/start 创建 session
  2. POST /agent/update_provider 设置 azure_openai + gpt-4.1
  3. POST /reply 发一条消息,看 SSE 是否出现 Message 事件

为什么这能作为“硬证据”?

  • /reply 是真正会触发 agent.reply() 的路径
  • provider 会走到 OpenAiCompatibleProviderchat/completions
  • 如果没连到供应商,通常会返回 Error 事件或直接 5xx

后端链路代码入口:

关键片段(摘录):

1
2
3
// crates/goose-server/src/routes/reply.rs
let mut stream = agent.reply(user_message, session_config, Some(cancel_token)).await?;
// 然后把 AgentEvent::Message 作为 SSE 输出给客户端

而真正发出 chat/completions 的位置是:

关键片段(摘录):

1
2
let completions_path = format!("{}chat/completions", self.completions_prefix);
let resp = self.api_client.response_post(session_id, &completions_path, &payload).await?;

我实际用来验证的 PowerShell(可复用)

如果你想完整照抄一遍验证,可以参考文末附录里的 PowerShell 脚本(负责创建 session、设置 azure_openai + gpt-4.1 并调用 /reply,确认 SSE 里出现 Message 事件)。

5)Azure 列模型 404:根因与修复

根因就是列模型时 URL 拼错了:旧代码把 deployment 塞进了所有请求共用的 host(如 endpoint/openai/deployments/gpt-4.1),导致列模型也发成 GET .../deployments/gpt-4.1/models,而 Azure 只认 GET .../openai/models,所以 404。

官方修法(PR #7034):把 URL 拼对——列模型用 {endpoint}/openai/models,发对话时才在路径前加 deployments/{name}/(见 openai_compatible.rscompletions_prefix),各用各的路径,404 消失。

参考实现(我本地的 patch 思路)

说明:下面是“修复方向”的代码形态。你也可以把它理解为:Azure 既然不支持 /openai/models,那就别去打它,返回空列表让 UI 手输 deployment name

1)服务端:Azure 直接返回空列表,避免触发 GET /openai/models

文件:crates/goose-server/src/routes/config_management.rs

1
2
3
4
5
6
7
// crates/goose-server/src/routes/config_management.rs
pub async fn get_provider_models(Path(name): Path<String>) -> Result<Json<Vec<String>>, ErrorResponse> {
if name == "azure_openai" {
return Ok(Json(Vec::new()));
}
// ...其它 provider 走原逻辑...
}

2)Provider 元数据:允许 UI 手输“不在列表里的 model”(Azure 语义上就是 deployment name)

文件:crates/goose/src/providers/azure.rs

1
2
3
4
5
// crates/goose/src/providers/azure.rs
fn metadata() -> ProviderMetadata {
ProviderMetadata::new(/* ... */)
.with_unlisted_models()
}

与 PR #7034 实际改动的对比

下面用三张流程图说明:修复前为什么 404当时推测的改法官方实际改法。不涉及任何前端知识,只看“请求往哪儿发、结果是什么”即可。


① 修复前:为什么配置时会 404?

当时代码把「deployment 名」塞进了所有请求的共用地址里,导致「列模型」和「发对话」都用了同一个错误的基础路径:

flowchart LR
    subgraph 旧代码的共用地址
        A["host = endpoint/openai/deployments/gpt-4.1"]
    end

    subgraph 列模型时
        B["GET host + 'models'"]
        C["即 GET .../deployments/gpt-4.1/models"]
        D["Azure 不支持 → 404"]
        B --> C --> D
    end

    subgraph 发对话时
        E["POST host + 'chat/completions'"]
        F["即 POST .../deployments/gpt-4.1/chat/completions"]
        G["Azure 支持 → 200"]
        E --> F --> G
    end

    A --> B
    A --> E

一句话:列模型本不该带 deployments/xxx,但旧代码把 host 设成了带 deployment 的地址,所以「列模型」请求发错地方,404;配置页就卡死。


② 当时推测的改法(应用层兜底)

不动 URL 构造,在「列模型」这一步直接不请求 Azure,返回空列表,让用户在界面里自己填 deployment 名;发对话时仍然用原来的路径(带 deployment):

flowchart LR
    subgraph 列模型
        A1["Desktop 要列模型"]
        B1["服务端发现是 azure_openai"]
        C1["直接返回空列表 []"]
        D1["不请求 Azure → 不会 404"]
        A1 --> B1 --> C1 --> D1
    end

    subgraph 填配置
        E1["用户在界面手填 deployment 名"]
    end

    subgraph 发对话
        F1["POST .../deployments/xxx/chat/completions"]
        G1["和以前一样 → 200"]
        F1 --> G1
    end

    D1 --> E1
    E1 --> F1

一句话:不修 URL,而是「列模型」时对 Azure 直接不请求、返回空,让用户手填模型名,从而绕过 404。


③ 官方 PR #7034 的改法(修对 URL)

把「共用地址」和「仅对话用的前缀」拆开:列模型用「不带 deployment」的地址,发对话时才在路径前加上 deployments/xxx/

flowchart LR
    subgraph 新代码的地址
        H["host = endpoint/openai<br/>(不再带 deployment)"]
        I["completions_prefix = 'deployments/gpt-4.1/'<br/>(只用于发对话)"]
    end

    subgraph 列模型
        J["GET host + 'models'"]
        K["即 GET .../openai/models"]
        L["不带 deployment → Azure 可接受 → 200"]
        J --> K --> L
    end

    subgraph 发对话
        M["POST host + completions_prefix + 'chat/completions'"]
        N["即 POST .../openai/deployments/gpt-4.1/chat/completions"]
        O["和以前一样 → 200"]
        M --> N --> O
    end

    H --> J
    H --> I
    I --> M

一句话:列模型用 .../openai/models,发对话用 .../openai/deployments/xxx/chat/completions,各用各的路径,404 消失。


小结

维度 当时推测 PR #7034 实际改动
根因 列模型时请求了 Azure 不支持的路径 → 404 一致:deployment 路径被错误地加到了「列模型」的请求上
实现 config_management.rs 里对 azure_openai 直接返回 [],不再请求 Azure;provider 加 with_unlisted_models() 不改 config 层,改 URL 构造host 只保留 {endpoint}/openai在发 chat/completions 时加上前缀 deployments/{name}/(见 openai_compatible.rs 新增的 completions_prefix
结果 列模型不再打 Azure,404 消失 列模型请求变为 GET {endpoint}/openai/models(与 openai-python SDK 行为一致),不再带 deployment 路径,404 消失

两种做法都能打破配置死循环;官方做法是把 URL 写对,推测做法是在应用层不请求、让用户手填。文中前面的技术细节看不懂也没关系,只要看懂上面三张图:① 以前错在哪儿 → ② 我们想的办法 → ③ 官方修的办法,就够用了。


AI agent 编排工具 Goose Desktop 持续出现 404 错误的长期未解决 Issue
https://gou7ma7.github.io/2026/02/07/devops/@2026_execute_in_goose/
作者
Roy Lee
发布于
2026年2月7日
许可协议