Documentation
¶
Overview ¶
Package client is the model client of spike-ai-pipeline: Go primitives over the inference engines' native APIs, with the wire format a pluggable layer beneath a stable request and response model. README.md states its role in the spike.
The module is four layers, importing one way. Package protocol is the model, Open Responses' item model in its stateless subset; package format is the contract a wire format meets; each wire format is a package below it, named for the endpoint it targets, format/completions, format/messages, and format/responses; and this package is the transport, which sends a protocol.Request in the format chosen by its constructor at the caller's composition root. Embeddings and transcription sit beside the protocol, outside the Format, on the OpenAI-compatible shape whatever format the Client speaks.
The library sets no policy of its own: no default model, token budget, timeout, retry, or size limit. The caller's http.Client and context carry the timeouts, and retry is opt-in, the RetryPolicy the caller sets in Config.
Its exported identifiers:
- Config describes an endpoint: base URL, query, token source, http.Client, format, and retry policy.
- TokenSource returns the bearer token for one request.
- New validates a Config and returns a Client.
- RetryPolicy is the caller's opt-in policy for sending a request again: its most attempts and the backoff's base and most delays.
- Client makes requests of one endpoint in one format.
- Client.Generate sends a Request and returns its Response.
- Client.GenerateStream sends a Request when ranged and yields its response's streaming events.
- Client.Embed sends an *EmbeddingRequest to the OpenAI-compatible embeddings endpoint.
- EmbeddingRequest is an embedding model, its inputs, and a native-tier field.
- Embeddings is one vector per input, in input order, and usage.
- EmbeddingUsage counts the tokens an embedding request took.
- Client.Transcribe streams a *TranscriptionRequest's clip to the OpenAI-compatible transcription endpoint as a multipart upload.
- TranscriptionRequest is an audio model, the clip as a reader and its file name, the optional language and prompt, and a native-tier field of extra form fields.
- Transcription is a clip's transcript and usage.
- TranscriptionUsage counts the tokens a transcription took.
- APIError is a non-2xx reply, matched with errors.As.
Index ¶
- type APIError
- type Client
- func (c *Client) Embed(ctx context.Context, req *EmbeddingRequest) (*Embeddings, error)
- func (c *Client) Generate(ctx context.Context, req *protocol.Request) (*protocol.Response, error)
- func (c *Client) GenerateStream(ctx context.Context, req *protocol.Request) iter.Seq2[protocol.Event, error]
- func (c *Client) Transcribe(ctx context.Context, req *TranscriptionRequest) (*Transcription, error)
- type Config
- type EmbeddingRequest
- type EmbeddingUsage
- type Embeddings
- type RetryPolicy
- type TokenSource
- type Transcription
- type TranscriptionRequest
- type TranscriptionUsage
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type APIError ¶
type APIError struct {
// Status is the HTTP status code.
Status int
// Message is the endpoint's error message, when Body gave one: decoded by the Client's
// Format for Generate, and as an OpenAI-style error body for Embed and Transcribe.
Message string
// Type is the endpoint's error type, such as "invalid_request_error", when it gave one.
Type string
// Code is the endpoint's error code, when it gave one. A numeric code is kept as its
// decimal text.
Code string
// Body is the reply's body.
Body []byte
}
APIError is a non-2xx reply from the endpoint. Requests return it wrapped, so callers reach it with errors.As.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client makes requests of one endpoint in one wire format. It is safe for concurrent use when its TokenSource and Format are.
func New ¶
New returns a Client for cfg. It makes no request, so it reports only a Config that can't describe an endpoint: no HTTP client, no Format, a BaseURL that isn't an absolute http or https URL without a query or fragment, or a Retry policy with fewer than one attempt, a negative delay, or a base delay above its most delay.
func (*Client) Embed ¶
func (c *Client) Embed(ctx context.Context, req *EmbeddingRequest) (*Embeddings, error)
Embed sends req to the endpoint's embeddings route and returns one vector per input, in input order. It refuses a nil request, one with no model or no input, and a Native that isn't a JSON object with unique member names or that clashes, before the token source is called or anything is sent. The vectors travel as base64 of little-endian float32 (encoding_format "base64"), and each reply item's index places its vector; a reply whose items don't match the inputs one to one, or whose vector isn't whole float32s, is an error. A non-2xx reply is an *APIError with the OpenAI-style error body's details; a transport failure or a cancelled ctx is returned wrapped. With a Config.Retry policy, a reply or failure the policy retries is sent again, and the last attempt's error is returned as it would be without one.
func (*Client) Generate ¶
Generate sends req in the Client's format and returns the model's response. It validates req with its Validate method, and the format encodes it, before the token source is called or anything is sent. A non-2xx reply is an *APIError; a transport failure or a cancelled ctx is returned wrapped. With a Config.Retry policy, a reply or failure the policy retries is sent again, and the last attempt's error is returned as it would be without one.
func (*Client) GenerateStream ¶ added in v0.2.0
func (c *Client) GenerateStream(ctx context.Context, req *protocol.Request) iter.Seq2[protocol.Event, error]
GenerateStream streams req's response as the spec's streaming events, which a protocol.Accumulator folds into the Response Generate would return. The sequence is lazy: nothing is validated, encoded, or sent until it is ranged, and each range sends a new request. It asks for text/event-stream and reads the events as they arrive, inside the range loop, with no goroutine; breaking out of the loop closes the reply's body.
Each pair holds an event or an error, never both. The events run from protocol.ResponseCreated to one terminal event, after which the sequence ends. A failure the endpoint reports in the stream, such as llama.cpp's error frame, data {"error": {...}}, is no error pair: the stream ends as the spec has it, with protocol.ErrorEvent, holding the error's type, code, message, and param, and then the terminal protocol.ResponseFailed, whose Response is in protocol.StatusFailed, its Error the failure, holding the output made before it. The caller checks the folded Response's Status for StatusFailed as it does for StatusIncomplete.
Error pairs are for a stream that breaks, or never begins. An error ends the sequence as its last pair, and no event follows it; the events before it fold, in a protocol.Accumulator, to the partial Response, in protocol.StatusInProgress:
- a request Validate or the format refuses, a token source error, a transport failure, or a non-2xx reply, the same *APIError as Generate, is the sequence's only pair
- a cancelled or expired ctx is ctx's error, wrapped, matching context.Canceled or context.DeadlineExceeded; cancelling closes the connection, which stops the generation, and no event is yielded once ctx is done
- a stream that ends before its terminal event matches io.ErrUnexpectedEOF, as does a read that fails, such as on a connection dropped or reset, whose error is wrapped too
- an event the format can't decode is the format's error, wrapped
With a Config.Retry policy, a request the policy retries is sent again before the stream starts: until a 2xx reply arrives, its first byte unread. A stream once started is never sent again; its error ends the sequence as above.
Comments, such as llama.cpp's ping, a ":" line every 30 seconds, are no events and change nothing.
func (*Client) Transcribe ¶
func (c *Client) Transcribe(ctx context.Context, req *TranscriptionRequest) (*Transcription, error)
Transcribe uploads req's clip to the endpoint's transcription route and returns its transcript. It refuses a nil request, one with no model, file, or file name, and a Native key that is empty or clashes with a typed field, before the token source is called or anything is sent.
The upload streams: a goroutine writes the multipart form (the model, response_format json, the language and prompt when set, Native's fields, then the file) into a pipe the request reads, so the clip is read as it is sent, never held whole, and goes chunked, its length unknown. Transcribe returns only once that goroutine has ended, so it has stopped reading File: when the request ends first (a cancelled ctx, a transport failure, an early reply), the pipe is closed and the goroutine's next write fails. A File whose Read blocks holds Transcribe until the Read returns.
A File read error is returned wrapped, ahead of whatever the transport made of the upload it cut short. A transport failure or a cancelled ctx is returned wrapped too. A non-2xx reply is an *APIError with the OpenAI-style error body's details.
Transcribe is never retried, whatever Config.Retry says: File is a one-shot reader, uploaded as it is read, so a second attempt would have no clip to send. A caller who can read the clip again retries by calling Transcribe again with a new reader.
type Config ¶
type Config struct {
// BaseURL is the endpoint's API root, such as "http://localhost:8080/v1". Each route, such
// as "chat/completions", is appended to its path with exactly one slash between them. It is
// an absolute http or https URL with no query or fragment; Query holds the query.
BaseURL string
// Query is added to every request's URL. New copies it, so changing the url.Values after New
// doesn't change the requests.
Query url.Values
// Token authenticates each request as a bearer token. Nil sends no Authorization header.
Token TokenSource
// HTTP sends the requests. It is required, and its transport and timeouts are the Client's:
// the library sets no timeout or limit of its own. A timeout is the caller's, set by the
// context, or by the transport's ResponseHeaderTimeout, which bounds the wait for a reply
// without cutting off a stream; http.Client.Timeout, which bounds the whole exchange, ends a
// long stream part way.
HTTP *http.Client
// Format is the wire format Generate speaks, chosen by its constructor, such as
// completions.New(). It is required.
Format format.Format
// Retry is the policy for sending a request again when the endpoint may answer it later, as
// RetryPolicy describes. Nil, the default, retries nothing. New copies it, so changing the
// policy after New doesn't change the Client.
Retry *RetryPolicy
}
Config describes the endpoint a Client talks to and the format it speaks there.
type EmbeddingRequest ¶
type EmbeddingRequest struct {
// Model is the embedding model's id. It is required.
Model string
// Input is the texts to embed, at least one. An empty string is sent as it is: llama.cpp
// b11669 embeds it, and an endpoint that refuses it answers with an *APIError.
Input []string
// Native is the native tier: a JSON object whose members are merged into the top level of
// the wire body as they are, for a feature of the endpoint the typed fields don't cover,
// such as dimensions. Its members mean what the endpoint makes them mean. A member that
// clashes with one the typed fields set (model, input, encoding_format) is refused.
Native jsontext.Value
}
EmbeddingRequest asks an embedding model for one vector per input. Embeddings sit outside the protocol and the Format: they go to the OpenAI-compatible embeddings endpoint whatever format the Client speaks.
type EmbeddingUsage ¶
type EmbeddingUsage struct {
// PromptTokens is the tokens of every input together.
PromptTokens int
// TotalTokens is the tokens the request took in all.
TotalTokens int
}
EmbeddingUsage counts the tokens an embedding request took, in the endpoint's names. PromptTokens is what protocol.Usage calls InputTokens; an embedding generates no output, so there is no output count, and TotalTokens is PromptTokens on endpoints that count nothing else.
type Embeddings ¶
type Embeddings struct {
// Vectors holds the embedding of each input, Vectors[i] that of Input[i].
Vectors [][]float32
// Usage counts the tokens the request took.
Usage EmbeddingUsage
}
Embeddings is an embedding model's reply: one vector per input, in input order.
type RetryPolicy ¶ added in v0.2.0
type RetryPolicy struct {
// MaxAttempts is the most requests sent for one call, the first included, at least 1. 1 sends
// one and retries nothing.
MaxAttempts int
// BaseDelay is the cap on the wait before the second attempt, which doubles for each attempt
// after it. It is at least 0 and at most MaxDelay.
BaseDelay time.Duration
// MaxDelay is the cap on any wait, at least 0, and the longest Retry-After the Client
// honors.
MaxDelay time.Duration
}
RetryPolicy is when and how long the Client waits to send a request again. It is the caller's policy, set in Config.Retry; unset, nothing is retried.
A request is sent again only when its attempt ends without a 2xx reply in a way that says the endpoint may answer later: a 408 Request Timeout, 429 Too Many Requests, or 503 Service Unavailable reply, or a connection failure before any reply, such as a refused dial or a connection reset or closed before the reply's header. Nothing else is: not a 500 or any other status, not a cancelled or expired context, not a failure of the token source, and never a stream once its 2xx reply has arrived.
A connection that fails before the reply may fail after the endpoint has read the request, and the requests the Client sends are POSTs, which nothing marks as safe to repeat: a retried generation may run twice on the endpoint, and be counted twice. The caller who sets a policy accepts that.
Each attempt calls the token source again and sends the same encoded body. Between attempts the Client waits an exponential backoff with full jitter: before attempt n+1, a random wait of up to BaseDelay doubled n-1 times, capped at MaxDelay. A reply's Retry-After, in seconds or as an HTTP date, replaces the backoff; one that can't be read, or is negative, is ignored. When the wait is longer than MaxDelay, which only a Retry-After makes it, or would end past ctx's deadline, the Client doesn't wait: it returns the last attempt's error at once. A ctx cancelled while waiting returns ctx's error. Once MaxAttempts are made, the last attempt's error is returned, as it is without a policy, such as the *APIError of the last reply.
type TokenSource ¶
TokenSource returns the bearer token for one request. The Client calls it once per request, and again for each attempt a RetryPolicy makes, so a source whose tokens expire can refresh them. An empty token sends no Authorization header.
type Transcription ¶
type Transcription struct {
// Text is the clip's transcript.
Text string
// Usage counts the tokens the request took.
Usage TranscriptionUsage
}
Transcription is an audio model's transcript of a clip.
type TranscriptionRequest ¶
type TranscriptionRequest struct {
// Model is the audio model's id. It is required.
Model string
// File is the clip, read once, as it is uploaded, and never held whole in memory. It is
// required. Transcribe neither closes it nor reads it after returning.
File io.Reader
// FileName names the clip in the upload, such as "clip.wav". It is required: the endpoint
// takes the clip as a file, and a file part has a name.
FileName string
// Language is the clip's language, such as "en", when the caller knows it; empty sends none.
// llama.cpp b11669 adds it to its transcription instruction as a hint.
Language string
// Prompt guides the transcription; empty sends none. Its effect is the endpoint's: OpenAI
// reads it as preceding context, while llama.cpp b11669 sends it to the model in place of its
// own transcription instruction.
Prompt string
// Native is the native tier: form fields sent as they are beside the typed ones, for a
// feature of the endpoint the typed fields don't cover, such as temperature. Each key goes
// out as a field once per value, keys in sorted order and each key's values in order, after
// the typed fields and before the file. Its fields mean what the endpoint makes them mean;
// one that changes the reply's shape, such as stream, makes a reply Transcribe can't decode.
// A key that clashes with a field the typed fields set is refused: model, response_format,
// and file always, and language and prompt when Language and Prompt are set. So is an empty
// key, which names no field.
Native url.Values
}
TranscriptionRequest asks an audio model for the text of a clip. Transcription sits outside the protocol and the Format: it goes to the OpenAI-compatible transcription endpoint whatever format the Client speaks.
The endpoint's other form fields are not typed. response_format is always json, the one llama.cpp b11669 answers (it refuses text, srt, verbose_json, and vtt), so the reply carries no timestamps, and timestamp_granularities, which only verbose_json honors, has nothing to shape. temperature and max_tokens, which llama.cpp passes to its chat completion, and stream, which changes the reply's shape, are left to Native.
type TranscriptionUsage ¶
type TranscriptionUsage struct {
// InputTokens is the tokens of the clip and the transcription instruction.
InputTokens int
// OutputTokens is the tokens of the transcript.
OutputTokens int
// TotalTokens is input and output tokens together.
TotalTokens int
}
TranscriptionUsage counts the tokens a transcription took, in the endpoint's names, which are protocol.Usage's: the clip and the instruction are input, the transcript output.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package format is the contract a wire format meets: Format encodes the protocol package's request in one API's wire shape and decodes the reply into its response, whole or streamed as the protocol's events.
|
Package format is the contract a wire format meets: Format encodes the protocol package's request in one API's wire shape and decodes the reply into its response, whole or streamed as the protocol's events. |
|
completions
Package completions is the Chat Completions wire format: the adapter that encodes the protocol's request model as an OpenAI-compatible /chat/completions request and decodes the reply into its response model, whole or streamed.
|
Package completions is the Chat Completions wire format: the adapter that encodes the protocol's request model as an OpenAI-compatible /chat/completions request and decodes the reply into its response model, whole or streamed. |
|
messages
Package messages is the Anthropic Messages wire format: the adapter that encodes the protocol's request model as a /messages request and decodes the reply into its response model.
|
Package messages is the Anthropic Messages wire format: the adapter that encodes the protocol's request model as a /messages request and decodes the reply into its response model. |
|
responses
Package responses is the Open Responses wire format: the adapter that encodes the protocol's request model as a /responses request and decodes the reply into its response model.
|
Package responses is the Open Responses wire format: the adapter that encodes the protocol's request model as a /responses request and decodes the reply into its response model. |
|
Package integration is the client's integration tier: tests behind the integration build tag that drive the exported API black-box against the llama.cpp router SPIKE_ROUTER_URL names, gb10-2's serving set A by default.
|
Package integration is the client's integration tier: tests behind the integration build tag that drive the exported API black-box against the llama.cpp router SPIKE_ROUTER_URL names, gb10-2's serving set A by default. |
|
internal
|
|
|
clienttest
Package clienttest holds the test helpers the module's test packages share: a loopback HTTP server that answers with a fixed reply and records the requests it receives.
|
Package clienttest holds the test helpers the module's test packages share: a loopback HTTP server that answers with a fixed reply and records the requests it receives. |
|
native
Package native holds what the module's native tier shares across its layers: the check of a request's Native field, the lookup of a member in it, and the encoding of a wire body that merges it, which protocol, the formats, and the client's operations outside the Format each need and none can import from another.
|
Package native holds what the module's native tier shares across its layers: the check of a request's Native field, the lookup of a member in it, and the encoding of a wire body that merges it, which protocol, the formats, and the client's operations outside the Format each need and none can import from another. |
|
sse
Package sse is a decoder for a server-sent event stream, the text/event-stream body of a streamed reply, written to the WHATWG HTML standard's rules for parsing and interpreting an event stream.
|
Package sse is a decoder for a server-sent event stream, the text/event-stream body of a streamed reply, written to the WHATWG HTML standard's rules for parsing and interpreting an event stream. |
|
Package protocol is the client's request and response model: Open Responses' item model (https://www.openresponses.org, spec 2026-04-24), in its stateless subset, using the spec's names.
|
Package protocol is the client's request and response model: Open Responses' item model (https://www.openresponses.org, spec 2026-04-24), in its stateless subset, using the spec's names. |