module
LLM::HttpTransport
Overview
Shared HTTP transport for the provider clients (OpenAI-compatible and Ollama).
Both clients used to post through the bare one-shot helpers
(HTTP::Client.post / Crest.post), which set no timeouts at all: a
provider that accepts the connection and then never answers — a wedged
local ollama serve, a proxy that black-holes the request, a hosted
endpoint that stalls mid-generation — hung the scan forever, with no
output and no indication of what it was waiting on. Requests now fail
after a bounded wait.
It also retries the transient failures every hosted provider produces (429 rate limits, 502/503 from a load balancer, a dropped socket). Without that, one blip turned into a silent zero-endpoint AI result: the clients map any failure to "", which the analyzer treats as "this code defines no endpoints".
Defined in:
llm/http_transport.crConstant Summary
-
CONNECT_TIMEOUT_ENV =
"NOIR_AI_CONNECT_TIMEOUT" -
DEFAULT_CONNECT_TIMEOUT =
10.seconds -
Connecting is fast even for remote providers, so a short budget here only shortens the "server isn't running" feedback loop that local provider users (ollama, vLLM, LM Studio) hit most often.
-
DEFAULT_TIMEOUT =
300.seconds -
Generation is not fast: a large bundle against a CPU-bound local model legitimately takes minutes, so the read budget is generous and tunable rather than tight.
-
MAX_ATTEMPTS =
3 -
MAX_ERROR_SNIPPET_SIZE =
1024 -
MAX_RETRY_AFTER =
30.seconds -
Cap on how long a provider's
Retry-Aftermay park the scan. -
RETRYABLE_STATUS =
Set {408, 425, 429, 500, 502, 503, 504, 529} -
Status codes worth another attempt: rate limits plus the transient gateway/overload family. A 400/401/404 is a configuration problem that retrying can only make slower.
-
TIMEOUT_ENV =
"NOIR_AI_TIMEOUT"
Class Method Summary
-
.backoff(attempt : Int32) : Time::Span
1s, then 2s — bounded, and short enough that exhausting all attempts still returns while the caller is waiting.
- .connect_timeout : Time::Span
-
.duration_from_env(name : String) : Time::Span | Nil
Reads a timeout override, in seconds.
-
.post_json(url : String, body : String, headers : HTTP::Headers) : String | Nil
POSTs a JSON body and returns the response body, or nil when the request could not be completed.
-
.retry_after(response : HTTP::Client::Response | Nil) : Time::Span | Nil
A provider that tells us when to come back (429s usually do) knows better than the fixed backoff, as long as it stays within our cap.
- .retry_delay(response : HTTP::Client::Response | Nil, attempt : Int32) : Time::Span
- .retryable_status?(code : Int32) : Bool
- .timeout : Time::Span
- .truncate_error_snippet(body : String) : String
Class Method Detail
1s, then 2s — bounded, and short enough that exhausting all attempts still returns while the caller is waiting.
Reads a timeout override, in seconds. Anything that isn't a positive
number (empty, 0, a word) falls back to the default instead of
disabling the timeout — the point of the override is to move the
bound, not to remove it.
POSTs a JSON body and returns the response body, or nil when the request could not be completed. Failures are reported here so every provider path surfaces them the same way instead of each client inventing its own (or, in Ollama's case, staying silent).
A provider that tells us when to come back (429s usually do) knows better than the fixed backoff, as long as it stays within our cap.