Update go version

This commit is contained in:
dwrz
2026-08-21 10:23:37 +00:00
parent 78248a6145
commit c2e2d9ea02
466 changed files with 67766 additions and 2881 deletions

View File

@@ -2,3 +2,11 @@ coverage:
ignore:
- "examples/**"
- "internal/test/**"
status:
project:
default:
target: auto
threshold: 2%
patch:
default:
target: 80%

View File

@@ -19,4 +19,6 @@
.idea
# Generated by tests
test.mp3
test.mp3
.env

View File

@@ -22,7 +22,7 @@ linters:
- gocyclo
- godot
- gomoddirectives
- gomodguard
- gomodguard_v2
- goprintffuncname
- gosec
- govet
@@ -66,21 +66,20 @@ linters:
paramsOnly: false
underef:
skipRecvDeref: false
gomodguard:
gomodguard_v2:
blocked:
modules:
- github.com/golang/protobuf:
recommendations:
- google.golang.org/protobuf
reason: see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules
- github.com/satori/go.uuid:
recommendations:
- github.com/google/uuid
reason: satori's package is not maintained
- github.com/gofrs/uuid:
recommendations:
- github.com/google/uuid
reason: 'see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw'
- module: github.com/golang/protobuf
recommendations:
- google.golang.org/protobuf
reason: see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules
- module: github.com/satori/go.uuid
recommendations:
- github.com/google/uuid
reason: satori's package is not maintained
- module: github.com/gofrs/uuid
recommendations:
- github.com/google/uuid
reason: 'see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw'
govet:
disable:
- fieldalignment

File diff suppressed because it is too large Load Diff

View File

@@ -11,9 +11,13 @@ import (
utils "github.com/sashabaranov/go-openai/internal"
)
// Whisper Defines the models provided by OpenAI to use when processing audio with OpenAI.
// Audio transcription models provided by OpenAI.
const (
Whisper1 = "whisper-1"
Whisper1 = "whisper-1"
GPT4oTranscribe = "gpt-4o-transcribe"
GPT4oMiniTranscribe = "gpt-4o-mini-transcribe"
GPT4oTranscribeDiarize = "gpt-4o-transcribe-diarize"
GPTTranscribe = "gpt-transcribe"
)
// Response formats; Whisper uses AudioResponseFormatJSON by default.

View File

@@ -17,6 +17,7 @@ const (
BatchEndpointChatCompletions BatchEndpoint = "/v1/chat/completions"
BatchEndpointCompletions BatchEndpoint = "/v1/completions"
BatchEndpointEmbeddings BatchEndpoint = "/v1/embeddings"
BatchEndpointResponses BatchEndpoint = "/v1/responses"
)
type BatchLineItem interface {
@@ -54,6 +55,18 @@ type BatchEmbeddingRequest struct {
URL BatchEndpoint `json:"url"`
}
type BatchResponseRequest struct {
CustomID string `json:"custom_id"`
Body CreateResponseRequest `json:"body"`
Method string `json:"method"`
URL BatchEndpoint `json:"url"`
}
func (r BatchResponseRequest) MarshalBatchLineItem() []byte {
marshal, _ := json.Marshal(r)
return marshal
}
func (r BatchEmbeddingRequest) MarshalBatchLineItem() []byte {
marshal, _ := json.Marshal(r)
return marshal
@@ -146,7 +159,7 @@ func (r *UploadBatchFileRequest) AddChatCompletion(customerID string, body ChatC
r.Lines = append(r.Lines, BatchChatCompletionRequest{
CustomID: customerID,
Body: body,
Method: "POST",
Method: http.MethodPost,
URL: BatchEndpointChatCompletions,
})
}
@@ -155,7 +168,7 @@ func (r *UploadBatchFileRequest) AddCompletion(customerID string, body Completio
r.Lines = append(r.Lines, BatchCompletionRequest{
CustomID: customerID,
Body: body,
Method: "POST",
Method: http.MethodPost,
URL: BatchEndpointCompletions,
})
}
@@ -164,11 +177,20 @@ func (r *UploadBatchFileRequest) AddEmbedding(customerID string, body EmbeddingR
r.Lines = append(r.Lines, BatchEmbeddingRequest{
CustomID: customerID,
Body: body,
Method: "POST",
Method: http.MethodPost,
URL: BatchEndpointEmbeddings,
})
}
func (r *UploadBatchFileRequest) AddResponse(customerID string, body CreateResponseRequest) {
r.Lines = append(r.Lines, BatchResponseRequest{
CustomID: customerID,
Body: body,
Method: http.MethodPost,
URL: BatchEndpointResponses,
})
}
// UploadBatchFile — upload batch file.
func (c *Client) UploadBatchFile(ctx context.Context, request UploadBatchFileRequest) (File, error) {
if request.FileName == "" {

View File

@@ -265,6 +265,7 @@ type ChatCompletionRequest struct {
Messages []ChatCompletionMessage `json:"messages"`
// MaxTokens The maximum number of tokens that can be generated in the chat completion.
// This value can be used to control costs for text generated via API.
//
// Deprecated: use MaxCompletionTokens. Not compatible with o1-series models.
// refs: https://platform.openai.com/docs/api-reference/chat/create#chat-create-max_tokens
MaxTokens int `json:"max_tokens,omitempty"`
@@ -274,7 +275,7 @@ type ChatCompletionRequest struct {
Temperature float32 `json:"temperature,omitempty"`
TopP float32 `json:"top_p,omitempty"`
N int `json:"n,omitempty"`
Stream bool `json:"stream,omitempty"`
Stream bool `json:"stream"`
Stop []string `json:"stop,omitempty"`
PresencePenalty float32 `json:"presence_penalty,omitempty"`
ResponseFormat *ChatCompletionResponseFormat `json:"response_format,omitempty"`
@@ -346,12 +347,45 @@ type StreamOptions struct {
type ToolType string
const (
ToolTypeFunction ToolType = "function"
ToolTypeFunction ToolType = "function"
ToolTypeWebSearch ToolType = "web_search"
ToolTypeWebSearchPreview ToolType = "web_search_preview"
ToolTypeFileSearch ToolType = "file_search"
ToolTypeComputer ToolType = "computer"
ToolTypeComputerUsePreview ToolType = "computer_use_preview"
ToolTypeComputerUse ToolType = "computer_use"
ToolTypeCodeInterpreter ToolType = "code_interpreter"
ToolTypeImageGeneration ToolType = "image_generation"
ToolTypeMCP ToolType = "mcp"
ToolTypeCustom ToolType = "custom"
ToolTypeLocalShell ToolType = "local_shell"
ToolTypeShell ToolType = "shell"
ToolTypeApplyPatch ToolType = "apply_patch"
ToolTypeToolSearch ToolType = "tool_search"
)
type Tool struct {
Type ToolType `json:"type"`
Function *FunctionDefinition `json:"function,omitempty"`
// Parameters contains Responses API tool properties that are serialized next to type.
// For example: {"search_context_size": "low"} for a web search tool.
Parameters map[string]any `json:"-"`
}
// MarshalJSON preserves the nested Chat Completions function format while also
// supporting the inline properties used by built-in and function tools in the Responses API.
func (t Tool) MarshalJSON() ([]byte, error) {
tool := map[string]any{"type": t.Type}
if t.Function != nil {
tool["function"] = t.Function
}
for key, value := range t.Parameters {
if key == "type" || key == "function" {
return nil, errors.New("tool parameters cannot override type or function")
}
tool[key] = value
}
return json.Marshal(tool)
}
type ToolChoice struct {

View File

@@ -257,9 +257,9 @@ func withModel(model string) fullURLOption {
}
var azureDeploymentsEndpoints = []string{
"/completions",
completionsSuffix,
"/embeddings",
"/chat/completions",
chatCompletionsSuffix,
"/audio/transcriptions",
"/audio/translations",
"/audio/speech",

View File

@@ -5,10 +5,9 @@ import (
"net/http"
)
// GPT3 Defines the models provided by OpenAI to use when generating
// completions from OpenAI.
// GPT3 Models are designed for text-based tasks. For code-specific
// tasks, please refer to the Codex series of models.
const completionsSuffix = "/completions"
// Text generation and reasoning models provided by OpenAI.
const (
O1Mini = "o1-mini"
O1Mini20240912 = "o1-mini-2024-09-12"
@@ -20,8 +19,11 @@ const (
O320250416 = "o3-2025-04-16"
O3Mini = "o3-mini"
O3Mini20250131 = "o3-mini-2025-01-31"
O3Pro = "o3-pro"
O3DeepResearch = "o3-deep-research"
O4Mini = "o4-mini"
O4Mini20250416 = "o4-mini-2025-04-16"
O4MiniDeepResearch = "o4-mini-deep-research"
GPT432K0613 = "gpt-4-32k-0613"
GPT432K0314 = "gpt-4-32k-0314"
GPT432K = "gpt-4-32k"
@@ -52,7 +54,30 @@ const (
GPT5 = "gpt-5"
GPT5Mini = "gpt-5-mini"
GPT5Nano = "gpt-5-nano"
GPT5Pro = "gpt-5-pro"
GPT5ChatLatest = "gpt-5-chat-latest"
GPT5Codex = "gpt-5-codex"
GPT5Dot1 = "gpt-5.1"
GPT5Dot1ChatLatest = "gpt-5.1-chat-latest"
GPT5Dot1Codex = "gpt-5.1-codex"
GPT5Dot1CodexMini = "gpt-5.1-codex-mini"
GPT5Dot1CodexMax = "gpt-5.1-codex-max"
GPT5Dot2 = "gpt-5.2"
GPT5Dot2ChatLatest = "gpt-5.2-chat-latest"
GPT5Dot2Pro = "gpt-5.2-pro"
GPT5Dot2Codex = "gpt-5.2-codex"
GPT5Dot3ChatLatest = "gpt-5.3-chat-latest"
GPT5Dot3Codex = "gpt-5.3-codex"
GPT5Dot4 = "gpt-5.4"
GPT5Dot4Mini = "gpt-5.4-mini"
GPT5Dot4Nano = "gpt-5.4-nano"
GPT5Dot4Pro = "gpt-5.4-pro"
GPT5Dot5 = "gpt-5.5"
GPT5Dot5Pro = "gpt-5.5-pro"
GPT5Dot6 = "gpt-5.6"
GPT5Dot6Sol = "gpt-5.6-sol"
GPT5Dot6Terra = "gpt-5.6-terra"
GPT5Dot6Luna = "gpt-5.6-luna"
GPT3Dot5Turbo0125 = "gpt-3.5-turbo-0125"
GPT3Dot5Turbo1106 = "gpt-3.5-turbo-1106"
GPT3Dot5Turbo0613 = "gpt-3.5-turbo-0613"
@@ -100,15 +125,18 @@ const (
)
var disabledModelsForEndpoints = map[string]map[string]bool{
"/completions": {
completionsSuffix: {
O1Mini: true,
O1Mini20240912: true,
O1Preview: true,
O1Preview20240912: true,
O3Mini: true,
O3Mini20250131: true,
O3Pro: true,
O3DeepResearch: true,
O4Mini: true,
O4Mini20250416: true,
O4MiniDeepResearch: true,
O3: true,
O320250416: true,
GPT3Dot5Turbo: true,
@@ -149,7 +177,30 @@ var disabledModelsForEndpoints = map[string]map[string]bool{
GPT5: true,
GPT5Mini: true,
GPT5Nano: true,
GPT5Pro: true,
GPT5ChatLatest: true,
GPT5Codex: true,
GPT5Dot1: true,
GPT5Dot1ChatLatest: true,
GPT5Dot1Codex: true,
GPT5Dot1CodexMini: true,
GPT5Dot1CodexMax: true,
GPT5Dot2: true,
GPT5Dot2ChatLatest: true,
GPT5Dot2Pro: true,
GPT5Dot2Codex: true,
GPT5Dot3ChatLatest: true,
GPT5Dot3Codex: true,
GPT5Dot4: true,
GPT5Dot4Mini: true,
GPT5Dot4Nano: true,
GPT5Dot4Pro: true,
GPT5Dot5: true,
GPT5Dot5Pro: true,
GPT5Dot6: true,
GPT5Dot6Sol: true,
GPT5Dot6Terra: true,
GPT5Dot6Luna: true,
},
chatCompletionsSuffix: {
CodexCodeDavinci002: true,
@@ -218,7 +269,7 @@ type CompletionRequest struct {
PresencePenalty float32 `json:"presence_penalty,omitempty"`
Seed *int `json:"seed,omitempty"`
Stop []string `json:"stop,omitempty"`
Stream bool `json:"stream,omitempty"`
Stream bool `json:"stream"`
Suffix string `json:"suffix,omitempty"`
Temperature float32 `json:"temperature,omitempty"`
TopP float32 `json:"top_p,omitempty"`
@@ -269,7 +320,7 @@ func (c *Client) CreateCompletion(
return
}
urlSuffix := "/completions"
urlSuffix := completionsSuffix
if !checkEndpointSupportsModel(urlSuffix, request.Model) {
err = ErrCompletionUnsupportedModel
return

View File

@@ -3,6 +3,7 @@ package openai
import (
"net/http"
"regexp"
"strings"
)
const (
@@ -70,7 +71,11 @@ func DefaultAzureConfig(apiKey, baseURL string) ClientConfig {
APIType: APITypeAzure,
APIVersion: "2023-05-15",
AzureModelMapperFunc: func(model string) string {
return regexp.MustCompile(`[.:]`).ReplaceAllString(model, "")
// only 3.5 models have the "." stripped in their names
if strings.Contains(model, "3.5") {
return regexp.MustCompile(`[.:]`).ReplaceAllString(model, "")
}
return strings.ReplaceAll(model, ":", "")
},
HTTPClient: &http.Client{},

View File

@@ -111,6 +111,7 @@ func (c *Client) CreateFineTune(ctx context.Context, request FineTuneRequest) (r
}
// CancelFineTune cancel a fine-tune job.
//
// Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API.
// This API will be officially deprecated on January 4th, 2024.
// OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go.

View File

@@ -18,7 +18,7 @@ const (
CreateImageSize1792x1024 = "1792x1024"
CreateImageSize1024x1792 = "1024x1792"
// gpt-image-1 supported only.
// GPT Image models only.
CreateImageSize1536x1024 = "1536x1024" // Landscape
CreateImageSize1024x1536 = "1024x1536" // Portrait
)
@@ -30,16 +30,20 @@ const (
)
const (
CreateImageModelDallE2 = "dall-e-2"
CreateImageModelDallE3 = "dall-e-3"
CreateImageModelGptImage1 = "gpt-image-1"
CreateImageModelDallE2 = "dall-e-2"
CreateImageModelDallE3 = "dall-e-3"
CreateImageModelGptImage1 = "gpt-image-1"
CreateImageModelGptImage1Mini = "gpt-image-1-mini"
CreateImageModelGptImage1Dot5 = "gpt-image-1.5"
CreateImageModelGptImage2 = "gpt-image-2"
CreateImageModelChatGPTImageLatest = "chatgpt-image-latest"
)
const (
CreateImageQualityHD = "hd"
CreateImageQualityStandard = "standard"
// gpt-image-1 only.
// GPT Image models only.
CreateImageQualityHigh = "high"
CreateImageQualityMedium = "medium"
CreateImageQualityLow = "low"
@@ -52,18 +56,18 @@ const (
)
const (
// gpt-image-1 only.
// GPT Image models only.
CreateImageBackgroundTransparent = "transparent"
CreateImageBackgroundOpaque = "opaque"
)
const (
// gpt-image-1 only.
// GPT Image models only.
CreateImageModerationLow = "low"
)
const (
// gpt-image-1 only.
// GPT Image models only.
CreateImageOutputFormatPNG = "png"
CreateImageOutputFormatJPEG = "jpeg"
CreateImageOutputFormatWEBP = "webp"
@@ -195,6 +199,27 @@ func (c *Client) CreateEditImage(ctx context.Context, request ImageEditRequest)
return
}
if request.Model != "" {
err = builder.WriteField("model", request.Model)
if err != nil {
return
}
}
if request.Quality != "" {
err = builder.WriteField("quality", request.Quality)
if err != nil {
return
}
}
if request.User != "" {
err = builder.WriteField("user", request.User)
if err != nil {
return
}
}
err = builder.WriteField("n", strconv.Itoa(request.N))
if err != nil {
return

View File

@@ -3,6 +3,7 @@ package openai
import (
"fmt"
"io"
"mime"
"mime/multipart"
"net/textproto"
"os"
@@ -50,6 +51,19 @@ func (fb *DefaultFormBuilder) CreateFormFileReader(fieldname string, r io.Reader
if f, ok := r.(interface{ ContentType() string }); ok {
contentType = f.ContentType()
}
// The OpenAI API rejects the file part when Content-Type is
// missing, so when the reader doesn't supply one and we can't
// derive it from the filename extension, fall back to the same
// default the stdlib's multipart.Writer.CreateFormFile uses.
// See #1010.
if contentType == "" {
if ext := filepath.Ext(filename); ext != "" {
contentType = mime.TypeByExtension(ext)
}
if contentType == "" {
contentType = "application/octet-stream"
}
}
h := make(textproto.MIMEHeader)
h.Set(
@@ -60,10 +74,7 @@ func (fb *DefaultFormBuilder) CreateFormFileReader(fieldname string, r io.Reader
escapeQuotes(filepath.Base(filename)),
),
)
// content type is optional, but it can be set
if contentType != "" {
h.Set("Content-Type", contentType)
}
h.Set("Content-Type", contentType)
fieldWriter, err := fb.writer.CreatePart(h)
if err != nil {

View File

@@ -140,7 +140,7 @@ func reflectSchema(t reflect.Type, defs map[string]Definition) (*Definition, err
return nil, err
}
d = *object
case reflect.Ptr:
case reflect.Pointer:
definition, err := reflectSchema(t.Elem(), defs)
if err != nil {
return nil, err

597
vendor/github.com/sashabaranov/go-openai/response.go generated vendored Normal file
View File

@@ -0,0 +1,597 @@
package openai
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
)
const responsesSuffix = "/responses"
var ErrResponseStreamNotSupported = errors.New(
"streaming is not supported with this method, please use CreateResponseStream",
)
// ResponseInclude identifies optional data to include in a response.
type ResponseInclude string
const (
ResponseIncludeFileSearchCallResults ResponseInclude = "file_search_call.results"
ResponseIncludeWebSearchCallResults ResponseInclude = "web_search_call.results"
ResponseIncludeWebSearchCallActionSources ResponseInclude = "web_search_call.action.sources"
ResponseIncludeInputImageURL ResponseInclude = "message.input_image.image_url"
ResponseIncludeComputerCallOutputImageURL ResponseInclude = "computer_call_output.output.image_url"
ResponseIncludeCodeInterpreterCallOutputs ResponseInclude = "code_interpreter_call.outputs"
ResponseIncludeReasoningEncryptedContent ResponseInclude = "reasoning.encrypted_content"
ResponseIncludeMessageOutputTextLogprobs ResponseInclude = "message.output_text.logprobs"
)
// ResponseStatus is the lifecycle status of a response.
type ResponseStatus string
const (
ResponseStatusQueued ResponseStatus = "queued"
ResponseStatusInProgress ResponseStatus = "in_progress"
ResponseStatusCompleted ResponseStatus = "completed"
ResponseStatusFailed ResponseStatus = "failed"
ResponseStatusIncomplete ResponseStatus = "incomplete"
ResponseStatusCancelling ResponseStatus = "cancelling"
ResponseStatusCancelled ResponseStatus = "cancelled"
)
// ResponseTruncation controls how input that exceeds the context window is handled.
type ResponseTruncation string
const (
ResponseTruncationAuto ResponseTruncation = "auto"
ResponseTruncationDisabled ResponseTruncation = "disabled"
)
// ResponseTool is an alias for Tool. Responses API tool-specific properties can
// be supplied through Tool.Parameters.
type ResponseTool = Tool
// NewResponseFunctionTool converts a function definition to the inline function
// tool representation expected by the Responses API.
func NewResponseFunctionTool(function FunctionDefinition) ResponseTool {
parameters := map[string]any{
"name": function.Name,
"parameters": function.Parameters,
}
if function.Description != "" {
parameters["description"] = function.Description
}
if function.Strict {
parameters["strict"] = true
}
return ResponseTool{Type: ToolTypeFunction, Parameters: parameters}
}
// ResponseReasoning represents reasoning configuration for the Responses API.
type ResponseReasoning struct {
Effort string `json:"effort,omitempty"`
GenerateSummary string `json:"generate_summary,omitempty"`
Summary string `json:"summary,omitempty"`
Context string `json:"context,omitempty"`
Mode string `json:"mode,omitempty"`
}
// ResponseStreamOptions controls Responses API streaming behavior.
type ResponseStreamOptions struct {
IncludeObfuscation *bool `json:"include_obfuscation,omitempty"`
}
// ResponseTextConfig controls plain-text or structured response output.
type ResponseTextConfig struct {
Format *ResponseTextFormat `json:"format,omitempty"`
Verbosity string `json:"verbosity,omitempty"`
}
// ResponseTextFormat describes the requested output format.
type ResponseTextFormat struct {
Type string `json:"type"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Schema any `json:"schema,omitempty"`
Strict bool `json:"strict,omitempty"`
}
// ResponsePrompt references a reusable prompt template.
type ResponsePrompt struct {
ID string `json:"id"`
Variables map[string]any `json:"variables,omitempty"`
Version string `json:"version,omitempty"`
}
// ResponsePromptCacheOptions controls prompt cache creation.
type ResponsePromptCacheOptions struct {
Mode string `json:"mode,omitempty"`
TTL string `json:"ttl,omitempty"`
}
// ResponsePromptCacheBreakpoint marks the end of a reusable prompt prefix.
type ResponsePromptCacheBreakpoint struct {
Mode string `json:"mode"`
}
// CreateResponseRequest represents a request to the Responses API. Input may be
// a string or a slice of response input items.
type CreateResponseRequest struct {
Background bool `json:"background,omitempty"`
ContextManagement []any `json:"context_management,omitempty"`
Conversation any `json:"conversation,omitempty"`
Include []ResponseInclude `json:"include,omitempty"`
Input any `json:"input"`
Instructions string `json:"instructions,omitempty"`
MaxOutputTokens int `json:"max_output_tokens,omitempty"`
MaxToolCalls int `json:"max_tool_calls,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
Model string `json:"model,omitempty"`
Moderation any `json:"moderation,omitempty"`
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
PreviousResponseID string `json:"previous_response_id,omitempty"`
Prompt *ResponsePrompt `json:"prompt,omitempty"`
PromptCacheKey string `json:"prompt_cache_key,omitempty"`
PromptCacheOptions *ResponsePromptCacheOptions `json:"prompt_cache_options,omitempty"`
PromptCacheRetention string `json:"prompt_cache_retention,omitempty"`
Reasoning *ResponseReasoning `json:"reasoning,omitempty"`
SafetyIdentifier string `json:"safety_identifier,omitempty"`
ServiceTier string `json:"service_tier,omitempty"`
Store *bool `json:"store,omitempty"`
Stream bool `json:"stream,omitempty"`
StreamOptions *ResponseStreamOptions `json:"stream_options,omitempty"`
Temperature *float32 `json:"temperature,omitempty"`
Text *ResponseTextConfig `json:"text,omitempty"`
ToolChoice any `json:"tool_choice,omitempty"`
Tools []ResponseTool `json:"tools,omitempty"`
TopLogprobs int `json:"top_logprobs,omitempty"`
TopP *float32 `json:"top_p,omitempty"`
Truncation ResponseTruncation `json:"truncation,omitempty"`
User string `json:"user,omitempty"`
ExtraBody map[string]any `json:"-"`
}
// MarshalJSON merges ExtraBody into the request payload. ExtraBody values take
// precedence over fields represented directly by CreateResponseRequest.
func (r CreateResponseRequest) MarshalJSON() ([]byte, error) {
type requestAlias CreateResponseRequest
base, err := json.Marshal(requestAlias(r))
if err != nil || len(r.ExtraBody) == 0 {
return base, err
}
var body map[string]any
if err = json.Unmarshal(base, &body); err != nil {
return nil, err
}
for key, value := range r.ExtraBody {
body[key] = value
}
return json.Marshal(body)
}
// ResponseInputMessage is a message supplied as structured input.
type ResponseInputMessage struct {
Type string `json:"type,omitempty"`
Role string `json:"role"`
Content any `json:"content"`
Status string `json:"status,omitempty"`
Phase string `json:"phase,omitempty"`
}
// ResponseInputText is a text content part in a structured input message.
type ResponseInputText struct {
Type string `json:"type"`
Text string `json:"text"`
PromptCacheBreakpoint *ResponsePromptCacheBreakpoint `json:"prompt_cache_breakpoint,omitempty"`
}
// ResponseInputImage is an image content part in a structured input message.
type ResponseInputImage struct {
Type string `json:"type"`
Detail string `json:"detail,omitempty"`
FileID string `json:"file_id,omitempty"`
ImageURL string `json:"image_url,omitempty"`
PromptCacheBreakpoint *ResponsePromptCacheBreakpoint `json:"prompt_cache_breakpoint,omitempty"`
}
// ResponseInputFile is a file content part in a structured input message.
type ResponseInputFile struct {
Type string `json:"type"`
FileData string `json:"file_data,omitempty"`
FileID string `json:"file_id,omitempty"`
FileURL string `json:"file_url,omitempty"`
Filename string `json:"filename,omitempty"`
Detail string `json:"detail,omitempty"`
PromptCacheBreakpoint *ResponsePromptCacheBreakpoint `json:"prompt_cache_breakpoint,omitempty"`
}
// ResponseFunctionCallOutput supplies the result of a prior function call.
type ResponseFunctionCallOutput struct {
Type string `json:"type"`
CallID string `json:"call_id"`
Output any `json:"output"`
Status string `json:"status,omitempty"`
}
// ResponseError is an error embedded in an otherwise successful Responses API request.
type ResponseError struct {
Code string `json:"code"`
Message string `json:"message"`
}
// ResponseIncompleteDetails explains why a response did not complete.
type ResponseIncompleteDetails struct {
Reason string `json:"reason,omitempty"`
}
// ResponseConversation identifies the conversation associated with a response.
type ResponseConversation struct {
ID string `json:"id"`
}
// ResponseUsage reports token use for a response.
type ResponseUsage struct {
InputTokens int `json:"input_tokens"`
InputTokensDetails *ResponseInputTokensDetails `json:"input_tokens_details,omitempty"`
OutputTokens int `json:"output_tokens"`
OutputTokensDetails *ResponseOutputTokensDetails `json:"output_tokens_details,omitempty"`
TotalTokens int `json:"total_tokens"`
}
// ResponseInputTokensDetails is the input-token usage breakdown.
type ResponseInputTokensDetails struct {
CachedTokens int `json:"cached_tokens"`
CacheWriteTokens int `json:"cache_write_tokens"`
}
// ResponseOutputTokensDetails is the output-token usage breakdown.
type ResponseOutputTokensDetails struct {
ReasoningTokens int `json:"reasoning_tokens"`
}
// ResponseAnnotation describes a citation or file annotation in output text.
type ResponseAnnotation struct {
Type string `json:"type"`
FileID string `json:"file_id,omitempty"`
Filename string `json:"filename,omitempty"`
Index int `json:"index,omitempty"`
StartIndex int `json:"start_index,omitempty"`
EndIndex int `json:"end_index,omitempty"`
URL string `json:"url,omitempty"`
Title string `json:"title,omitempty"`
}
// ResponseLogprob contains token log-probability information.
type ResponseLogprob struct {
Token string `json:"token"`
Bytes []int64 `json:"bytes,omitempty"`
Logprob float64 `json:"logprob"`
TopLogprobs []ResponseLogprob `json:"top_logprobs,omitempty"`
}
// ResponseOutputContent is a text or refusal content part in an output message.
type ResponseOutputContent struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
Refusal string `json:"refusal,omitempty"`
Annotations []ResponseAnnotation `json:"annotations,omitempty"`
Logprobs []ResponseLogprob `json:"logprobs,omitempty"`
}
// ResponseSummaryPart is a reasoning summary content part.
type ResponseSummaryPart struct {
Type string `json:"type"`
Text string `json:"text"`
}
// ResponseOutputItem contains the common fields shared by response output item variants.
// The top-level Output field remains []any so new variants can be consumed without a library release.
type ResponseOutputItem struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
Status string `json:"status,omitempty"`
Role string `json:"role,omitempty"`
Content []ResponseOutputContent `json:"content,omitempty"`
CallID string `json:"call_id,omitempty"`
Name string `json:"name,omitempty"`
Arguments string `json:"arguments,omitempty"`
Summary []ResponseSummaryPart `json:"summary,omitempty"`
Action any `json:"action,omitempty"`
Results any `json:"results,omitempty"`
Output any `json:"output,omitempty"`
}
// CreateResponseResponse represents a response returned by the Responses API.
type CreateResponseResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created_at"`
CompletedAt *int64 `json:"completed_at,omitempty"`
Status ResponseStatus `json:"status,omitempty"`
Error *ResponseError `json:"error,omitempty"`
IncompleteDetails *ResponseIncompleteDetails `json:"incomplete_details,omitempty"`
Instructions any `json:"instructions,omitempty"`
MaxOutputTokens *int `json:"max_output_tokens,omitempty"`
MaxToolCalls *int `json:"max_tool_calls,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
Model string `json:"model"`
Moderation any `json:"moderation,omitempty"`
Output []any `json:"output"`
OutputText string `json:"output_text,omitempty"`
ParallelToolCalls bool `json:"parallel_tool_calls,omitempty"`
PreviousResponseID string `json:"previous_response_id,omitempty"`
Reasoning *ResponseReasoning `json:"reasoning,omitempty"`
ServiceTier string `json:"service_tier,omitempty"`
Store bool `json:"store,omitempty"`
Temperature *float32 `json:"temperature,omitempty"`
Text *ResponseTextConfig `json:"text,omitempty"`
ToolChoice any `json:"tool_choice,omitempty"`
Tools []any `json:"tools,omitempty"`
TopLogprobs int `json:"top_logprobs,omitempty"`
TopP *float32 `json:"top_p,omitempty"`
Truncation ResponseTruncation `json:"truncation,omitempty"`
Usage *ResponseUsage `json:"usage,omitempty"`
Background *bool `json:"background,omitempty"`
Conversation *ResponseConversation `json:"conversation,omitempty"`
Prompt *ResponsePrompt `json:"prompt,omitempty"`
PromptCacheKey string `json:"prompt_cache_key,omitempty"`
PromptCacheOptions *ResponsePromptCacheOptions `json:"prompt_cache_options,omitempty"`
PromptCacheRetention string `json:"prompt_cache_retention,omitempty"`
SafetyIdentifier string `json:"safety_identifier,omitempty"`
User string `json:"user,omitempty"`
httpHeader
}
// GetOutputText returns the aggregated text output. It uses the API's output_text
// convenience field when present and otherwise extracts output_text content parts.
func (r CreateResponseResponse) GetOutputText() string {
if r.OutputText != "" {
return r.OutputText
}
var output strings.Builder
for _, rawItem := range r.Output {
data, err := json.Marshal(rawItem)
if err != nil {
continue
}
var item ResponseOutputItem
if err = json.Unmarshal(data, &item); err != nil {
continue
}
for _, content := range item.Content {
if content.Type == "output_text" {
output.WriteString(content.Text)
}
}
}
return output.String()
}
// RetrieveResponseOptions controls optional data returned by RetrieveResponse.
type RetrieveResponseOptions struct {
Include []ResponseInclude
IncludeObfuscation *bool
StartingAfter *int
}
// ResponseInputItemsListOptions controls pagination for ListResponseInputItems.
type ResponseInputItemsListOptions struct {
After string
Include []ResponseInclude
Limit int
Order string
}
// ResponseInputItemsList contains the input items for a response.
type ResponseInputItemsList struct {
Object string `json:"object"`
Data []any `json:"data"`
FirstID string `json:"first_id"`
LastID string `json:"last_id"`
HasMore bool `json:"has_more"`
httpHeader
}
// ResponseDeleteResponse is returned after deleting a stored response.
type ResponseDeleteResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Deleted bool `json:"deleted"`
httpHeader
}
// DeleteResponseResponse is kept as a descriptive alias for ResponseDeleteResponse.
type DeleteResponseResponse = ResponseDeleteResponse
// ResponseInputTokensResponse reports the token count for response input.
type ResponseInputTokensResponse struct {
Object string `json:"object"`
InputTokens int `json:"input_tokens"`
httpHeader
}
// ResponseInputTokensRequest contains the response input whose tokens should be counted.
type ResponseInputTokensRequest = CreateResponseRequest
// ResponseCompaction is a compacted response context.
type ResponseCompaction struct {
ID string `json:"id"`
Object string `json:"object"`
CreatedAt int64 `json:"created_at"`
Output []any `json:"output"`
Usage *ResponseUsage `json:"usage,omitempty"`
httpHeader
}
// CompactResponseRequest contains the response context to compact.
type CompactResponseRequest = CreateResponseRequest
// CreateResponse creates a non-streaming model response.
func (c *Client) CreateResponse(
ctx context.Context,
request CreateResponseRequest,
) (response CreateResponseResponse, err error) {
if request.Stream {
return response, ErrResponseStreamNotSupported
}
req, err := c.newRequest(ctx, http.MethodPost, c.fullURL(responsesSuffix), withBody(request))
if err != nil {
return response, err
}
err = c.sendRequest(req, &response)
return response, err
}
// RetrieveResponse gets a stored response by ID.
func (c *Client) RetrieveResponse(
ctx context.Context,
responseID string,
options ...RetrieveResponseOptions,
) (response CreateResponseResponse, err error) {
values := url.Values{}
if len(options) > 0 {
for _, include := range options[0].Include {
values.Add("include", string(include))
}
if options[0].IncludeObfuscation != nil {
values.Set("include_obfuscation", strconv.FormatBool(*options[0].IncludeObfuscation))
}
if options[0].StartingAfter != nil {
values.Set("starting_after", strconv.Itoa(*options[0].StartingAfter))
}
}
urlSuffix := responseResourceSuffix(responseID, "", values)
req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))
if err != nil {
return response, err
}
err = c.sendRequest(req, &response)
return response, err
}
// GetResponse is an alias for RetrieveResponse.
func (c *Client) GetResponse(
ctx context.Context,
responseID string,
options ...RetrieveResponseOptions,
) (CreateResponseResponse, error) {
return c.RetrieveResponse(ctx, responseID, options...)
}
// DeleteResponse deletes a stored response.
func (c *Client) DeleteResponse(ctx context.Context, responseID string) (response ResponseDeleteResponse, err error) {
urlSuffix := responseResourceSuffix(responseID, "", nil)
req, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(urlSuffix))
if err != nil {
return response, err
}
err = c.sendRequest(req, &response)
return response, err
}
// CancelResponse cancels a background response.
func (c *Client) CancelResponse(ctx context.Context, responseID string) (response CreateResponseResponse, err error) {
urlSuffix := responseResourceSuffix(responseID, "cancel", nil)
req, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix))
if err != nil {
return response, err
}
err = c.sendRequest(req, &response)
return response, err
}
// ListResponseInputItems lists the input items for a response.
func (c *Client) ListResponseInputItems(
ctx context.Context,
responseID string,
options ...ResponseInputItemsListOptions,
) (response ResponseInputItemsList, err error) {
values := url.Values{}
if len(options) > 0 {
if options[0].After != "" {
values.Set("after", options[0].After)
}
for _, include := range options[0].Include {
values.Add("include", string(include))
}
if options[0].Limit != 0 {
values.Set("limit", strconv.Itoa(options[0].Limit))
}
if options[0].Order != "" {
values.Set("order", options[0].Order)
}
}
urlSuffix := responseResourceSuffix(responseID, "input_items", values)
req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix))
if err != nil {
return response, err
}
err = c.sendRequest(req, &response)
return response, err
}
// CountResponseInputTokens returns the number of input tokens a request would use.
func (c *Client) CountResponseInputTokens(
ctx context.Context,
request ResponseInputTokensRequest,
) (response ResponseInputTokensResponse, err error) {
request.Stream = false
request.StreamOptions = nil
req, err := c.newRequest(
ctx,
http.MethodPost,
c.fullURL(responsesSuffix+"/input_tokens"),
withBody(request),
)
if err != nil {
return response, err
}
err = c.sendRequest(req, &response)
return response, err
}
// CompactResponse compacts a response context for use in a later request.
func (c *Client) CompactResponse(
ctx context.Context,
request CompactResponseRequest,
) (response ResponseCompaction, err error) {
request.Stream = false
request.StreamOptions = nil
req, err := c.newRequest(
ctx,
http.MethodPost,
c.fullURL(responsesSuffix+"/compact"),
withBody(request),
)
if err != nil {
return response, err
}
err = c.sendRequest(req, &response)
return response, err
}
func responseResourceSuffix(responseID, action string, values url.Values) string {
suffix := fmt.Sprintf("%s/%s", responsesSuffix, url.PathEscape(responseID))
if action != "" {
suffix += "/" + action
}
if len(values) != 0 {
suffix += "?" + values.Encode()
}
return suffix
}

View File

@@ -0,0 +1,131 @@
package openai
import (
"context"
"encoding/json"
"net/http"
)
// ResponseStreamEventType identifies an event emitted while a response is generated.
type ResponseStreamEventType string
const (
ResponseStreamEventCreated ResponseStreamEventType = "response.created"
ResponseStreamEventQueued ResponseStreamEventType = "response.queued"
ResponseStreamEventInProgress ResponseStreamEventType = "response.in_progress"
ResponseStreamEventCompleted ResponseStreamEventType = "response.completed"
ResponseStreamEventFailed ResponseStreamEventType = "response.failed"
ResponseStreamEventIncomplete ResponseStreamEventType = "response.incomplete"
ResponseStreamEventOutputItemAdded ResponseStreamEventType = "response.output_item.added"
ResponseStreamEventOutputItemDone ResponseStreamEventType = "response.output_item.done"
ResponseStreamEventContentPartAdded ResponseStreamEventType = "response.content_part.added"
ResponseStreamEventContentPartDone ResponseStreamEventType = "response.content_part.done"
ResponseStreamEventOutputTextDelta ResponseStreamEventType = "response.output_text.delta"
ResponseStreamEventOutputTextDone ResponseStreamEventType = "response.output_text.done"
ResponseStreamEventOutputTextAnnotationAdded ResponseStreamEventType = "response.output_text.annotation.added"
ResponseStreamEventRefusalDelta ResponseStreamEventType = "response.refusal.delta"
ResponseStreamEventRefusalDone ResponseStreamEventType = "response.refusal.done"
ResponseStreamEventFunctionArgumentsDelta ResponseStreamEventType = "response.function_call_arguments.delta"
ResponseStreamEventFunctionArgumentsDone ResponseStreamEventType = "response.function_call_arguments.done"
ResponseStreamEventReasoningSummaryTextDelta ResponseStreamEventType = "response.reasoning_summary_text.delta"
ResponseStreamEventReasoningSummaryTextDone ResponseStreamEventType = "response.reasoning_summary_text.done"
ResponseStreamEventReasoningSummaryPartAdded ResponseStreamEventType = "response.reasoning_summary_part.added"
ResponseStreamEventReasoningSummaryPartDone ResponseStreamEventType = "response.reasoning_summary_part.done"
ResponseStreamEventReasoningTextDelta ResponseStreamEventType = "response.reasoning_text.delta"
ResponseStreamEventReasoningTextDone ResponseStreamEventType = "response.reasoning_text.done"
ResponseStreamEventAudioDelta ResponseStreamEventType = "response.audio.delta"
ResponseStreamEventAudioDone ResponseStreamEventType = "response.audio.done"
ResponseStreamEventAudioTranscriptDelta ResponseStreamEventType = "response.audio.transcript.delta"
ResponseStreamEventAudioTranscriptDone ResponseStreamEventType = "response.audio.transcript.done"
ResponseStreamEventWebSearchInProgress ResponseStreamEventType = "response.web_search_call.in_progress"
ResponseStreamEventWebSearchSearching ResponseStreamEventType = "response.web_search_call.searching"
ResponseStreamEventWebSearchCompleted ResponseStreamEventType = "response.web_search_call.completed"
ResponseStreamEventFileSearchInProgress ResponseStreamEventType = "response.file_search_call.in_progress"
ResponseStreamEventFileSearchSearching ResponseStreamEventType = "response.file_search_call.searching"
ResponseStreamEventFileSearchCompleted ResponseStreamEventType = "response.file_search_call.completed"
ResponseStreamEventCodeInterpreterInProgress ResponseStreamEventType = "response.code_interpreter_call.in_progress"
ResponseStreamEventCodeInterpreterInterpreting ResponseStreamEventType = "response.code_interpreter_call.interpreting"
ResponseStreamEventCodeInterpreterCompleted ResponseStreamEventType = "response.code_interpreter_call.completed"
ResponseStreamEventCodeInterpreterCodeDelta ResponseStreamEventType = "response.code_interpreter_call_code.delta"
ResponseStreamEventCodeInterpreterCodeDone ResponseStreamEventType = "response.code_interpreter_call_code.done"
ResponseStreamEventCustomToolInputDelta ResponseStreamEventType = "response.custom_tool_call_input.delta"
ResponseStreamEventCustomToolInputDone ResponseStreamEventType = "response.custom_tool_call_input.done"
ResponseStreamEventImageGenerationInProgress ResponseStreamEventType = "response.image_generation_call.in_progress"
ResponseStreamEventImageGenerationGenerating ResponseStreamEventType = "response.image_generation_call.generating"
ResponseStreamEventImageGenerationCompleted ResponseStreamEventType = "response.image_generation_call.completed"
ResponseStreamEventImageGenerationPartialImage ResponseStreamEventType = "response.image_generation_call.partial_image"
ResponseStreamEventMCPCallInProgress ResponseStreamEventType = "response.mcp_call.in_progress"
ResponseStreamEventMCPCallCompleted ResponseStreamEventType = "response.mcp_call.completed"
ResponseStreamEventMCPCallFailed ResponseStreamEventType = "response.mcp_call.failed"
ResponseStreamEventMCPCallArgumentsDelta ResponseStreamEventType = "response.mcp_call_arguments.delta"
ResponseStreamEventMCPCallArgumentsDone ResponseStreamEventType = "response.mcp_call_arguments.done"
ResponseStreamEventMCPListToolsInProgress ResponseStreamEventType = "response.mcp_list_tools.in_progress"
ResponseStreamEventMCPListToolsCompleted ResponseStreamEventType = "response.mcp_list_tools.completed"
ResponseStreamEventMCPListToolsFailed ResponseStreamEventType = "response.mcp_list_tools.failed"
ResponseStreamEventError ResponseStreamEventType = "error"
)
// ResponseStreamEvent contains the common fields across Responses API SSE event variants.
type ResponseStreamEvent struct {
Type ResponseStreamEventType `json:"type"`
SequenceNumber int `json:"sequence_number,omitempty"`
Response *CreateResponseResponse `json:"response,omitempty"`
Item *ResponseOutputItem `json:"item,omitempty"`
Part *ResponseOutputContent `json:"part,omitempty"`
Annotation *ResponseAnnotation `json:"annotation,omitempty"`
ItemID string `json:"item_id,omitempty"`
OutputIndex int `json:"output_index,omitempty"`
ContentIndex int `json:"content_index,omitempty"`
SummaryIndex int `json:"summary_index,omitempty"`
Delta string `json:"delta,omitempty"`
Text string `json:"text,omitempty"`
Arguments string `json:"arguments,omitempty"`
PartialImageB64 string `json:"partial_image_b64,omitempty"`
PartialImageIndex int `json:"partial_image_index,omitempty"`
Logprobs []ResponseLogprob `json:"logprobs,omitempty"`
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
Param any `json:"param,omitempty"`
Error *ResponseError `json:"error,omitempty"`
Obfuscation string `json:"obfuscation,omitempty"`
Raw json.RawMessage `json:"-"`
}
// UnmarshalJSON decodes known event fields and retains the complete event for
// forward compatibility with event variants added by the API.
func (e *ResponseStreamEvent) UnmarshalJSON(data []byte) error {
type eventAlias ResponseStreamEvent
if err := json.Unmarshal(data, (*eventAlias)(e)); err != nil {
return err
}
e.Raw = append(e.Raw[:0], data...)
return nil
}
// ResponseStream reads server-sent events from a streaming Responses API request.
type ResponseStream struct {
*streamReader[ResponseStreamEvent]
}
// CreateResponseStream creates a response and streams its generation events.
func (c *Client) CreateResponseStream(
ctx context.Context,
request CreateResponseRequest,
) (stream *ResponseStream, err error) {
request.Stream = true
req, err := c.newRequest(
ctx,
http.MethodPost,
c.fullURL(responsesSuffix),
withBody(request),
)
if err != nil {
return nil, err
}
reader, err := sendRequestStream[ResponseStreamEvent](c, req)
if err != nil {
return nil, err
}
return &ResponseStream{streamReader: reader}, nil
}

View File

@@ -22,7 +22,7 @@ func (c *Client) CreateCompletionStream(
ctx context.Context,
request CompletionRequest,
) (stream *CompletionStream, err error) {
urlSuffix := "/completions"
urlSuffix := completionsSuffix
if !checkEndpointSupportsModel(urlSuffix, request.Model) {
err = ErrCompletionUnsupportedModel
return

View File

@@ -17,7 +17,7 @@ var (
)
type streamable interface {
ChatCompletionStreamResponse | CompletionResponse
ChatCompletionStreamResponse | CompletionResponse | ResponseStreamEvent
}
type streamReader[T streamable] struct {
@@ -64,7 +64,13 @@ func (stream *streamReader[T]) processLines() ([]byte, error) {
rawLine, readErr := stream.reader.ReadBytes('\n')
if readErr != nil || hasErrorPrefix {
respErr := stream.unmarshalError()
if respErr != nil {
// respErr.Error can be nil even when respErr itself isn't:
// any payload that round-trips through unmarshaler.Unmarshal
// (an empty object, an aborted partial frame after context
// cancellation, etc.) ends up as a non-nil *ErrorResponse with
// a nil Error field. Wrapping that produced the famously
// useless "error, <nil>" message in #1060.
if respErr != nil && respErr.Error != nil {
return nil, fmt.Errorf("error, %w", respErr.Error)
}
return nil, readErr