feat(admin): 面板暴露 contextWindow(可见可改)+ DocumentRef 语义注释同步
- 面板读写面与 maxTokens 逐处对齐:读 DTO 增 contextWindow(omitempty)、写请求 ContextWindow *int(缺失=保留、显式 0=未声明同义、负数 400)、回写 models.json;负数红线仍由 runtimeconfig.Group.Validate 唯一裁决,网关守卫只做「更早失败 + 面板可读字段名」。前端:类型 + 表单字段(提交前 /^\d+$/ 校验)+ 分组行角标 + Notice + 目录候选用早已解析却没人用的 contextLimit 预填。文案写明「重启后生效」(推导只在进程启动时算一次)。 - 顺手修既有红线违反:ProviderFields 87→76(抽 ApiKeyAndBudgetField)、LlmProviderForm 113→74(buildRequest 提为模块级纯函数)——本次碰过的文件里已无 >80 的函数,行为/文案逐字未变。 - DocumentRef 的 chars/truncated 注释从「抽取侧」改为「本回合注入侧」(全文长度只在 document-read 的 totalChars、extractTruncated 专指撞 8 MiB 安全上限);机械校验为空 ⇒ 代码零改动。 - 主会话独立验收:隔离 worktree Go 侧 build/vet + gateway/history 两包绿;主树 npx tsc --noEmit exit 0、npm run build 成功并 copy 到 internal/webui/dist;注释机械校验为空;变异由子代理复现(写路径不认该键 ⇒ 3 红;去掉网关负数守卫 ⇒ 400 变 500)。
This commit is contained in:
1 parent
3a01b4e236
commit
47e56ca13c
11 files changed
+355
-68
No files matched your search
@@ -47,8 +47,8 @@ type llmReasoningRequest struct {
|
||||
// 「缺失」(保留已存值)与「显式零值」(覆盖):来自面板的局部
|
||||
// 编辑绝不清除它未触碰的字段。Name 是客户端侧
|
||||
// 显示标签(不持久化 —— models.json 结构含 id/provider/
|
||||
// baseUrl/apiKey/model/fallback/multimodal/maxTokens/reasoning/personaModels/
|
||||
// personaProviders)。
|
||||
// baseUrl/apiKey/model/fallback/multimodal/maxTokens/contextWindow/reasoning/
|
||||
// personaModels/personaProviders)。
|
||||
type llmProviderRequest struct {
|
||||
ID *string `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
@@ -61,6 +61,9 @@ type llmProviderRequest struct {
|
||||
// MaxTokens 是该分组自身的 token 预算。缺失则保留已存
|
||||
// 预算(新分组 0 = 未设置)。负值被拒绝。
|
||||
MaxTokens *int `json:"maxTokens"`
|
||||
// ContextWindow 是该分组声明的模型上下文窗口(token)。缺失则保留已存窗口
|
||||
// (新分组 0 = 未声明);显式 0 与缺失同义(都按回落窗口推导);负值被拒绝。
|
||||
ContextWindow *int `json:"contextWindow"`
|
||||
// Reasoning 缺失 = 保留已存形状;给出(含 `{}`)= 用其中的 kind 覆盖
|
||||
// (kind 缺失/空串 = 回到「未声明」,等同 auto)。
|
||||
Reasoning *llmReasoningRequest `json:"reasoning"`
|
||||
@@ -74,7 +77,7 @@ type llmProviderRequest struct {
|
||||
// adminAddLlmProvider 把 provider 分组保存到 models.json(read-modify-write,
|
||||
// 并将其提升为活动分组),再热重载运行中的 LLM 栈,使
|
||||
// 新的 provider/model 无需重启即生效(design/04 §3.1)。客户端省略的
|
||||
// 字段保持其已存值,因此局部编辑绝不复位预算或
|
||||
// 字段保持其已存值,因此局部编辑绝不复位预算、窗口或
|
||||
// key。API key 被持久化进被 gitignore 的文件,但绝不返回/记日志。
|
||||
func (s *Server) adminAddLlmProvider(w http.ResponseWriter, r *http.Request) {
|
||||
var req llmProviderRequest
|
||||
@@ -87,6 +90,14 @@ func (s *Server) adminAddLlmProvider(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, apperr.Wrap(apperr.ErrBadRequest, "bad_request", "maxTokens must be >= 0"))
|
||||
return
|
||||
}
|
||||
// 窗口没有负数语义(与 runtimeconfig.Group.Validate 同一口径:只拒绝负数,
|
||||
// 0 与缺失都是「未声明」)。这里提前报错是为了给出面板能直接显示的字段名,
|
||||
// 规则本身不在网关另立一套。
|
||||
if req.ContextWindow != nil && *req.ContextWindow < 0 {
|
||||
s.auditClaims(r, "llm.addProvider", "error")
|
||||
writeError(w, apperr.Wrap(apperr.ErrBadRequest, "bad_request", "contextWindow must be >= 0"))
|
||||
return
|
||||
}
|
||||
if s.llm == nil {
|
||||
s.auditClaims(r, "llm.addProvider", "error")
|
||||
writeError(w, apperr.Wrap(apperr.ErrInternal, "llm_unavailable", "LLM 配置服务未注入"))
|
||||
@@ -168,6 +179,9 @@ func mergeLlmProvider(groups []runtimeconfig.Group, req llmProviderRequest) (run
|
||||
if req.MaxTokens != nil {
|
||||
g.MaxTokens = *req.MaxTokens
|
||||
}
|
||||
if req.ContextWindow != nil {
|
||||
g.ContextWindow = *req.ContextWindow
|
||||
}
|
||||
if req.Reasoning != nil {
|
||||
g.Reasoning = runtimeconfig.ReasoningOptions{Kind: reasoningKindOf(req.Reasoning)}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user