807 lines
32 KiB
Go
807 lines
32 KiB
Go
// Package llm implements an OpenAI-compatible inference client and its
|
|
// protocol types.
|
|
package llm
|
|
|
|
import (
|
|
"encoding/json/v2"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// APIError contains the structured error details returned by an
|
|
// OpenAI-compatible API. Error values are defined by the server. Compatible
|
|
// servers need not use the same set of types or codes. Callers should use
|
|
// Message for diagnostics and Type and Code for programmatic error handling.
|
|
//
|
|
// See [OpenAI API error codes].
|
|
//
|
|
// [OpenAI API error codes]: https://developers.openai.com/api/docs/guides/error-codes
|
|
type APIError struct {
|
|
// Code identifies the precise error condition reported by the server.
|
|
// Callers may use it to distinguish errors that have the same Type.
|
|
Code string `json:"code"`
|
|
|
|
// Message explains what went wrong. Its wording is server-defined and
|
|
// should not be parsed by callers.
|
|
Message string `json:"message"`
|
|
|
|
// Type identifies the general kind of failure reported by the server.
|
|
// Callers may use it to handle related error conditions as a group.
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
// Error returns the server-defined code and diagnostic message.
|
|
func (e *APIError) Error() string {
|
|
return e.Code + ": " + e.Message
|
|
}
|
|
|
|
// APITool describes a tool that a model may call. Function must be set when
|
|
// Type is ToolTypeFunction.
|
|
type APITool struct {
|
|
// Function defines the callable function when Type is ToolTypeFunction.
|
|
Function *FunctionDef `json:"function,omitzero"`
|
|
// Type selects the representation of the tool.
|
|
Type ToolType `json:"type"`
|
|
}
|
|
|
|
// ChatRequest is the request body of POST /chat/completions.
|
|
//
|
|
// See [OpenAI Chat Completions API].
|
|
//
|
|
// [OpenAI Chat Completions API]: https://platform.openai.com/docs/api-reference/chat/create
|
|
type ChatRequest struct {
|
|
// ChatTemplateKWARGS contains arguments made available to the
|
|
// endpoint's chat template.
|
|
// It is a llama.cpp extension to the OpenAI API.
|
|
ChatTemplateKWARGS map[string]any `json:"chat_template_kwargs,omitzero"`
|
|
// FrequencyPenalty adjusts token likelihood according to frequency in
|
|
// the generated text. Nil leaves the choice to the endpoint.
|
|
FrequencyPenalty *float32 `json:"frequency_penalty,omitzero"`
|
|
// LogProbs requests log probabilities for generated tokens.
|
|
LogProbs bool `json:"logprobs,omitzero"`
|
|
// MaxCompletionTokens bounds generated tokens, including reasoning
|
|
// tokens.
|
|
MaxCompletionTokens int `json:"max_completion_tokens,omitzero"`
|
|
// MaxTokens is the deprecated predecessor of MaxCompletionTokens.
|
|
MaxTokens int `json:"max_tokens,omitzero"`
|
|
// Messages is the conversation presented to the model, in order.
|
|
Messages []Message `json:"messages"`
|
|
// Model identifies the model to use according to the endpoint.
|
|
Model string `json:"model"`
|
|
// N is the number of alternatives to generate.
|
|
N int `json:"n,omitzero"`
|
|
// ParallelToolCalls controls whether the model may request multiple
|
|
// function calls in one turn. Nil leaves the choice to the endpoint.
|
|
ParallelToolCalls *bool `json:"parallel_tool_calls,omitzero"`
|
|
// PresencePenalty adjusts token likelihood according to whether a
|
|
// token has already appeared. Nil leaves the choice to the endpoint.
|
|
PresencePenalty *float32 `json:"presence_penalty,omitzero"`
|
|
// ResponseFormat constrains the representation of generated content.
|
|
ResponseFormat *ResponseFormat `json:"response_format,omitzero"`
|
|
// Seed requests deterministic sampling on endpoints that support it.
|
|
// Equal seeds and parameters do not guarantee equal results.
|
|
Seed *int `json:"seed,omitzero"`
|
|
// ServiceTier selects the endpoint's request-processing tier.
|
|
ServiceTier string `json:"service_tier,omitzero"`
|
|
// Stop contains sequences that end generation.
|
|
// The returned text excludes the matched sequence.
|
|
Stop []string `json:"stop,omitzero"`
|
|
// Stream requests a sequence of StreamChunk values instead of
|
|
// ChatResponse.
|
|
Stream bool `json:"stream,omitzero"`
|
|
// StreamOptions configures a streaming response.
|
|
// It is meaningful only when Stream is true.
|
|
StreamOptions *StreamOptions `json:"stream_options,omitzero"`
|
|
// Temperature controls sampling randomness.
|
|
// Nil leaves the choice to the endpoint.
|
|
Temperature *float32 `json:"temperature,omitzero"`
|
|
// ThinkingBudgetTokens is a llama.cpp extension controlling the token
|
|
// budget for reasoning. Nil omits it, a negative value requests an
|
|
// unrestricted budget, zero requests an immediate end, and a positive
|
|
// value sets the budget.
|
|
ThinkingBudgetTokens *int `json:"thinking_budget_tokens,omitzero"`
|
|
// ToolChoice controls whether the model may or must call a tool.
|
|
// It accepts a mode string or an endpoint-defined object selecting a
|
|
// particular tool.
|
|
ToolChoice any `json:"tool_choice,omitzero"`
|
|
// Tools describes the tools available to the model.
|
|
Tools []APITool `json:"tools,omitzero"`
|
|
// TopLogProbs is the number of most likely tokens reported at each
|
|
// position when LogProbs is true.
|
|
TopLogProbs int `json:"top_logprobs,omitzero"`
|
|
// TopP controls nucleus sampling.
|
|
// Nil leaves the choice to the endpoint.
|
|
TopP *float32 `json:"top_p,omitzero"`
|
|
}
|
|
|
|
// Metadata describes one selected completion, separately from its message.
|
|
// Nil optional fields mean the endpoint did not report a value. Reported zero
|
|
// counts and empty strings remain distinct from absent values. Only known body
|
|
// fields are retained; response headers are not part of Metadata.
|
|
//
|
|
// See the response and streaming definitions in
|
|
// [OpenAI completion response] and [OpenAI completion chunk].
|
|
//
|
|
// [OpenAI completion response]: https://developers.openai.com/api/reference/resources/chat/subresources/completions
|
|
// [OpenAI completion chunk]: https://developers.openai.com/api/reference/resources/chat/subresources/completions/streaming-events
|
|
type Metadata struct {
|
|
// ChoiceIndex is the endpoint's zero-based index of the selected
|
|
// choice.
|
|
ChoiceIndex *int `json:"choice_index"`
|
|
// Created is the endpoint's completion creation time in Unix seconds,
|
|
// not the database message's creation time.
|
|
Created *int64 `json:"created"`
|
|
// FinishReason is the endpoint's reason for ending the selected choice.
|
|
// A reported reason does not establish receipt of the stream's DONE
|
|
// marker.
|
|
FinishReason *string `json:"finish_reason"`
|
|
// ID is the completion ID from the response body, not a request-ID
|
|
// header or a database message ID.
|
|
ID *string `json:"id"`
|
|
// Logprobs contains token probabilities for the selected choice.
|
|
Logprobs *Logprobs `json:"logprobs"`
|
|
// Model is the model identifier reported in the response body. It can
|
|
// differ from RequestedModel, which was sent in the request.
|
|
Model *string `json:"model"`
|
|
// Provider is the application-defined label of the configured endpoint.
|
|
// It is not a provider-reported identity or a response header.
|
|
Provider *string `json:"provider"`
|
|
// RequestedModel is the model identifier sent to the endpoint for this
|
|
// inference, before any endpoint-side model alias resolution.
|
|
RequestedModel *string `json:"requested_model"`
|
|
// ServiceTier is the processing tier reported in the response body,
|
|
// which can differ from a requested tier.
|
|
ServiceTier *string `json:"service_tier"`
|
|
// SystemFingerprint identifies the backend configuration reported by
|
|
// the endpoint; it is not a model identifier.
|
|
SystemFingerprint *string `json:"system_fingerprint"`
|
|
// Timings contains performance measurements for the LLM request that
|
|
// produced this assistant message. During streaming, a newer timing
|
|
// report replaces the earlier report: the measurements cover the same
|
|
// request at a later point, not additional work to add together. Fields
|
|
// missing from the newer report remain nil.
|
|
Timings *Timings `json:"timings"`
|
|
// Usage contains token counts for the LLM request that produced this
|
|
// assistant message. During streaming, a newer usage report replaces
|
|
// the earlier report rather than adding to its counts. Fields missing
|
|
// from the newer report remain nil.
|
|
Usage *Usage `json:"usage"`
|
|
}
|
|
|
|
// Usage reports input, output, and total token counts for one inference.
|
|
// Nil counts mean unreported, not zero. Category counts are endpoint-reported
|
|
// breakdowns, not additional tokens to add to the totals.
|
|
//
|
|
// See [OpenAI completion usage].
|
|
//
|
|
// [OpenAI completion usage]: https://developers.openai.com/api/reference/resources/chat/subresources/completions
|
|
type Usage struct {
|
|
// CompletionTokens is the number of tokens generated by the model.
|
|
CompletionTokens *int `json:"completion_tokens"`
|
|
// CompletionTokensDetails breaks down CompletionTokens by category.
|
|
CompletionTokensDetails *CompletionTokensDetails `json:"completion_tokens_details"`
|
|
// PromptTokens is the number of tokens in the input.
|
|
PromptTokens *int `json:"prompt_tokens"`
|
|
// PromptTokensDetails breaks down PromptTokens by category.
|
|
PromptTokensDetails *PromptTokensDetails `json:"prompt_tokens_details"`
|
|
// TotalTokens is the reported sum of prompt and completion token
|
|
// counts.
|
|
TotalTokens *int `json:"total_tokens"`
|
|
}
|
|
|
|
// CompletionTokensDetails reports output-token categories in [Usage].
|
|
// Nil counts mean the category was not reported. The categories follow
|
|
// [OpenAI completion usage].
|
|
//
|
|
// [OpenAI completion usage]: https://developers.openai.com/api/reference/resources/chat/subresources/completions
|
|
type CompletionTokensDetails struct {
|
|
// AcceptedPredictionTokens counts predicted-output tokens that appeared
|
|
// in the completion.
|
|
AcceptedPredictionTokens *int `json:"accepted_prediction_tokens"`
|
|
// AudioTokens counts audio tokens generated by the model.
|
|
AudioTokens *int `json:"audio_tokens"`
|
|
// ReasoningTokens is the number of output tokens used for reasoning.
|
|
ReasoningTokens *int `json:"reasoning_tokens"`
|
|
// RejectedPredictionTokens counts predicted-output tokens that did not
|
|
// appear in the completion. OpenAI includes these in completion counts
|
|
// for billing, output, and context-window limits.
|
|
RejectedPredictionTokens *int `json:"rejected_prediction_tokens"`
|
|
// TextTokens counts text tokens generated by the model.
|
|
TextTokens *int `json:"text_tokens"`
|
|
}
|
|
|
|
// PromptTokensDetails reports input-token categories in [Usage].
|
|
// Nil counts mean the category was not reported. The categories follow
|
|
// [OpenAI completion usage].
|
|
//
|
|
// [OpenAI completion usage]: https://developers.openai.com/api/reference/resources/chat/subresources/completions
|
|
type PromptTokensDetails struct {
|
|
// AudioTokens counts audio input tokens in the prompt.
|
|
AudioTokens *int `json:"audio_tokens"`
|
|
// CacheWriteTokens is the unadjusted count of prompt tokens written to
|
|
// cache, as reported by the endpoint.
|
|
CacheWriteTokens *int `json:"cache_write_tokens"`
|
|
// CachedTokens is the number of input tokens served from a cache.
|
|
CachedTokens *int `json:"cached_tokens"`
|
|
// ImageTokens counts image input tokens in the prompt.
|
|
ImageTokens *int `json:"image_tokens"`
|
|
// TextTokens counts text input tokens in the prompt.
|
|
TextTokens *int `json:"text_tokens"`
|
|
}
|
|
|
|
// Logprobs reports token log probabilities for one choice, when returned.
|
|
// Content and refusal tokens are separate ordered sequences. Receiving these
|
|
// values does not cause the client to request log probabilities. Nil slices
|
|
// represent absent or null arrays; non-nil empty slices retain reported [].
|
|
//
|
|
// See [OpenAI token log probabilities] and [OpenAI completion response].
|
|
//
|
|
// [OpenAI token log probabilities]: https://developers.openai.com/api/reference/resources/chat/subresources/completions
|
|
// [OpenAI completion response]: https://developers.openai.com/api/reference/resources/chat/subresources/completions
|
|
type Logprobs struct {
|
|
// Content contains log probabilities for message content tokens in
|
|
// order.
|
|
Content []TokenLogprob `json:"content"`
|
|
// Refusal contains log probabilities for refusal tokens in order.
|
|
Refusal []TokenLogprob `json:"refusal"`
|
|
}
|
|
|
|
// TokenLogprob describes a generated token and its alternatives at one
|
|
// position, using [OpenAI token log probabilities]. Nil optional scalars mean
|
|
// unreported; a zero log probability remains a reported value.
|
|
//
|
|
// [OpenAI token log probabilities]: https://developers.openai.com/api/reference/resources/chat/subresources/completions
|
|
type TokenLogprob struct {
|
|
// Bytes is the token's UTF-8 byte representation as integers, not
|
|
// base64.
|
|
// A token can contain only part of a multibyte character. Nil means no
|
|
// byte representation was reported.
|
|
Bytes []int `json:"bytes"`
|
|
// Logprob is the token's log probability. OpenAI uses -9999 for a token
|
|
// outside the top 20 most likely tokens.
|
|
Logprob *float64 `json:"logprob"`
|
|
// Token is the token text reported by the endpoint.
|
|
Token *string `json:"token"`
|
|
// TopLogprobs contains alternatives at this position; the endpoint can
|
|
// return fewer alternatives than requested.
|
|
TopLogprobs []TopLogprob `json:"top_logprobs"`
|
|
}
|
|
|
|
// TopLogprob describes one alternative token at a generated token's position.
|
|
// Alternatives have no nested alternatives in [OpenAI token log probabilities].
|
|
// Nil optional scalars mean unreported, rather than zero or empty text.
|
|
//
|
|
// [OpenAI token log probabilities]: https://developers.openai.com/api/reference/resources/chat/subresources/completions
|
|
type TopLogprob struct {
|
|
// Bytes is the alternative's UTF-8 byte representation as integers,
|
|
// not base64. Nil means no byte representation was reported.
|
|
Bytes []int `json:"bytes"`
|
|
// Logprob is the alternative's log probability. OpenAI uses -9999 for
|
|
// a token outside the top 20 most likely tokens.
|
|
Logprob *float64 `json:"logprob"`
|
|
// Token is the alternative token text reported by the endpoint.
|
|
Token *string `json:"token"`
|
|
}
|
|
|
|
// Timings reports llama.cpp prompt-processing and token-generation performance.
|
|
// The N fields are token counts, MS fields are elapsed milliseconds,
|
|
// PerTokenMS fields are milliseconds per token, and PerSecond fields are tokens
|
|
// per second. Timings is a llama.cpp extension to the OpenAI API. Nil fields
|
|
// mean unreported. Streaming objects are snapshots, not increments to sum.
|
|
//
|
|
// See [llama.cpp API documentation].
|
|
//
|
|
// [llama.cpp API documentation]: https://llama.app/docs/api#timings-and-usage
|
|
type Timings struct {
|
|
// CacheN is the number of prompt tokens reused from the cache.
|
|
CacheN *int `json:"cache_n"`
|
|
// DraftN is the number of tokens drafted during speculative decoding.
|
|
DraftN *int `json:"draft_n"`
|
|
// DraftNAccepted is the number of drafted tokens accepted during
|
|
// speculative decoding.
|
|
DraftNAccepted *int `json:"draft_n_accepted"`
|
|
// PredictedMS is the time spent generating output, in milliseconds.
|
|
PredictedMS *float64 `json:"predicted_ms"`
|
|
// PredictedN is the number of generated tokens.
|
|
PredictedN *int `json:"predicted_n"`
|
|
// PredictedPerSecond is the output-generation rate in tokens per
|
|
// second.
|
|
PredictedPerSecond *float64 `json:"predicted_per_second"`
|
|
// PredictedPerTokenMS is the mean output-generation time per token, in
|
|
// milliseconds.
|
|
PredictedPerTokenMS *float64 `json:"predicted_per_token_ms"`
|
|
// PromptMS is the time spent processing the prompt, in milliseconds.
|
|
PromptMS *float64 `json:"prompt_ms"`
|
|
// PromptN is the number of prompt tokens processed.
|
|
PromptN *int `json:"prompt_n"`
|
|
// PromptPerSecond is the prompt-processing rate in tokens per second.
|
|
PromptPerSecond *float64 `json:"prompt_per_second"`
|
|
// PromptPerTokenMS is the mean prompt-processing time per token, in
|
|
// milliseconds.
|
|
PromptPerTokenMS *float64 `json:"prompt_per_token_ms"`
|
|
}
|
|
|
|
// ChatResponse is the body of a non-streaming chat completion
|
|
// response. Nil metadata fields mean unreported values.
|
|
type ChatResponse struct {
|
|
// Choices contains the generated alternatives.
|
|
Choices []Choice `json:"choices"`
|
|
// Created is the endpoint's completion creation time in Unix seconds.
|
|
Created *int64 `json:"created,omitzero"`
|
|
// ID identifies the completion in the response body, not a request
|
|
// header.
|
|
ID *string `json:"id,omitzero"`
|
|
// Model identifies the model that generated the response.
|
|
Model *string `json:"model,omitzero"`
|
|
// ServiceTier is the processing tier reported by the endpoint.
|
|
ServiceTier *string `json:"service_tier,omitzero"`
|
|
// SystemFingerprint identifies the reported backend configuration.
|
|
SystemFingerprint *string `json:"system_fingerprint,omitzero"`
|
|
// Timings contains llama.cpp performance data, when reported.
|
|
Timings *Timings `json:"timings,omitzero"`
|
|
// Usage contains token counts, when reported.
|
|
Usage *Usage `json:"usage,omitzero"`
|
|
}
|
|
|
|
// Choice is one generated alternative in a non-streaming response. Index is its
|
|
// position among the alternatives, and FinishReason reports why generation
|
|
// ended. Nil metadata fields mean the endpoint did not report those values.
|
|
type Choice struct {
|
|
// FinishReason is the endpoint's reason for ending generation.
|
|
FinishReason *string `json:"finish_reason,omitzero"`
|
|
// Index is the zero-based position of this choice in the response.
|
|
Index *int `json:"index,omitzero"`
|
|
// Logprobs contains probabilities for this choice's content and
|
|
// refusal.
|
|
Logprobs *Logprobs `json:"logprobs,omitzero"`
|
|
// Message is the generated message.
|
|
Message Message `json:"message"`
|
|
}
|
|
|
|
// ContentType identifies the representation of a ContentPart. Which content
|
|
// types are valid depends on the message role and the endpoint.
|
|
//
|
|
// See [OpenAI Chat Completions API].
|
|
//
|
|
// [OpenAI Chat Completions API]: https://platform.openai.com/docs/api-reference/chat/create
|
|
type ContentType string
|
|
|
|
const (
|
|
ContentTypeFile ContentType = "file"
|
|
ContentTypeImageURL ContentType = "image_url"
|
|
ContentTypeInputAudio ContentType = "input_audio"
|
|
ContentTypeRefusal ContentType = "refusal"
|
|
ContentTypeText ContentType = "text"
|
|
)
|
|
|
|
// ContentPart is one element of a message's content array. Type determines
|
|
// which payload field is meaningful: File, ImageURL, InputAudio, Refusal, or
|
|
// Text. The package does not validate that correspondence.
|
|
type ContentPart struct {
|
|
// File contains the payload for ContentTypeFile.
|
|
File *FileInput `json:"file,omitzero"`
|
|
// ImageURL contains the payload for ContentTypeImageURL.
|
|
ImageURL *ImageURL `json:"image_url,omitzero"`
|
|
// InputAudio contains the payload for ContentTypeInputAudio.
|
|
InputAudio *InputAudio `json:"input_audio,omitzero"`
|
|
// Refusal contains the payload for ContentTypeRefusal.
|
|
Refusal string `json:"refusal,omitzero"`
|
|
// Text contains the payload for ContentTypeText.
|
|
Text string `json:"text,omitzero"`
|
|
// Type identifies which payload field is meaningful.
|
|
Type ContentType `json:"type"`
|
|
}
|
|
|
|
// MarshalJSON writes the payload field that Type selects, then the type.
|
|
// The payload field is always written, even when it is empty. The OpenAI
|
|
// API requires the payload field in every content part, and a text part
|
|
// without a text key makes llama.cpp reject the request. Struct tags
|
|
// cannot express this: a field that is required for one part type must be
|
|
// omitted from the other part types. An unknown Type falls back to the
|
|
// struct tags.
|
|
func (p ContentPart) MarshalJSON() ([]byte, error) {
|
|
switch p.Type {
|
|
case ContentTypeText:
|
|
return json.Marshal(struct {
|
|
Text string `json:"text"`
|
|
Type ContentType `json:"type"`
|
|
}{p.Text, p.Type})
|
|
case ContentTypeRefusal:
|
|
return json.Marshal(struct {
|
|
Refusal string `json:"refusal"`
|
|
Type ContentType `json:"type"`
|
|
}{p.Refusal, p.Type})
|
|
case ContentTypeImageURL:
|
|
return json.Marshal(struct {
|
|
ImageURL *ImageURL `json:"image_url"`
|
|
Type ContentType `json:"type"`
|
|
}{p.ImageURL, p.Type})
|
|
case ContentTypeInputAudio:
|
|
return json.Marshal(struct {
|
|
InputAudio *InputAudio `json:"input_audio"`
|
|
Type ContentType `json:"type"`
|
|
}{p.InputAudio, p.Type})
|
|
case ContentTypeFile:
|
|
return json.Marshal(struct {
|
|
File *FileInput `json:"file"`
|
|
Type ContentType `json:"type"`
|
|
}{p.File, p.Type})
|
|
}
|
|
// Unknown types keep the struct-tag encoding. The method must not
|
|
// reject them: the endpoint, not this client, defines which types
|
|
// are valid.
|
|
type alias ContentPart
|
|
return json.Marshal(alias(p))
|
|
}
|
|
|
|
// Delta is an incremental message update in a StreamChunk. Reasoning and
|
|
// ReasoningContent are alternative field names used by compatible endpoints;
|
|
// StreamAccumulator preserves fragments from both.
|
|
type Delta struct {
|
|
// Content is the next fragment of message text.
|
|
Content string `json:"content,omitzero"`
|
|
// Reasoning is the next reasoning fragment under the reasoning field
|
|
// name.
|
|
Reasoning string `json:"reasoning,omitzero"`
|
|
// ReasoningContent is the next reasoning fragment under the
|
|
// reasoning_content field name.
|
|
ReasoningContent string `json:"reasoning_content,omitzero"`
|
|
// Refusal is the next fragment of refusal text.
|
|
Refusal string `json:"refusal,omitzero"`
|
|
// Role identifies the message role when supplied by the endpoint.
|
|
Role Role `json:"role,omitzero"`
|
|
// ToolCalls contains the next fragments of one or more tool calls.
|
|
ToolCalls []ToolCall `json:"tool_calls,omitzero"`
|
|
}
|
|
|
|
// FileInput identifies file content either inline through FileData or by a
|
|
// server-assigned FileID.
|
|
type FileInput struct {
|
|
// FileData is the base64-encoded contents of an inline file.
|
|
FileData string `json:"file_data,omitzero"`
|
|
// FileID references a file previously uploaded to the server.
|
|
FileID string `json:"file_id,omitzero"`
|
|
// Filename names an inline file supplied through FileData.
|
|
Filename string `json:"filename,omitzero"`
|
|
}
|
|
|
|
// Function identifies a called function and carries its arguments as a JSON
|
|
// string. The model may emit invalid JSON, so callers must validate Arguments
|
|
// before invoking the function.
|
|
type Function struct {
|
|
// Arguments is the JSON-encoded argument object generated by the model.
|
|
Arguments string `json:"arguments,omitzero"`
|
|
// Name identifies the function to call.
|
|
Name string `json:"name,omitzero"`
|
|
}
|
|
|
|
// FunctionDef declares a function the model may call.
|
|
type FunctionDef struct {
|
|
// Description states what the function does and when to call it.
|
|
Description string `json:"description,omitzero"`
|
|
// Name identifies the function.
|
|
Name string `json:"name"`
|
|
// Parameters is a JSON Schema describing the arguments accepted by the
|
|
// function.
|
|
Parameters any `json:"parameters"`
|
|
// Strict requests exact conformance to Parameters, where the server
|
|
// supports it.
|
|
Strict bool `json:"strict,omitzero"`
|
|
}
|
|
|
|
// ImageDetail selects the fidelity used when processing an image. Auto lets the
|
|
// endpoint choose.
|
|
type ImageDetail string
|
|
|
|
const (
|
|
ImageDetailAuto ImageDetail = "auto"
|
|
ImageDetailHigh ImageDetail = "high"
|
|
ImageDetailLow ImageDetail = "low"
|
|
)
|
|
|
|
// ImageURL carries an image content part.
|
|
type ImageURL struct {
|
|
// Detail is optional; when omitted the server chooses.
|
|
Detail ImageDetail `json:"detail,omitzero"`
|
|
// URL is a public image URL or a base64 data URI.
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// AudioFormat names an audio encoding accepted in an input_audio
|
|
// part.
|
|
type AudioFormat string
|
|
|
|
const (
|
|
AudioFormatWAV AudioFormat = "wav"
|
|
AudioFormatMP3 AudioFormat = "mp3"
|
|
)
|
|
|
|
// InputAudio carries base64-encoded WAV or MP3 audio.
|
|
type InputAudio struct {
|
|
// Data is base64-encoded audio data.
|
|
Data string `json:"data"`
|
|
// Format identifies the encoding of Data.
|
|
Format AudioFormat `json:"format"`
|
|
}
|
|
|
|
// Message is one message in a conversation or completion response.
|
|
//
|
|
// The JSON content field may be a string or an array. UnmarshalJSON normalizes
|
|
// either representation into ContentParts; marshaling a Message encodes
|
|
// ContentParts as an array.
|
|
//
|
|
// See [OpenAI Chat Completions API].
|
|
//
|
|
// [OpenAI Chat Completions API]: https://platform.openai.com/docs/api-reference/chat/create
|
|
type Message struct {
|
|
// ContentParts holds the message content in parts; it is the
|
|
// in-memory form of the JSON "content" field.
|
|
ContentParts []ContentPart `json:"content,omitzero"`
|
|
// Name optionally identifies the sender.
|
|
Name string `json:"name,omitzero"`
|
|
// Reasoning and ReasoningContent are alternative wire fields used by
|
|
// compatible servers. After unmarshalling, ReasoningContent contains
|
|
// either representation, with reasoning_content taking precedence when
|
|
// both occur.
|
|
Reasoning string `json:"reasoning,omitzero"`
|
|
ReasoningContent string `json:"reasoning_content,omitzero"`
|
|
// Refusal contains the endpoint's refusal text, when present.
|
|
Refusal string `json:"refusal,omitzero"`
|
|
// Role identifies the author or purpose of the message.
|
|
Role Role `json:"role"`
|
|
// ToolCallID links a tool response to the call it answers.
|
|
ToolCallID string `json:"tool_call_id,omitzero"`
|
|
// ToolCalls contains calls requested by an assistant message.
|
|
ToolCalls []ToolCall `json:"tool_calls,omitzero"`
|
|
}
|
|
|
|
// Text concatenates the Text payload of every text content part in order. It
|
|
// ignores non-text parts and does not fall back to Refusal or reasoning text.
|
|
func (m *Message) Text() string {
|
|
var b strings.Builder
|
|
for _, p := range m.ContentParts {
|
|
if p.Type == ContentTypeText {
|
|
b.WriteString(p.Text)
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// Validate checks conformance to the OpenAI API specification.
|
|
func (m *Message) Validate() error {
|
|
if len(m.ToolCalls) != 0 && m.Role != RoleAssistant {
|
|
return fmt.Errorf(
|
|
"%q message with tool calls", m.Role,
|
|
)
|
|
}
|
|
if m.ToolCallID != "" && m.Role != RoleTool {
|
|
return fmt.Errorf("%q message with tool call id", m.Role)
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, call := range m.ToolCalls {
|
|
if err := call.Validate(); err != nil {
|
|
return fmt.Errorf("invalid tool call: %w", err)
|
|
}
|
|
if seen[call.ID] {
|
|
return fmt.Errorf("duplicate tool call id %q", call.ID)
|
|
}
|
|
seen[call.ID] = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateToolCall replaces the call identified by callId, keeping its
|
|
// position in ToolCalls. The replacement must have the same ID: the
|
|
// corresponding tool response carries it in Message.ToolCallID.
|
|
func (m *Message) UpdateToolCall(call ToolCall) error {
|
|
if err := call.Validate(); err != nil {
|
|
return fmt.Errorf("invalid tool call: %w", err)
|
|
}
|
|
for i := range m.ToolCalls {
|
|
if m.ToolCalls[i].ID == call.ID {
|
|
m.ToolCalls[i] = call
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("not found: call %q", call.ID)
|
|
}
|
|
|
|
// AddToolCall appends call to the message's ToolCalls. The call must be
|
|
// valid and its ID must not already appear in the message.
|
|
func (m *Message) AddToolCall(call ToolCall) error {
|
|
if err := call.Validate(); err != nil {
|
|
return fmt.Errorf("invalid tool call: %w", err)
|
|
}
|
|
for _, existing := range m.ToolCalls {
|
|
if existing.ID == call.ID {
|
|
return fmt.Errorf("duplicate call id %q", call.ID)
|
|
}
|
|
}
|
|
m.ToolCalls = append(m.ToolCalls, call)
|
|
return nil
|
|
}
|
|
|
|
// UnmarshalJSON decodes a message while normalizing a string-valued content
|
|
// field to one text ContentPart. An array-valued content field is decoded in
|
|
// order. If reasoning_content is absent, reasoning is copied into
|
|
// ReasoningContent so callers can use the latter for either server spelling.
|
|
func (m *Message) UnmarshalJSON(data []byte) error {
|
|
type Alias Message
|
|
aux := &struct {
|
|
Content any `json:"content"`
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(m),
|
|
}
|
|
if err := json.Unmarshal(data, &aux); err != nil {
|
|
return err
|
|
}
|
|
switch c := aux.Content.(type) {
|
|
case string:
|
|
m.ContentParts = []ContentPart{{Type: ContentTypeText, Text: c}}
|
|
case []any:
|
|
// Array content replaces any content already held by the Message.
|
|
parts := make([]ContentPart, 0, len(c))
|
|
for _, raw := range c {
|
|
partBytes, err := json.Marshal(raw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var part ContentPart
|
|
if err := json.Unmarshal(partBytes, &part); err != nil {
|
|
return err
|
|
}
|
|
parts = append(parts, part)
|
|
}
|
|
m.ContentParts = parts
|
|
}
|
|
// If ReasoningContent is empty but Reasoning is set, use Reasoning.
|
|
if m.ReasoningContent == "" && m.Reasoning != "" {
|
|
m.ReasoningContent = m.Reasoning
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Model describes one model exposed by the server's /models
|
|
// endpoint.
|
|
//
|
|
// See [OpenAI Models API].
|
|
//
|
|
// [OpenAI Models API]: https://platform.openai.com/docs/api-reference/models/list
|
|
type Model struct {
|
|
// ContextLength is the model's context window in tokens, when reported.
|
|
ContextLength int `json:"context_length,omitzero"`
|
|
// Created is the model's creation time as Unix seconds.
|
|
Created int64 `json:"created"`
|
|
// ID identifies the model in inference requests.
|
|
ID string `json:"id"`
|
|
// Meta contains endpoint-specific model metadata, when reported.
|
|
Meta *ModelMeta `json:"meta,omitzero"`
|
|
// Object is the endpoint's object type for the model entry.
|
|
Object string `json:"object"`
|
|
// OwnedBy identifies the organization that owns the model, when
|
|
//reported.
|
|
OwnedBy string `json:"owned_by"`
|
|
}
|
|
|
|
// ModelMeta contains llama.cpp model metadata. Context-size fields are measured
|
|
// in tokens and are zero when omitted.
|
|
//
|
|
// See [llama.cpp API documentation].
|
|
//
|
|
// [llama.cpp API documentation]: https://llama.app/docs/api
|
|
type ModelMeta struct {
|
|
// NCtx is the context size with which llama.cpp loaded the model.
|
|
NCtx int `json:"n_ctx,omitzero"`
|
|
// NCtxTrain is the model's training context size.
|
|
NCtxTrain int `json:"n_ctx_train,omitzero"`
|
|
}
|
|
|
|
// ModelsResponse is the response body of GET /models.
|
|
type ModelsResponse struct {
|
|
// Data contains the models exposed by the endpoint.
|
|
Data []Model `json:"data"`
|
|
// Object is the endpoint's object type for the response.
|
|
Object string `json:"object"`
|
|
}
|
|
|
|
// Role identifies the author or purpose of a message.
|
|
type Role string
|
|
|
|
const (
|
|
RoleAssistant Role = "assistant"
|
|
RoleDeveloper Role = "developer"
|
|
RoleSystem Role = "system"
|
|
RoleTool Role = "tool"
|
|
RoleUser Role = "user"
|
|
)
|
|
|
|
// ResponseFormat constrains the format of the generated response.
|
|
type ResponseFormat struct {
|
|
// JSONSchema defines the required output schema when Type is
|
|
// json_schema.
|
|
JSONSchema any `json:"json_schema,omitzero"`
|
|
// Type selects text, json_object, or json_schema output.
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
// StreamChunk is one data event in a streaming response. Delta fields are
|
|
// incremental and must be combined in order to reconstruct a message. A chunk
|
|
// may contain Usage or Timings without containing a choice. Nil metadata fields
|
|
// mean unreported values, not reported zero counts or empty strings.
|
|
type StreamChunk struct {
|
|
// Choices contains incremental updates for generated alternatives.
|
|
// It may be empty in a metadata-only chunk.
|
|
Choices []struct {
|
|
// Delta contains the incremental message update.
|
|
Delta Delta `json:"delta"`
|
|
// FinishReason is the reported reason for ending this choice;
|
|
// it does not establish receipt of DONE.
|
|
FinishReason *string `json:"finish_reason,omitzero"`
|
|
// Index identifies the choice updated by this delta. Nil means
|
|
// unreported and selects the first choice for accumulation.
|
|
Index *int `json:"index,omitzero"`
|
|
// Logprobs contains the next content and refusal token
|
|
// probabilities.
|
|
Logprobs *Logprobs `json:"logprobs,omitzero"`
|
|
} `json:"choices"`
|
|
// Created is the endpoint's completion creation time in Unix seconds.
|
|
Created *int64 `json:"created,omitzero"`
|
|
// ID identifies the completion in the response body, not a request
|
|
// header.
|
|
ID *string `json:"id,omitzero"`
|
|
// Model identifies the model generating the response.
|
|
Model *string `json:"model,omitzero"`
|
|
// ServiceTier is the processing tier reported by the endpoint.
|
|
ServiceTier *string `json:"service_tier,omitzero"`
|
|
// SystemFingerprint identifies the reported backend configuration.
|
|
SystemFingerprint *string `json:"system_fingerprint,omitzero"`
|
|
// Timings contains llama.cpp performance data, when reported.
|
|
Timings *Timings `json:"timings,omitzero"`
|
|
// Usage contains aggregate token counts, when reported.
|
|
Usage *Usage `json:"usage,omitzero"`
|
|
}
|
|
|
|
// StreamOptions configures a streaming response.
|
|
type StreamOptions struct {
|
|
// IncludeUsage requests an additional chunk containing aggregate token
|
|
// usage before the terminal data marker. If the stream is interrupted,
|
|
// that chunk may not arrive.
|
|
IncludeUsage bool `json:"include_usage,omitzero"`
|
|
}
|
|
|
|
// ToolCall is one tool call requested by the model.
|
|
type ToolCall struct {
|
|
// Function identifies the function and its generated arguments.
|
|
Function Function `json:"function"`
|
|
// ID identifies the call. The corresponding tool response carries the
|
|
// same value in Message.ToolCallID.
|
|
ID string `json:"id,omitzero"`
|
|
// Index identifies the call whose fragments a streaming delta
|
|
// contains. It is omitted from non-streaming responses.
|
|
Index *int `json:"index,omitzero"`
|
|
// Type identifies the kind of tool being called.
|
|
Type ToolType `json:"type"`
|
|
}
|
|
|
|
// Validate checks conformance to the OpenAI API specification.
|
|
func (t *ToolCall) Validate() error {
|
|
if t.ID == "" {
|
|
return fmt.Errorf("missing id")
|
|
}
|
|
if t.Type != ToolTypeFunction {
|
|
return fmt.Errorf("invalid type: %q", t.Type)
|
|
}
|
|
if t.Function.Name == "" {
|
|
return fmt.Errorf("missing function name")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ToolType identifies the kind of tool in a declaration or tool call.
|
|
type ToolType string
|
|
|
|
const (
|
|
ToolTypeFunction ToolType = "function"
|
|
)
|