# Conflicts: # apps/cli/package.json # apps/web/tests/smoke-real.e2e.ts # apps/web/tests/snapshots/fresh-round-trip/ui.expected.md # apps/web/tests/snapshots/seeded-history/ui.expected.md # docs/config-catalog.md # packages/client/connection/tests/fake-api.ts # packages/client/runtime/src/client/index.ts # packages/client/runtime/src/client/sessions/conversation.ts # packages/client/runtime/src/client/sessions/session.ts # packages/client/runtime/tests/fake-api.ts # packages/client/ui-conversation/src/client/apply.ts # packages/client/ui-conversation/src/client/contract/slots.ts # packages/client/ui-conversation/src/client/index.ts # packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx # packages/client/ui-conversation/src/client/skeleton/InputBar.tsx # packages/client/ui-conversation/tests/chat-stats-bash-sample.spec.tsx # packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx # packages/client/ui-conversation/tests/chat-view.spec.tsx # packages/client/ui-conversation/tests/gate-branch-tails.spec.tsx # packages/client/ui-conversation/tests/input-bar.spec.tsx # packages/client/ui-conversation/tests/skeleton.spec.tsx # packages/host/apiproxy/README.md # packages/host/apiproxy/src/api-proxy.ts # pnpm-lock.yaml # scripts/verify-package-readme-model-experience.ts # tsconfig.base.json
6.0 KiB
Agent Note: Web 对话输入区的会话模型选择
Status: implemented
English | 中文
问题
Web 对话原本通过 Host 固定的提供方与模型路由显示并发送消息,既不呈现该路由,也不允许用户更改。TUI 已经具备会话级的路由目标,但如果照搬其呈现方式,或在浏览器中硬编码 DeepSeek 模型,就会让模型发现逻辑和步骤边界语义分散到不同前门中。响应运行期间发生的切换还需要一个原子边界:提示词变量与请求路由不能观测到不同的目标。
决策
Web Host 为每个新建或恢复的 agent(智能体)复用 installAgentLlmTarget。如果会话已经使用过模型,目标从最新的 request/header 开始;否则采用 Host 默认值。session.selectModel 会更改会话级可变目标,提示词组装则将该目标与请求路由一并捕获,因此运行中步骤发生的切换会应用于下一个组装步骤。下一条实际采用的路由通过现有的完整 request/header 快照持久化;尚未进入请求的选择则仅保存在当前进程中。
会话 RPC 领域公开 session.history 的当前 modelTarget、session.models 模型目录与 session.selectModel。该目录从 LLM(大语言模型)注册表动态构建,并按提供方分组。各提供方目录会并发加载,且彼此独立失败,因此成功加载的分组仍可与可重试的失败记录一同使用。模型是否位于目录仅供参考:如果当前模型的已注册提供方没有列出该模型,系统会将其作为未列出行插入;在已注册提供方下选择未列出的模型仍然有效。
浏览器中的 Session 对象持有当前目标、分组目录、提供方失败记录、操作错误,以及 idle、loading、ready、selecting、error 状态。Host 会话的选择器挂载时会预加载目录,使紧凑型触发器能够解析目录名称;此后每次打开菜单都会刷新目录。常驻壳在 Workspace 选择连接或复用 Host 会话之前没有会话模型路由,因此其禁用的无会话输入栏不会分发选择器。目录与选择调用共用操作代次,防止较早响应覆盖较新结果;另设目标变更代次,使历史恢复即使与挂载时的目录刷新并发,也能还原日志记录的模型,同时防止旧历史覆盖用户选择。失败时保留先前的当前目标和可用分组。
@deepseek-ai/dsh-client-ui-conversation 将会话作用域的单实例 slot conversation.input.model 声明为其输入栏 entry 的子 slot。InputBar 在尾部控件区将该 seat 渲染于 pending 指示器与主按钮之前;该 seat 接收输入栏的 locked owner prop 与会话标准工具包。@deepseek-ai/dsh-client-ui-model-selector 占用该专用 seat,Host 拥有的空白会话 hero 也包括在内。其紧凑型触发器和单选菜单项显示目录名称;当前目标未列出时则回退到模型 ID。向上展开的菜单只显示一次提供方标题,同时提供键盘导航、关闭操作、重试状态和当前选择标记。
生产环境的浏览器名册是 apps/cli/cordis.yml 中的平铺 config tree;选择器对应其中一行 dshClient 配置项,而不是 Web boot 代码中硬编码的包。其包 manifest(元数据清单)仍声明对 ui-conversation 的图依赖边,激活则由 Cordis 服务可用性驱动。
考虑过的替代方案
分别使用提供方与模型下拉框。 模型列表依赖提供方,每次更改都需要经过两阶段交互。单个分组菜单仍以提供方组织模型,同时不会增加触发器或各行的显示长度。
在 Web 客户端中硬编码当前 DeepSeek 目录。 该目录会与已注册适配器发生偏离,也会排除部署自有的提供方。LLM 注册表继续作为提供方与模型元数据的真源,也负责呈现部分查询失败。
将选择设为全局默认值。 全局变更会意外改道其他已打开的对话。目标仅属于一个实时会话;对于没有已记录请求的会话,Host 配置仍是默认值。
agent 运行期间拒绝更改。 共享原子目标已经将当前组装步骤与下一次选择分离。保持选择器可用,可以让用户为下一个步骤预先选择模型,而不会改变正在执行的请求。
将每次点击作为新的会话事件持久化。 只有提示词组装采用某项选择后,该选择才对模型可见。持久化尚未使用的 UI 意图,会增加一个无法重建模型请求的持久事件;现有 request/header 会记录首次实际使用该路由的请求。
影响
任何由 Host 支撑的 Web 对话(包括空白会话)都可以在动态发现的提供方分组之间切换,而无需显示重复的 provider/model 标签;当前实际使用的路由会在恢复和重连后保留。目录名称仅用于呈现;选择和持久化仍然使用提供方/模型 ID。某个提供方的目录不可用时,只有相应分组会降级。路由变更可能降低提供方侧的缓存复用率,但选择器不会添加任何提示词内容,也不会干扰正在执行的步骤。常驻壳仅在没有当前会话时使用 Host 默认值且不暴露选择器。
测试
Host 测试固定分组发现、重复目录项隔离、部分提供方失败、已记录目标恢复、当前未列出目标、不可用提供方拒绝,以及切换仅影响下一次组装。客户端测试固定状态转换、失败时保留原状态、传输错误、过时响应栅栏、挂载与打开重叠、从历史记录恢复,以及快照引用稳定性。UI 测试固定专用模型 seat 的生命周期与锁定状态传播、显示目录名称并回退到 ID、提供方分组、单选语义、重试与错误状态、选择成功与失败、点击外部关闭,以及 Arrow/Home/End/Escape 键盘导航。无密钥 built-app fixture(测试前置数据)通过与生产环境同形的 boot 图加载选择器,选择 OpenAI 的 GPT-5,发起一个轮次,并验证下一条生成的响应会报告所选路由。