/** * Media search and RAG: index what media say and show, find the media a question is about, and hand * the model the media themselves, not only their text. */ import type { VectorStore } from '../hallucination/retrieval.js'; import type { AssetStore } from '../images/asset-support.js'; import type { EmbeddingRequest, EmbeddingResponse } from '../types/embeddings.js'; import type { Store } from '../types/store.js'; import { type MediaAnswer, type MediaInput, type MediaKind, type MediaManager } from './index.js'; /** Anything that embeds, such as an `EmbeddingManager` or a client's `embeddings`. */ export interface MediaEmbeddingSource { /** Embeds texts, or media as parts. */ embed(request: EmbeddingRequest): Promise; } /** A medium to index, under its own id. */ export interface MediaDocument { /** The medium's id, which every hit carries. */ id: string; /** The image or document. */ media: MediaInput; /** Where it came from, such as a path or a URL, carried with every hit. */ source?: string; /** Application data carried with every hit, and filterable. */ metadata?: Record; } /** Where a hit's medium is kept, so it can be handed back: by its URL, or as a stored asset. */ export interface MediaReference { /** An image or a document. */ type: MediaKind; /** Its MIME type, when known. */ mimeType?: string; /** Its file name, when it has one. */ filename?: string; /** The URL that holds it. */ url?: string; /** Its id in the index's asset store. */ assetId?: string; } /** What `createMediaIndex()` indexes, and where. */ export interface MediaIndexOptions { /** The vector store the index's chunks go to. */ vectors: VectorStore; /** Reads pages and describes images: a `MediaManager`. */ media: Pick; /** Embeds chunks and questions. */ embeddings: MediaEmbeddingSource; /** The embedding model. Defaults to the embedder's own. */ model?: string; /** * Keeps each medium held as bytes or base64, so a hit can hand the model the medium itself. A medium * at a URL is kept by reference and needs no store. */ assets?: { store: AssetStore; tenantId: string; }; /** * What is indexed for each medium: its pages' text, a caption (for images), and its own vector, * embedded from the medium itself by a model that takes it. Defaults to pages and captions. */ index?: { pages?: boolean; captions?: boolean; vectors?: boolean; }; /** The longest chunk of page text, in characters; a longer page is split at paragraphs. Defaults to 4,000. */ maxChunkChars?: number; /** * Where the index remembers what it holds: any `Store`, so it lives in memory, Redis, Postgres, or * SQLite. With one, `add()` skips a medium whose content and options are unchanged and replaces * exactly the chunks of one that changed, and `delete()`, `reindex()`, `describe()`, and `list()` * work by a medium's id. A store with an item limit must hold every medium. */ manifest?: Store; /** Names the index. Its manifest is kept under this name, so two indexes never share one. Defaults to `media`. */ name?: string; } /** Who produced a medium's text or caption. */ export interface MediaIndexReader { /** The provider that answered. */ provider: string; /** The model it used. */ model: string; } /** What the manifest records for one medium. */ export interface MediaIndexEntry { /** The medium's id. */ id: string; /** * A hash of the medium's content, source, metadata, and how it was indexed: the medium's version. * Adding it again with the same hash writes nothing. */ hash: string; /** Where it came from. */ source?: string; /** Its metadata. */ metadata?: Record; /** Where the medium is kept: its URL, or its asset. */ reference: MediaReference; /** The pages that were read. */ pages: number; /** Every chunk's id in the vector store. */ chunkIds: string[]; /** The embedding model, when one was named. */ model?: string; /** Who read its pages, when they were read. */ reader?: MediaIndexReader; /** Who described it, when it was described. */ describer?: MediaIndexReader; /** ISO-8601 time it was first indexed. */ createdAt: string; /** ISO-8601 time it was last indexed. */ updatedAt: string; /** * A change that began and has not finished, as a crash leaves it: its version, its chunks, and the * asset it stored. The next `add()`, `reindex()`, or `delete()` of the medium finishes or undoes it. */ pending?: { hash: string; chunkIds: string[]; assetId?: string; }; } /** What `add()` indexed. */ export interface MediaIndexReport { /** Media indexed. */ media: number; /** Media the manifest already held at the same version, which were not indexed again. */ unchanged: number; /** Chunks of page text. */ pages: number; /** Captions. */ captions: number; /** Vectors of the media themselves. */ vectors: number; /** Every chunk's id, by medium, for removing a medium from the store. */ ids: Record; } /** A medium a search found: the best of its chunks, and where to find the medium. */ export interface MediaHit { /** The medium's id. */ id: string; /** The best chunk's similarity, 0 to 1. */ score: number; /** What matched best: a page's text, the caption, or the medium's own vector. */ kind: 'page' | 'caption' | 'image'; /** The page that matched, from 0, when a page did. */ page?: number; /** The matched chunk's text. */ text: string; /** Where the medium came from. */ source?: string; /** The medium's own metadata. */ metadata: Record; /** Where the medium is kept. */ reference: MediaReference; } /** Options for a search. */ export interface MediaSearchOptions { /** Media to return, the best first. Defaults to 5. */ topK?: number; /** Matches media by their metadata. */ filter?: Record; } /** An index of media in a vector store. */ export interface MediaIndex { /** * Reads, describes, and embeds each medium, and writes its chunks. Adding an id again replaces it; * with a manifest, only when its content, source, metadata, or the index's options changed. */ add(documents: readonly MediaDocument[]): Promise; /** Removes media by id: their chunks, their stored assets, and their manifest entries. Needs a manifest. */ delete(ids: readonly string[]): Promise; /** * Indexes media again by id, from where the manifest says each is kept, even when unchanged: after a * new embedding model or a better reader. Needs a manifest. */ reindex(ids: readonly string[]): Promise; /** What the manifest records for a medium, or `undefined`. Needs a manifest. */ describe(id: string): Promise; /** The manifest's entries, newest first, a page at a time. Needs a manifest. */ list(options?: { limit?: number; offset?: number; }): Promise; /** The media a question is about, each once, at its best chunk. */ search(query: string, options?: MediaSearchOptions): Promise; /** The medium behind a hit, to hand a model; `undefined` when its asset is gone. */ resolve(hit: Pick): Promise; } /** * Builds an index of media in a vector store. Each medium becomes chunks: its pages' text, from * `read()`; for an image, its caption, from `describe()`; and, when asked, a vector of the medium * itself. A search finds the media, and `resolve()` hands back each one, so the model sees the image, * not only what was written about it. * * ```ts * const index = createMediaIndex({ vectors, media, embeddings: ai.embeddings, assets: { store, tenantId: 'acme' } }); * await index.add([{ id: 'invoice-17', media: { type: 'document', source: { buffer: pdf } } }]); * const answer = await answerFromMedia(media, index, 'What is the total on invoice 17?'); * ``` */ export declare function createMediaIndex(options: MediaIndexOptions): MediaIndex; /** An answer from media, with the hits it was given. */ export interface MediaRagAnswer extends MediaAnswer { /** The media the answer was given, the best first. */ hits: MediaHit[]; } /** * Answers a question from the media an index finds for it: the media themselves go to the model, so * it sees what a page or an image shows, not only what was written about it. */ export declare function answerFromMedia(media: Pick, index: MediaIndex, question: string, options?: MediaSearchOptions & { provider?: string; signal?: AbortSignal; }): Promise;