siyuan-note/siyuan · error
provider base URL is required
Error message
provider base URL is required
What it means
resolveAIProvider (kernel/api/ai.go:41-60) resolves the AI provider for a request either from saved config (by provider id) or from an inline providerConfig object. When an inline providerConfig is supplied, its baseURL must be a non-empty string after trimming; otherwise the request is rejected before any network call. This is deliberate: conf.AI.Normalize() would silently default an empty BaseURL to https://api.openai.com/v1 (conf/ai.go:593-596), so the API layer refuses blank URLs rather than let a misconfigured request hit OpenAI by accident.
Source
Thrown at kernel/api/ai.go:52
TaskID string `json:"taskID"`
IDs []string `json:"ids"`
Input string `json:"input"`
Action string `json:"action"`
History []model.AIEditorMessage `json:"history"`
}
func resolveAIProvider(arg map[string]any) (*conf.Provider, error) {
if providerConfig, ok := arg["providerConfig"]; ok && providerConfig != nil {
data, err := gulu.JSON.MarshalJSON(providerConfig)
if err != nil {
return nil, err
}
provider := &conf.Provider{}
if err = gulu.JSON.UnmarshalJSON(data, provider); err != nil {
return nil, err
}
if strings.TrimSpace(provider.BaseURL) == "" {
return nil, errors.New("provider base URL is required")
}
ai := &conf.AI{Providers: []*conf.Provider{provider}}
ai.Normalize()
if len(ai.Providers) != 1 {
return nil, errors.New("invalid provider config")
}
return ai.Providers[0], nil
}
providerID, _ := arg["provider"].(string)
for _, provider := range model.Conf.AI.Providers {
if provider != nil && provider.ID == providerID {
return provider, nil
}
}
return nil, errors.New("provider not found")
}
View on GitHub (pinned to afa823b6b4)
Solutions
- Set baseURL explicitly, including scheme and version path, e.g. https://api.openai.com/v1 or http://127.0.0.1:11434/v1 for local gateways
- Check the exact JSON key: the Provider struct binds baseURL (case-insensitive JSON match, but 'url' will not bind)
- If you want to use an already-configured provider, send its provider id instead of an inline providerConfig object
- Trim-test the value client-side before posting
Example fix
// before
{"providerConfig": {"name": "local-llm", "apiKey": "sk-x"}}
// after
{"providerConfig": {"name": "local-llm", "baseURL": "http://127.0.0.1:11434/v1", "apiKey": "sk-x"}} Defensive patterns
Strategy: validation
Validate before calling
const okProvider = (p: any) =>
!!p && typeof p.baseURL === 'string' && p.baseURL.trim().length > 0;
if (!okProvider(req.providerConfig)) throw new Error('provider base URL is required'); Type guard
const hasBaseURL = (p: unknown): p is {baseURL: string} =>
!!p && typeof (p as any).baseURL === 'string' && (p as any).baseURL.trim() !== ''; Try / catch
null
Prevention
- Always send baseURL with scheme and version path (…/v1) for inline provider configs
- Prefer referencing a saved provider by id instead of inline providerConfig when reusing the configured one
- Validate the exact JSON key names against the Provider struct (baseURL, apiKey, models)
When it happens
Trigger: Any AI API taking a providerConfig object (e.g. /api/ai/ chat endpoints and agent provider selection) with baseURL omitted, empty, or whitespace-only — for example a custom provider meant for a local gateway (Ollama/vLLM) sent as {"name": "local", "apiKey": "..."} with no baseURL.
Common situations: Frontend forms adding a provider but not sending the URL field; JSON field-name mismatch (url vs baseUrl vs baseURL — the struct tag is baseURL); users assuming the saved default applies to inline configs; copy-pasted configs with the URL line deleted.
Related errors
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/5b1d6608569497d4.
Report an issue: GitHub.