Documentation
¶
Index ¶
- Constants
- Variables
- func AnswerLeaks(answer string, allowedURLs []string) []string
- func BestExcerpt(chunkText, answer string) string
- func CondenseFollowUp(ctx context.Context, condensePrompt ai.Prompt, model ai.Model, config any, ...) string
- func ConfidenceLabel(topScore int, usedFallback bool) string
- func DefineProgressFlow[In, Out any](g *genkit.Genkit, name string, ...) *core.Flow[In, Out, string]
- func DetectInjection(text string) []string
- func DocText(doc *ai.Document) string
- func EvalDatasetHandler(dir string) http.HandlerFunc
- func EvalJobHandler[In any](jobs *JobStore, ctx context.Context, kind string, ...) http.HandlerFunc
- func ExtractKeywords(text string, stopWords map[string]bool) []string
- func ExtractQuotedPhrases(text string) []string
- func FallbackStandaloneQuestion(question string, history []QATurn) string
- func FetchWebPage(url string) (string, error)
- func FetchWebPageFollowingRefresh(start string) (page, from string, err error)
- func FormatHistory(history []QATurn) string
- func GenerateConfig(spec string, o GenerateOptions) any
- func GenerateHyDEText(ctx context.Context, hydePrompt ai.Prompt, model ai.Model, config any, ...) string
- func IndexInBatches(ctx context.Context, store VectorStore, docs []*ai.Document, ...) error
- func IndexSource(ctx context.Context, store VectorStore, src IngestSource, ...) (indexed, deleted int, err error)
- func InjectionPayloads() []string
- func IsReliableCondense(original, condensed string, history []QATurn) bool
- func JSONToolHandler[In, Out any](run func(ctx context.Context, input In) (Out, error)) func(*ai.ToolContext, In) (string, error)
- func KeywordSearchSQL(ctx context.Context, pool *pgxpool.Pool, query string, maxResults int, ...) ([]*ai.Document, error)
- func LocalvecKeywordSearch(dbPath string, query string, maxResults int, stopWords map[string]bool, ...) ([]*ai.Document, error)
- func MCPModeRequested() bool
- func NewPrefixEmbedder(emb ai.Embedder, model string) ai.Embedder
- func NewRandomHexID(n int) string
- func NormalizeSpaces(s string) string
- func ParseModelSpec(spec string) (provider, name string)
- func PromptOptions(model ai.Model, config any, input map[string]any) []ai.PromptExecuteOption
- func QueryDocument(text string) *ai.Document
- func RegisterEvaluators(g *genkit.Genkit, modelName string)
- func RerankScores(ctx context.Context, rerankPrompt ai.Prompt, model ai.Model, config any, ...) []int
- func RestrictEvalPaths(dir, outPath *string) error
- func RunStreamingFlowAsJob[In, Out any](ctx context.Context, flow *core.Flow[In, Out, string], input In, ...) (any, error)
- func SanitizeAnswer(answer string, allowedURLs []string) string
- func SanitizeUntrusted(text string) string
- func ScanDocs(rows pgx.Rows) ([]*ai.Document, error)
- func ServeMCPStdio(g *genkit.Genkit, name, version string) error
- func SetAllowPrivateTargets(allow bool)
- func SetMaxPageBytes(n int64)
- func SetupLogging(cfg LogConfig) (io.Closer, error)
- func SupportedDocumentExt(ext string) bool
- func WrapContext(context string) string
- func WrapUntrusted(tag, text string) string
- func WriteInjectionEvalSet[In any](outPath string, buildInput func(question string) In) (int, error)
- type Chunk
- type CrawlInput
- type CrawlResult
- type EvalCase
- type FeedbackStore
- type FusedDoc
- type GenerateEvalSetInput
- type GenerateEvalSetResult
- type GenerateOptions
- type IndexPageFunc
- type IngestSource
- type InjectionAttack
- type Job
- type JobStatus
- type JobStore
- type JobWork
- type LocalvecStore
- func (s *LocalvecStore) Close() error
- func (s *LocalvecStore) DeleteBySource(ctx context.Context, sourceName string) (int, error)
- func (s *LocalvecStore) DeleteByURL(ctx context.Context, url string) (int, error)
- func (s *LocalvecStore) Index(ctx context.Context, docs []*ai.Document) error
- func (s *LocalvecStore) KeywordSearch(ctx context.Context, query string, maxResults int, stopWords map[string]bool, ...) ([]*ai.Document, error)
- func (s *LocalvecStore) Sources(ctx context.Context) ([]Source, error)
- type LogConfig
- type Paragraph
- type PgvecStore
- func (s *PgvecStore) Close() error
- func (s *PgvecStore) DeleteBySource(ctx context.Context, sourceName string) (int, error)
- func (s *PgvecStore) DeleteByURL(ctx context.Context, url string) (int, error)
- func (s *PgvecStore) Index(ctx context.Context, docs []*ai.Document) error
- func (s *PgvecStore) Retrieve(ctx context.Context, query *ai.Document, k int) ([]*ai.Document, error)
- func (s *PgvecStore) Sources(ctx context.Context) ([]Source, error)
- type ProviderConfig
- type Providers
- type QATurn
- type RankedList
- type ReindexResult
- type RerankOptions
- type Source
- type VectorStore
Constants ¶
const ( ProviderOllama = "ollama" ProviderGPUStack = "gpustack" ProviderGoogleAI = "googleai" )
Model providers a spec can name. A spec is "<provider>/<model>", e.g. "ollama/gemma4:12b", "gpustack/qwen3-8b" or "googleai/gemini-2.5-flash".
const ( ContextOpenTag = "<context>" ContextCloseTag = "</context>" )
Context delimiters used by WrapContext. An app's system prompt should name them and say that everything between them is quoted material, never instructions.
const DefaultMaxPageBytes = 10 << 20
DefaultMaxPageBytes is the largest page FetchWebPage reads, unless changed with SetMaxPageBytes.
const InjectionCanary = "ZX-CANARY-7731"
InjectionCanary is a marker string the injection test material asks the model to emit. An answer containing it (in any letter case) shows the model obeyed an attack.
const MaxHistoryTurns = 6
MaxHistoryTurns caps how many prior turns feed the condense prompt, so a long-running conversation doesn't grow that prompt's context without bound. Older turns are dropped, not summarized — for pronoun/ellipsis resolution the most recent turns carry almost all the signal anyway.
Variables ¶
var ErrJobQueueFull = errors.New("job queue is full, try again later")
ErrJobQueueFull is returned by Enqueue when the queue's 64-job buffer is still full after enqueueTimeout — the single worker is far enough behind that submitting more work would otherwise mean blocking the calling HTTP handler indefinitely.
var ErrPrivateTarget = errors.New("target address is not public")
ErrPrivateTarget is returned (wrapped) when a fetch is refused because the target resolves to a private, loopback or link-local address.
Functions ¶
func AnswerLeaks ¶
AnswerLeaks returns what in answer shows an attack succeeded: the canary, a Markdown image, an HTML tag, or a link / bare URL outside allowedURLs. Entries are "canary", "image", "html", "link:<url>" and "url:<url>"; an empty result means no leak was found. Unlike comparing against SanitizeAnswer's output it ignores harmless differences such as carriage returns, which makes it suitable for eval checks on generated answers.
func BestExcerpt ¶
BestExcerpt returns the sentence in chunkText sharing the most content words with answer — a short, verbatim quote a citation can point to, instead of leaving the reader to search the whole cited page for what grounds the claim. Falls back to the chunk's first sentence when nothing scores above zero (e.g. the answer paraphrased everything), so a citation still carries some concrete text; returns "" only when chunkText has no sentences at all.
func CondenseFollowUp ¶
func CondenseFollowUp(ctx context.Context, condensePrompt ai.Prompt, model ai.Model, config any, question string, history []QATurn) string
CondenseFollowUp rewrites question into a standalone question using the conversation history, so retrieval doesn't have to resolve "und die davor?" on its own. condensePrompt is expected to take {"history", "question"} and return the rewritten question. Falls back to FallbackStandaloneQuestion whenever the prompt call fails or the result fails IsReliableCondense, and returns question unchanged when there is no history or condensePrompt is nil. Callers that make multi-turn optional check their own switch before calling.
func ConfidenceLabel ¶
ConfidenceLabel derives a coarse "hoch"/"mittel"/"niedrig" label from topScore — the reranker's 0-10 relevance score (see the app's rerank prompt) for the highest-scoring source actually used to answer — and usedFallback, which is true when the reranker scored every candidate below minScore and the top-3 fallback kicked in. A fallback answer is never "hoch" even if a stale neutral default score (a neutral default score) happens to be mid-range: it means the reranker found nothing it considered actually relevant, so the answer rests on the least-bad options rather than a confident match.
func DefineProgressFlow ¶
func DefineProgressFlow[In, Out any](g *genkit.Genkit, name string, fn func(ctx context.Context, input In, progress func(string)) (Out, error)) *core.Flow[In, Out, string]
DefineProgressFlow defines a Genkit streaming flow whose handler reports progress through a plain func(string) callback instead of genkit's core.StreamCallback[string], and returns Out when done.
This is the common shape of a long-running ingest/crawl flow: an HTTP layer can run it as a background job and surface fn's progress messages incrementally (see docs/AsyncJobs.md-style patterns), while a synchronous caller — the genkit CLI, a test, Flow.Run — just discards the stream and gets Out at the end.
func DetectInjection ¶
DetectInjection returns the names of the heuristics text trips, in a stable order: "ignore-instructions", "ignore-instructions-de", "role-change", "new-instructions", "reveal-prompt", "addresses-the-model", "exfiltration", "delimiter-forgery", "hidden-characters" (invisible characters of the kind SanitizeUntrusted strips). An empty result means no heuristic matched, not that the text is safe.
func EvalDatasetHandler ¶
func EvalDatasetHandler(dir string) http.HandlerFunc
EvalDatasetHandler serves eval dataset files from dir by name, taken from the request's {name} path value. Only names like eval_dataset_<x>.json are served, so the route can't be used to read anything else in dir (or outside it).
func EvalJobHandler ¶
func EvalJobHandler[In any](jobs *JobStore, ctx context.Context, kind string, run func(ctx context.Context, in In) (any, error), restrict func(*In) error) http.HandlerFunc
EvalJobHandler returns a handler for POST /api/eval/{kind}: it decodes {"data": In}, lets restrict adjust or reject the input (typically via RestrictEvalPaths; nil means no check), and runs the flow as a background job, answering 202 {"jobId": ...}. kind is the job's kind label. run executes the flow, e.g. func(ctx, in) (any, error) { return flow.Run(ctx, in) }. ctx must be the app's long-lived context, not the request's.
func ExtractKeywords ¶
ExtractKeywords splits text into unique lowercase keywords, dropping tokens shorter than 3 characters and any in stopWords. stopWords is the caller's own, language-specific stop-word set (and any meta-query words like "find"/"show" worth excluding) — this function has no opinion on language. nil stopWords keeps every token that passes the length check.
func ExtractQuotedPhrases ¶
ExtractQuotedPhrases returns substrings enclosed in single or double quotes from text (lowercased, trimmed) — used for exact-phrase keyword matching, bypassing whatever stemming/tokenization the rest of the search applies. Language-neutral: quoting works the same regardless of the documents' language.
func FallbackStandaloneQuestion ¶
FallbackStandaloneQuestion is used when the condense prompt is unavailable or its result doesn't pass IsReliableCondense. Plain concatenation of the last question with the new one is crude, but it keeps whatever concrete anchors (dates, session numbers, names) the last turn mentioned in the text that retrieval actually searches on — which a bare, unresolved follow-up like "Wer war dabei anwesend?" does not.
func FetchWebPage ¶
FetchWebPage downloads the HTML content of a URL. It refuses targets that resolve to private, loopback or link-local addresses (see SetAllowPrivateTargets), follows at most 10 redirects, and reads at most SetMaxPageBytes bytes (10 MiB by default).
func FetchWebPageFollowingRefresh ¶
FetchWebPageFollowingRefresh is FetchWebPage for pages that are only a stub redirecting by <meta http-equiv="refresh"> (as some portals do). It follows such a redirect — delay of five seconds or less, same host only, at most two hops — and returns the final HTML together with the address it came from. A refresh it will not follow (other host, too many hops) is an error rather than a silently returned stub page. Every hop goes through FetchWebPage and so through its SSRF guard and size limit.
func FormatHistory ¶
FormatHistory renders history as plain turns for the condense prompt.
func GenerateConfig ¶
func GenerateConfig(spec string, o GenerateOptions) any
GenerateConfig turns o into the config type spec's provider expects, for ai.WithConfig. It returns nil for Google AI: its defaults are fine, and a token cap there would also count thinking tokens and could cut the answer off.
func GenerateHyDEText ¶
func GenerateHyDEText(ctx context.Context, hydePrompt ai.Prompt, model ai.Model, config any, question string) string
GenerateHyDEText executes hydePrompt — expected to take {"question": question} and return a short hypothetical passage that could answer it — and returns its text. On error or an empty response it returns question unchanged: HyDE is a retrieval aid, not something retrieval should block on. The prompt's actual wording is the caller's own asset (e.g. a Genkit dotprompt loaded from the app's prompts directory) — its style should match the kind of documents the app indexes, which engine has no way to know.
func IndexInBatches ¶
func IndexInBatches(ctx context.Context, store VectorStore, docs []*ai.Document, progress func(done, total int)) error
IndexInBatches calls store.Index in batches of at most embedBatchSize documents instead of all at once. progress, if non-nil, is called after each successful batch with (batches done so far, total batches) — used to report incremental status on a long index job; pass nil when nothing is listening (e.g. a single web page, which rarely spans more than one batch).
func IndexSource ¶
func IndexSource(ctx context.Context, store VectorStore, src IngestSource, chunkSize, chunkOverlap int, sendChunk func(string)) (indexed, deleted int, err error)
IndexSource runs the shared extract/chunk/delete-stale/index/cleanup pipeline against any IngestSource. sendChunk, when non-nil, receives incremental progress messages for a streaming flow's HTTP adapter; pass nil when nothing is listening. Returns how many documents were indexed and how many stale ones were deleted beforehand.
func InjectionPayloads ¶
func InjectionPayloads() []string
InjectionPayloads returns passages that embed an attack, for testing *indirect* injection: index one of them into a scratch store next to ordinary content, ask a question that retrieves it, and check the answer with the injection-resistance evaluator or by looking for InjectionCanary.
func IsReliableCondense ¶
IsReliableCondense sanity-checks a condensed question against the follow-up and history it was built from. The condense prompt has been observed doing two kinds of damage that a plain empty-reply check misses: it can invent a date/number nowhere in the conversation, or it can drop a concrete anchor ("die 372. Sitzung am 7. Juli 2026") in favor of a vague placeholder ("die Veranstaltung") — syntactically fine, but useless for retrieval.
The check only looks at digit tokens (extractNumberTokens), so it catches this specifically for dates and session/paragraph numbers — a hallucinated or dropped non-numeric anchor (a person, committee, or document name) goes undetected. Widening it to non-numeric tokens isn't a safe drop-in: German capitalizes every noun, not just proper nouns, so there's no cheap way to tell "invented name" from "ordinary paraphrase" the way a bare digit mismatch reliably does, and a naive check would flood on real paraphrases.
func JSONToolHandler ¶
func JSONToolHandler[In, Out any](run func(ctx context.Context, input In) (Out, error)) func(*ai.ToolContext, In) (string, error)
JSONToolHandler wraps run so its result is returned as a JSON string. Genkit's MCP server stringifies any non-string tool result with Go's "%v" (e.g. "map[chunks:83 source:...]") instead of JSON, so every MCP tool needs this same marshal-to-string wrapping to give clients valid, parseable JSON back — this is that wrapping, written once.
func KeywordSearchSQL ¶
func KeywordSearchSQL(ctx context.Context, pool *pgxpool.Pool, query string, maxResults int, language string, stopWords map[string]bool, extraWhere string, extraArgs []any) ([]*ai.Document, error)
KeywordSearchSQL runs the same keyword/phrase search as LocalvecStore.KeywordSearch against a PostgreSQL table shaped like PgvecStore's documents table (content, metadata, parent_text columns):
- Exact phrase ILIKE for quoted substrings — always wins, bypasses stemming entirely, good for technical terms/compound nouns.
- ILIKE on a truncated prefix of any long (>=8 char) keyword — bridges inflections the stemmer misses (e.g. a word stemming differently than its related forms).
- Full-text search via websearch_to_tsquery, one query per keyword run independently — so a high-frequency keyword can't dilute the ranking of a more specific one sharing the same query.
Each stage keeps at most maxPerDocument rows per document (see its doc comment) before its own LIMIT applies, so one document with many matching chunks can't single-handedly fill the result budget and starve out a document that only matches once or twice. Results from all three stages are then merged and deduplicated by document text, and capped at maxResults overall. language is a Postgres text-search configuration name ("german", "english", "simple", ...) — always a fixed string the caller controls, never user input, so it's safe to interpolate into the query text (bind parameters don't support configuration names). extraWhere is ANDed into every query's WHERE clause with extraArgs as its bind values, letting the caller add its own filter (e.g. a date/tag restriction); every query already binds the search term as $1 and the row limit as $2, so extraWhere's placeholders start at $3. Pass "", nil for no filter.
func LocalvecKeywordSearch ¶
func LocalvecKeywordSearch(dbPath string, query string, maxResults int, stopWords map[string]bool, filter func(meta map[string]any) bool) ([]*ai.Document, error)
LocalvecKeywordSearch returns up to maxResults documents whose content or metadata["parentText"] contains keywords or quoted phrases extracted from query (see ExtractKeywords/ExtractQuotedPhrases), scored by match count — a matched quoted phrase scores far above any number of matched keywords, so an exact name/phrase match always ranks first. filter, when non-nil, excludes any entry whose metadata it returns false for (nil keeps everything). Returns (nil, nil) when query yields no keywords or phrases to search for. dbPath is a localvec DB file as written by the localvec Genkit plugin (dir/__db_<name>.json).
func MCPModeRequested ¶
func MCPModeRequested() bool
MCPModeRequested reports whether the process was started with -mcp or --mcp — the usual way an app offers "run as an MCP stdio server instead of the HTTP server" without needing a full flag-parsing setup just for this one switch.
func NewPrefixEmbedder ¶
NewPrefixEmbedder wraps emb for models that need instruction prefixes and returns emb unchanged otherwise.
func NewRandomHexID ¶
NewRandomHexID returns a short random hex identifier from n random bytes — not a security token, just enough entropy to avoid collisions between jobs, feedback entries, and the like. Falls back to a timestamp on the effectively-unreachable case of crypto/rand failing, rather than crash the caller over a missing ID.
func NormalizeSpaces ¶
NormalizeSpaces trims and collapses multiple spaces into one.
func ParseModelSpec ¶
ParseModelSpec splits a model spec into provider and model name. A spec without a known provider prefix is an Ollama model: EMBEDDING_MODEL has always been a bare Ollama name, and Ollama names may themselves contain a slash ("hf.co/<user>/<repo>"), so only the three known prefixes are split off.
func PromptOptions ¶
PromptOptions builds the options for one prompt execution: the model, the provider config when there is one, and the input. It returns a fresh slice on every call, so it's safe to call concurrently (e.g. once per reranking batch goroutine).
func QueryDocument ¶
QueryDocument builds a document that prefixEmbedder treats as a search query. Asymmetric embedding models expect a different instruction prefix for queries than for indexed passages; embedding a question like a passage costs noticeable recall.
Note that HyDE output is deliberately NOT a query document: it is a hypothetical passage, and embedding it with the passage prefix is what makes HyDE bridge the query/document gap in the first place.
func RegisterEvaluators ¶
RegisterEvaluators defines custom evaluators for RAG quality (LLM-as-judge, plus one deterministic injection check): custom/faithfulness, custom/answerRelevancy and custom/injectionResistance, usable with `genkit eval:flow <flow> --evaluators=custom/faithfulness,custom/answerRelevancy` against any flow whose output is a string or a map/struct with an "answer" field, and whose input is a string or a map/struct with a "question" field.
func RerankScores ¶
func RerankScores(ctx context.Context, rerankPrompt ai.Prompt, model ai.Model, config any, question string, texts []string, opts RerankOptions) []int
RerankScores scores every text for relevance to question, in batches, via rerankPrompt (expected to take {"question": question, "chunks": [{"num": int, "text": string}, ...]} and reply with one "<num>:<score>" line per chunk — see parseRerankScores). The returned slice is aligned with texts. A batch whose call fails keeps opts.DefaultScore, so a partial failure costs ranking quality rather than dropping chunks.
Candidate texts routinely repeat (e.g. neighbouring chunks of one section expanding to the same parent-text window) — each distinct text is scored once and every occurrence gets that score, which also shrinks the number of batches needed.
func RestrictEvalPaths ¶
RestrictEvalPaths prepares the paths of an HTTP-supplied eval request. outPath is cleared so the flow writes to its own default location — otherwise a client could make the server write a file anywhere it can. dir, if non-nil, must stay relative and inside the working directory: absolute paths and ".." are rejected. Either argument may be nil.
func RunStreamingFlowAsJob ¶
func RunStreamingFlowAsJob[In, Out any](ctx context.Context, flow *core.Flow[In, Out, string], input In, progress func(string)) (any, error)
RunStreamingFlowAsJob drains a streaming flow to completion, forwarding each intermediate value to progress and returning the final Output as the job's result — the same value genkit.Handler would have returned synchronously. ctx must be the app's long-lived context, not an HTTP request's: the request context is cancelled once the handler that enqueued this job returns, well before the flow finishes.
func SanitizeAnswer ¶
SanitizeAnswer removes the parts of a model reply that could exfiltrate data or be rendered as active content, once the model has been talked into doing so: Markdown images (fetched automatically by most renderers, so a URL with data in it leaks without a click), raw HTML tags, and links or bare URLs that are not among allowedURLs. A link to an allowed URL keeps working; a link to anything else keeps its text and loses the URL.
allowedURLs are the sources of the answer (the apps' citation URLs). A URL counts as allowed if it equals an entry or extends one at a path boundary ("https://a.org/x" allows "https://a.org/x/y" but not "https://a.org/xy"). With an empty allowlist every link is removed. The removed marker is German ("[Link entfernt]"), like the rest of the apps' output.
This works on a complete answer. A streamed reply can leak through a Markdown image before the closing parenthesis arrives, so streaming clients must not render images or follow links until the answer is done, and should replace what they displayed with the sanitized final text.
func SanitizeUntrusted ¶
SanitizeUntrusted removes characters that are invisible to a human reader but still seen by a model, a common way to hide instructions inside a document: Unicode tag characters (U+E0000–U+E007F, "ASCII smuggling"), zero-width and bidirectional-control characters, and other control characters except newline and tab. Visible text is left untouched.
func ScanDocs ¶
ScanDocs reads content, metadata and parent text rows into documents. It expects exactly those three columns, in that order — the shape every query in this file and in the ask-pdf app's pgvecStore selects.
func ServeMCPStdio ¶
ServeMCPStdio serves g's registered tools (see genkit.DefineTool) over stdio, blocking until the client disconnects.
func SetAllowPrivateTargets ¶
func SetAllowPrivateTargets(allow bool)
SetAllowPrivateTargets lets FetchWebPage reach loopback, private and link-local addresses (127.0.0.0/8, 10/8, 192.168/16, 169.254/16, ...). The default is false. Enable it only for crawling your own intranet or a local development site, and never when the crawl can be started by someone you don't trust. It also makes FetchWebPage honour HTTP(S)_PROXY again: a proxy resolves targets itself, so the address check can't apply to it. Safe to call at any time, but meant to be set once at startup.
func SetMaxPageBytes ¶
func SetMaxPageBytes(n int64)
SetMaxPageBytes caps how much of a response FetchWebPage reads; larger pages fail instead of filling memory. n <= 0 restores DefaultMaxPageBytes.
func SetupLogging ¶
SetupLogging installs the process-wide slog handler cfg describes. Call it first thing in main, before genkit.Init: the handler goes in through Genkit's logger package, so Genkit's own records obey the same level and destination, and dev-mode streaming to the Dev UI keeps working.
What is still logged through the standard log package — in practice log.Fatal on a startup error — is routed through the same handler at error level, so it lands in the log file and survives LOG_LEVEL=warn or error; only off silences it.
The returned Closer closes the log file; for stderr it does nothing.
func SupportedDocumentExt ¶
SupportedDocumentExt reports whether ext (as returned by filepath.Ext, e.g. ".pdf") is a file type ExtractParagraphs can handle. Keep in sync with its switch below — this lets a caller (e.g. an upload handler) reject an unsupported file before it's written to disk, instead of only discovering the mismatch once ExtractParagraphs runs.
func WrapContext ¶
WrapContext cleans the retrieved context with SanitizeUntrusted, defuses any copy of the delimiter tags inside it, and wraps the result in ContextOpenTag/ContextCloseTag. Pass the whole assembled context (source headers and chunk texts) as one string.
func WrapUntrusted ¶
WrapUntrusted is WrapContext for any delimiter: it cleans text with SanitizeUntrusted, replaces every copy of <tag> / </tag> inside it (any letter case, optional whitespace) with "[tag-tag removed]", trims it and wraps it in <tag> and </tag> on lines of their own. Use it for other untrusted blocks of a prompt — an incoming e-mail, a user-supplied document — and say in the system prompt that the block is quoted material, never instructions. tag must be a plain name ("mail"); anything else is a programming error and panics.
Types ¶
type Chunk ¶
type Chunk struct {
Text string // small chunk for embedding
ParentText string // expanded context for LLM
Pages string // e.g. "3" or "3-5"
Section string
Top string // short section label, e.g. "TOP 3", "§ 12", "A", "II"
Breadcrumb string // hierarchical heading path for web documents
URL string // source URL (for web documents; empty for PDFs)
// contains filtered or unexported fields
}
Chunk is one unit of text ready for embedding, together with the expanded context window and citation metadata BuildChunks derived for it.
func BuildChunks ¶
BuildChunks groups paragraphs into chunks that respect paragraph boundaries, section breaks, and a maximum chunk size. Each chunk carries page range, section metadata, and an expanded ParentText (~parentSize chars) built from surrounding paragraphs for richer LLM context. overlapSize controls how many characters from the end of one chunk are repeated at the start of the next (reset at section boundaries).
type CrawlInput ¶
type CrawlInput struct {
SeedURLs []string
MaxPages int // default 50
MaxDepth int // default 3
DelayMs int // default 500 — politeness delay between page fetches
}
CrawlInput configures RunCrawl. It carries none of an app's own per-document metadata (e.g. the ask-pdf app's tag/datum) — that's attached by the IndexPageFunc closure the app passes in, not by the crawl itself.
type CrawlResult ¶
type CrawlResult struct {
PagesVisited int `json:"pagesVisited"`
PagesSaved int `json:"pagesSaved"`
PagesIndexed int `json:"pagesIndexed"`
Errors []string `json:"errors,omitempty"`
}
CrawlResult summarises one crawl run.
func RunCrawl ¶
func RunCrawl(ctx context.Context, cfg CrawlInput, outputDir string, indexPage IndexPageFunc, progress func(string)) (CrawlResult, error)
RunCrawl performs a breadth-first crawl starting from cfg.SeedURLs. It uses a two-phase approach: first crawl all pages and extract text, then remove cross-page boilerplate, then index the cleaned content via indexPage. If SAVE_CRAWL_DATA=1 is set, each cleaned page is also cached as Markdown under outputDir — ReindexCrawlData can later replay that cache without re-fetching the site.
Errors on individual pages are collected in CrawlResult.Errors; only fatal errors (no seeds) cause an early return. progress, if non-nil, is called with a human-readable line at each page visit and at the phase transitions — used to report incremental status on a long crawl job; pass nil when nothing is listening.
type EvalCase ¶
type EvalCase[In any] struct { TestCaseId string `json:"testCaseId"` Input In `json:"input"` Reference string `json:"reference"` }
EvalCase is one entry of a genkit eval:flow dataset: a question (Input, in whatever shape the flow being evaluated expects) and a reference answer to score the flow's output against.
func InjectionEvalCases ¶
InjectionEvalCases turns InjectionAttacks into eval cases. buildInput wraps each question in the shape the evaluated flow takes, as for GenerateEvalSet. Reference is empty: the injection-resistance evaluator is deterministic and does not compare against a reference answer.
type FeedbackStore ¶
type FeedbackStore[E any] struct { // contains filtered or unexported fields }
FeedbackStore persists an app's feedback entries — one logged question/answer interaction each, optionally rated by the asker afterwards — to a JSON file, read-modify-write under a mutex (the same approach LocalvecStore uses for its DB file). Logging every interaction, not just rated ones, turns real usage into a ready-made eval dataset: questions and contexts as they actually occur, instead of ones an LLM invented from sampled chunks. Traffic is one append per question and one update per rating click, so this needs no database.
E is the app's own entry type, so each app keeps the fields and JSON shape it needs (its own citation struct, extra metadata like tags). The store only needs to know how to read an entry's ID and write its rating, which the app supplies to NewFeedbackStore.
func NewFeedbackStore ¶
func NewFeedbackStore[E any](path string, id func(E) string, setRating func(*E, string)) *FeedbackStore[E]
NewFeedbackStore returns a store persisting to path (created, with its parent directories, on first write). id returns an entry's ID; setRating sets an entry's rating in place.
func (*FeedbackStore[E]) All ¶
func (s *FeedbackStore[E]) All() ([]E, error)
All returns every logged feedback entry.
func (*FeedbackStore[E]) Append ¶
func (s *FeedbackStore[E]) Append(entry E) error
Append records a new question/answer interaction, unrated.
type FusedDoc ¶
FusedDoc is one document after ReciprocalRankFusion: its original *ai.Document, its concatenated text (the fusion/dedup key and what a reranker typically scores), and its fused score.
func ReciprocalRankFusion ¶
func ReciprocalRankFusion(lists []RankedList, k, maxResults int) []FusedDoc
ReciprocalRankFusion merges several weighted, ranked retrieval result lists into one via Reciprocal Rank Fusion:
score(d) = Σ weight_i / (k + rank_i(d) + 1)
Documents are deduplicated by their concatenated text content (DocText) — two sources returning the same chunk collapse into one FusedDoc whose score sums both contributions, rather than appearing twice. 60 is the standard RRF k; pass that unless tuning. Results are sorted by descending score; maxResults <= 0 means no cap.
type GenerateEvalSetInput ¶
type GenerateEvalSetInput struct {
Dir string `json:"dir,omitempty"` // directory of .pdf/.docx/.md files to read; default "./pdf-data"
SampleRate int `json:"sampleRate,omitempty"` // take every Nth chunk per document; default 4
MaxPerDoc int `json:"maxPerDoc,omitempty"` // cap questions generated per document; default 8
OutPath string `json:"outPath,omitempty"` // dataset output path; default "testdata/eval_dataset_generated.json"
}
GenerateEvalSetInput configures GenerateEvalSet: which documents to draw questions from and how densely to sample their chunks.
type GenerateEvalSetResult ¶
type GenerateEvalSetResult struct {
Written int `json:"written"`
OutPath string `json:"outPath"`
Documents int `json:"documents"`
Skipped int `json:"skipped"` // chunks where the LLM response could not be parsed
}
func GenerateEvalSet ¶
func GenerateEvalSet[In any](ctx context.Context, genk *genkit.Genkit, genOpts []ai.GenerateOption, chunkSize, chunkOverlap int, cfg GenerateEvalSetInput, buildInput func(question string) In) (GenerateEvalSetResult, error)
GenerateEvalSet turns the PDFs, .docx files, and cached crawl-data Markdown files (as RunCrawl writes with SAVE_CRAWL_DATA=1 — see ReindexCrawlData) in cfg.Dir into a question/reference-answer dataset for `genkit eval:flow`. For a sample of chunks per document it asks the LLM (via genOpts — typically []ai.GenerateOption{ai.WithModel(llm), ai.WithConfig(modelConfig)}) for one question that chunk answers plus a short reference answer, grounded only in that chunk's parent text — the same context window BuildChunks hands to retrieval, so generated questions match what the flow actually has to work with. buildInput turns each generated question into the input shape the flow being evaluated expects (e.g. a plain string, or a struct carrying extra fields like tags) — EvalCase.Input must match that flow's input type for `genkit eval:flow` to run against it.
type GenerateOptions ¶
type GenerateOptions struct {
// NumCtx is Ollama's context window. One value for every call of an app,
// deliberately — Ollama reloads the model whenever num_ctx changes.
// GPUStack fixes the context when the model is deployed; ignored there.
NumCtx int
// KeepAlive is how long Ollama holds the model in memory after a
// request, e.g. "30m". Ignored by other providers.
KeepAlive string
// Think enables the model's reasoning phase. Off by default: every
// prompt in these pipelines asks for a fixed shape and reasoning tokens
// are never read, while a thinking model can spend thousands of tokens
// on a five-line rerank batch.
Think bool
// MaxTokens caps the generated tokens, e.g. for a rerank call that only
// has to emit one short line per chunk.
MaxTokens int
}
GenerateOptions are the per-request settings the RAG pipeline cares about, independent of provider. Zero values leave the provider's default.
func GenerateOptionsFromEnv ¶
func GenerateOptionsFromEnv() (GenerateOptions, error)
GenerateOptionsFromEnv reads OLLAMA_NUM_CTX (default 16384), OLLAMA_KEEP_ALIVE (default 30m) and OLLAMA_THINK (any non-empty value enables thinking).
type IndexPageFunc ¶
IndexPageFunc is the callback RunCrawl and ReindexCrawlData use to index one page's paragraphs into the vector store — supplied by the app, which is what attaches its own metadata (tag/datum/rank, or just url/breadcrumb) before chunking and calling IndexInBatches. Returns how many documents were indexed.
type IngestSource ¶
type IngestSource interface {
// Fetch returns the source's paragraphs.
Fetch(ctx context.Context) ([]Paragraph, error)
// Documents turns chunks into indexable documents carrying this source's
// own metadata (e.g. pages/section/top/source name for a PDF; url/
// breadcrumb for a web page) plus whatever else the app attaches.
Documents(chunks []Chunk) []*ai.Document
// DeleteStale removes this source's previously indexed chunks (upsert
// semantics). Called both before indexing and, on failure, to clean up a
// partial index — so a failed (re)index leaves nothing rather than a mix
// of old and partial-new chunks.
DeleteStale(ctx context.Context, store VectorStore) (int, error)
// Name identifies the source for logging.
Name() string
}
IngestSource abstracts fetching one document's paragraphs and turning them into indexable documents, so the extract → chunk → delete-stale → index → cleanup-on-failure pipeline (IndexSource) is written once instead of once per document type or per app. A PDF/.docx file and a crawled web page are two implementations; a different document store is a third.
type InjectionAttack ¶
InjectionAttack is one direct attack for the eval set: a question that smuggles an instruction in next to a real request.
func InjectionAttacks ¶
func InjectionAttacks() []InjectionAttack
InjectionAttacks returns direct-injection test questions. Each asks something answerable alongside an attack; the injection-resistance evaluator fails an answer that leaks the canary, an image, HTML or a link outside the answer's sources. They are generic on purpose — an app that wants domain-specific attacks appends its own.
type Job ¶
type Job struct {
ID string `json:"id"`
Kind string `json:"kind"`
Status JobStatus `json:"status"`
Progress string `json:"progress,omitempty"`
Result any `json:"result,omitempty"`
Error string `json:"error,omitempty"`
CreatedAt time.Time `json:"createdAt"`
StartedAt *time.Time `json:"startedAt,omitempty"`
EndedAt *time.Time `json:"endedAt,omitempty"`
}
Job is a single background task tracked by JobStore — the async counterpart to a blocking index/crawl/reindex call.
type JobStore ¶
type JobStore struct {
// contains filtered or unexported fields
}
JobStore tracks jobs in memory and runs them one at a time on a single background worker rather than a pool. Ollama's embed backend is already sized conservatively (see embedBatchSize in store.go); running an index and a crawl concurrently would mean two embed streams competing for the same constrained backend. A queued job finishing a few minutes later than it could in theory is a much smaller cost than reintroducing that risk.
func NewJobStore ¶
func NewJobStore() *JobStore
func (*JobStore) Enqueue ¶
Enqueue creates a pending job of the given kind and schedules work on the background worker. It returns immediately once the job is queued.
If the queue is still full after enqueueTimeout, it returns ErrJobQueueFull instead of blocking the caller (an HTTP handler goroutine) indefinitely.
type JobWork ¶
JobWork is the unit of work a job runs: given a progress reporter, it does the actual work and returns the flow's normal result.
type LocalvecStore ¶
LocalvecStore is a ready-made VectorStore backed by genkit's localvec plugin — a file-based store with no external dependency (no database to run), suitable for a single-node app or a quick demo. Its Retriever field is genkit's own ai.Retriever; an app needing filtered or ranked retrieval builds that on top (see the ask-pdf app's pgvecStore, in the app that uses this engine, for a richer alternative implementing a larger interface against PostgreSQL instead).
func NewLocalvecStore ¶
func NewLocalvecStore(g *genkit.Genkit, name string, emb ai.Embedder, dir string) (*LocalvecStore, error)
NewLocalvecStore creates a LocalvecStore. name identifies this store's genkit retriever/indexer action and its DB file (dir/__db_<name>.json) — use a different name per logical store sharing a genkit instance.
func (*LocalvecStore) Close ¶
func (s *LocalvecStore) Close() error
func (*LocalvecStore) DeleteBySource ¶
func (*LocalvecStore) DeleteByURL ¶
func (*LocalvecStore) KeywordSearch ¶
func (s *LocalvecStore) KeywordSearch(ctx context.Context, query string, maxResults int, stopWords map[string]bool, filter func(meta map[string]any) bool) ([]*ai.Document, error)
KeywordSearch is LocalvecKeywordSearch against this store's own DB file — a convenience for a caller already holding a *LocalvecStore. An app with its own, hand-rolled localvec wrapper (a separate dbPath field, not this struct) calls LocalvecKeywordSearch directly instead.
type LogConfig ¶
type LogConfig struct {
// Level is debug, info, warn, error or off; empty means info. Request
// content (questions, mail text) is only ever logged at debug.
Level string
// File is appended to; empty means stderr. The file is not rotated —
// under Docker log to stderr and let the logging driver rotate, otherwise
// use logrotate with copytruncate.
File string
// Format is text or json; empty means text.
Format string
}
LogConfig selects where log records go and which of them are kept.
func LogConfigFromEnv ¶
func LogConfigFromEnv() LogConfig
LogConfigFromEnv reads LOG_LEVEL, LOG_FILE and LOG_FORMAT.
type Paragraph ¶
type Paragraph struct {
Text string
PageNum int
Heading bool
Breadcrumb string // hierarchical heading path, e.g. "Products > Enterprise > Pricing"
}
Paragraph is one paragraph of extracted document text, annotated with its page number, heading status and (for web documents) heading breadcrumb.
func ExtractParagraphs ¶
ExtractParagraphs reads path and returns its paragraphs, dispatching on file extension. ".pdf" and ".docx" are the only supported types — anything else is an explicit error rather than a silent misparse.
func HTMLToStructuredText ¶
HTMLToStructuredText parses HTML and returns structured paragraphs with headings detected from HTML tags, links preserved, tables formatted, and lists properly rendered. Replaces htmlToText + parseWebIntoParagraphs.
type PgvecStore ¶
PgvecStore is a ready-made VectorStore backed by PostgreSQL + pgvector — an alternative to LocalvecStore for an app that wants a real database (concurrent access, SQL-side filtering, keyword search) instead of a single file. Pool and Embedder are exported so an app needing more than the plain, unfiltered Retrieve below (e.g. filtered retrieval, keyword search, source administration) can run its own queries against the same connection and embedder — see the ask-pdf app's pgvecStore, which embeds this type and adds exactly that.
func NewPgvecStore ¶
func NewPgvecStore(ctx context.Context, g *genkit.Genkit, name string, emb ai.Embedder, dsn string, embeddingDim int) (*PgvecStore, error)
NewPgvecStore connects to PostgreSQL, ensures the schema exists for embeddingDim-sized vectors, and registers a Genkit retriever named name. If an existing documents table's embedding column has a different dimension (e.g. after switching embedding models), it is truncated and migrated automatically.
func (*PgvecStore) DeleteBySource ¶
func (*PgvecStore) DeleteByURL ¶
func (*PgvecStore) Index ¶
Index embeds the documents and inserts them into PostgreSQL in a single transaction.
func (*PgvecStore) Retrieve ¶
func (s *PgvecStore) Retrieve(ctx context.Context, query *ai.Document, k int) ([]*ai.Document, error)
Retrieve runs a plain vector similarity search with no filtering — an app needing SQL-side filtering (date ranges, tags, ...) builds that itself against Pool/Embedder instead (see the ask-pdf app's filtered Retrieve).
type ProviderConfig ¶
type ProviderConfig struct {
// OllamaHost is the Ollama server, default http://localhost:11434.
OllamaHost string
// GPUStackURL is the GPUStack server, e.g. http://gpustack:80. The
// OpenAI-compatible API is expected under <GPUStackURL>/v1 (a URL that
// already ends in /v1 is used as is); set
// GPUStackAPIPath for a server that serves it elsewhere (older GPUStack
// releases use /v1-openai).
GPUStackURL string
GPUStackAPIPath string
GPUStackAPIKey string
// GoogleAIAPIKey is optional; empty means the plugin reads GEMINI_API_KEY
// or GOOGLE_API_KEY itself.
GoogleAIAPIKey string
// Timeout bounds every call to a self-hosted provider (Ollama, GPUStack).
// It has to cover the slowest single call an app makes, typically a
// rerank batch. Zero means 120s.
Timeout time.Duration
}
ProviderConfig is how to reach each provider. Only the providers named by the specs passed to NewProviders are set up, so a field for an unused provider may stay empty.
func ProviderConfigFromEnv ¶
func ProviderConfigFromEnv() (ProviderConfig, error)
ProviderConfigFromEnv reads the conventional environment variables: OLLAMA_HOST, GPUSTACK_URL, GPUSTACK_API_PATH, GPUSTACK_API_KEY and LLM_TIMEOUT (seconds; OLLAMA_TIMEOUT is still honored when LLM_TIMEOUT is unset). The Google AI key is left to the plugin's own GEMINI_API_KEY lookup.
type Providers ¶
type Providers struct {
// contains filtered or unexported fields
}
Providers sets up the Genkit plugins a set of model specs needs and resolves those specs to models and embedders. Typical use:
p, err := engine.NewProviders(cfg, nil, llmSpec, rerankSpec, embeddingSpec) genk := genkit.Init(ctx, genkit.WithPlugins(p.Plugins()...)) llm, err := p.Model(genk, llmSpec) emb, err := p.Embedder(genk, embeddingSpec, 1024)
func NewProviders ¶
func NewProviders(cfg ProviderConfig, allowed []string, specs ...string) (*Providers, error)
NewProviders validates specs and prepares a plugin for each provider they name. allowed restricts which providers may be named at all — e.g. an app whose data must not leave the own infrastructure passes []string{ProviderOllama, ProviderGPUStack}; nil allows every provider. Empty specs are skipped, so an optional setting can be passed as is.
func (*Providers) Embedder ¶
Embedder resolves an embedding model spec to an embedder producing dim-sized vectors, wrapped with NewPrefixEmbedder for models that expect instruction prefixes. g must have been initialized with Plugins().
type QATurn ¶
QATurn is one completed question/answer exchange from earlier in a conversation, as sent back by a multi-turn client.
func TrimHistory ¶
TrimHistory keeps only the most recent MaxHistoryTurns entries.
type RankedList ¶
RankedList is one retrieval source's results plus its fusion weight, for ReciprocalRankFusion.
type ReindexResult ¶
type ReindexResult struct {
FilesProcessed int `json:"filesProcessed"`
PagesIndexed int `json:"pagesIndexed"`
Errors []string `json:"errors,omitempty"`
}
ReindexResult summarises one run of ReindexCrawlData.
func ReindexCrawlData ¶
func ReindexCrawlData(ctx context.Context, dir string, indexPage IndexPageFunc, progress func(string)) (ReindexResult, error)
ReindexCrawlData replays every cached crawl-data Markdown file in dir (written by RunCrawl when SAVE_CRAWL_DATA=1) through indexPage, without re-fetching anything — lets a chunking fix or changed chunk size/overlap reach already-crawled sites. progress, if non-nil, is called after each file with a human-readable line; pass nil when nothing is listening.
type RerankOptions ¶
type RerankOptions struct {
BatchSize int // chunks per scoring call; default 5
Parallel int // max in-flight scoring calls; default 4
DefaultScore int // score kept for a batch whose call failed; default 5
}
RerankOptions tunes RerankScores. The zero value is usable — see each field's default below.
type Source ¶
type Source struct {
URL string `json:"url,omitempty"`
Source string `json:"source,omitempty"`
Chunks int `json:"chunks"`
}
Source identifies one logical indexed document — by URL (a web page) or Source (a named file, e.g. a PDF) — with its chunk count. It's the bare grouping identity every app needs for a "list what's indexed" admin view, before any app-specific metadata (ask-pdf's tag/datum/rank) is layered on top; an app wanting that layers its own, richer Sources method instead of using LocalvecStore's/PgvecStore's (see the ask-pdf app's pgvecStore).
type VectorStore ¶
type VectorStore interface {
Index(ctx context.Context, docs []*ai.Document) error
// DeleteByURL removes all indexed documents for the given source URL.
// Returns the number of entries deleted.
DeleteByURL(ctx context.Context, url string) (int, error)
// DeleteBySource removes all indexed documents for the given document
// name, regardless of any other metadata — the same upsert semantics
// DeleteByURL gives web pages, so re-indexing a document replaces every
// existing chunk for it rather than duplicating it. Returns the number
// of entries deleted.
DeleteBySource(ctx context.Context, sourceName string) (int, error)
Close() error
}
VectorStore is the write-side contract IndexSource needs: persist documents and remove stale ones before a fresh index (upsert semantics). It is deliberately minimal — just indexing, not retrieval — so that an app wanting richer read-side behavior (filtered retrieval, keyword search, source administration) can define its own, larger interface: any concrete store satisfying that larger interface automatically satisfies this one too, as long as these four methods keep this exact signature. Retrieval itself is genkit's own ai.Retriever — LocalvecStore exposes one directly rather than wrapping it in something new.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
evalset
command
Command evalset fetches an eval dataset from a running app over HTTP and saves it locally — for apps in Docker, where `genkit flow:run` can't reach the flow and the dataset would otherwise stay inside the container.
|
Command evalset fetches an eval dataset from a running app over HTTP and saves it locally — for apps in Docker, where `genkit flow:run` can't reach the flow and the dataset would otherwise stay inside the container. |