ExLLM.Adapters.Shared.HTTPClient (ex_llm v0.5.0)

View Source

Shared HTTP client utilities for ExLLM adapters.

Provides common HTTP functionality including:

  • JSON API requests with proper headers
  • Server-Sent Events (SSE) streaming support
  • Standardized error handling
  • Response parsing utilities

This module abstracts common HTTP patterns to reduce duplication across provider adapters.

Summary

Functions

Add custom headers specified by the user.

Add idempotency headers for safe request retries.

Add rate limit headers to the request.

Build provider-specific headers for API requests.

Handle API error responses consistently.

Parse Server-Sent Events from a data buffer.

Make a POST request with JSON body to an API endpoint.

Make a multipart form POST request to upload files.

Make a streaming POST request using Req's into option.

Prepare headers with common defaults.

Make a streaming POST request that returns Server-Sent Events.

Build a standardized User-Agent header for ExLLM.

Functions

add_custom_headers(headers, opts)

@spec add_custom_headers(
  [{String.t(), String.t()}],
  keyword()
) :: [{String.t(), String.t()}]

Add custom headers specified by the user.

Allows users to add arbitrary headers for specific use cases.

add_idempotency_headers(headers, provider, opts \\ [])

@spec add_idempotency_headers([{String.t(), String.t()}], atom(), keyword()) :: [
  {String.t(), String.t()}
]

Add idempotency headers for safe request retries.

Some providers support idempotency keys to prevent duplicate operations.

add_rate_limit_headers(headers, provider, opts \\ [])

@spec add_rate_limit_headers([{String.t(), String.t()}], atom(), keyword()) :: [
  {String.t(), String.t()}
]

Add rate limit headers to the request.

Some providers support rate limit hints in request headers.

build_provider_headers(provider, opts \\ [])

@spec build_provider_headers(
  atom(),
  keyword()
) :: [{String.t(), String.t()}]

Build provider-specific headers for API requests.

Options

  • :provider - Provider name for specific headers
  • :api_key - API key for authorization
  • :version - API version header
  • :organization - Organization ID (OpenAI)
  • :project - Project ID (OpenAI)

Examples

HTTPClient.build_provider_headers(:openai, 
  api_key: "sk-...", 
  organization: "org-123"
)

handle_api_error(status, body)

@spec handle_api_error(integer(), String.t() | map()) :: {:error, term()}

Handle API error responses consistently.

Attempts to parse error details from JSON responses.

parse_sse_chunks(data, buffer)

@spec parse_sse_chunks(binary(), binary()) :: {[map()], binary()}

Parse Server-Sent Events from a data buffer.

Returns parsed events and remaining buffer.

Examples

{events, new_buffer} = HTTPClient.parse_sse_chunks("data: {"text":"Hi"}\n\n", "")

post_json(url, body, headers, opts \\ [])

@spec post_json(String.t(), map(), [{String.t(), String.t()}], keyword()) ::
  {:ok, map()} | {:error, term()}

Make a POST request with JSON body to an API endpoint.

Options

  • :timeout - Request timeout in milliseconds (default: 60s)
  • :recv_timeout - Receive timeout for streaming (default: 5m)
  • :stream - Whether this is a streaming request

Examples

HTTPClient.post_json("https://api.example.com/v1/chat", 
  %{messages: messages},
  [{"Authorization", "Bearer sk-..."}],
  timeout: 30_000
)

post_multipart(url, form_data, headers, opts \\ [])

@spec post_multipart(String.t(), keyword(), [{String.t(), String.t()}], keyword()) ::
  {:ok, map()} | {:error, term()}

Make a multipart form POST request to upload files.

Parameters

  • url - The endpoint URL
  • form_data - A keyword list of form fields, where file fields are {:file, path}
  • headers - Request headers
  • opts - Additional options

Examples

HTTPClient.post_multipart(
  "https://api.openai.com/v1/files",
  [
    purpose: "fine-tune",
    file: {:file, "/path/to/file.jsonl"}
  ],
  [{"Authorization", "Bearer sk-..."}]
)

post_stream(url, body, opts \\ [])

@spec post_stream(String.t(), map(), keyword()) :: {:ok, term()} | {:error, term()}

Make a streaming POST request using Req's into option.

This is used by StreamingCoordinator for unified streaming.

Examples

HTTPClient.post_stream(url, body, [
  headers: headers,
  into: stream_collector_fn
])

prepare_headers(headers)

@spec prepare_headers([{String.t(), String.t()}]) :: [{String.t(), String.t()}]

Prepare headers with common defaults.

Adds Content-Type and User-Agent if not present.

stream_request(url, body, headers, callback, opts \\ [])

@spec stream_request(
  String.t(),
  map(),
  [{String.t(), String.t()}],
  function(),
  keyword()
) ::
  {:ok, any()} | {:error, term()}

Make a streaming POST request that returns Server-Sent Events.

The callback function will be called for each chunk received.

Examples

HTTPClient.stream_request(url, body, headers, fn chunk ->
  # Process each SSE chunk
  IO.puts("Received: " <> chunk)
end)

user_agent()

@spec user_agent() :: {String.t(), String.t()}

Build a standardized User-Agent header for ExLLM.