Help us shape the future of Go! Take the 2026 Go Developer Survey

llm

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2026 License: MIT Imports: 12 Imported by: 0

README

llm

Вызов языковой модели одним контрактом поверх нескольких плеч. Корень пакета — контракт, повторы, бюджет времени, классы отказа и наблюдатель; протокол поставщика — в подпакете. Есть ollama (нативный /api/chat), gigachat (OAuth, /files) и yandex (OpenAI-совместимый chat/completions).

c := llm.New(ollama.New("http://localhost:11434"), 90*time.Second)

res, err := c.Chat(ctx, llm.Request{
	Task:  "receipt",
	Model: "gemma4:31b-cloud",
	Messages: []llm.Message{
		{Role: llm.RoleSystem, Text: prompt},
		{Role: llm.RoleUser, Text: "скриншот", Images: []llm.Image{{MIME: "image/png", Data: png}}},
	},
	Output: llm.Output{Mode: llm.ModeJSON},
})

Что делает клиент

  • Срок стоит на попытке, не на вызове: повтор после просрочки получает полный срок. Budget() считает худший случай — его обязана отвести себе джоба.
  • Классы отказа. immediate (сеть, 5xx, 408, просрочка) и after_delay (429) клиент повторяет сам с удвоением паузы и Retry-After. needs_configuration (401, 402, 403, 404: ключ, баланс, модель) и never (прочие 4xx) — не повторяет; первый при этом восстановим (llm.Recoverable) — чинит оператор, а не запрос.
  • Просьба подождать дольше MaxRetryAfter не ждётся: отказ уходит наверх со сроком как есть. Повторяет его очередь потребителя, а не клиент, заснувший на час.
  • Наблюдатель получает отчёт на каждую попытку и на вызов: исход, фаза, длительность, расход, CallID и номер попытки. Он синхронный, без контекста и без права на ошибку — счётчики, не журнал. Журнал пишет обёртка потребителя по Result.Report и CallError.Report: отчёт есть и у отказа, расход в нём — всех попыток.
  • Расход — по попыткам и непересекающимися частями: BillableInput и CachedInput — вход, Output и Reasoning — выход, Raw — счётчики поставщика как есть. Usage.Known ложно, если хоть одна попытка генерации счётчиков не сообщила или сообщила противоречивые; счётчики тогда — нижняя граница. Известный ноль — только отказ до генерации: вход, загрузка кадров, 4xx кроме 408; у 5xx и сетевого отказа генерации расход неизвестен.
  • Обрезанный, отфильтрованный и отклонённый моделью ответ (Finish.Kind length, content_filter, refusal) — отказ never с исходом truncated/filtered/refused и расходом: он оплачен, повтор оплатил бы его ещё раз. finish_reason=error GigaChat повторяется как негодный ответ и может оплатиться повторно — не нужен повтор, ставьте Attempts = 1.
  • Уборка кадров (gigachat) идёт после срока попытки своим бюджетом CleanupBudget на попытку — Budget() его не включает. Неубранное — CleanupWarning в отчёте попытки; Uncertain — загрузки без ответа, чей файл мог остаться с неизвестным id.
  • Негодный запрос (без модели, schema без схемы) и запрос сверх профиля плеча (Provider.Capabilities: кадры, json, схема, опции) отбиваются до плеча классом never; неизвестный профиль не подтверждает ничего.
  • Result и CallError несут начало вызова и отчёты попыток с RequestID поставщика.
  • Ollama: ответ принимается только с done=true и ровно одним конвертом — первый фрагмент потока за ответ не сойдёт.

Что остаётся потребителю

Промпты и схемы, разбор ответа, пороги качества, тарифы, журнал вызовов, квоты. pricing.Estimate и pricing.EstimateCall оценивают стоимость по отчётам попыток и тарифам потребителя — оценка, а не счёт: неизвестный расход или тариф дают unknown, а не ноль. EstimateCall ждёт тарифы одного маршрута с различными ValidFrom: одинаковые разрешаются порядком аргументов. llm.Fingerprint(...) даёт идентичность вызова до его выполнения — плечо, модель, ревизия, хеш промпта, схема, подготовка входа — чтобы дедупить прогоны и не смешивать плечи.

Documentation

Overview

Package llm — вызов языковой модели одним контрактом поверх нескольких плеч: повторы, бюджет времени, классы отказа и наблюдатель — здесь, протокол поставщика — в подпакете (ollama, gigachat, yandex).

Index

Constants

View Source
const (
	DefaultTimeout       = 90 * time.Second
	DefaultAttempts      = 3
	DefaultPause         = 2 * time.Second
	DefaultMaxPause      = time.Minute
	DefaultMaxRetryAfter = 30 * time.Second
)

Умолчания клиента. Attempts × Timeout + паузы — то, что вызывающий обязан себе отвести (Client.Budget), иначе очередь оборвёт последнюю попытку.

View Source
const (
	OutcomeOK          = "ok"
	OutcomeTruncated   = "truncated"
	OutcomeFiltered    = "filtered"
	OutcomeRefused     = "refused"
	OutcomeCancelled   = "cancelled"
	OutcomeTimeout     = "timeout"
	OutcomeHTTP4xx     = "http_4xx"
	OutcomeHTTP5xx     = "http_5xx"
	OutcomeBadResponse = "bad_response"
	OutcomeBadRequest  = "bad_request"
	OutcomeNetwork     = "network"
	OutcomeError       = "error"
)

Исходы попытки — про транспорт и годность конверта, а не про контракт модели.

Variables

View Source
var (
	ErrTruncated = errors.New("ответ обрезан пределом длины")
	ErrFiltered  = errors.New("ответ остановлен фильтром")
	ErrRefused   = errors.New("модель отказалась отвечать")
)

ErrTruncated, ErrFiltered и ErrRefused — генерация кончилась пределом длины, фильтром или отказом модели: ответ оплачен, и повтор оплатил бы тот же исход ещё раз.

Functions

func Fingerprint

func Fingerprint(parts ...string) string

Fingerprint — отпечаток набора строк, из которых складывается идентичность вызова: плечо, модель, ревизия, хеш промпта, схема, опции, подготовка входа. Части кодируются длиной, поэтому границы однозначны; порядок значим. 128 бит hex.

func Latency

func Latency(err error) time.Duration

Latency — сколько длился отказавший вызов; не наш отказ даёт ноль.

func Ptr

func Ptr[T any](v T) *T

Ptr — указатель на значение для необязательных полей запроса.

func Recoverable

func Recoverable(err error) bool

Recoverable отвечает, имеет ли смысл повторить вызов позже: всё, кроме RetryNever.

func RetryAfter

func RetryAfter(h http.Header) time.Duration

RetryAfter разбирает заголовок в обеих формах: секунды числом и HTTP-дата. Отсутствующий, непонятный и отрицательный — ноль; чрезмерный насыщается.

Types

type AttemptReport

type AttemptReport struct {
	CallID         string
	Attempt        int
	Provider       string
	RequestedModel string
	Model          string
	Task           string
	Outcome        string
	Phase          Phase
	RequestID      string
	Finish         Finish
	StartedAt      time.Time
	Duration       time.Duration
	ServerLatency  time.Duration
	Usage          Usage
	Cleanup        *CleanupWarning
}

AttemptReport — одна попытка. Model и RequestID — из ответа, когда поставщик их назвал.

type CallError

type CallError struct {
	Provider   string
	Model      string
	Phase      Phase
	Status     int // код HTTP; 0 — ответа не было (сеть, просрочка, отмена)
	Message    string
	RetryAfter time.Duration
	Finish     Finish
	StartedAt  time.Time
	Latency    time.Duration
	Class      RetryClass
	Report     CallReport
	Attempts   []AttemptReport
	Err        error
}

CallError — отказ вызова целиком, с решением о повторе, потраченным временем и тем же отчётом, что получает наблюдатель.

func (*CallError) Error

func (e *CallError) Error() string

func (*CallError) Unwrap

func (e *CallError) Unwrap() error

type CallReport

type CallReport struct {
	CallID         string
	Provider       string
	RequestedModel string
	Model          string
	Task           string
	Outcome        string
	Class          RetryClass
	Attempts       int
	Duration       time.Duration
	Usage          Usage
}

CallReport — вызов целиком: исход, класс отказа, число попыток, расход всех попыток.

type Capabilities added in v0.3.0

type Capabilities struct {
	Vision              bool
	JSON                bool
	Schema              bool
	Strict              bool
	MaxImagesPerMessage int
	MaxImagesPerRequest int
	Temperature         bool
	Reasoning           bool
	MaxOutputTokens     bool
}

Capabilities — что подтверждено у сочетания плеча, модели и режима API. Предел кадров ноль — предела нет; незаявленная возможность отбивается до вызова.

func (Capabilities) Check added in v0.3.0

func (c Capabilities) Check(r Request) error

Check отбивает запрос, требующий неподтверждённого профилем; ошибка — *RequestError.

type Chat

type Chat interface {
	Chat(ctx context.Context, req Request) (Result, error)
}

Chat — узкий интерфейс потребителя: то, что делает Client.

type CleanupWarning added in v0.3.0

type CleanupWarning struct {
	Files     []string
	Uncertain int
	Err       error
}

CleanupWarning — созданное попыткой у поставщика могло остаться неубранным: генерацию это не отменяет, и повтор ради уборки не оплачивается. Files — что осталось, Err — почему; Uncertain — загрузки без ответа: файл мог создаться, а id его неизвестен.

type Client

type Client struct {
	Provider Provider
	// Timeout — на одну попытку, не на весь вызов: повтор после просрочки получает полный срок.
	Timeout  time.Duration
	Attempts int
	// Pause — базовая пауза между попытками, дальше удваивается до MaxPause.
	Pause    time.Duration
	MaxPause time.Duration
	// MaxRetryAfter — потолок просьбы подождать: дольше — отказ [RetryAfterDelay]
	// без ожидания, срок в нём исходный; повторяет его очередь потребителя.
	MaxRetryAfter time.Duration
	// Observe — приёмник отчётов; пустое поле не считает ничего.
	Observe Observer
}

Client — вызов с повторами поверх одного плеча. Поля открыты: бюджет сверяется с таймаутом джобы снаружи, а не прячется в константах.

func New

func New(p Provider, timeout time.Duration) *Client

New собирает клиента с умолчаниями бюджета.

func (*Client) Budget

func (c *Client) Budget() time.Duration

Budget — все попытки со своими таймаутами плюс худшие паузы. Уборку плеча после срока попытки (gigachat.CleanupBudget) не включает; переполнение насыщается.

func (*Client) Chat

func (c *Client) Chat(ctx context.Context, req Request) (Result, error)

Chat вызывает модель, повторяя отказы классов RetryImmediate и RetryAfterDelay в пределах числа попыток и потолка ожидания. Негодный запрос и запрос сверх профиля плеча отбиваются до вызова; обрезанный и отфильтрованный ответ — RetryNever.

type Effort added in v0.3.0

type Effort string

Effort — глубина рассуждений модели; пустое — умолчание модели.

const (
	EffortLow    Effort = "low"
	EffortMedium Effort = "medium"
	EffortHigh   Effort = "high"
)

Глубина рассуждений.

type Finish added in v0.3.0

type Finish struct {
	Raw  string
	Kind FinishKind
}

Finish — причина конца генерации: как её назвал поставщик и нормализованная.

type FinishKind added in v0.3.0

type FinishKind string

FinishKind — нормализованная причина конца генерации; пустая — поставщик не сообщил или сообщил неизвестную.

const (
	FinishStop          FinishKind = "stop"
	FinishLength        FinishKind = "length"
	FinishContentFilter FinishKind = "content_filter"
	FinishRefusal       FinishKind = "refusal"
	FinishToolCall      FinishKind = "tool_call"
)

Причины конца генерации.

type Image

type Image struct {
	MIME string
	Data []byte
}

Image — кадр при сообщении: байты и MIME, как их видит поставщик.

type Message

type Message struct {
	Role   Role
	Text   string
	Images []Image
}

Message — одно сообщение разговора; кадры едут при данных, а не при инструкции.

type Mode

type Mode string

Mode — форма ответа: свободный текст, любой JSON или JSON по схеме.

const (
	ModeText   Mode = "text"
	ModeJSON   Mode = "json"
	ModeSchema Mode = "schema"
)

Формы ответа. Схему соблюдают не все модели — контракт полей живёт и в промпте.

type Observer

type Observer interface {
	Attempt(AttemptReport)
	Call(CallReport)
}

Observer — приёмник отчётов: быстрый и потокобезопасный, зовётся синхронно внутри вызова без контекста и без права на ошибку — счётчики, не журнал. Запись в базу делает обёртка потребителя по Result.Report и CallError.Report.

type Options added in v0.3.0

type Options struct {
	Temperature     *float64
	Reasoning       Effort
	MaxOutputTokens int
}

Options — параметры генерации; нулевое значение поля — умолчание модели, не ноль.

type Output

type Output struct {
	Mode   Mode
	Schema json.RawMessage
	Name   string
	Strict bool
}

Output — требуемая форма ответа; Schema обязательна при ModeSchema. Name и Strict — только при схеме: имя схемы требует OpenAI-совместимый протокол, Strict — точное соблюдение.

type Phase

type Phase string

Phase — где отказал вызов: у облачных плеч перед генерацией стоят вход и загрузка кадров.

const (
	PhaseAuth      Phase = "auth"
	PhaseUpload    Phase = "upload"
	PhaseInference Phase = "inference"
)

Фазы вызова.

type PhaseError added in v0.2.0

type PhaseError struct {
	Phase Phase
	Err   error
}

PhaseError помечает фазой любую причину — сеть при OAuth, отказ загрузки кадра.

func (*PhaseError) Error added in v0.2.0

func (e *PhaseError) Error() string

func (*PhaseError) Unwrap added in v0.2.0

func (e *PhaseError) Unwrap() error

type Provider

type Provider interface {
	Name() string
	Capabilities(model string) (Capabilities, bool)
	Complete(ctx context.Context, req Request) (Result, error)
}

Provider — одна попытка вызова у конкретного плеча. Отказ HTTP возвращается *StatusError, негодный ответ — *ResponseError (с расходом, если он был в конверте), негодный запрос — *RequestError, сеть и контекст — как есть; неудавшаяся уборка при отказе — *WarnedError поверх любого из них. Capabilities без сети; ложное ok — профиль модели неизвестен, не подтверждено ничего.

type Request

type Request struct {
	CallID   string
	Task     string
	Model    string
	Messages []Message
	Output   Output
	Options  Options
}

Request — один вызов модели. CallID связывает отчёты попыток и вызова между собой и с журналом потребителя; Task — имя задачи для метрик, набор значений ограничен конфигом потребителя.

func (Request) Validate added in v0.2.0

func (r Request) Validate() error

Validate отбивает запрос, который поставщик не сможет собрать: без модели, с неизвестным режимом, со схемой без схемы. Ошибка — *RequestError.

type RequestError added in v0.2.0

type RequestError struct {
	Message string
	Err     error
}

RequestError — запрос не собрать: поставщика не звали, повтор бессмыслен.

func (*RequestError) Error added in v0.2.0

func (e *RequestError) Error() string

func (*RequestError) Unwrap added in v0.2.0

func (e *RequestError) Unwrap() error

type ResponseError

type ResponseError struct {
	Message       string
	Usage         Usage
	Model         string
	Finish        Finish
	RequestID     string
	ServerLatency time.Duration
	Err           error
}

ResponseError — поставщик ответил, но ответа модели в теле нет: отказ маршрута под кодом 200, битый или незаконченный конверт, пустое содержимое. Причина сохраняется: просрочка при чтении тела обязана остаться просрочкой.

func (*ResponseError) Error

func (e *ResponseError) Error() string

func (*ResponseError) Unwrap added in v0.2.0

func (e *ResponseError) Unwrap() error

type Result

type Result struct {
	// Model — имя из ответа поставщика; пустое у ответа заменяется запрошенным.
	Model  string
	Text   string
	Finish Finish
	Usage  Usage
	// RequestID — идентификатор запроса у поставщика, если он его вернул.
	RequestID string
	// ServerLatency — время на стороне поставщика, если он его сообщает; ноль — не сообщил.
	ServerLatency time.Duration
	// Cleanup — уборка удавшейся попытки не удалась; nil — убирать было нечего или убрано.
	Cleanup *CleanupWarning
	// StartedAt — начало вызова; Latency — сколько ждал вызывающий: все попытки и паузы.
	StartedAt time.Time
	Latency   time.Duration
	Report    CallReport
	Attempts  []AttemptReport
}

Result — ответ модели. Usage, Finish и ServerLatency — удавшейся попытки; расход всех попыток — в Report, тот же отчёт получает наблюдатель.

type RetryClass

type RetryClass string

RetryClass — что делать с отказом. Немедленный повтор и повтор после паузы делает клиент; NeedsConfiguration и Never клиент не повторяет, но первый остаётся восстановимым — его чинит оператор, а не запрос.

const (
	RetryImmediate          RetryClass = "immediate"
	RetryAfterDelay         RetryClass = "after_delay"
	RetryNeedsConfiguration RetryClass = "needs_configuration"
	RetryNever              RetryClass = "never"
)

Классы отказа.

type Role

type Role string

Role — роль сообщения. Инструкция и данные едут разными ролями: приехавшее с данными «забудь инструкции» иначе получило бы ту же роль, что правила.

const (
	RoleSystem    Role = "system"
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
)

Роли сообщений.

type StatusError

type StatusError struct {
	Status     int
	Message    string
	RetryAfter time.Duration
	Phase      Phase
	RequestID  string
}

StatusError — не-2xx от поставщика вместе с просьбой подождать.

func (*StatusError) Error

func (e *StatusError) Error() string

type Usage

type Usage struct {
	Raw           map[string]int
	BillableInput int
	CachedInput   int
	Reasoning     int
	Output        int
	Known         bool
}

Usage — расход токенов. Части не пересекаются: вход — BillableInput плюс CachedInput, выход — Output плюс Reasoning; Raw — счётчики поставщика как есть. Known ложно, когда хотя бы часть расхода не сообщена: счёт тогда — нижняя граница.

func (Usage) Add added in v0.2.0

func (u Usage) Add(part Usage) Usage

Add складывает расход по частям; неполнота любой части и переполнение делают сумму неполной.

func (Usage) InputTokens

func (u Usage) InputTokens() int

InputTokens — весь вход, с кешем.

func (Usage) OutputTokens

func (u Usage) OutputTokens() int

OutputTokens — весь выход, с рассуждениями.

type WarnedError added in v0.3.0

type WarnedError struct {
	Err     error
	Cleanup *CleanupWarning
}

WarnedError — отказ попытки вместе с предупреждением уборки; цепочка ведёт к отказу, а не к уборке.

func (*WarnedError) Error added in v0.3.0

func (e *WarnedError) Error() string

func (*WarnedError) Unwrap added in v0.3.0

func (e *WarnedError) Unwrap() error

Directories

Path Synopsis
Package gigachat — плечо GigaChat: OAuth-токен с кешем на клиенте, корневой сертификат Минцифры в пуле транспорта, кадры через `/files` и `attachments`.
Package gigachat — плечо GigaChat: OAuth-токен с кешем на клиенте, корневой сертификат Минцифры в пуле транспорта, кадры через `/files` и `attachments`.
internal
httpjson
Package httpjson — общее у плеч поверх JSON по HTTP: запрос, ограниченное тело, отказ по статусу.
Package httpjson — общее у плеч поверх JSON по HTTP: запрос, ограниченное тело, отказ по статусу.
Package ollama — плечо Ollama поверх нативного `/api/chat`: кадры base64 в сообщении, `format` подсказкой или схемой, серверные длительности в ответе.
Package ollama — плечо Ollama поверх нативного `/api/chat`: кадры base64 в сообщении, `format` подсказкой или схемой, серверные длительности в ответе.
Package pricing оценивает стоимость вызова по расходу попыток и тарифу потребителя.
Package pricing оценивает стоимость вызова по расходу попыток и тарифу потребителя.
Package yandex — плечо Yandex AI Studio через OpenAI-совместимый `chat/completions`: ключ API или IAM-токен, каталог заголовком `OpenAI-Project`, кадры data-URI в частях сообщения.
Package yandex — плечо Yandex AI Studio через OpenAI-совместимый `chat/completions`: ключ API или IAM-токен, каталог заголовком `OpenAI-Project`, кадры data-URI в частях сообщения.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL