Files
Enterprise-Knowledge-Base/internal/models/chat/config_from_model_test.go
T
wizardchen fd3e2992b1 feat(models): support custom HTTP headers across all remote model calls
Introduce per-model `custom_headers` config (similar to OpenAI Python SDK's
`extra_headers`) so users can inject gateway auth tokens, trace IDs, etc.
into every outbound API request for chat / embedding / rerank / VLLM / ASR
models. Reserved headers like Authorization / Content-Type are always
preserved to avoid breaking auth or signing flows.

Along the way, unify production and "test connection" paths onto a single
`ConfigFromModel(*types.Model, appID, appSecret)` constructor in each
model package. Both `service.modelService.GetXxxModel` and the four
`handler.initialization.Check*Model` / `TestEmbeddingModel` endpoints now
go through the same field mapping, so new parameters only need to be
added in one place.

Backend:
- Add `CustomHeaders map[string]string` to `types.ModelParameters` with
  JSON/YAML `omitempty` tags for forward compatibility.
- New `internal/utils/extraheaders.go` providing `ApplyCustomHeaders`
  (for hand-rolled HTTP paths) and `WrapHTTPClientWithHeaders` /
  `CustomHeadersRoundTripper` (for SDK paths like go-openai). Reserved
  headers (Authorization, api-key, Content-Type, Accept, Host,
  Content-Length, User-Agent) are filtered out.
- Inject headers in every remote model path: chat (SDK + raw HTTP),
  embedding (openai/aliyun/jina/volcengine/nvidia/azure_openai), rerank
  (remote_api/aliyun/jina/nvidia/zhipu — via shared `customHeaderSetter`
  interface), VLM and ASR (via http.Client wrapping).
- Add `ConfigFromModel` to each of chat/embedding/rerank/vlm/asr,
  consolidating field mapping (ExtraConfig, CustomHeaders, InterfaceType
  defaulting, WeKnoraCloud credentials, etc.) and cover it with
  dedicated unit tests per package.
- Refactor `service/model.go`: replace ~100 lines of hand-written Config
  literals with one-line `ConfigFromModel` calls; drop the now-unused
  `stringMapToAnyMap` helper.
- Refactor `handler/initialization.go` test-connection endpoints: merge
  four ad-hoc request structs into one `ModelTestRequest`, extract
  `buildTestModel` and `resolveTenantWeKnoraCloudCreds` helpers, and
  route all four endpoints through `ConfigFromModel` + `NewXxx` so the
  test path is now behaviorally identical to production.

Frontend:
- Add `custom_headers` to the `ModelConfig` API type.
- `ModelEditorDialog.vue`: new Key-Value header editor (add/remove rows),
  auto-converts map <-> array when loading/saving, and passes the
  current header set to the `Test Connection` button so the preview
  reflects exactly what production will send.
- `ModelSettings.vue`: serialize the header array back into a map for
  the backend (drops empty rows).
- i18n: add custom-header labels / descriptions / placeholders to
  zh-CN, en-US, ru-RU, ko-KR.

Tests:
- `internal/utils/extraheaders_test.go` covers reserved-header filtering
  and round-tripper wrapping (including nil-client fallback).
- New `config_from_model_test.go` in chat / embedding / rerank / vlm /
  asr verifies all fields (CustomHeaders, ExtraConfig, InterfaceType
  defaulting, AppID/AppSecret) are propagated end-to-end.
2026-04-23 23:36:40 +08:00

60 lines
1.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package chat
import (
"testing"
"github.com/Tencent/WeKnora/internal/types"
)
// TestConfigFromModel 验证 ConfigFromModel 能把 types.Model 完整映射到 ChatConfig,
// 避免后续新加字段时只在生产路径或测试路径其中一侧更新导致功能漏配。
func TestConfigFromModel(t *testing.T) {
m := &types.Model{
ID: "model-123",
Name: "gpt-4o-mini",
Source: types.ModelSourceRemote,
Parameters: types.ModelParameters{
BaseURL: "https://api.example.com/v1",
APIKey: "sk-xxx",
Provider: "openai",
ExtraConfig: map[string]string{
"deployment": "prod",
},
CustomHeaders: map[string]string{
"X-Gateway-Token": "abc",
"X-Trace-ID": "t-1",
},
},
}
cfg := ConfigFromModel(m, "app-id", "app-secret")
if cfg == nil {
t.Fatal("ConfigFromModel returned nil")
}
if cfg.ModelID != "model-123" || cfg.ModelName != "gpt-4o-mini" {
t.Errorf("model identity not propagated: %+v", cfg)
}
if cfg.BaseURL != "https://api.example.com/v1" || cfg.APIKey != "sk-xxx" {
t.Errorf("connection fields not propagated: %+v", cfg)
}
if cfg.Provider != "openai" {
t.Errorf("provider not propagated: %q", cfg.Provider)
}
if cfg.ExtraConfig["deployment"] != "prod" {
t.Errorf("ExtraConfig not propagated: %+v", cfg.ExtraConfig)
}
if cfg.CustomHeaders["X-Gateway-Token"] != "abc" ||
cfg.CustomHeaders["X-Trace-ID"] != "t-1" {
t.Errorf("CustomHeaders not propagated: %+v", cfg.CustomHeaders)
}
if cfg.AppID != "app-id" || cfg.AppSecret != "app-secret" {
t.Errorf("cloud credentials not propagated: %+v", cfg)
}
}
func TestConfigFromModel_Nil(t *testing.T) {
if got := ConfigFromModel(nil, "", ""); got != nil {
t.Fatalf("expected nil for nil model, got %+v", got)
}
}