fd3e2992b1
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.
60 lines
1.7 KiB
Go
60 lines
1.7 KiB
Go
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)
|
||
}
|
||
}
|