Repository navigation
release: v1.16.2 — LLM 配置与请求身份完善 - #301
Conversation
Back-merge the accepted v1.16.1 release and mark the next patch development target.
Introduce strict JSON configuration, legacy asset migration, immutable runtime snapshots and bot-owned apply/probe operations. Expose protected configuration APIs and cover persistence, fencing, pricing, group references and lifecycle boundaries.
Add provider/model editors, parameter inheritance, routing, pricing, behavior settings and separate draft, publish and bot status views. Preserve edits across saves and conflicts, bind imports to previews and recover pending operations across sessions.
Initialize configuration before consumers, retain server database and migrated assets, and provide SSH configuration operation drivers. Update Windows and Docker startup checks, public configuration contracts, OpenAPI and bundled self-docs.
Represent Unix timestamps as floats so public examples comply with the ID literal guard.
Inspect parsed URL schemes before accepting configuration and reject invalid URLs without echoing values. Validate inherited provider defaults for group overrides before writes and publication; cover failures against actual SQLite and bot apply operations.
Bind diagnostic requests to published revisions and stable request IDs, display queue progress and recover uncertain submissions without automatic model replay. Retain sample text, thinking, trace, duration and usage attribution; cover asynchronous HTTP consumers and bounded observation.
Copy configuration assets with checked discovery and bounded noninteractive sudo when permissions require it; assign new candidate assets to the deploy user. Stop activation on copy failures and preserve existing candidate assets and prior release inodes.
Track provider entry identity across remounts and saves, isolate polling sessions and age bot heartbeat independently of stalled requests and clock offsets. Cover prefix collisions, row replacement during save, late polling responses and actual timer-driven staleness.
Accept normalized save results only when the current provider entries are the entries that were submitted, preserving same-value replacements created during a save.
feat(llm): 集中配置管理与独立发布运行时
Mark the integrated LLM configuration database and migration preparation checkpoint.
Describe pricing and Responses compatibility using public protocol contracts.
Persist chat identities through archive and restore; fence lifecycle transitions and late replies. Carry immutable identities through chat, task, tool and retry paths, and expose session IDs in HTTP traces. Reserve the client projection contract while preserving existing wire requests.
Fence closed-loop results by scope generation in the write transaction and recheck the session after tool and image-description awaits. Apply the session fence to error replies and retain images only while the originating session is current. Cover archive, restore, and failed-continuation boundaries with event-coordinated regressions.
Close the recorder before propagating a session error from an exception handler, including generation-only recall invalidation. Verify failed continuations leave no pending loop and the next reply uses the normal recorded path. Both recall regression cases fail on the previous commit.
feat(llm): persist session lifecycle and trace IDs
Load editable TOML profiles and project deployment and epoch identities into opt-in QuickQuip headers across four protocols. Expose provider selection and global cluster in configuration and Web; atomically upgrade configuration schema and preserve deployed profile assets.
Share a canonical catalog snapshot, pass the target configuration directory through CLI, and isolate schema metadata from profile files. Require explicit stopped-service initialization for schema 1 upgrades; cover installed and missing package metadata in tests.
Make the unrelated default directory visible to metadata so the regression test detects accidental profile I/O during schema projection.
feat(llm): add file-defined client fingerprint profiles
Define native Chat layout and endpoint compatibility in editable TOML. Project business Sessions and isolated HTTP spans, preserve per-attempt endpoint policies, and support Brotli/Zstandard responses. Validate dynamic header ownership and cover final HTTP behavior, retries, fallback, concurrency and cancellation.
Apply file-defined casing and order after HTTPX adds transport headers, preserving authentication and additional headers.
Split stable session projections from attempt trace context, share binding registries, and encode each prepared attempt once. Verify the real HTTP/1.1 Host ordering boundary, preserve provider error classification, and document editable binding sources.
feat(profiles): add OpenCode 2 Chat fingerprint
Add editable Codex TUI identity and stable business Session bindings for Responses headers and prompt_cache_key. Protect managed Body fields at configuration and dispatch boundaries while preserving protocol payloads and existing Profile behavior. Recognize the declared cache-affinity field separately from credentials and verify the exception remains scoped to Provider and Model extra_body.
Share the standard cache-affinity field name between binding validation and credential classification, retain Profile ownership checks, and document the manual configuration contract. Type the Body projection request and separate retry, stream fallback, and history downgrade tests for focused failures.
feat(profiles): add lightweight Codex identity
Project file-defined Headers and Body from the business Session; preserve persona, native history and tool data. Add persistent independent device identity and global override, conditional beta and cache policy, request budgets and legacy fallback.
Reuse the same storage availability predicate in chat guards and core readiness while preserving vocabulary fallback. Verify the deployment gate through vocabulary degradation and actual SQLite failure.
fix(storage): preserve Turn links across history deletion
Advance one development batch after merging PR #296.
Add file-defined Gemini CLI defaults and legacy compatibility, durable session model anchors, installation identity overrides, and native tool evidence preservation. Validate configuration ownership and actual transport, migrations, concurrency, and authentication behavior.
Describe persistent Gemini model anchors and projection boundaries, synchronize bundled references, and align the configuration example formatting.
feat(llm): add Gemini CLI fingerprint profile
Add an idempotent state/time covering index to avoid scanning large trace payloads during the first pending-expiration check. Verify legacy database upgrades and actual query coverage; retain pending expiration and completion semantics.
fix(trace): avoid payload scans for stale pending
…ema 3) Schema 3 adds config_revisions (saved_at, note) so every revision carries a save time and an editable note; status/history/diagnostics expose a version object and PUT /note relabels any revision. Pricing unit is (provider_id, configured model ID): catch-all rows are removed, usage prices by request.model, and exchange format_version 2 requires pricing.provider_id. Format 1 documents and the schema 2 draft are upgraded deterministically with a report; snapshots stay immutable.
…pricing panel Version labels show 'YYYY-MM-DD HH:mm (note)' instead of vN, with note editing on save, apply and history. The pricing panel is rebuilt around provider and model selection with one-click add, orphan/duplicate hints and copy-to-provider. Also fixes nav duplication, the context alias, epoch deep link, apply refresh misreporting and unsaved provider rename; removes the orphaned MCP dashboard view.
Update admin and developer docs, prod.example README and synced self-docs references.
feat(llm): LLM config workbench — revision notes and provider-scoped pricing
Format IPv6 connection hosts with one bracket pair and preserve wildcard-to-loopback mapping. Cover domain, IPv4, IPv6 and bracketed inputs.
Use /start_session in the private-chat enable hint while retaining both registered command spellings.
Exercise Web and command writes against isolated real stores. Observe frozen in-flight requests, next-request provider/model/persona selection and isolation of another group.
Make closure order explicit and verify tied timestamps under count, byte and age limits. Assert exact retained loops and active-anchor blocking.
Connect configuration, Profile and replay contracts to a lifecycle-based troubleshooting guide with evidence and usage boundaries. Synchronize the bundled self-docs copy.
fix: close v1.16.2 acceptance gaps
Define ordinary and milestone releases, evidence-based integration review, and separate human approvals for merge and tag. Align the release template, reviewer scope, configuration guide and bundled self-docs references.
Freeze version and summarize configuration, session identity, protocol profiles and stability work since v1.16.1. Document schema 3 migration, rollback, pricing and profile changes; retain known limitations and synchronize self-docs.
Install the project with the embedded Python build backend and verify package/client versions in the existing smoke path. Preserve the CWD source layout and tag-only publishing; align the Gemini default policy documentation and release notes.
Clarify provider parameters, pricing, Profile migration and channel retirement. Publish rollback Session maintenance and fix MCP/Skill setup, navigation and roadmap; sync self-docs references.
There was a problem hiding this comment.
Bot Review — 💬 Comment
The final review stage did not produce a usable verdict; this automated comment is generated by the review pipeline.
Programmatic comment — the final adjudication result could not be used (reason: final_incomplete:terminal_tool_misuse). Reviewer findings below are shown as reported.
Findings
🟡 SHOULD-FIX: 该路由改用 published = get_config_service().read("published") 后仍是普通 APIRouter()(未套用其他配置路由统一使用的 ConfigurationRoute),且 get_config_service().initialize() 抛出的 ConfigError(如发布说明中明确预期会存在的 schema 1/2 数据库待 --services-stopped 升级、unsupported_schema、只读初始化失败等场景)没有任何捕获——app.py 中也没有 ConfigError 的全局异常处理器。旧实现里 load_llm_config(...) 的 config.load_error 分支会安全返回 {"servers": []},本改动使同一故障态从降级展示退化为未捕获异常 → HTTP 500 Internal Server Error,前端只能显示通用失败而拿不到结构化原因;同批次的 group_settings.get_options 明确 except Exception 兜底、其他配置路由经 ConfigurationRoute 转成 4xx JSON,本路由成为不一致的例外。
File: src/quickquip/app/web/routes/mcp_dashboard.py:67
与其余配置路由对齐:给 router 换用 route_class=ConfigurationRoute(router = APIRouter(route_class=ConfigurationRoute)),或在 get_mcp_dashboard() 中捕获 ConfigError,按旧 load_error 语义返回 {"servers": []} 或携带 code/message 的 4xx JSON。
⚪ NIT: 同一计费型 probe 操作现在有两个入口:/llm-configuration/probe(probe_config 中调用了 _audit(request, "probe", ...))与 /diagnostics/providers/probe(本行 get_config_service().probe(...) 后直接返回 may_charge: True),后者没有记录审计。审计日志是运维追责“谁发起了一次可能计费的探测”的依据,两个入口覆盖不一致会留下审计盲区。
File: src/quickquip/app/web/routes/diagnostics.py:63
在 probe_providers 中同样调用 audit_logger.log(request, action="probe", target_type="llm_configuration", target_id=result["id"])(或复用 llm_configuration._audit),使两个 probe 入口的审计口径一致。
🟡 SHOULD-FIX: 新增的 RuntimeSnapshotMixin 通过 for _attribute, _field in {...} + setattr(RuntimeSnapshotMixin, _attribute, _resource_property(_field))(以及 371 行处 _task_tools_property 的同类循环)动态生成 _mcp_dirty、_mcp_lock、_mcp_tool_names、_skills_catalog_by_name、_skill_tools_registered、_skill_registered_names 等属性。这些属性的真实定义只存在于 dict 字面量里,service_parts/mcp_lifecycle.py、service_parts/skills.py 中大量 self._mcp_dirty = True / self._skills_catalog_by_name = ... 的读写无法通过跳转或 grep 找到其 getter/setter,读者必须知道『属性被运行时注入』这一隐式约定才能理解写入会经过 ContextVar 快照路由。另外 131 行的 config property getter 在属性读取时执行 boot check 并可能 fence_configuration_runtime() 抛异常——读一个属性触发运行时围栏是隐藏副作用。这违反了可发现性与『状态变更应在所属 API 中可见』的结构原则。
File: src/quickquip/llm/runtime_snapshot.py:348
改为显式声明:为每个资源属性写出手写的 @property/setter(委托给一个 _resource(field) 小助手),或至少把三组属性名定义为模块级常量表并在类体中逐个赋值 property,让 IDE/跳转能从 mcp_lifecycle.py 的写入定位到定义;boot check 从 config getter 中移出,改为显式方法(如 ensure_boot_valid())在进入 lease 前调用。
🟡 SHOULD-FIX: replace_report_schedules 把两份插件的调度 job 身份复制到了适配层:[f"daily_briefing_{p}" for p in ("morning","noon","evening")]、["daily_summary_generate", "daily_summary_publish"] 等 job ID 规则本应由 daily_briefing_plugin / daily_summary_plugin 自己拥有,同时直接调用它们的私有函数 briefing._register_scheduler_jobs、summary._register_scheduler_jobs、summary._register_period_jobs。job ID 命名规则一旦在任一插件里改动,config_control 必须同步修改否则保存/回滚会漏掉或误删 job——这是典型的跨模块锁步变更与对兄弟模块内部实现的 reach-through。
File: src/quickquip/adapters/nonebot/config_control.py:20
让每个插件导出公开契约,例如 daily_briefing_plugin.REPORT_JOB_IDS / def register_report_jobs() 与 daily_summary_plugin.register_period_jobs(period_types=...),config_control 只组合这些公开的『标识 + 注册函数』,不再拼接字符串、不再导入下划线函数。
🟡 SHOULD-FIX: 轮询循环里 if runtime._configuration_fenced: 直接读取 LLM 服务(runtime_snapshot.py 中 _init_configuration_leases 初始化)的私有状态。适配层依赖另一域的私有字段名,重命名或把围栏状态收进封装后此处静默失效(循环不再退出),违反模块边界的窄接口原则。
File: src/quickquip/adapters/nonebot/config_control.py:92
在服务/RuntimeSnapshotMixin 上提供公开只读属性(如 configuration_fenced),config_control 改用该属性;或改为监听 fence_configuration_runtime() 触发的公开事件/回调。
🟡 SHOULD-FIX: ConfigService 在应用服务层直接执行持久化 SQL:_publish_locked 写 config_meta/config_operations(318-326 行),register_bot_boot 写 config_operations 与 bot_runtime 并做租约清场(412-430 行),report_bot/status/read/_check_revision 等共 20 余处 conn.execute。同一批表同时由 ConfigStore 与 ConfigTaskQueue(config_tasks.py 中的 config_operations 读写)通过自己的 SQL 访问——配置事务这一持久化状态散落在三层代码里,schema 变更需要同步改三处,违反『持久化状态单一 owner』。此外该类同时承担文档 CRUD、操作队列发布、探测、群覆盖校验、bot 启动围栏、备份/恢复,是本次新增文件中职责最宽的类。
File: src/quickquip/llm/config_service.py:323
把 config_meta/config_operations/bot_runtime 的读写语句下沉到 ConfigStore(或专责的 BotRuntimeRepository),ConfigService 只调用 store.publish_revision(...)、store.register_bot_boot(...) 等领域方法;bot 围栏与群覆盖校验可进一步拆为独立协作对象,缩窄该类的变更原因。
🟡 SHOULD-FIX: 本地 const L = { id: { hint: '全局唯一,创建后不可修改。只允许字母、数字、_ . -' }, base_url: { hint: '禁止在 URL 里直接写账号密码。', ... }, api_key_env: ..., default_model: ..., proxy: ..., fallback_urls: ..., headers: ... } 与本文件已 import 的 frontend/src/lib/llmConfig.ts 中 PROVIDER_FIELD_LABELS 的 hint/placeholder 字符串逐字重复(模板里 label="渠道 ID" 等也重复了其中的 label)。同一字段提示存在两份拷贝,改文案时必然漂移,违反『复用既有领域常量而非维护平行数组』。
File: frontend/src/components/llm-config/ProvidersPanel.vue:222
删除本地 L,统一从 PROVIDER_FIELD_LABELS(必要时补 placeholder)取 hint/placeholder/label,模板改为 :label="PROVIDER_FIELD_LABELS.id?.label" 或封装一个取值函数。
⚪ NIT: validate_group_overrides 用 with store._connect() as conn: 直接进入 LLMStore 的私有连接方法读取 group_settings 表。虽然仓库 web routes 中已有类似先例,但在配置服务域中读取另一域 store 的私有实现,store 内部改连接管理时此处无编译期保护。
File: src/quickquip/llm/config_service.py:360
在 LLMStore(store_parts/group_settings.py)上新增公开只读方法,如 list_group_provider_references() -> list[tuple],ConfigService 只依赖该契约。
⚪ NIT: validate_document 内对 parameters.extra_body 的三段式校验链(validate_body_binding_overrides → isinstance(body_policy, ClaudeBodyPolicy) → GeminiBodyPolicy → 兜底 validate_body_overrides)在渠道级(340-353 行)与模型级(371-384 行)原样重复两遍;新增一种 body policy 时需要同步改两处。
File: src/quickquip/llm/config_document.py:340
抽取模块内私有函数 _validate_extra_body(profile, protocol, params, path)(内部完成 binding + 按 policy 分派),两处循环各调用一次。
⚪ NIT: 新增的生成物 docs/dev/llm-configuration.openapi.json(5695 行)由本脚本导出,脚本位于 scripts/ci/ 且文档要求『更新约束或路由后运行并同时提交』,但 _tests.yml 只挂了 sync_self_docs_references.py --check,没有对 OpenAPI 生成物做『导出 + git diff --exit-code』的漂移检查——路由/Schema 变更后契约文件会静默过期,CI 目录命名也暗示了它本应受检。
File: scripts/ci/export_llm_configuration_openapi.py:6
在 _tests.yml 的 Lint/文档检查步骤后增加 python scripts/ci/export_llm_configuration_openapi.py && git diff --exit-code docs/dev/llm-configuration.openapi.json,与 self-docs 检查并列。
⚪ NIT: 本次改动给 BaseProviderClient 新增了约 10 处函数内 import(_assemble_headers/_project_body/_prepare_http_attempt/complete 等),其中 retry_context 的 import 被放在 _dispatch_with_retry 的 while True 循环体内,每次重试迭代都重复执行 import 语句。改动前该文件的 quickquip 导入全部在模块顶层,且 client_fingerprints 模块对 provider.base 只有 TYPE_CHECKING 引用,看不出必须延迟导入的循环依赖;若确有循环依赖,应在导入处加一行注释说明原因。
File: src/quickquip/llm/provider/base.py:530
把 retry_context、assembly 等导入提升到模块顶层(与既有 8 处顶层 quickquip 导入并列);若因循环依赖无法提升,将导入移出 while 循环并注明构成环的具体模块。
Review provenance
- General reviewer: complete — agent session submitted a review
- Style reviewer: complete — agent session submitted a review
- Coverage: complete
- Reviewed head:
a0a6a9c(base5c5f9f4)
Automated review by @KHPilot. Reply with @KHPilot to ask follow-up questions.
| # 3) Config-only: no runtime data available at all | ||
| config = load_llm_config(CONFIG_LLM_TOML) | ||
| # Published configuration describes expected servers; bot status is the runtime authority. | ||
| published = get_config_service().read("published") |
There was a problem hiding this comment.
SHOULD-FIX: 该路由改用 published = get_config_service().read("published") 后仍是普通 APIRouter()(未套用其他配置路由统一使用的 ConfigurationRoute),且 get_config_service().initialize() 抛出的 ConfigError(如发布说明中明确预期会存在的 schema 1/2 数据库待 --services-stopped 升级、unsupported_schema、只读初始化失败等场景)没有任何捕获——app.py 中也没有 ConfigError 的全局异常处理器。旧实现里 load_llm_config(...) 的 config.load_error 分支会安全返回 {"servers": []},本改动使同一故障态从降级展示退化为未捕获异常 → HTTP 500 Internal Server Error,前端只能显示通用失败而拿不到结构化原因;同批次的 group_settings.get_options 明确 except Exception 兜底、其他配置路由经 ConfigurationRoute 转成 4xx JSON,本路由成为不一致的例外。
与其余配置路由对齐:给 router 换用 route_class=ConfigurationRoute(router = APIRouter(route_class=ConfigurationRoute)),或在 get_mcp_dashboard() 中捕获 ConfigError,按旧 load_error 语义返回 {"servers": []} 或携带 code/message 的 4xx JSON。
| } | ||
| @router.post("/diagnostics/providers/probe", status_code=202) | ||
| def probe_providers(body: ProbeBody): | ||
| result = get_config_service().probe(body.source, body.provider_id, body.model, body.request_id) |
There was a problem hiding this comment.
NIT: 同一计费型 probe 操作现在有两个入口:/llm-configuration/probe(probe_config 中调用了 _audit(request, "probe", ...))与 /diagnostics/providers/probe(本行 get_config_service().probe(...) 后直接返回 may_charge: True),后者没有记录审计。审计日志是运维追责“谁发起了一次可能计费的探测”的依据,两个入口覆盖不一致会留下审计盲区。
在 probe_providers 中同样调用 audit_logger.log(request, action="probe", target_type="llm_configuration", target_id=result["id"])(或复用 llm_configuration._audit),使两个 probe 入口的审计口径一致。
| return property(get, set_) | ||
|
|
||
|
|
||
| for _attribute, _field in { |
There was a problem hiding this comment.
SHOULD-FIX: 新增的 RuntimeSnapshotMixin 通过 for _attribute, _field in {...} + setattr(RuntimeSnapshotMixin, _attribute, _resource_property(_field))(以及 371 行处 _task_tools_property 的同类循环)动态生成 _mcp_dirty、_mcp_lock、_mcp_tool_names、_skills_catalog_by_name、_skill_tools_registered、_skill_registered_names 等属性。这些属性的真实定义只存在于 dict 字面量里,service_parts/mcp_lifecycle.py、service_parts/skills.py 中大量 self._mcp_dirty = True / self._skills_catalog_by_name = ... 的读写无法通过跳转或 grep 找到其 getter/setter,读者必须知道『属性被运行时注入』这一隐式约定才能理解写入会经过 ContextVar 快照路由。另外 131 行的 config property getter 在属性读取时执行 boot check 并可能 fence_configuration_runtime() 抛异常——读一个属性触发运行时围栏是隐藏副作用。这违反了可发现性与『状态变更应在所属 API 中可见』的结构原则。
改为显式声明:为每个资源属性写出手写的 @property/setter(委托给一个 _resource(field) 小助手),或至少把三组属性名定义为模块级常量表并在类体中逐个赋值 property,让 IDE/跳转能从 mcp_lifecycle.py 的写入定位到定义;boot check 从 config getter 中移出,改为显式方法(如 ensure_boot_valid())在进入 lease 前调用。
| from quickquip.adapters.nonebot import daily_summary_plugin as summary | ||
|
|
||
| groups = { | ||
| "daily_briefing": ([f"daily_briefing_{p}" for p in ("morning", "noon", "evening")], |
There was a problem hiding this comment.
SHOULD-FIX: replace_report_schedules 把两份插件的调度 job 身份复制到了适配层:[f"daily_briefing_{p}" for p in ("morning","noon","evening")]、["daily_summary_generate", "daily_summary_publish"] 等 job ID 规则本应由 daily_briefing_plugin / daily_summary_plugin 自己拥有,同时直接调用它们的私有函数 briefing._register_scheduler_jobs、summary._register_scheduler_jobs、summary._register_period_jobs。job ID 命名规则一旦在任一插件里改动,config_control 必须同步修改否则保存/回滚会漏掉或误删 job——这是典型的跨模块锁步变更与对兄弟模块内部实现的 reach-through。
让每个插件导出公开契约,例如 daily_briefing_plugin.REPORT_JOB_IDS / def register_report_jobs() 与 daily_summary_plugin.register_period_jobs(period_types=...),config_control 只组合这些公开的『标识 + 注册函数』,不再拼接字符串、不再导入下划线函数。
| except Exception: | ||
| # Error contents may originate from expanded configuration; log type-free context. | ||
| logger.error("Configuration control iteration failed") | ||
| if runtime._configuration_fenced: |
There was a problem hiding this comment.
SHOULD-FIX: 轮询循环里 if runtime._configuration_fenced: 直接读取 LLM 服务(runtime_snapshot.py 中 _init_configuration_leases 初始化)的私有状态。适配层依赖另一域的私有字段名,重命名或把围栏状态收进封装后此处静默失效(循环不再退出),违反模块边界的窄接口原则。
在服务/RuntimeSnapshotMixin 上提供公开只读属性(如 configuration_fenced),config_control 改用该属性;或改为监听 fence_configuration_runtime() 触发的公开事件/回调。
| import { PARAM_LABELS, PARAM_SOURCE_LABELS, PROTOCOL_LABELS, fieldTemplate, isObject, providerEnvVars, type JsonObject, type JsonValue } from '../../lib/llmConfig' | ||
|
|
||
| const store = injectLlmConfigStore() | ||
| const L = { id: { hint: '全局唯一,创建后不可修改。只允许字母、数字、_ . -' }, base_url: { hint: '禁止在 URL 里直接写账号密码。', placeholder: 'https://api.example.com/v1' }, api_key_env: { hint: '只填写根目录 .env 里的变量名,不是密钥本身。', placeholder: 'EXAMPLE_API_KEY' }, default_model: { hint: '未指定模型时的兜底选择,必须在模型列表中。' }, proxy: { placeholder: 'http://127.0.0.1:7890' }, fallback_urls: { hint: '主地址失败时依次尝试。' }, headers: { hint: '涉及密钥的值必须写成 ${变量名} 引用。' } } |
There was a problem hiding this comment.
SHOULD-FIX: 本地 const L = { id: { hint: '全局唯一,创建后不可修改。只允许字母、数字、_ . -' }, base_url: { hint: '禁止在 URL 里直接写账号密码。', ... }, api_key_env: ..., default_model: ..., proxy: ..., fallback_urls: ..., headers: ... } 与本文件已 import 的 frontend/src/lib/llmConfig.ts 中 PROVIDER_FIELD_LABELS 的 hint/placeholder 字符串逐字重复(模板里 label="渠道 ID" 等也重复了其中的 label)。同一字段提示存在两份拷贝,改文案时必然漂移,违反『复用既有领域常量而非维护平行数组』。
删除本地 L,统一从 PROVIDER_FIELD_LABELS(必要时补 placeholder)取 hint/placeholder/label,模板改为 :label="PROVIDER_FIELD_LABELS.id?.label" 或封装一个取值函数。
| def validate_group_overrides(self, document, store): | ||
| if not store.path.exists(): | ||
| return | ||
| with store._connect() as conn: |
There was a problem hiding this comment.
NIT: validate_group_overrides 用 with store._connect() as conn: 直接进入 LLMStore 的私有连接方法读取 group_settings 表。虽然仓库 web routes 中已有类似先例,但在配置服务域中读取另一域 store 的私有实现,store 内部改连接管理时此处无编译期保护。
在 LLMStore(store_parts/group_settings.py)上新增公开只读方法,如 list_group_provider_references() -> list[tuple],ConfigService 只依赖该契约。
| check_value(base["fallback_urls"], list[str], path + ".fallback_urls", "fallback_urls") | ||
| _text(base["proxy"], path + ".proxy") | ||
| parameters(base["parameters"], path + ".parameters") | ||
| validate_body_binding_overrides( |
There was a problem hiding this comment.
NIT: validate_document 内对 parameters.extra_body 的三段式校验链(validate_body_binding_overrides → isinstance(body_policy, ClaudeBodyPolicy) → GeminiBodyPolicy → 兜底 validate_body_overrides)在渠道级(340-353 行)与模型级(371-384 行)原样重复两遍;新增一种 body policy 时需要同步改两处。
抽取模块内私有函数 _validate_extra_body(profile, protocol, params, path)(内部完成 binding + 按 policy 分派),两处循环各调用一次。
| from pathlib import Path | ||
| import json | ||
|
|
||
| from quickquip.app.web.llm_configuration_schema import configuration_openapi |
There was a problem hiding this comment.
NIT: 新增的生成物 docs/dev/llm-configuration.openapi.json(5695 行)由本脚本导出,脚本位于 scripts/ci/ 且文档要求『更新约束或路由后运行并同时提交』,但 _tests.yml 只挂了 sync_self_docs_references.py --check,没有对 OpenAPI 生成物做『导出 + git diff --exit-code』的漂移检查——路由/Schema 变更后契约文件会静默过期,CI 目录命名也暗示了它本应受检。
在 _tests.yml 的 Lint/文档检查步骤后增加 python scripts/ci/export_llm_configuration_openapi.py && git diff --exit-code docs/dev/llm-configuration.openapi.json,与 self-docs 检查并列。
| policy = self.retry_policy | ||
| attempt = 0 | ||
| while True: | ||
| try: | ||
| return await dispatch(request) | ||
| from quickquip.llm.client_fingerprints.retry_context import retry_context |
There was a problem hiding this comment.
NIT: 本次改动给 BaseProviderClient 新增了约 10 处函数内 import(_assemble_headers/_project_body/_prepare_http_attempt/complete 等),其中 retry_context 的 import 被放在 _dispatch_with_retry 的 while True 循环体内,每次重试迭代都重复执行 import 语句。改动前该文件的 quickquip 导入全部在模块顶层,且 client_fingerprints 模块对 provider.base 只有 TYPE_CHECKING 引用,看不出必须延迟导入的循环依赖;若确有循环依赖,应在导入处加一行注释说明原因。
把 retry_context、assembly 等导入提升到模块顶层(与既有 8 处顶层 quickquip 导入并列);若因循环依赖无法提升,将导入移出 while 循环并注明构成环的具体模块。
Add the public documentation standard and align guides, templates, and user-facing explanations. Complete beginner tutorial paths and synchronize self-docs resources and search coverage. Finalize release notes with the approved breaking-upgrade Note, rollback guidance, and release date.
本次发布将 LLM 配置迁入数据库,完善管理工作台、稳定会话身份、四协议客户端 Profile 和 HTTP Trace 关联,为 1.16.5 的 Minecraft 致敬版本奠基。CHANGELOG 开头的升级 Note 给出 1.16.1 → 1.16.2 的停服备份、配置迁移、检查与回退步骤。
发布候选:
385ee54cb474b9d03194c6740141e66ae0080fcf;版本1.16.2,发行日期2026-10-14。维护者已批准该候选在 CI 通过后合并,并授权在合并提交上创建v1.16.2、执行发行和回灌。KHPilot 按维护者此前授权跳过。发布专项 CR 复用已合并实现 PR 的审查证据,重点核对配置迁移/应用/回退、Session/纪元/群绑定/Profile 身份、启动与打包入口。独立专项回归 238 项通过,Windows 懒人包补齐发行元数据安装及版本 smoke,已发现问题均已收口。
公开文档增加统一写作规范,修订配置、运维、开发与用户指南,同步 self-docs。教程专项补齐首次安装与启用、正则练习、Skill 和 MCP 的操作路径,并实际运行隔离示例验证。
验证结果:
真实渠道与 QQ 端到端补验按系列计划安排到 1.16.5。Windows GUI/SmartScreen 与生产部署属于后续实际环境验收。
Refs #159
Refs #246
Refs #289
Refs #291
发行执行结果:PR #301 与 Windows 修复 PR #302/#303 均已合并。维护者明确授权重建失败发行的 v1.16.2 Tag 和镜像;最终 Tag 指向
710f09e022afd2ff41eaef5e607eab37d4e4950c。最终 main CI 和 发行流水线 38547851141全部通过,包含 Windows ZIP 与 Docker 的构建及 smoke。GitHub Release v1.16.2 已发布,包含完整迁移 Note。镜像四个版本标签的 digest 一致。发布收尾:Windows ZIP 的 SHA-256、1.16.2 安装元数据和 18 个关键源文件验收通过;main 已快进回灌 dev,开发目标
1.16.3-dev.0,dev CI 38548848585四项全部通过。