Refactol llm pkg
This commit is contained in:
@@ -72,9 +72,13 @@ func (c *Client) Completions(
|
|||||||
func (c *Client) completions(
|
func (c *Client) completions(
|
||||||
ctx context.Context, p CompletionsParams,
|
ctx context.Context, p CompletionsParams,
|
||||||
) (*ChatResponse, error) {
|
) (*ChatResponse, error) {
|
||||||
kwargs := map[string]any{}
|
// A nil map is omitted from the request; an empty map is
|
||||||
|
// sent as {}. Keep kwargs nil without a budget.
|
||||||
|
var kwargs map[string]any
|
||||||
if p.ThinkingBudgetTokens != nil {
|
if p.ThinkingBudgetTokens != nil {
|
||||||
kwargs[chatTemplateEnableThinking] = *p.ThinkingBudgetTokens != 0
|
kwargs = map[string]any{
|
||||||
|
chatTemplateEnableThinking: *p.ThinkingBudgetTokens != 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(ChatRequest{
|
body, err := json.Marshal(ChatRequest{
|
||||||
Model: p.Model,
|
Model: p.Model,
|
||||||
@@ -270,9 +274,13 @@ func (c *Client) CompletionsStream(
|
|||||||
) (*CompletionStream, error) {
|
) (*CompletionStream, error) {
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
|
|
||||||
kwargs := map[string]any{}
|
// A nil map is omitted from the request; an empty map is
|
||||||
|
// sent as {}. Keep kwargs nil without a budget.
|
||||||
|
var kwargs map[string]any
|
||||||
if p.ThinkingBudgetTokens != nil {
|
if p.ThinkingBudgetTokens != nil {
|
||||||
kwargs[chatTemplateEnableThinking] = *p.ThinkingBudgetTokens != 0
|
kwargs = map[string]any{
|
||||||
|
chatTemplateEnableThinking: *p.ThinkingBudgetTokens != 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
body, err := json.Marshal(ChatRequest{
|
body, err := json.Marshal(ChatRequest{
|
||||||
ChatTemplateKWARGS: kwargs,
|
ChatTemplateKWARGS: kwargs,
|
||||||
|
|||||||
+117
-75
@@ -39,7 +39,7 @@ func (e *APIError) Error() string {
|
|||||||
// Type is ToolTypeFunction.
|
// Type is ToolTypeFunction.
|
||||||
type APITool struct {
|
type APITool struct {
|
||||||
// Function defines the callable function when Type is ToolTypeFunction.
|
// Function defines the callable function when Type is ToolTypeFunction.
|
||||||
Function *FunctionDef `json:"function,omitempty"`
|
Function *FunctionDef `json:"function,omitzero"`
|
||||||
// Type selects the representation of the tool.
|
// Type selects the representation of the tool.
|
||||||
Type ToolType `json:"type"`
|
Type ToolType `json:"type"`
|
||||||
}
|
}
|
||||||
@@ -53,65 +53,65 @@ type ChatRequest struct {
|
|||||||
// ChatTemplateKWARGS contains arguments made available to the
|
// ChatTemplateKWARGS contains arguments made available to the
|
||||||
// endpoint's chat template.
|
// endpoint's chat template.
|
||||||
// It is a llama.cpp extension to the OpenAI API.
|
// It is a llama.cpp extension to the OpenAI API.
|
||||||
ChatTemplateKWARGS map[string]any `json:"chat_template_kwargs,omitempty"`
|
ChatTemplateKWARGS map[string]any `json:"chat_template_kwargs,omitzero"`
|
||||||
// FrequencyPenalty adjusts token likelihood according to frequency in
|
// FrequencyPenalty adjusts token likelihood according to frequency in
|
||||||
// the generated text. Nil leaves the choice to the endpoint.
|
// the generated text. Nil leaves the choice to the endpoint.
|
||||||
FrequencyPenalty *float32 `json:"frequency_penalty,omitempty"`
|
FrequencyPenalty *float32 `json:"frequency_penalty,omitzero"`
|
||||||
// LogProbs requests log probabilities for generated tokens.
|
// LogProbs requests log probabilities for generated tokens.
|
||||||
LogProbs bool `json:"logprobs,omitempty"`
|
LogProbs bool `json:"logprobs,omitzero"`
|
||||||
// MaxCompletionTokens bounds generated tokens, including reasoning
|
// MaxCompletionTokens bounds generated tokens, including reasoning
|
||||||
// tokens.
|
// tokens.
|
||||||
MaxCompletionTokens int `json:"max_completion_tokens,omitempty"`
|
MaxCompletionTokens int `json:"max_completion_tokens,omitzero"`
|
||||||
// MaxTokens is the deprecated predecessor of MaxCompletionTokens.
|
// MaxTokens is the deprecated predecessor of MaxCompletionTokens.
|
||||||
MaxTokens int `json:"max_tokens,omitempty"`
|
MaxTokens int `json:"max_tokens,omitzero"`
|
||||||
// Messages is the conversation presented to the model, in order.
|
// Messages is the conversation presented to the model, in order.
|
||||||
Messages []Message `json:"messages"`
|
Messages []Message `json:"messages"`
|
||||||
// Model identifies the model to use according to the endpoint.
|
// Model identifies the model to use according to the endpoint.
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
// N is the number of alternatives to generate.
|
// N is the number of alternatives to generate.
|
||||||
N int `json:"n,omitempty"`
|
N int `json:"n,omitzero"`
|
||||||
// ParallelToolCalls controls whether the model may request multiple
|
// ParallelToolCalls controls whether the model may request multiple
|
||||||
// function calls in one turn. Nil leaves the choice to the endpoint.
|
// function calls in one turn. Nil leaves the choice to the endpoint.
|
||||||
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
|
ParallelToolCalls *bool `json:"parallel_tool_calls,omitzero"`
|
||||||
// PresencePenalty adjusts token likelihood according to whether a
|
// PresencePenalty adjusts token likelihood according to whether a
|
||||||
// token has already appeared. Nil leaves the choice to the endpoint.
|
// token has already appeared. Nil leaves the choice to the endpoint.
|
||||||
PresencePenalty *float32 `json:"presence_penalty,omitempty"`
|
PresencePenalty *float32 `json:"presence_penalty,omitzero"`
|
||||||
// ResponseFormat constrains the representation of generated content.
|
// ResponseFormat constrains the representation of generated content.
|
||||||
ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
|
ResponseFormat *ResponseFormat `json:"response_format,omitzero"`
|
||||||
// Seed requests deterministic sampling on endpoints that support it.
|
// Seed requests deterministic sampling on endpoints that support it.
|
||||||
// Equal seeds and parameters do not guarantee equal results.
|
// Equal seeds and parameters do not guarantee equal results.
|
||||||
Seed *int `json:"seed,omitempty"`
|
Seed *int `json:"seed,omitzero"`
|
||||||
// ServiceTier selects the endpoint's request-processing tier.
|
// ServiceTier selects the endpoint's request-processing tier.
|
||||||
ServiceTier string `json:"service_tier,omitempty"`
|
ServiceTier string `json:"service_tier,omitzero"`
|
||||||
// Stop contains sequences that end generation.
|
// Stop contains sequences that end generation.
|
||||||
// The returned text excludes the matched sequence.
|
// The returned text excludes the matched sequence.
|
||||||
Stop []string `json:"stop,omitempty"`
|
Stop []string `json:"stop,omitzero"`
|
||||||
// Stream requests a sequence of StreamChunk values instead of
|
// Stream requests a sequence of StreamChunk values instead of
|
||||||
// ChatResponse.
|
// ChatResponse.
|
||||||
Stream bool `json:"stream,omitempty"`
|
Stream bool `json:"stream,omitzero"`
|
||||||
// StreamOptions configures a streaming response.
|
// StreamOptions configures a streaming response.
|
||||||
// It is meaningful only when Stream is true.
|
// It is meaningful only when Stream is true.
|
||||||
StreamOptions *StreamOptions `json:"stream_options,omitempty"`
|
StreamOptions *StreamOptions `json:"stream_options,omitzero"`
|
||||||
// Temperature controls sampling randomness.
|
// Temperature controls sampling randomness.
|
||||||
// Nil leaves the choice to the endpoint.
|
// Nil leaves the choice to the endpoint.
|
||||||
Temperature *float32 `json:"temperature,omitempty"`
|
Temperature *float32 `json:"temperature,omitzero"`
|
||||||
// ThinkingBudgetTokens is a llama.cpp extension controlling the token
|
// ThinkingBudgetTokens is a llama.cpp extension controlling the token
|
||||||
// budget for reasoning. Nil omits it, a negative value requests an
|
// budget for reasoning. Nil omits it, a negative value requests an
|
||||||
// unrestricted budget, zero requests an immediate end, and a positive
|
// unrestricted budget, zero requests an immediate end, and a positive
|
||||||
// value sets the budget.
|
// value sets the budget.
|
||||||
ThinkingBudgetTokens *int `json:"thinking_budget_tokens,omitempty"`
|
ThinkingBudgetTokens *int `json:"thinking_budget_tokens,omitzero"`
|
||||||
// ToolChoice controls whether the model may or must call a tool.
|
// ToolChoice controls whether the model may or must call a tool.
|
||||||
// It accepts a mode string or an endpoint-defined object selecting a
|
// It accepts a mode string or an endpoint-defined object selecting a
|
||||||
// particular tool.
|
// particular tool.
|
||||||
ToolChoice any `json:"tool_choice,omitempty"`
|
ToolChoice any `json:"tool_choice,omitzero"`
|
||||||
// Tools describes the tools available to the model.
|
// Tools describes the tools available to the model.
|
||||||
Tools []APITool `json:"tools,omitempty"`
|
Tools []APITool `json:"tools,omitzero"`
|
||||||
// TopLogProbs is the number of most likely tokens reported at each
|
// TopLogProbs is the number of most likely tokens reported at each
|
||||||
// position when LogProbs is true.
|
// position when LogProbs is true.
|
||||||
TopLogProbs int `json:"top_logprobs,omitempty"`
|
TopLogProbs int `json:"top_logprobs,omitzero"`
|
||||||
// TopP controls nucleus sampling.
|
// TopP controls nucleus sampling.
|
||||||
// Nil leaves the choice to the endpoint.
|
// Nil leaves the choice to the endpoint.
|
||||||
TopP *float32 `json:"top_p,omitempty"`
|
TopP *float32 `json:"top_p,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata describes one selected completion, separately from its message.
|
// Metadata describes one selected completion, separately from its message.
|
||||||
@@ -327,20 +327,20 @@ type ChatResponse struct {
|
|||||||
// Choices contains the generated alternatives.
|
// Choices contains the generated alternatives.
|
||||||
Choices []Choice `json:"choices"`
|
Choices []Choice `json:"choices"`
|
||||||
// Created is the endpoint's completion creation time in Unix seconds.
|
// Created is the endpoint's completion creation time in Unix seconds.
|
||||||
Created *int64 `json:"created,omitempty"`
|
Created *int64 `json:"created,omitzero"`
|
||||||
// ID identifies the completion in the response body, not a request
|
// ID identifies the completion in the response body, not a request
|
||||||
// header.
|
// header.
|
||||||
ID *string `json:"id,omitempty"`
|
ID *string `json:"id,omitzero"`
|
||||||
// Model identifies the model that generated the response.
|
// Model identifies the model that generated the response.
|
||||||
Model *string `json:"model,omitempty"`
|
Model *string `json:"model,omitzero"`
|
||||||
// ServiceTier is the processing tier reported by the endpoint.
|
// ServiceTier is the processing tier reported by the endpoint.
|
||||||
ServiceTier *string `json:"service_tier,omitempty"`
|
ServiceTier *string `json:"service_tier,omitzero"`
|
||||||
// SystemFingerprint identifies the reported backend configuration.
|
// SystemFingerprint identifies the reported backend configuration.
|
||||||
SystemFingerprint *string `json:"system_fingerprint,omitempty"`
|
SystemFingerprint *string `json:"system_fingerprint,omitzero"`
|
||||||
// Timings contains llama.cpp performance data, when reported.
|
// Timings contains llama.cpp performance data, when reported.
|
||||||
Timings *Timings `json:"timings,omitempty"`
|
Timings *Timings `json:"timings,omitzero"`
|
||||||
// Usage contains token counts, when reported.
|
// Usage contains token counts, when reported.
|
||||||
Usage *Usage `json:"usage,omitempty"`
|
Usage *Usage `json:"usage,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Choice is one generated alternative in a non-streaming response. Index is its
|
// Choice is one generated alternative in a non-streaming response. Index is its
|
||||||
@@ -348,12 +348,12 @@ type ChatResponse struct {
|
|||||||
// ended. Nil metadata fields mean the endpoint did not report those values.
|
// ended. Nil metadata fields mean the endpoint did not report those values.
|
||||||
type Choice struct {
|
type Choice struct {
|
||||||
// FinishReason is the endpoint's reason for ending generation.
|
// FinishReason is the endpoint's reason for ending generation.
|
||||||
FinishReason *string `json:"finish_reason,omitempty"`
|
FinishReason *string `json:"finish_reason,omitzero"`
|
||||||
// Index is the zero-based position of this choice in the response.
|
// Index is the zero-based position of this choice in the response.
|
||||||
Index *int `json:"index,omitempty"`
|
Index *int `json:"index,omitzero"`
|
||||||
// Logprobs contains probabilities for this choice's content and
|
// Logprobs contains probabilities for this choice's content and
|
||||||
// refusal.
|
// refusal.
|
||||||
Logprobs *Logprobs `json:"logprobs,omitempty"`
|
Logprobs *Logprobs `json:"logprobs,omitzero"`
|
||||||
// Message is the generated message.
|
// Message is the generated message.
|
||||||
Message Message `json:"message"`
|
Message Message `json:"message"`
|
||||||
}
|
}
|
||||||
@@ -379,48 +379,90 @@ const (
|
|||||||
// Text. The package does not validate that correspondence.
|
// Text. The package does not validate that correspondence.
|
||||||
type ContentPart struct {
|
type ContentPart struct {
|
||||||
// File contains the payload for ContentTypeFile.
|
// File contains the payload for ContentTypeFile.
|
||||||
File *FileInput `json:"file,omitempty"`
|
File *FileInput `json:"file,omitzero"`
|
||||||
// ImageURL contains the payload for ContentTypeImageURL.
|
// ImageURL contains the payload for ContentTypeImageURL.
|
||||||
ImageURL *ImageURL `json:"image_url,omitempty"`
|
ImageURL *ImageURL `json:"image_url,omitzero"`
|
||||||
// InputAudio contains the payload for ContentTypeInputAudio.
|
// InputAudio contains the payload for ContentTypeInputAudio.
|
||||||
InputAudio *InputAudio `json:"input_audio,omitempty"`
|
InputAudio *InputAudio `json:"input_audio,omitzero"`
|
||||||
// Refusal contains the payload for ContentTypeRefusal.
|
// Refusal contains the payload for ContentTypeRefusal.
|
||||||
Refusal string `json:"refusal,omitempty"`
|
Refusal string `json:"refusal,omitzero"`
|
||||||
// Text contains the payload for ContentTypeText.
|
// Text contains the payload for ContentTypeText.
|
||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitzero"`
|
||||||
// Type identifies which payload field is meaningful.
|
// Type identifies which payload field is meaningful.
|
||||||
Type ContentType `json:"type"`
|
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
|
// Delta is an incremental message update in a StreamChunk. Reasoning and
|
||||||
// ReasoningContent are alternative field names used by compatible endpoints;
|
// ReasoningContent are alternative field names used by compatible endpoints;
|
||||||
// StreamAccumulator preserves fragments from both.
|
// StreamAccumulator preserves fragments from both.
|
||||||
type Delta struct {
|
type Delta struct {
|
||||||
// Content is the next fragment of message text.
|
// Content is the next fragment of message text.
|
||||||
Content string `json:"content,omitempty"`
|
Content string `json:"content,omitzero"`
|
||||||
// Reasoning is the next reasoning fragment under the reasoning field
|
// Reasoning is the next reasoning fragment under the reasoning field
|
||||||
// name.
|
// name.
|
||||||
Reasoning string `json:"reasoning,omitempty"`
|
Reasoning string `json:"reasoning,omitzero"`
|
||||||
// ReasoningContent is the next reasoning fragment under the
|
// ReasoningContent is the next reasoning fragment under the
|
||||||
// reasoning_content field name.
|
// reasoning_content field name.
|
||||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
ReasoningContent string `json:"reasoning_content,omitzero"`
|
||||||
// Refusal is the next fragment of refusal text.
|
// Refusal is the next fragment of refusal text.
|
||||||
Refusal string `json:"refusal,omitempty"`
|
Refusal string `json:"refusal,omitzero"`
|
||||||
// Role identifies the message role when supplied by the endpoint.
|
// Role identifies the message role when supplied by the endpoint.
|
||||||
Role Role `json:"role,omitempty"`
|
Role Role `json:"role,omitzero"`
|
||||||
// ToolCalls contains the next fragments of one or more tool calls.
|
// ToolCalls contains the next fragments of one or more tool calls.
|
||||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
ToolCalls []ToolCall `json:"tool_calls,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileInput identifies file content either inline through FileData or by a
|
// FileInput identifies file content either inline through FileData or by a
|
||||||
// server-assigned FileID.
|
// server-assigned FileID.
|
||||||
type FileInput struct {
|
type FileInput struct {
|
||||||
// FileData is the base64-encoded contents of an inline file.
|
// FileData is the base64-encoded contents of an inline file.
|
||||||
FileData string `json:"file_data,omitempty"`
|
FileData string `json:"file_data,omitzero"`
|
||||||
// FileID references a file previously uploaded to the server.
|
// FileID references a file previously uploaded to the server.
|
||||||
FileID string `json:"file_id,omitempty"`
|
FileID string `json:"file_id,omitzero"`
|
||||||
// Filename names an inline file supplied through FileData.
|
// Filename names an inline file supplied through FileData.
|
||||||
Filename string `json:"filename,omitempty"`
|
Filename string `json:"filename,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function identifies a called function and carries its arguments as a JSON
|
// Function identifies a called function and carries its arguments as a JSON
|
||||||
@@ -428,15 +470,15 @@ type FileInput struct {
|
|||||||
// before invoking the function.
|
// before invoking the function.
|
||||||
type Function struct {
|
type Function struct {
|
||||||
// Arguments is the JSON-encoded argument object generated by the model.
|
// Arguments is the JSON-encoded argument object generated by the model.
|
||||||
Arguments string `json:"arguments,omitempty"`
|
Arguments string `json:"arguments,omitzero"`
|
||||||
// Name identifies the function to call.
|
// Name identifies the function to call.
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionDef declares a function the model may call.
|
// FunctionDef declares a function the model may call.
|
||||||
type FunctionDef struct {
|
type FunctionDef struct {
|
||||||
// Description states what the function does and when to call it.
|
// Description states what the function does and when to call it.
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitzero"`
|
||||||
// Name identifies the function.
|
// Name identifies the function.
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
// Parameters is a JSON Schema describing the arguments accepted by the
|
// Parameters is a JSON Schema describing the arguments accepted by the
|
||||||
@@ -444,7 +486,7 @@ type FunctionDef struct {
|
|||||||
Parameters any `json:"parameters"`
|
Parameters any `json:"parameters"`
|
||||||
// Strict requests exact conformance to Parameters, where the server
|
// Strict requests exact conformance to Parameters, where the server
|
||||||
// supports it.
|
// supports it.
|
||||||
Strict bool `json:"strict,omitempty"`
|
Strict bool `json:"strict,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageDetail selects the fidelity used when processing an image. Auto lets the
|
// ImageDetail selects the fidelity used when processing an image. Auto lets the
|
||||||
@@ -460,7 +502,7 @@ const (
|
|||||||
// ImageURL carries an image content part.
|
// ImageURL carries an image content part.
|
||||||
type ImageURL struct {
|
type ImageURL struct {
|
||||||
// Detail is optional; when omitted the server chooses.
|
// Detail is optional; when omitted the server chooses.
|
||||||
Detail ImageDetail `json:"detail,omitempty"`
|
Detail ImageDetail `json:"detail,omitzero"`
|
||||||
// URL is a public image URL or a base64 data URI.
|
// URL is a public image URL or a base64 data URI.
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
@@ -494,23 +536,23 @@ type InputAudio struct {
|
|||||||
type Message struct {
|
type Message struct {
|
||||||
// ContentParts holds the message content in parts; it is the
|
// ContentParts holds the message content in parts; it is the
|
||||||
// in-memory form of the JSON "content" field.
|
// in-memory form of the JSON "content" field.
|
||||||
ContentParts []ContentPart `json:"content,omitempty"`
|
ContentParts []ContentPart `json:"content,omitzero"`
|
||||||
// Name optionally identifies the sender.
|
// Name optionally identifies the sender.
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitzero"`
|
||||||
// Reasoning and ReasoningContent are alternative wire fields used by
|
// Reasoning and ReasoningContent are alternative wire fields used by
|
||||||
// compatible servers. After unmarshalling, ReasoningContent contains
|
// compatible servers. After unmarshalling, ReasoningContent contains
|
||||||
// either representation, with reasoning_content taking precedence when
|
// either representation, with reasoning_content taking precedence when
|
||||||
// both occur.
|
// both occur.
|
||||||
Reasoning string `json:"reasoning,omitempty"`
|
Reasoning string `json:"reasoning,omitzero"`
|
||||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
ReasoningContent string `json:"reasoning_content,omitzero"`
|
||||||
// Refusal contains the endpoint's refusal text, when present.
|
// Refusal contains the endpoint's refusal text, when present.
|
||||||
Refusal string `json:"refusal,omitempty"`
|
Refusal string `json:"refusal,omitzero"`
|
||||||
// Role identifies the author or purpose of the message.
|
// Role identifies the author or purpose of the message.
|
||||||
Role Role `json:"role"`
|
Role Role `json:"role"`
|
||||||
// ToolCallID links a tool response to the call it answers.
|
// ToolCallID links a tool response to the call it answers.
|
||||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
ToolCallID string `json:"tool_call_id,omitzero"`
|
||||||
// ToolCalls contains calls requested by an assistant message.
|
// ToolCalls contains calls requested by an assistant message.
|
||||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
ToolCalls []ToolCall `json:"tool_calls,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text concatenates the Text payload of every text content part in order. It
|
// Text concatenates the Text payload of every text content part in order. It
|
||||||
@@ -628,13 +670,13 @@ func (m *Message) UnmarshalJSON(data []byte) error {
|
|||||||
// [OpenAI Models API]: https://platform.openai.com/docs/api-reference/models/list
|
// [OpenAI Models API]: https://platform.openai.com/docs/api-reference/models/list
|
||||||
type Model struct {
|
type Model struct {
|
||||||
// ContextLength is the model's context window in tokens, when reported.
|
// ContextLength is the model's context window in tokens, when reported.
|
||||||
ContextLength int `json:"context_length,omitempty"`
|
ContextLength int `json:"context_length,omitzero"`
|
||||||
// Created is the model's creation time as Unix seconds.
|
// Created is the model's creation time as Unix seconds.
|
||||||
Created int64 `json:"created"`
|
Created int64 `json:"created"`
|
||||||
// ID identifies the model in inference requests.
|
// ID identifies the model in inference requests.
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
// Meta contains endpoint-specific model metadata, when reported.
|
// Meta contains endpoint-specific model metadata, when reported.
|
||||||
Meta *ModelMeta `json:"meta,omitempty"`
|
Meta *ModelMeta `json:"meta,omitzero"`
|
||||||
// Object is the endpoint's object type for the model entry.
|
// Object is the endpoint's object type for the model entry.
|
||||||
Object string `json:"object"`
|
Object string `json:"object"`
|
||||||
// OwnedBy identifies the organization that owns the model, when
|
// OwnedBy identifies the organization that owns the model, when
|
||||||
@@ -650,9 +692,9 @@ type Model struct {
|
|||||||
// [llama.cpp API documentation]: https://llama.app/docs/api
|
// [llama.cpp API documentation]: https://llama.app/docs/api
|
||||||
type ModelMeta struct {
|
type ModelMeta struct {
|
||||||
// NCtx is the context size with which llama.cpp loaded the model.
|
// NCtx is the context size with which llama.cpp loaded the model.
|
||||||
NCtx int `json:"n_ctx,omitempty"`
|
NCtx int `json:"n_ctx,omitzero"`
|
||||||
// NCtxTrain is the model's training context size.
|
// NCtxTrain is the model's training context size.
|
||||||
NCtxTrain int `json:"n_ctx_train,omitempty"`
|
NCtxTrain int `json:"n_ctx_train,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModelsResponse is the response body of GET /models.
|
// ModelsResponse is the response body of GET /models.
|
||||||
@@ -678,7 +720,7 @@ const (
|
|||||||
type ResponseFormat struct {
|
type ResponseFormat struct {
|
||||||
// JSONSchema defines the required output schema when Type is
|
// JSONSchema defines the required output schema when Type is
|
||||||
// json_schema.
|
// json_schema.
|
||||||
JSONSchema any `json:"json_schema,omitempty"`
|
JSONSchema any `json:"json_schema,omitzero"`
|
||||||
// Type selects text, json_object, or json_schema output.
|
// Type selects text, json_object, or json_schema output.
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
@@ -695,29 +737,29 @@ type StreamChunk struct {
|
|||||||
Delta Delta `json:"delta"`
|
Delta Delta `json:"delta"`
|
||||||
// FinishReason is the reported reason for ending this choice;
|
// FinishReason is the reported reason for ending this choice;
|
||||||
// it does not establish receipt of DONE.
|
// it does not establish receipt of DONE.
|
||||||
FinishReason *string `json:"finish_reason,omitempty"`
|
FinishReason *string `json:"finish_reason,omitzero"`
|
||||||
// Index identifies the choice updated by this delta. Nil means
|
// Index identifies the choice updated by this delta. Nil means
|
||||||
// unreported and selects the first choice for accumulation.
|
// unreported and selects the first choice for accumulation.
|
||||||
Index *int `json:"index,omitempty"`
|
Index *int `json:"index,omitzero"`
|
||||||
// Logprobs contains the next content and refusal token
|
// Logprobs contains the next content and refusal token
|
||||||
// probabilities.
|
// probabilities.
|
||||||
Logprobs *Logprobs `json:"logprobs,omitempty"`
|
Logprobs *Logprobs `json:"logprobs,omitzero"`
|
||||||
} `json:"choices"`
|
} `json:"choices"`
|
||||||
// Created is the endpoint's completion creation time in Unix seconds.
|
// Created is the endpoint's completion creation time in Unix seconds.
|
||||||
Created *int64 `json:"created,omitempty"`
|
Created *int64 `json:"created,omitzero"`
|
||||||
// ID identifies the completion in the response body, not a request
|
// ID identifies the completion in the response body, not a request
|
||||||
// header.
|
// header.
|
||||||
ID *string `json:"id,omitempty"`
|
ID *string `json:"id,omitzero"`
|
||||||
// Model identifies the model generating the response.
|
// Model identifies the model generating the response.
|
||||||
Model *string `json:"model,omitempty"`
|
Model *string `json:"model,omitzero"`
|
||||||
// ServiceTier is the processing tier reported by the endpoint.
|
// ServiceTier is the processing tier reported by the endpoint.
|
||||||
ServiceTier *string `json:"service_tier,omitempty"`
|
ServiceTier *string `json:"service_tier,omitzero"`
|
||||||
// SystemFingerprint identifies the reported backend configuration.
|
// SystemFingerprint identifies the reported backend configuration.
|
||||||
SystemFingerprint *string `json:"system_fingerprint,omitempty"`
|
SystemFingerprint *string `json:"system_fingerprint,omitzero"`
|
||||||
// Timings contains llama.cpp performance data, when reported.
|
// Timings contains llama.cpp performance data, when reported.
|
||||||
Timings *Timings `json:"timings,omitempty"`
|
Timings *Timings `json:"timings,omitzero"`
|
||||||
// Usage contains aggregate token counts, when reported.
|
// Usage contains aggregate token counts, when reported.
|
||||||
Usage *Usage `json:"usage,omitempty"`
|
Usage *Usage `json:"usage,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamOptions configures a streaming response.
|
// StreamOptions configures a streaming response.
|
||||||
@@ -725,7 +767,7 @@ type StreamOptions struct {
|
|||||||
// IncludeUsage requests an additional chunk containing aggregate token
|
// IncludeUsage requests an additional chunk containing aggregate token
|
||||||
// usage before the terminal data marker. If the stream is interrupted,
|
// usage before the terminal data marker. If the stream is interrupted,
|
||||||
// that chunk may not arrive.
|
// that chunk may not arrive.
|
||||||
IncludeUsage bool `json:"include_usage,omitempty"`
|
IncludeUsage bool `json:"include_usage,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToolCall is one tool call requested by the model.
|
// ToolCall is one tool call requested by the model.
|
||||||
@@ -734,10 +776,10 @@ type ToolCall struct {
|
|||||||
Function Function `json:"function"`
|
Function Function `json:"function"`
|
||||||
// ID identifies the call. The corresponding tool response carries the
|
// ID identifies the call. The corresponding tool response carries the
|
||||||
// same value in Message.ToolCallID.
|
// same value in Message.ToolCallID.
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitzero"`
|
||||||
// Index identifies the call whose fragments a streaming delta
|
// Index identifies the call whose fragments a streaming delta
|
||||||
// contains. It is omitted from non-streaming responses.
|
// contains. It is omitted from non-streaming responses.
|
||||||
Index *int `json:"index,omitempty"`
|
Index *int `json:"index,omitzero"`
|
||||||
// Type identifies the kind of tool being called.
|
// Type identifies the kind of tool being called.
|
||||||
Type ToolType `json:"type"`
|
Type ToolType `json:"type"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,17 @@ func TestContentPartMarshalJSON(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: `{"refusal":"cannot comply","type":"refusal"}`,
|
want: `{"refusal":"cannot comply","type":"refusal"}`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// The payload field is required, also when it is empty.
|
||||||
|
name: "empty text",
|
||||||
|
part: ContentPart{Type: ContentTypeText},
|
||||||
|
want: `{"text":"","type":"text"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty refusal",
|
||||||
|
part: ContentPart{Type: ContentTypeRefusal},
|
||||||
|
want: `{"refusal":"","type":"refusal"}`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "image URL",
|
name: "image URL",
|
||||||
part: ContentPart{
|
part: ContentPart{
|
||||||
@@ -157,3 +168,102 @@ func TestMessageUnmarshalJSONReplacesArrayContent(t *testing.T) {
|
|||||||
t.Errorf("Message.Text() = %q, want %q", got, "new")
|
t.Errorf("Message.Text() = %q, want %q", got, "new")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestMessageEmptyTextRoundTrip is a regression test. ContentPart.Text used
|
||||||
|
// the omitzero option, so a text part with empty text marshaled as
|
||||||
|
// {"type":"text"}, without a text key. The message was stored in that form
|
||||||
|
// and sent to the model server on the next inference, and the server
|
||||||
|
// rejected the request: the OpenAI API requires the text field, and
|
||||||
|
// llama.cpp fails to parse the part. MarshalJSON now always writes the
|
||||||
|
// payload field. Rows stored in the old form are corrected when they are
|
||||||
|
// decoded and encoded again.
|
||||||
|
func TestMessageEmptyTextRoundTrip(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// Model replies that contain only tool calls can send
|
||||||
|
// an empty content string.
|
||||||
|
name: "empty content string",
|
||||||
|
input: `{"role":"assistant","content":""}`,
|
||||||
|
want: `{"content":[{"text":"","type":"text"}],"role":"assistant"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// The UI sends this when the user edits a message's
|
||||||
|
// text to empty.
|
||||||
|
name: "empty text part",
|
||||||
|
input: `{"role":"user","content":[{"type":"text","text":""}]}`,
|
||||||
|
want: `{"content":[{"text":"","type":"text"}],"role":"user"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Rows stored before the fix have this form.
|
||||||
|
name: "text part without text key",
|
||||||
|
input: `{"role":"user","content":[{"type":"text"}]}`,
|
||||||
|
want: `{"content":[{"text":"","type":"text"}],"role":"user"}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var message Message
|
||||||
|
if err := json.Unmarshal([]byte(tt.input), &message); err != nil {
|
||||||
|
t.Fatalf("json.Unmarshal() error = %v", err)
|
||||||
|
}
|
||||||
|
got, err := json.Marshal(message)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("json.Marshal() error = %v", err)
|
||||||
|
}
|
||||||
|
if string(got) != tt.want {
|
||||||
|
t.Errorf("json.Marshal() = %s, want %s", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestResponseFormatMarshalJSON records the schema-omission policy of
|
||||||
|
// ResponseFormat. The omitzero option drops only a nil schema. An empty
|
||||||
|
// JSON schema object is valid and permits any output, so it must be
|
||||||
|
// sent, not dropped. The omitempty option would drop it.
|
||||||
|
func TestResponseFormatMarshalJSON(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
format ResponseFormat
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil schema",
|
||||||
|
format: ResponseFormat{Type: "json_object"},
|
||||||
|
want: `{"type":"json_object"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "schema object",
|
||||||
|
format: ResponseFormat{
|
||||||
|
JSONSchema: map[string]any{"name": "calendar"},
|
||||||
|
Type: "json_schema",
|
||||||
|
},
|
||||||
|
want: `{"json_schema":{"name":"calendar"},"type":"json_schema"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty schema object",
|
||||||
|
format: ResponseFormat{
|
||||||
|
JSONSchema: map[string]any{},
|
||||||
|
Type: "json_schema",
|
||||||
|
},
|
||||||
|
want: `{"json_schema":{},"type":"json_schema"}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := json.Marshal(tt.format)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("json.Marshal() error = %v", err)
|
||||||
|
}
|
||||||
|
if string(got) != tt.want {
|
||||||
|
t.Errorf("json.Marshal() = %s, want %s", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user