llmclient

package
v0.1.91 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package llmclient provides a base HTTP client for LLM providers with: - Request marshaling/unmarshaling - Retries with exponential backoff and jitter - Standardized error parsing, including errors embedded in 200-status bodies - Circuit breaking with half-open state protection

Index

Constants

View Source
const (
	OperationChat            = "chat"
	OperationGenerateContent = "generate_content"
	OperationTextCompletion  = "text_completion"
	OperationEmbeddings      = "embeddings"
)

Well-known GenAI operation names carried through observability hooks. Provider adapters select these explicitly so instrumentation never has to infer semantics from provider-specific URL shapes.

View Source
const UnknownModel = "unknown"

extractModel attempts to extract the model name from a request body UnknownModel is the model label reported for requests whose model cannot be recovered from the request body (body-less discovery GETs, availability probes, multipart uploads). Hook consumers that attribute traffic per model should treat it as "not model-attributed".

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a base HTTP client for LLM providers

func New

func New(cfg Config, headerSetter HeaderSetter) *Client

New creates a new LLM client with the given configuration

func NewWithHTTPClient

func NewWithHTTPClient(httpClient *http.Client, cfg Config, headerSetter HeaderSetter) *Client

NewWithHTTPClient creates a new LLM client with a custom HTTP client

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL returns the current base URL (thread-safe)

func (*Client) Do

func (c *Client) Do(ctx context.Context, req Request, result any) error

Do executes a request with retries and circuit breaking, then unmarshals the response

func (*Client) DoPassthrough

func (c *Client) DoPassthrough(ctx context.Context, req Request) (*http.Response, error)

DoPassthrough executes a request and returns the raw upstream HTTP response. Unlike DoRaw, it preserves non-200 responses for the caller to proxy unchanged.

func (*Client) DoRaw

func (c *Client) DoRaw(ctx context.Context, req Request) (*Response, error)

DoRaw executes a request with retries and circuit breaking, returning the raw response.

Metrics Behavior

Metrics hooks (OnRequestStart/OnRequestEnd) are called at this level to track logical requests from the caller's perspective, not individual retry attempts. This ensures:

  • Request counts reflect user-facing requests, not internal HTTP calls
  • Duration metrics include total time across all retries (useful for SLOs)
  • In-flight gauge accurately reflects concurrent logical requests

Behavior comparison (hooks at DoRaw vs per-attempt):

| Scenario                             | Per-attempt (old)           | DoRaw level (current)            |
|--------------------------------------|-----------------------------|----------------------------------|
| 1 request, succeeds first try        | 1 observation               | 1 observation                    |
| 1 request, fails twice then succeeds | 3 observations              | 1 observation (success)          |
| 1 request, fails all 3 retries       | 3 observations              | 1 observation (error)            |
| Duration metric                      | Each attempt's duration     | Total duration including retries |
| In-flight gauge                      | Bounces up/down per attempt | Accurate concurrent count        |

The final status code and error in metrics reflect the outcome after all retry attempts.

func (*Client) DoStream

func (c *Client) DoStream(ctx context.Context, req Request) (io.ReadCloser, error)

DoStream executes a streaming request, returning a ReadCloser Note: Streaming requests do NOT retry (as partial data may have been sent) Metrics note: Duration is measured from start to stream establishment, not stream close

func (*Client) SetBaseURL

func (c *Client) SetBaseURL(url string)

SetBaseURL updates the base URL (thread-safe)

type Config

type Config struct {
	// ProviderName is the identifier used in logs and metrics (e.g., "openai", "anthropic").
	ProviderName string
	// BaseURL is the base URL for the provider's API (e.g., "https://api.openai.com/v1").
	BaseURL string
	// Retry specifies retry behaviour for failed requests, including backoff and jitter settings.
	Retry config.RetryConfig
	// CircuitBreaker configures the circuit breaker that prevents cascading failures by
	// stopping requests to an unhealthy provider until it recovers.
	CircuitBreaker config.CircuitBreakerConfig
	// Hooks provides optional observability callbacks invoked on request start and end.
	Hooks Hooks
}

Config holds configuration for the LLM client

func DefaultConfig

func DefaultConfig(providerName, baseURL string) Config

DefaultConfig returns default client configuration

type HeaderSetter

type HeaderSetter func(req *http.Request)

HeaderSetter is a function that sets headers on an HTTP request

type Hooks

type Hooks struct {
	// OnRequestStart is called before a request is sent.
	// The returned context can be used to propagate trace spans or request IDs.
	OnRequestStart func(ctx context.Context, info RequestInfo) context.Context

	// OnRequestEnd is called after a request completes (success or failure).
	// For streaming requests, this is called when the stream starts, not when it closes.
	OnRequestEnd func(ctx context.Context, info ResponseInfo)

	// OnStreamFirstChunk is called once when a successful streaming response
	// body first returns bytes. It is not called for empty or unread streams.
	OnStreamFirstChunk func(ctx context.Context, info ResponseInfo)

	// OnStreamEmpty is called once when a successful streaming response body
	// ends (EOF or read error) before returning any bytes: the stream was
	// established but never delivered. OnStreamFirstChunk is not called for
	// it. Error carries the read error, io.EOF for a clean empty stream.
	OnStreamEmpty func(ctx context.Context, info ResponseInfo)
}

Hooks defines observability callbacks for request lifecycle events. These hooks enable instrumentation without polluting business logic.

func JoinHooks

func JoinHooks(hooks ...Hooks) Hooks

JoinHooks composes several hook sets into one. OnRequestStart callbacks run in order, threading the context through; OnRequestEnd callbacks run in order. Hook sets with nil callbacks are skipped.

type Request

type Request struct {
	Method   string
	Endpoint string
	Model    string
	// Operation explicitly identifies model inference semantics for
	// observability. Leave empty for control-plane and other non-inference calls.
	Operation       string
	Stream          bool   // explicit stream intent; Accept: text/event-stream remains a fallback
	StreamUncertain bool   // bounded opaque-body inspection could not determine stream intent
	Body            any    // Will be JSON marshaled if not nil
	RawBody         []byte // Used as-is (e.g., multipart form bodies). Mutually exclusive with Body and RawBodyReader.
	// RawBodyReader streams the request body without buffering it in memory.
	// It is intended for one-shot passthrough requests and is not replayable for retries.
	RawBodyReader io.Reader
	Headers       http.Header
}

Request represents an HTTP request to be made

type RequestInfo

type RequestInfo struct {
	Provider     string // Configured provider name
	ProviderType string // Provider implementation type (e.g., "openai", "anthropic")
	Model        string // Model name (e.g., "gpt-4", "claude-3-opus")
	Operation    string // Semantic GenAI operation; empty for non-inference calls
	Endpoint     string // API endpoint (e.g., "/chat/completions", "/models")
	Method       string // HTTP method (e.g., "POST", "GET")
	Stream       bool   // Whether this is a streaming request
	// StreamUncertain means a bounded opaque-body inspection could not
	// determine intent before the upstream call began.
	StreamUncertain bool
}

RequestInfo contains metadata about a request for observability hooks

type Response

type Response struct {
	StatusCode int
	// ContentType is the upstream response Content-Type header, preserved so
	// callers can describe the bytes actually returned (e.g. audio formats).
	ContentType string
	// Header carries the upstream response headers. It is used to audit failed
	// provider attempts; it is not relayed to API clients.
	Header http.Header
	Body   []byte
}

Response represents an HTTP response

type ResponseInfo

type ResponseInfo struct {
	Provider        string        // Configured provider name
	ProviderType    string        // Provider implementation type
	Model           string        // Model name
	Operation       string        // Semantic GenAI operation
	Endpoint        string        // API endpoint
	Method          string        // HTTP method
	StatusCode      int           // HTTP status code (0 if network error)
	Duration        time.Duration // Request duration
	Stream          bool          // Whether this was a streaming request
	StreamUncertain bool          // Whether request stream intent was unknown at dispatch
	Error           error         // Error if request failed (nil on success)
	// CircuitState is the selected provider or model breaker state after this request
	// completed ("closed", "half-open", "open"); empty when the breaker is
	// disabled. It reflects the moment of completion, so metrics built from it
	// update as traffic flows.
	CircuitState string
}

ResponseInfo contains metadata about a response for observability hooks

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL