{"record":{"id":"66f91e8c81f32330","repo":"siyuan-note/siyuan","slug":"ai-editor-request-timeout","errorCode":null,"errorMessage":"AI editor request timeout","messagePattern":"AI editor request timeout","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/ai.go","lineNumber":193,"sourceCode":"\t}\n\n\tmessages := buildAIEditorMessages(prompt, history, editing.MaxHistoryMessages)\n\n\treq := openai.ChatCompletionRequest{\n\t\tModel:               m.Name,\n\t\tMaxCompletionTokens: editing.MaxCompletionTokens,\n\t\tTemperature:         float32(editing.Temperature),\n\t\tMessages:            messages,\n\t\tStream:              true,\n\t}\n\tstreamCtx, cancel := context.WithCancel(ctx)\n\trequestTimeout := time.Duration(prov.RequestTimeout) * time.Second\n\trequestTimer, requestTimerDone := startAIEditorCancelTimer(requestTimeout, cancel)\n\tclient := util.NewOpenAIClientWithModel(prov.APIKey, prov.BaseURL, m.Name)\n\tcompletionStream, err := util.CreateOpenAICompletionStream(streamCtx, client, prov.Protocol, req, nil)\n\trequestTimedOut := stopAIEditorCancelTimer(requestTimer, requestTimerDone)\n\tif requestTimedOut {\n\t\terr = errors.New(\"AI editor request timeout\")\n\t}\n\tif nil != err {\n\t\tcancel()\n\t\treturn nil, err\n\t}\n\tif nil == completionStream {\n\t\tcancel()\n\t\treturn nil, errors.New(\"AI editor model returned nil stream\")\n\t}\n\treturn &AIEditorChatStream{\n\t\tstream:      completionStream,\n\t\tcancel:      cancel,\n\t\tidleTimeout: 120 * time.Second,\n\t}, nil\n}\n\nfunc startAIEditorCancelTimer(timeout time.Duration, cancel context.CancelFunc) (*time.Timer, <-chan struct{}) {\n\tif 0 >= timeout {","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/ai.go#L175-L211","documentation":"NewAIEditorChatStream (kernel/model/ai.go:193) arms a timer of prov.RequestTimeout seconds around util.CreateOpenAICompletionStream, which must dial the provider, authenticate, and receive the streaming response headers. If that whole setup exceeds the configured RequestTimeout, the timer cancels the context and the error is replaced with this message. It bounds how long the kernel waits for a stream to be established.","triggerScenarios":"Provider endpoint unreachable (wrong BaseURL, DNS failure, TLS hang) so connection setup never completes; or a reachable but slow provider (cold-start self-hosted model, queued service) that takes longer than RequestTimeout seconds to return the first SSE response.","commonSituations":"Small RequestTimeout (e.g. 30 s) with a distant or overloaded self-hosted LLM; wrong or unreachable BaseURL in provider settings; proxies/firewalls that stall outbound HTTPS; first-request cold start of a serverless model endpoint.","solutions":["Verify the provider endpoint responds quickly outside SiYuan (curl a minimal streaming request with the same key and URL)","Increase the provider's RequestTimeout in 设置 - 人工智能 (e.g. 120 s or more for slow self-hosted models)","Fix the BaseURL (scheme, port, path) and check proxy/firewall rules for the kernel process","If the provider cold-starts, send a warm-up request before long editing sessions, or switch to a lower-latency endpoint"],"exampleFix":"// before: provider config {\"requestTimeout\": 30} -> \"AI editor request timeout\"\n// after: provider config {\"requestTimeout\": 180}, or fix baseUrl from \"https://llm.local/v3\" to \"https://llm.local/v1\"","handlingStrategy":"retry","validationCode":"// smoke-test the endpoint before a long editing session\ncurl -N -m 10 -H \"Authorization: Bearer $KEY\" \"$BASE_URL/chat/completions\" \\\n  -d '{\"model\":\"...\",\"stream\":true,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}'\n// if headers do not arrive within RequestTimeout, raise the timeout or fix the URL","typeGuard":null,"tryCatchPattern":"if err != nil && err.Error() == \"AI editor request timeout\" {\n    if attempt < maxAttempts {\n        attempt++\n        continue // transient slow start; back off before retrying\n    }\n    suggestUserIncreaseRequestTimeout(prov.Name)\n    return err\n}","preventionTips":["Set RequestTimeout generously for self-hosted or cold-starting providers (120 s+)","Validate BaseURL with a quick curl streaming probe when configuring a provider","Keep the kernel's outbound path proxy-clean; SSE endpoints must not buffer"],"tags":["ai","timeout","network","configuration","openai"],"backgroundTag":"request-timeout","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}