Skip to content
skyl

Request

A provider-agnostic model call. The same Request works on any provider.

Request is the one shape every adapter accepts. Fields a provider does not support are ignored rather than rejected — except where ignoring them would silently lose data, in which case you get ErrUnsupported.

Reference#

type Request struct
FieldTypeDescription
ModelstringThe provider's model identifier, passed through untouched. skyl never validates it against a list, so a model released after your skyl build works immediately — and a typo surfaces as the provider's own not-found error rather than a local one.
Zero value: rejected by Validate: the model is required
SystemstringThe system prompt. Adapters place it where the provider expects — a top-level field for Anthropic, a leading message for OpenAI, systemInstruction for Gemini.
Zero value: no system prompt is sent
Messages[]MessageThe conversation so far. skyl does not police role ordering: providers disagree about what is legal, and rejecting a shape one vendor accepts would be skyl deciding something it has no business deciding.
Zero value: rejected by Validate: at least one message is required
MaxTokensintCaps the response length.
Zero value: the provider's default — which for Anthropic is an error, so skyl supplies 4096 there. A negative value is rejected by Validate.
Temperature*float64Sampling temperature. A non-nil value is always sent: several current reasoning models reject it outright, and skyl does not second-guess that, because silently dropping a field you set is worse than the provider’s own error.
Zero value: nil means the provider's default; leave it nil unless you mean it
TopP*float64Nucleus sampling. Same always-sent semantics as Temperature.
Zero value: nil means the provider's default
Stop[]stringSequences that end generation.
Zero value: no stop sequences
Tools[]ToolTools the model may call. A tool with no name is rejected by Validate.
Zero value: no tools offered
ToolChoice*ToolChoiceConstrains whether and how the model may call tools: auto, none, required, or a named tool. All four modes are mapped on all four adapters.
Zero value: nil means ToolChoiceAuto
Thinking*ThinkingRequests reasoning. A nil pointer and a zero value mean different things: nil is "provider default", &Thinking{} is "explicitly off".
Zero value: nil means the provider's default
ProviderOptionsmap[string]anyAn escape hatch: arbitrary vendor-specific fields merged into the outbound payload, overriding anything skyl set. skyl does not validate the contents — that is the point.
Zero value: nothing extra is sent

Methods#

  • Validate() error — reports whether the request is well-formed. Client calls it before dispatching.

Caveats

  • Model is never validated against a list. A typo reaches the provider and returns ErrNotFound after a round trip. See Model IDs.
  • Temperature and TopP are always sent when non-nil, even to models that reject them. Silently dropping a field you set would be worse than the provider's error.
  • MaxTokens zero means the provider's default — except on Anthropic, whose API requires the field, so that adapter supplies 4096.
  • Thinking nil ≠ &Thinking{}. Nil is "provider default"; the zero value is "explicitly off".
  • ProviderOptions is a shallow top-level merge on openai, openaicompat and gemini — a nested object you set replaces the whole object. Anthropic applies options by JSON path instead.
  • Client reuses req across retries. Do not mutate it concurrently.

Usage#

A minimal request

goCompiles
req := &skyl.Request{
	Model:     "gpt-5.6",
	MaxTokens: 1024,
	Messages:  []skyl.Message{skyl.UserText("Explain Go channels.")},
}
req := &skyl.Request{
	Model:     "gpt-5.6",
	MaxTokens: 1024,
	Messages:  []skyl.Message{skyl.UserText("Explain Go channels.")},
}

With a system prompt and history

goCompiles
req := &skyl.Request{
	Model:  "claude-opus-5",
	System: "You are a terse Go expert. Answer in one sentence.",
	Messages: []skyl.Message{
		skyl.UserText("What is a nil map?"),
		skyl.AssistantText("A map that is declared but not allocated."),
		skyl.UserText("Can I read from one?"),
	},
	MaxTokens: 256,
}
req := &skyl.Request{
	Model:  "claude-opus-5",
	System: "You are a terse Go expert. Answer in one sentence.",
	Messages: []skyl.Message{
		skyl.UserText("What is a nil map?"),
		skyl.AssistantText("A map that is declared but not allocated."),
		skyl.UserText("Can I read from one?"),
	},
	MaxTokens: 256,
}

Deterministic sampling

func f(v float64) *float64 { return &v }

req.Temperature = f(0) // explicitly deterministic, NOT "unset"
func f(v float64) *float64 { return &v }

req.Temperature = f(0) // explicitly deterministic, NOT "unset"

With tools

goCompiles
req.Tools = []skyl.Tool{{
	Name:        "get_weather",
	Description: "Get the current weather for a city. Call this whenever the user asks about weather in a named place.",
	Parameters: map[string]any{
		"type":       "object",
		"properties": map[string]any{"city": map[string]any{"type": "string"}},
		"required":   []string{"city"},
	},
}}
req.ToolChoice = &skyl.ToolChoice{Mode: skyl.ToolChoiceAuto}
req.Tools = []skyl.Tool{{
	Name:        "get_weather",
	Description: "Get the current weather for a city. Call this whenever the user asks about weather in a named place.",
	Parameters: map[string]any{
		"type":       "object",
		"properties": map[string]any{"city": map[string]any{"type": "string"}},
		"required":   []string{"city"},
	},
}}
req.ToolChoice = &skyl.ToolChoice{Mode: skyl.ToolChoiceAuto}

Reaching a field skyl does not model

goCompiles
req.ProviderOptions = map[string]any{"top_k": 40}
req.ProviderOptions = map[string]any{"top_k": 40}

Troubleshooting#

ErrBadRequest before any network call

That is Validate. The message names the problem exactly — a missing model, an empty message list, an image with no media type, a tool with no name.

Temperature caused a 400 on a reasoning model

Several current reasoning models reject sampling parameters. skyl sends a non-nil value deliberately rather than dropping it silently. Leave Temperature nil unless you mean it.

My generationConfig override lost maxOutputTokens

The merge is shallow on Gemini — setting generationConfig replaces the whole object. Restate every sibling key. See Provider Options.

&Thinking{Enabled: false} did nothing on OpenAI

The OpenAI-format adapters ignore Thinking unless both Enabled and Effort are set, so an explicit "off" has no wire representation there. Send reasoning_effort through ProviderOptions instead.

Edit this page on GitHub