vision

package
v0.11.1 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package vision turns images into searchable text: a vision LLM describes an image, and the resulting title/description/transcription is indexed as ordinary markdown by every backend (full-text, vector, metadata filtering).

The package is deliberately independent of the ingestion pipeline: it is used by convert/vision to index standalone image files, and by the markdown enrichment of embedded images. Callers pass an llm.Client already decorated with llmx.NewRetryClient (retry + rate limit), exactly as the HyDE and Judge retrieval stages do.

Index

Constants

View Source
const DefaultMaxImageBytes int64 = 10 << 20 // 10 MiB

DefaultMaxImageBytes bounds, by default, the size of an image submitted to the vision model. Providers reject oversized payloads anyway, and the base64 encoding inflates the request by a third.

View Source
const DefaultMaxSourceBytes int64 = 64 << 20 // 64 MiB

DefaultMaxSourceBytes bounds the size of an image *before* shrinking: an image between MaxImageBytes and this limit is re-encoded to fit (see Shrink), a larger one is refused outright. It exists because shrinking decodes the pixels into memory — a 300 MiB PNG is not worth the RAM of a whole ingestion worker.

View Source
const DefaultPrompt = `` /* 201-byte string literal not displayed */

DefaultPrompt asks for a dense, factual description. The description is what gets indexed and searched, so it must be rich in the vocabulary a user would actually type: named entities, object names, chart values, UI labels.

It is deliberately terse, and the detailed field requirements live in descriptionSchema instead. Spelling them out here made an OCR-specialized model (glm-ocr) — whose instinct is to transcribe whatever text it is handed — copy the instructions themselves into the answer, which would then have been indexed verbatim for every image. Keep any change to this prompt short and free of enumerations, and re-run the vision integration test: it asserts that no fragment of the prompt comes back in the answer.

View Source
const MaxTitleRunes = 120

MaxTitleRunes is the length beyond which a "title" is not one. The title becomes the heading of the emitted markdown, so a model answering with a whole paragraph — or with the instructions it was given — must not turn it into a monstrous `#` line.

View Source
const PromptVersion = "1"

PromptVersion identifies the default prompt. It participates in the cache key (see CachingDescriber): bump it whenever DefaultPrompt changes, so descriptions produced by an older prompt are not served from the cache.

View Source
const ShrinkMaxDimension = 2048

ShrinkMaxDimension caps the longest side of a re-encoded image. The vision providers downscale anything larger anyway (Anthropic works at ~1568px), so carrying more pixels costs bytes without buying any detail.

View Source
const ShrinkQuality = 85

ShrinkQuality is the JPEG quality used when re-encoding an oversized image. High enough to keep the small text of a screenshot or a dashboard legible, which is precisely what the description has to transcribe.

Variables

View Source
var ErrImageTooLarge = errors.New("image too large")

ErrImageTooLarge is returned when an image exceeds the configured size limit and could not be shrunk below it (see Shrink). It is returned *before* any call to the model.

View Source
var ErrUnsupportedImageFormat = errors.New("unsupported image format")

ErrUnsupportedImageFormat is returned when the media type of an image is not one the vision providers accept. Like ErrImageTooLarge, it is returned *before* any call to the model: the provider client rejects such an image while building its request parameters, and reports it as a generic "unavailable" error that is indistinguishable from a transient outage — so it would otherwise be retried, pointlessly, on every image.

View Source
var SupportedMimeTypes = []string{
	"image/png",
	"image/jpeg",
	"image/webp",
	"image/gif",
}

SupportedMimeTypes are the media types accepted by the mainstream vision providers (and the only ones the OpenAI-compatible client of genai lets through). It mirrors convert/vision.DefaultExtensions.

Functions

func IsSupportedMimeType added in v0.11.0

func IsSupportedMimeType(mimeType string) bool

IsSupportedMimeType reports whether a vision provider accepts mimeType. Any media type parameter (`; charset=...`, as returned by http.DetectContentType) is ignored, and the comparison is case-insensitive.

func Namespace

func Namespace(model, prompt string) string

Namespace derives a cache namespace identifying both the vision model and the prompt it was given: two descriptions may only share a cache entry when both match. Pass it to NewCachingDescriber.

func Shrink added in v0.11.0

func Shrink(mimeType string, data []byte, maxBytes int64) (string, []byte, error)

Shrink re-encodes data as JPEG so that it fits within maxBytes, and reports the media type of the result. Data already under the limit is returned untouched, with its original media type.

It is what keeps a big screenshot — a Grafana dashboard exported as a 12 MiB PNG is the typical case — from failing the indexation of the file that carries it: those images are large because PNG stores them losslessly, not because they hold that much detail.

An image that cannot be decoded (no Go decoder for the format) or that cannot be brought under the limit without falling below a legible size is an error: the caller decides whether that is fatal.

Types

type CachingDescriber

type CachingDescriber struct {
	// contains filtered or unexported fields
}

CachingDescriber decorates a Describer with a persistent on-disk cache keyed by the *content* of the image — sha256(promptVersion, namespace, bytes) — sharded on the first hex byte below dir/vision, the same layout as llmx.CachingClient.

A dedicated cache is required: llmx.CachingClient deliberately refuses to cache any chat completion carrying an attachment (serializing the image into a JSON key would be wasteful), so vision calls would otherwise never be cached. Keying on the bytes rather than on the file path also means a renamed, moved or duplicated image is described only once, and a full re-index costs nothing.

The namespace must identify the vision model *and* its prompt — build it with Namespace(model, prompt) — otherwise switching model would serve descriptions produced by the previous one.

func NewCachingDescriber

func NewCachingDescriber(describer Describer, dir, namespace string) (*CachingDescriber, error)

NewCachingDescriber wraps describer with a cache rooted at dir (created if missing), keyed under namespace.

func (*CachingDescriber) Describe

func (c *CachingDescriber) Describe(ctx context.Context, mimeType string, data []byte) (*Description, error)

Describe implements Describer.

func (*CachingDescriber) MaxImageBytes

func (c *CachingDescriber) MaxImageBytes() int64

MaxImageBytes reports the limit of the wrapped describer, when it exposes one, so the decorator stays transparent to size-aware callers.

func (*CachingDescriber) MaxSourceBytes added in v0.11.0

func (c *CachingDescriber) MaxSourceBytes() int64

MaxSourceBytes reports the source limit of the wrapped describer, when it exposes one, so the decorator stays transparent to size-aware callers.

func (*CachingDescriber) Stats

func (c *CachingDescriber) Stats() (hits, misses int64)

Stats returns the number of cache hits and misses served so far.

type Describer

type Describer interface {
	Describe(ctx context.Context, mimeType string, data []byte) (*Description, error)
}

Describer produces a textual description of an image.

type Description

type Description struct {
	// Title is a short, one-line title for the image.
	Title string `json:"title"`
	// Description is the detailed markdown description: content, context,
	// notable elements, data of charts and diagrams.
	Description string `json:"description"`
	// Text is the text visible in the image, transcribed verbatim (implicit
	// OCR). Empty when the image carries no text.
	Text string `json:"text"`
}

Description is the structured result of describing an image.

func (*Description) IsEmpty

func (d *Description) IsEmpty() bool

IsEmpty reports whether the description carries no usable text at all.

type LLMDescriber

type LLMDescriber struct {
	// contains filtered or unexported fields
}

LLMDescriber describes images with a vision-capable chat model.

func NewLLMDescriber

func NewLLMDescriber(client llm.Client, funcs ...OptionFunc) *LLMDescriber

NewLLMDescriber builds a Describer on top of client, which must be a vision-capable chat model and is expected to be already decorated with llmx.NewRetryClient by the caller.

func (*LLMDescriber) Describe

func (d *LLMDescriber) Describe(ctx context.Context, mimeType string, data []byte) (*Description, error)

Describe implements Describer.

func (*LLMDescriber) MaxImageBytes

func (d *LLMDescriber) MaxImageBytes() int64

MaxImageBytes reports the configured image size limit, so callers can bound their reads before handing over the bytes.

func (*LLMDescriber) MaxSourceBytes added in v0.11.0

func (d *LLMDescriber) MaxSourceBytes() int64

MaxSourceBytes reports the largest image Describe accepts before shrinking, so callers can bound their reads accordingly.

func (*LLMDescriber) Prompt

func (d *LLMDescriber) Prompt() string

Prompt reports the effective description prompt (used to derive the cache namespace).

type OptionFunc

type OptionFunc func(opts *Options)

func WithMaxImageBytes

func WithMaxImageBytes(maxBytes int64) OptionFunc

WithMaxImageBytes bounds the size of an accepted image; <= 0 keeps the default (DefaultMaxImageBytes).

func WithMaxSourceBytes added in v0.11.0

func WithMaxSourceBytes(maxBytes int64) OptionFunc

WithMaxSourceBytes bounds the size of an image accepted for shrinking; <= 0 keeps the default (DefaultMaxSourceBytes). It is always raised to at least MaxImageBytes.

func WithPrompt

func WithPrompt(prompt string) OptionFunc

WithPrompt replaces the default description prompt. A custom prompt must be reflected in the cache namespace (see Namespace).

func WithStructuredOutput

func WithStructuredOutput(enabled bool) OptionFunc

WithStructuredOutput toggles the JSON response schema.

type Options

type Options struct {
	Prompt string
	// MaxImageBytes is the largest image submitted to the model; a larger one
	// is re-encoded to fit (see Shrink).
	MaxImageBytes int64
	// MaxSourceBytes is the largest image Describe accepts at all: between
	// MaxImageBytes and this limit an image is shrunk, above it Describe fails
	// with ErrImageTooLarge without decoding it.
	MaxSourceBytes int64
	// StructuredOutput requests a JSON response matching Description. Disable
	// it for providers that reject a response schema; the whole reply then
	// lands in Description.
	StructuredOutput bool
}

Options configures an LLMDescriber.

func NewOptions

func NewOptions(funcs ...OptionFunc) *Options

Jump to

Keyboard shortcuts

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