【Bug已解决】ChatGPT Android Remote creates usable but hidden threads with model_provider=openai instead of host config provider 解决方案

原始报错线索:ChatGPT Android Remote creates usable but hidden threads with model_provider=openai instead of host config provider(Android 远程端在创建线程时,错误地用了默认的 model_provider=openai 而不是宿主(host)配置里指定的 provider,导致创建了「能用但看不见」的隐藏线程)。


一、现象长什么样

用户在本机(host)配置了「用我自建的模型提供方 Provider-X」。然后:

  1. 通过 Android 远程端发起一个新对话(线程);
  2. 线程能用,但在本机宿主界面里看不到——它是「隐藏」的;
  3. 排查发现:这个线程的 model_provideropenai(客户端硬编码默认值),而不是宿主配置里的 Provider-X
  4. 因为 provider 不同,线程被存到了另一套命名空间/账户下,宿主视图自然不显示;
  5. 用户困惑:明明在「我的配置」下建的,怎么跑到了别处。 根因是远端客户端没继承宿主配置,自作主张用了自己的默认值

二、背景:配置作用域与继承

配置通常有多层:

层级 示例 优先级
全局默认 model_provider=openai 最低
宿主配置(host) 用户在桌面端设的 Provider-X
本次请求显式参数 调用时传的 provider=Y 最高
正确解析应是:显式参数 > 宿主配置 > 全局默认。远端客户端创建线程时,应当拿到「宿主配置」这一层作为基础,而不是退回到「全局默认」。

三、为什么用错 provider:根因

3.1 远端不读取宿主上下文

Android 远程端发起请求时,没把「当前 host 的 config」带过去,或宿主没下发给它,于是它用了自己包里的默认 openai

3.2 默认值优先级盖过宿主配置

解析逻辑写成「有默认值就用默认值」,宿主配置反被当成「可选项」没生效——优先级反转。

3.3 创建与查询不在同一作用域

线程创建时用了 provider A 的命名空间,宿主查询时用 provider B 的命名空间,两边对不上 → 隐藏。

3.4 缺少「来源可追溯」

创建出的线程没记录「它是用哪个 provider 创建的」,事后无法审计为何隐藏。

四、最小可运行复现(默认值覆盖宿主配置)

下面演示「默认值优先级高于宿主配置」如何选错 provider:

def resolve_provider_bad(explicit, host_config, default="openai"):
    # 错误:default 优先级最高 -> 永远用 openai
    if default:
        return default
    if host_config:
        return host_config
    if explicit:
        return explicit
    return default
if __name__ == "__main__":
    chosen = resolve_provider_bad(
        explicit=None,
        host_config="Provider-X",     # 宿主配置指定了 X
        default="openai",
    )
    print("错误解析得到:", chosen)   # openai —— 覆盖了宿主配置

默认值被当成最高优先级,宿主的 Provider-X 被无视。

五、解决方案一:正确的优先级链(显式 > 宿主 > 默认)

解析顺序必须「显式最高、默认最低」:

def resolve_provider(explicit, host_config, default="openai"):
    """优先级:显式参数 > 宿主配置 > 全局默认。"""
    if explicit is not None:
        return explicit
    if host_config is not None:
        return host_config
    return default
if __name__ == "__main__":
    print(resolve_provider(None, "Provider-X"))
    # Provider-X ✅ 宿主配置生效
    print(resolve_provider("Provider-Y", "Provider-X"))
    # Provider-Y ✅ 显式覆盖

把「默认」放到最低优先级,宿主配置自然生效(解决 3.2)。

六、解决方案二:远端必须继承宿主上下文

远端客户端创建资源前,先从宿主拉取「有效配置快照」,用快照作为基础:

def build_thread_request(remote_input, host_config_snapshot):
    """远端发起创建线程:以宿主配置快照为基础,仅允许显式覆盖。"""
    base = {
        "model_provider": host_config_snapshot.get("model_provider"),  # 来自宿主
        "scope": host_config_snapshot.get("scope"),                    # 同命名空间
    }
    # 远端传来的显式参数可覆盖,但默认不覆盖宿主
    for k, v in (remote_input or {}).items():
        if v is not None:
            base[k] = v
    return base
if __name__ == "__main__":
    host = {"model_provider": "Provider-X", "scope": "host-ns"}
    req = build_thread_request({}, host)        # 远端没传 -> 用宿主
    print("创建线程用:", req)   # model_provider=Provider-X, scope=host-ns

远端以宿主快照为基,线程自然落在宿主可见的命名空间,不再隐藏(解决 3.1/3.3)。

七、解决方案三:记录「创建来源」便于审计

每条创建的线程都带上「用了哪个 provider、来自哪层配置」,事后可查为何隐藏:

import time
def create_thread(req, resolved_provider, source_layer):
    thread = {
        "id": f"th_{int(time.time())}",
        "model_provider": resolved_provider,
        "provider_source": source_layer,   # 'explicit' / 'host' / 'default'
        "visible_to_host": resolved_provider == req.get("expected_host_provider"),
    }
    return thread
if __name__ == "__main__":
    req = {"expected_host_provider": "Provider-X"}
    t = create_thread(req, "Provider-X", "host")
    print(t)   # visible_to_host=True,来源清楚
    t2 = create_thread(req, "openai", "default")
    print(t2)  # visible_to_host=False,立刻能看出错配

provider_source 让「是否隐藏」可立即判定,审计/排查一目了然。

八、跨端一致性要点

  • 单一配置服务:宿主与远端都从同一配置服务解析,避免各自维护默认;
  • 下发而非各猜:宿主在远程会话建立时把有效配置下发,远端不自己编默认值;
  • 作用域绑定:线程的可见性由其 scope/provider 决定,创建时必须和查询时同源;
  • 变更同步:宿主改了 provider,进行中的远程会话应收到更新。

九、排查清单

「远程端用错 provider 创建隐藏线程」,按下面排查:

  1. 优先级链对吗?是否显式 > 宿主 > 默认(第五节);
  2. 远端是否继承宿主配置?还是用自己的默认值(第六节);
  3. 创建与查询同作用域吗?provider/scope 是否一致;
  4. 是否记录创建来源provider_source 可追溯吗(第七节);
  5. 默认值是否硬编码在客户端?应来自配置服务而非写死;
  6. 宿主改配置后远程会话同步了吗
  7. 隐藏线程能否被检索到?可见性是否可判定(第七节 visible_to_host);
  8. 日志是否打印实际解析到的 provider

十、小结

「远程客户端用错 provider 创建隐藏线程」的根因是配置解析优先级反转 + 远端不继承宿主上下文,导致用客户端默认值而非宿主配置,线程落到错误命名空间而隐藏。通用修复:

  1. 正确优先级:显式 > 宿主 > 默认,默认永远最低(第五节);
  2. 继承宿主快照:远端以宿主下发的配置快照为基础,仅显式参数可覆盖(第六节);
  3. 记录来源:每条资源带 provider_source,可见性可立即判定;
  4. 跨端一致:单一配置服务 + 下发 + 变更同步(第八节,呼应第 104/112 篇)。 一句话:远端替用户创建资源时,绝不能用「自己的默认值」冒充「用户的配置」;配置必须沿「显式 → 宿主 → 默认」的链解析,且创建与查询必须同源。把配置来源钉死,就不会再出现「能用却看不见」的幽灵线程——这与第 104 篇偏好持久化、第 105 篇数据源聚合、第 112 篇跨设备同步,共同指向「分布式环境下的配置必须单一真相源且可追溯」。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐