mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-19 16:39:15 +00:00
feat(secrets): encrypt sensitive store fields (AES-GCM, master.key 0600, api_key_env) + fast models-endpoint probing (fix zen backlog + source status) + UI cleanup (drop redundant parens labels, grid models 4/row)
This commit is contained in:
@ -180,9 +180,54 @@ func (p *Provider) Available() bool {
|
||||
return p.health.available()
|
||||
}
|
||||
|
||||
// Probe performs a lightweight reachability + auth check against the source
|
||||
// using its best chat model (1-token). It records the result for Status().
|
||||
// Probe performs a lightweight reachability + auth check against the source.
|
||||
// It first tries GET <base>/models (fast, ~1s for OpenAI-compatible upstreams)
|
||||
// and only falls back to a 1-token chat call when that endpoint is
|
||||
// unavailable. It does NOT touch the health/backoff state so probing never
|
||||
// disables a source.
|
||||
func (p *Provider) Probe(ctx context.Context) (bool, string) {
|
||||
ok, msg := p.probeModels(ctx)
|
||||
if !ok && msg == "" {
|
||||
ok, msg = p.probeChat(ctx)
|
||||
}
|
||||
p.mu.Lock()
|
||||
p.lastProbe.ok = ok
|
||||
p.lastProbe.err = msg
|
||||
p.lastProbe.at = time.Now().Unix()
|
||||
p.mu.Unlock()
|
||||
return ok, msg
|
||||
}
|
||||
|
||||
// probeModels GETs <base>/models. Returns (true,…) when reachable, (false,
|
||||
// errortext) on an auth/permanent failure, and (false,"") when the endpoint
|
||||
// simply isn't available so the caller can fall back to a chat probe.
|
||||
func (p *Provider) probeModels(ctx context.Context) (bool, string) {
|
||||
u := strings.TrimRight(p.cfg.BaseURL, "/") + "/models"
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
if hdrs, herr := p.buildHeaders("{}", u); herr == nil {
|
||||
req.Header = hdrs
|
||||
}
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
switch {
|
||||
case resp.StatusCode == 200:
|
||||
return true, ""
|
||||
case resp.StatusCode == 404 || resp.StatusCode == 405:
|
||||
return false, ""
|
||||
default:
|
||||
return false, fmt.Sprintf("api error %d: %s", resp.StatusCode, truncate(string(raw), 300))
|
||||
}
|
||||
}
|
||||
|
||||
// probeChat sends a minimal single-token chat request to the chat endpoint.
|
||||
func (p *Provider) probeChat(ctx context.Context) (bool, string) {
|
||||
ok := false
|
||||
msg := ""
|
||||
model := p.bestChatModel()
|
||||
@ -194,25 +239,32 @@ func (p *Provider) Probe(ctx context.Context) (bool, string) {
|
||||
model = ms[0]
|
||||
}
|
||||
}
|
||||
if model != "" {
|
||||
_, err := p.Chat(ctx, &types.ChatRequest{
|
||||
Model: model,
|
||||
Messages: []types.ChatMessage{{Role: "user", Content: types.StringContent("hi")}},
|
||||
MaxTokens: 1,
|
||||
})
|
||||
if model == "" {
|
||||
msg = "no chat model configured"
|
||||
} else {
|
||||
probe := map[string]interface{}{
|
||||
"model": model,
|
||||
"messages": []map[string]interface{}{{"role": "user", "content": "hi"}},
|
||||
"max_tokens": 1,
|
||||
}
|
||||
body, err := json.Marshal(probe)
|
||||
if err == nil {
|
||||
ok = true
|
||||
var hdr http.Header
|
||||
if hdrs, herr := p.buildHeaders(string(body), p.URL()); herr == nil {
|
||||
hdr = hdrs
|
||||
}
|
||||
raw, status, derr := p.do(ctx, p.URL(), string(body), hdr)
|
||||
if derr != nil {
|
||||
msg = derr.Error()
|
||||
} else if status == 200 {
|
||||
ok = true
|
||||
} else {
|
||||
msg = fmt.Sprintf("api error %d: %s", status, truncate(raw, 500))
|
||||
}
|
||||
} else {
|
||||
msg = err.Error()
|
||||
}
|
||||
} else {
|
||||
msg = "no chat model configured"
|
||||
}
|
||||
p.mu.Lock()
|
||||
p.lastProbe.ok = ok
|
||||
p.lastProbe.err = msg
|
||||
p.lastProbe.at = time.Now().Unix()
|
||||
p.mu.Unlock()
|
||||
return ok, msg
|
||||
}
|
||||
|
||||
|
||||
@ -208,7 +208,7 @@ func (r *Registry) ProbeAll(ctx context.Context) {
|
||||
wg.Add(1)
|
||||
go func(p *Provider) {
|
||||
defer wg.Done()
|
||||
probeCtx, cancel := context.WithTimeout(ctx, 6*time.Second)
|
||||
probeCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
p.Probe(probeCtx)
|
||||
}(p)
|
||||
|
||||
Reference in New Issue
Block a user