vector

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: May 6, 2026 License: Apache-2.0 Imports: 14 Imported by: 1

Documentation

Overview

Package vector provides embeddings, chunking, similarity search, and an in-process vector store for goagent's long-term memory subsystem.

Overview

The package is built around four orthogonal concerns:

  1. Embedding — converting message content into dense float32 vectors.
  2. Chunking — splitting content into pieces that fit a model's context window.
  3. Similarity — measuring how close two vectors are in semantic space.
  4. Storage — persisting (id, vector, message) triples and searching by similarity.

Embedders

OllamaEmbedder calls a local Ollama server and supports any text embedding model (e.g. "nomic-embed-text"):

e := vector.NewOllamaEmbedder("nomic-embed-text")

FallbackEmbedder filters blocks by type before delegating to a primary embedder, enabling text-only embedders to gracefully handle multimodal input:

fe := vector.NewFallbackEmbedder(
    vector.NewOllamaEmbedder("nomic-embed-text"),
    vector.WithSupportedType(goagent.ContentText),
)

Chunkers

NoOpChunker (default) passes content through as a single chunk:

c := vector.NewNoOpChunker()

TextChunker splits text at word boundaries with configurable overlap:

c := vector.NewTextChunker(
    vector.WithMaxSize(500),
    vector.WithOverlap(50),
)

SentenceChunker splits text at sentence boundaries, grouping complete sentences until the size limit is reached. Overlap is counted in sentences, not tokens, which keeps adjacent chunks semantically coherent:

c := vector.NewSentenceChunker(
    vector.WithSentenceMaxSize(300),
    vector.WithSentenceOverlap(2),
)

BlockChunker processes mixed content (text, images, PDFs) per block:

c := vector.NewBlockChunker(vector.WithPDFExtractor(myExtractor))

Estimators

Estimators measure text size in model-specific units for the Chunker:

&vector.HeuristicTokenEstimator{} // default, ±15% error, no deps
&vector.CharEstimator{}           // character count
vector.NewOllamaTokenEstimator("nomic-embed-text") // exact, via Ollama

Storage

InMemoryStore is a thread-safe in-process store suitable for development and small deployments (< ~10,000 entries). Search is O(n):

store := vector.NewInMemoryStore()

Session filtering

InMemoryStore.Search can filter results to a single session when the context carries a session ID (injected via internal/session.NewContext). Agent.Run sets this automatically when WithName is configured. IDs must follow the "sessionID:baseID:chunkIndex" convention used by memory.LongTermMemory. session.NewContext rejects IDs containing ":" so the first ":" always marks the boundary between the session prefix and the base ID:

ctx, err := session.NewContext(ctx, "user-42")
results, err := store.Search(ctx, queryVec, 3)
// results[i].Message holds the message; results[i].Score is cosine similarity.

Similarity

CosineSimilarity assumes unit-length vectors (as returned by most modern models). Use CosineSimilarityRaw for non-normalized vectors, or call Normalize explicitly:

v := vector.Normalize(rawVec)
score := vector.CosineSimilarity(v, queryVec)
Example

Example demonstrates splitting a text document into chunks with the default TextChunker. Short texts that fit within MaxSize are returned as a single chunk.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	ctx := context.Background()
	chunker := vector.NewTextChunker()

	content := vector.ChunkContent{
		Blocks: []goagent.ContentBlock{
			goagent.TextBlock("Go is a compiled, statically typed language designed for simplicity."),
		},
	}
	chunks, err := chunker.Chunk(ctx, content)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(chunks))
}
Output:
1

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNoEmbeddeableContent = errors.New("vector: no embeddeable content in blocks")

ErrNoEmbeddeableContent is returned when none of the input blocks can be processed by the Embedder (e.g. a text-only embedder receives only images). This is not a fatal error in Store — callers may skip the chunk with errors.Is.

Functions

func ChunkToMessage

func ChunkToMessage(orig goagent.Message, chunk ChunkResult) goagent.Message

ChunkToMessage builds a Message from an original message and a ChunkResult. The returned message preserves the original Role and ToolCallID but replaces Content with the chunk's blocks.

Example

ExampleChunkToMessage shows building a Message from a ChunkResult while preserving the original Role.

package main

import (
	"fmt"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	orig := goagent.AssistantMessage("original content")
	chunk := vector.ChunkResult{
		Blocks:   []goagent.ContentBlock{goagent.TextBlock("chunk content")},
		Metadata: map[string]any{"chunk_index": 0},
	}
	msg := vector.ChunkToMessage(orig, chunk)
	fmt.Println(msg.TextContent())
}
Output:
chunk content

func CosineSimilarity

func CosineSimilarity(a, b []float32) float64

CosineSimilarity computes the cosine similarity between two normalized vectors. Both vectors must have the same length. For unit-length vectors this is equivalent to the dot product — O(n), no sqrt required. Returns values in the range [-1, 1].

Example

ExampleCosineSimilarity shows computing the dot-product similarity between two unit-length vectors — equivalent to cosine similarity for normalized vectors.

package main

import (
	"fmt"

	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	a := []float32{1, 0, 0}
	b := []float32{1, 0, 0}
	fmt.Printf("%.1f\n", vector.CosineSimilarity(a, b)) // identical direction

	c := []float32{1, 0, 0}
	d := []float32{0, 1, 0}
	fmt.Printf("%.1f\n", vector.CosineSimilarity(c, d)) // orthogonal
}
Output:
1.0
0.0

func CosineSimilarityRaw

func CosineSimilarityRaw(a, b []float32) float64

CosineSimilarityRaw computes cosine similarity for vectors that are NOT unit-length. It computes norms in the same pass as the dot product. Returns 0 if either vector is the zero vector.

func EmbedAll

func EmbedAll(ctx context.Context, e goagent.Embedder, chunks []ChunkResult) ([][]float32, error)

EmbedAll embeds each ChunkResult and returns the vectors in the same order. If any chunk fails, it returns an error identifying the failing chunk index. Does not parallelize — the caller is responsible for concurrency.

func ExtractText

func ExtractText(blocks []goagent.ContentBlock) string

ExtractText is the exported counterpart of extractText. It concatenates all ContentText blocks from blocks separated by spaces.

Example

ExampleExtractText shows concatenating all ContentText blocks while ignoring non-text blocks such as images.

package main

import (
	"fmt"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	blocks := []goagent.ContentBlock{
		goagent.TextBlock("hello"),
		goagent.ImageBlock([]byte{0xFF, 0xD8}, "image/jpeg"), // non-text, silently ignored
		goagent.TextBlock("world"),
	}
	fmt.Println(vector.ExtractText(blocks))
}
Output:
hello world

func Normalize

func Normalize(v []float32) []float32

Normalize scales v to unit length (L2 norm = 1.0). Returns a new slice — the input is not modified. If v is the zero vector, returns v unchanged (a zero vector has no direction). Most modern embedding models (nomic-embed-text, voyage-3) already return unit-length vectors; call Normalize only when the model does not.

Example

ExampleNormalize shows scaling a vector to unit length (L2 norm = 1).

package main

import (
	"fmt"

	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	v := []float32{3, 4} // norm = 5
	n := vector.Normalize(v)
	fmt.Printf("%.1f\n", n[0]) // 3/5
	fmt.Printf("%.1f\n", n[1]) // 4/5
}
Output:
0.6
0.8

Types

type BlockChunker

type BlockChunker struct {
	TextMaxSize   int
	TextOverlap   int
	TextEstimator SizeEstimator
	PDFExtractor  PDFExtractor // nil disables page extraction
}

BlockChunker generates chunks per ContentBlock, preserving block types. Long text blocks are sub-divided using TextChunker logic. Images pass through unchanged — they are never split. PDFs are split per page when a PDFExtractor is configured. Requires a multimodal Embedder capable of processing images (Phase 3+).

func NewBlockChunker

func NewBlockChunker(opts ...BlockChunkerOption) *BlockChunker

NewBlockChunker creates a BlockChunker with defaults: MaxSize=500, Overlap=50, Estimator=HeuristicTokenEstimator.

func (*BlockChunker) Chunk

func (c *BlockChunker) Chunk(ctx context.Context, content ChunkContent) ([]ChunkResult, error)

Chunk splits content into typed chunks.

type BlockChunkerOption

type BlockChunkerOption func(*BlockChunker)

BlockChunkerOption configures a BlockChunker.

func WithBlockEstimator

func WithBlockEstimator(e SizeEstimator) BlockChunkerOption

WithBlockEstimator sets the SizeEstimator for text blocks. Default: HeuristicTokenEstimator.

func WithBlockMaxSize

func WithBlockMaxSize(n int) BlockChunkerOption

WithBlockMaxSize sets the maximum text chunk size in estimator units. Default: 500.

func WithBlockOverlap

func WithBlockOverlap(n int) BlockChunkerOption

WithBlockOverlap sets the text overlap in estimator units. Default: 50.

func WithPDFExtractor

func WithPDFExtractor(e PDFExtractor) BlockChunkerOption

WithPDFExtractor sets the PDFExtractor for document blocks. Without an extractor, document blocks pass through as single chunks.

type CharEstimator

type CharEstimator struct{}

CharEstimator measures text size in Unicode code points (runes). No external dependencies. Useful when the model limit is expressed in characters rather than tokens.

Example

ExampleCharEstimator shows measuring text size in Unicode code points. Useful when the model limit is expressed in characters rather than tokens.

package main

import (
	"context"
	"fmt"

	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	ctx := context.Background()
	est := &vector.CharEstimator{}
	fmt.Println(est.Measure(ctx, "hello"))
	fmt.Println(est.Measure(ctx, "日本語"))
}
Output:
5
3

func (*CharEstimator) Measure

func (e *CharEstimator) Measure(_ context.Context, text string) int

Measure returns the number of Unicode code points in text.

type ChunkContent

type ChunkContent struct {
	Blocks   []goagent.ContentBlock
	Metadata map[string]any
}

ChunkContent is the input to a Chunker. Blocks holds the content to be divided. Metadata carries source information that is propagated to every ChunkResult.

type ChunkResult

type ChunkResult struct {
	Blocks   []goagent.ContentBlock
	Metadata map[string]any
}

ChunkResult is the output of a Chunker — a piece ready for embedding. Metadata inherits from ChunkContent and may include chunk_index, chunk_total, page, source, etc., depending on the Chunker implementation.

type Chunker

type Chunker interface {
	Chunk(ctx context.Context, content ChunkContent) ([]ChunkResult, error)
}

Chunker splits a ChunkContent into pieces that can be embedded individually. Implementations decide how to handle each ContentBlock type.

type FallbackEmbedder

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

FallbackEmbedder wraps a primary Embedder and filters out blocks whose type is not the configured supported type. If at least one block survives the filter, it delegates to the primary Embedder. If none survive, it returns ErrNoEmbeddeableContent. Facilitates the transition from text-only to multimodal embedders without changing the calling API.

func NewFallbackEmbedder

func NewFallbackEmbedder(primary goagent.Embedder, opts ...FallbackEmbedderOption) *FallbackEmbedder

NewFallbackEmbedder creates a FallbackEmbedder wrapping primary. Default supported type: ContentText.

Example

ExampleNewFallbackEmbedder shows filtering out non-text blocks before delegating to the primary embedder, and counting skipped blocks.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/internal/testutil"
	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	skipped := 0
	emb := vector.NewFallbackEmbedder(
		&testutil.MockEmbedder{},
		vector.WithSupportedType(goagent.ContentText),
		vector.WithOnSkipped(func(_ goagent.ContentBlock) { skipped++ }),
	)

	blocks := []goagent.ContentBlock{
		goagent.TextBlock("embeddable text"),
		goagent.ImageBlock([]byte{0xFF}, "image/png"), // skipped — not ContentText
	}
	vec, err := emb.Embed(context.Background(), blocks)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(vec) > 0)
	fmt.Println(skipped)
}
Output:
true
1

func (*FallbackEmbedder) Embed

func (e *FallbackEmbedder) Embed(ctx context.Context, blocks []goagent.ContentBlock) ([]float32, error)

Embed filters blocks by the configured supported type and delegates to the primary Embedder. Returns ErrNoEmbeddeableContent when no blocks survive the filter.

type FallbackEmbedderOption

type FallbackEmbedderOption func(*FallbackEmbedder)

FallbackEmbedderOption configures a FallbackEmbedder.

func WithOnSkipped

func WithOnSkipped(fn func(goagent.ContentBlock)) FallbackEmbedderOption

WithOnSkipped registers a callback invoked for each block filtered out because its type is not the supported type. May be nil (default).

func WithSupportedType

func WithSupportedType(t goagent.ContentType) FallbackEmbedderOption

WithSupportedType sets the content type that the primary Embedder can handle. Blocks of other types are filtered out before calling Embed. Default: ContentText.

type HeuristicTokenEstimator

type HeuristicTokenEstimator struct{}

HeuristicTokenEstimator estimates token count using the heuristic len(text)/4 + 4. Typical error: ±15% compared to a real tokenizer. No external dependencies. Recommended default for most use cases. Uses the same formula as memory/policy.TokenWindowPolicy.

Example

ExampleHeuristicTokenEstimator shows the fast token estimator that uses len(text)/4 + 4. Typical error is ±15% compared to a real tokenizer.

package main

import (
	"context"
	"fmt"

	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	ctx := context.Background()
	est := &vector.HeuristicTokenEstimator{}
	fmt.Println(est.Measure(ctx, ""))            // 0/4 + 4 = 4
	fmt.Println(est.Measure(ctx, "hello world")) // 11/4 + 4 = 6
}
Output:
4
6

func (*HeuristicTokenEstimator) Measure

func (e *HeuristicTokenEstimator) Measure(_ context.Context, text string) int

Measure returns an estimated token count for text.

type InMemoryStore

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

InMemoryStore implements goagent.VectorStore using an in-process slice protected by sync.RWMutex. Search is O(n) — suitable for development, testing, and deployments with fewer than ~10,000 entries. For production at larger scale use a dedicated vector database.

func NewInMemoryStore

func NewInMemoryStore() *InMemoryStore

NewInMemoryStore creates an empty InMemoryStore.

Example

ExampleNewInMemoryStore shows upserting a vector, counting entries, and searching for the nearest neighbour.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	ctx := context.Background()
	store := vector.NewInMemoryStore()

	msg := goagent.AssistantMessage("vector databases store embeddings")
	vec := []float32{1, 0, 0} // unit-length vector

	if err := store.Upsert(ctx, "doc:0", vec, msg); err != nil {
		log.Fatal(err)
	}

	count, _ := store.Count(ctx)
	fmt.Println(count)

	results, err := store.Search(ctx, []float32{1, 0, 0}, 1)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(results[0].Message.TextContent())
}
Output:
1
vector databases store embeddings

func (*InMemoryStore) BulkDelete added in v0.5.4

func (s *InMemoryStore) BulkDelete(_ context.Context, ids []string) error

BulkDelete removes all entries with the given ids in a single lock acquisition. IDs that do not exist are silently ignored.

func (*InMemoryStore) BulkUpsert added in v0.5.4

func (s *InMemoryStore) BulkUpsert(_ context.Context, entries []goagent.UpsertEntry) error

BulkUpsert stores or replaces all entries under their respective ids in a single lock acquisition. A copy of each vector is made to protect against caller mutation.

func (*InMemoryStore) Count added in v0.5.5

func (s *InMemoryStore) Count(ctx context.Context, opts ...goagent.SearchOption) (int64, error)

Count returns the number of entries in the store that satisfy the given options. Session scope and metadata filter follow the same rules as Search. WithScoreThreshold is ignored because there is no query vector.

func (*InMemoryStore) Delete added in v0.5.3

func (s *InMemoryStore) Delete(_ context.Context, id string) error

Delete removes the entry with the given id from the store. It is a no-op if id does not exist.

func (*InMemoryStore) Search

func (s *InMemoryStore) Search(ctx context.Context, query []float32, topK int, opts ...goagent.SearchOption) ([]goagent.ScoredMessage, error)

Search returns the topK messages most similar to the query vector, ordered by cosine similarity descending, each paired with its cosine similarity score. If the context carries a session ID (set via WithSessionID), only entries whose id starts with "sessionID:" are considered. Returns fewer than topK results when the store contains fewer matching entries.

WithScoreThreshold is applied before topK truncation. WithFilter matches against each message's Metadata map using deep equality: every key-value pair in the filter must be present in Metadata. Messages without metadata are excluded when a filter is active.

func (*InMemoryStore) Upsert

func (s *InMemoryStore) Upsert(_ context.Context, id string, vec []float32, msg goagent.Message) error

Upsert stores or replaces the message and its embedding under id. The operation is idempotent: calling Upsert twice with the same id replaces the first entry with the second. A copy of the vector is made to protect against caller mutation.

type NoOpChunker

type NoOpChunker struct{}

NoOpChunker returns its input as a single unchanged chunk. Default for conversational messages whose size rarely exceeds model limits.

func NewNoOpChunker

func NewNoOpChunker() *NoOpChunker

NewNoOpChunker creates a NoOpChunker.

Example

ExampleNewNoOpChunker shows a chunker that returns its input as a single unchanged chunk — useful when the content already fits within model limits.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	ctx := context.Background()
	chunker := vector.NewNoOpChunker()

	content := vector.ChunkContent{
		Blocks:   []goagent.ContentBlock{goagent.TextBlock("original text")},
		Metadata: map[string]any{"source": "doc.md"},
	}
	chunks, err := chunker.Chunk(ctx, content)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(chunks))
	fmt.Println(vector.ExtractText(chunks[0].Blocks))
}
Output:
1
original text

func (*NoOpChunker) Chunk

func (c *NoOpChunker) Chunk(_ context.Context, content ChunkContent) ([]ChunkResult, error)

Chunk returns content as a single ChunkResult without modification.

type OllamaTokenEstimator

type OllamaTokenEstimator struct {
	BaseURL string
	Model   string
	// contains filtered or unexported fields
}

OllamaTokenEstimator obtains the exact token count via the Ollama tokenize API. Not suitable for hot paths — use in batch preprocessing or document ingestion only. The caller controls cancellation and timeouts via ctx. On any error (network, parse, cancellation) it silently falls back to HeuristicTokenEstimator.

func NewOllamaTokenEstimator

func NewOllamaTokenEstimator(model string) *OllamaTokenEstimator

NewOllamaTokenEstimator creates an OllamaTokenEstimator for the given model. The Ollama server is expected at http://localhost:11434.

Example

ExampleNewOllamaTokenEstimator shows constructing an estimator that calls the Ollama tokenize API for exact token counts. No Output: because it makes HTTP requests to a local Ollama server.

package main

import (
	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	est := vector.NewOllamaTokenEstimator("llama3.2")
	_ = est
}

func (*OllamaTokenEstimator) Measure

func (e *OllamaTokenEstimator) Measure(ctx context.Context, text string) int

Measure returns the token count from Ollama, falling back to the heuristic estimate on any error. ctx controls the lifetime of the HTTP request.

type PDFExtractor

type PDFExtractor interface {
	ExtractPages(ctx context.Context, data []byte) ([]PDFPage, error)
}

PDFExtractor extracts page content from a PDF document. Implementations may return text (PDFs with embedded text), images (scanned PDFs or pages with diagrams), or both.

type PDFPage

type PDFPage struct {
	Text  string
	Image []byte
}

PDFPage represents the content of a single extracted page. Text is empty when the page contains only images. Image is nil when the page has extractable text.

type PageChunker

type PageChunker struct {
	PDFExtractor PDFExtractor
}

PageChunker splits PDF documents into per-page chunks. Non-document blocks pass through unchanged. Each chunk carries metadata with "page", "total_pages", and "source". For v0.4 use only when processing PDFs with extractable text.

func NewPageChunker

func NewPageChunker(opts ...PageChunkerOption) *PageChunker

NewPageChunker creates a PageChunker with the given options.

func (*PageChunker) Chunk

func (c *PageChunker) Chunk(ctx context.Context, content ChunkContent) ([]ChunkResult, error)

Chunk splits document blocks by page; non-document blocks pass through.

type PageChunkerOption

type PageChunkerOption func(*PageChunker)

PageChunkerOption configures a PageChunker.

func WithPagePDFExtractor

func WithPagePDFExtractor(e PDFExtractor) PageChunkerOption

WithPagePDFExtractor sets the PDFExtractor for the PageChunker.

type RecursiveChunker added in v0.5.1

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

RecursiveChunker divides text by respecting a hierarchy of separators. It tries high-level separators first (\n\n, \n) and falls back to sentences and words only when necessary. Ideal for Markdown, documentation, and paragraph-structured text.

Overlap is applied post-split: the tail of chunk[i] is prepended to chunk[i+1] to preserve context at boundaries.

func NewRecursiveChunker added in v0.5.1

func NewRecursiveChunker(opts ...RecursiveChunkerOption) *RecursiveChunker

NewRecursiveChunker creates a RecursiveChunker with sensible defaults: separators=defaultSeparators, maxSize=500, overlap=50, estimator=HeuristicTokenEstimator.

Example

ExampleNewRecursiveChunker shows splitting a two-paragraph document at the paragraph boundary (\n\n) before falling back to finer separators.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	ctx := context.Background()
	chunker := vector.NewRecursiveChunker(
		vector.WithRCMaxSize(30),
		vector.WithRCOverlap(0),
		vector.WithRCEstimator(&vector.CharEstimator{}),
	)

	content := vector.ChunkContent{
		Blocks: []goagent.ContentBlock{
			goagent.TextBlock("First paragraph text here.\n\nSecond paragraph text here."),
		},
	}
	chunks, err := chunker.Chunk(ctx, content)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(chunks))
	fmt.Println(vector.ExtractText(chunks[0].Blocks))
	fmt.Println(vector.ExtractText(chunks[1].Blocks))
}
Output:
2
First paragraph text here.
Second paragraph text here.

func (*RecursiveChunker) Chunk added in v0.5.1

func (c *RecursiveChunker) Chunk(ctx context.Context, content ChunkContent) ([]ChunkResult, error)

Chunk splits content into hierarchically-aware chunks. ContentImage and ContentDocument blocks are silently ignored. Returns nil when there is no text. When all text fits in a single chunk, chunk_index and chunk_total are not added to the metadata.

type RecursiveChunkerOption added in v0.5.1

type RecursiveChunkerOption func(*RecursiveChunker)

RecursiveChunkerOption configures a RecursiveChunker.

func WithRCEstimator added in v0.5.1

func WithRCEstimator(e SizeEstimator) RecursiveChunkerOption

WithRCEstimator sets the SizeEstimator used to measure text length. Default: HeuristicTokenEstimator.

func WithRCMaxSize added in v0.5.1

func WithRCMaxSize(n int) RecursiveChunkerOption

WithRCMaxSize sets the maximum chunk size in estimator units. Default: 500.

func WithRCOverlap added in v0.5.1

func WithRCOverlap(n int) RecursiveChunkerOption

WithRCOverlap sets the overlap budget in estimator units appended from the tail of chunk[i] to the head of chunk[i+1]. Default: 50.

func WithRCSeparators added in v0.5.1

func WithRCSeparators(seps []string) RecursiveChunkerOption

WithRCSeparators sets the ordered list of separators the chunker tries when splitting text. The first separator that produces multiple fragments is used; shorter separators are only tried when larger ones produce no split.

type SentenceChunker added in v0.5.1

type SentenceChunker struct {
	MaxSize   int           // maximum chunk size in Estimator units
	Overlap   int           // number of sentences shared between adjacent chunks
	Estimator SizeEstimator // default: HeuristicTokenEstimator
}

SentenceChunker splits text at sentence boundaries and groups sentences into chunks that do not exceed MaxSize. Adjacent chunks share the last Overlap complete sentences for context, preserving semantic coherence better than token-level overlap.

ContentImage and ContentDocument blocks are silently ignored. Compatible with any text-only Embedder.

func NewSentenceChunker added in v0.5.1

func NewSentenceChunker(opts ...SentenceChunkerOption) *SentenceChunker

NewSentenceChunker creates a SentenceChunker with defaults: MaxSize=500, Overlap=1, Estimator=HeuristicTokenEstimator.

Example

ExampleNewSentenceChunker shows grouping sentences into chunks that respect MaxSize, with one overlapping sentence between adjacent chunks for context.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	ctx := context.Background()
	chunker := vector.NewSentenceChunker(
		vector.WithSentenceMaxSize(20),
		vector.WithSentenceOverlap(0),
		vector.WithSentenceEstimator(&vector.CharEstimator{}),
	)

	content := vector.ChunkContent{
		Blocks: []goagent.ContentBlock{
			goagent.TextBlock("Go is fast. It uses goroutines."),
		},
	}
	chunks, err := chunker.Chunk(ctx, content)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(chunks))
	fmt.Println(vector.ExtractText(chunks[0].Blocks))
	fmt.Println(vector.ExtractText(chunks[1].Blocks))
}
Output:
2
Go is fast.
It uses goroutines.

func (*SentenceChunker) Chunk added in v0.5.1

func (c *SentenceChunker) Chunk(ctx context.Context, content ChunkContent) ([]ChunkResult, error)

Chunk splits content into sentence-aware chunks. Returns nil when there is no text. When all sentences fit in a single chunk, chunk_index and chunk_total are not added to the metadata.

type SentenceChunkerOption added in v0.5.1

type SentenceChunkerOption func(*SentenceChunker)

SentenceChunkerOption configures a SentenceChunker.

func WithSentenceEstimator added in v0.5.1

func WithSentenceEstimator(e SizeEstimator) SentenceChunkerOption

WithSentenceEstimator sets the SizeEstimator used to measure chunk size. Default: HeuristicTokenEstimator.

func WithSentenceMaxSize added in v0.5.1

func WithSentenceMaxSize(n int) SentenceChunkerOption

WithSentenceMaxSize sets the maximum chunk size in estimator units. Default: 500.

func WithSentenceOverlap added in v0.5.1

func WithSentenceOverlap(n int) SentenceChunkerOption

WithSentenceOverlap sets the number of complete sentences carried from one chunk into the next. Unlike TextChunker, overlap is counted in sentences, not tokens, which preserves semantic coherence at boundaries. Default: 1.

type SizeEstimator

type SizeEstimator interface {
	Measure(ctx context.Context, text string) int
}

SizeEstimator measures the size of a text string in implementation-defined units. The units determine the semantics of MaxSize and Overlap in chunkers. ctx is passed through so implementations that perform I/O (e.g. OllamaTokenEstimator) can be cancelled or time-limited by the caller.

type TextChunker

type TextChunker struct {
	MaxSize   int           // maximum chunk size in Estimator units
	Overlap   int           // overlap between adjacent chunks in Estimator units
	Estimator SizeEstimator // default: HeuristicTokenEstimator
}

TextChunker extracts ContentText blocks and splits them when they exceed MaxSize. ContentImage and ContentDocument blocks are silently ignored. Compatible with any text-only Embedder.

func NewTextChunker

func NewTextChunker(opts ...TextChunkerOption) *TextChunker

NewTextChunker creates a TextChunker with defaults: MaxSize=500, Overlap=50, Estimator=HeuristicTokenEstimator.

Example

ExampleNewTextChunker shows splitting text at word boundaries when it exceeds MaxSize, with zero overlap between adjacent chunks.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/vector"
)

func main() {
	ctx := context.Background()
	chunker := vector.NewTextChunker(
		vector.WithMaxSize(10),
		vector.WithOverlap(0),
		vector.WithEstimator(&vector.CharEstimator{}),
	)

	content := vector.ChunkContent{
		Blocks: []goagent.ContentBlock{goagent.TextBlock("hello world foo bar")},
	}
	chunks, err := chunker.Chunk(ctx, content)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(chunks))
	fmt.Println(vector.ExtractText(chunks[0].Blocks))
	fmt.Println(vector.ExtractText(chunks[1].Blocks))
}
Output:
2
hello world
foo bar

func (*TextChunker) Chunk

func (c *TextChunker) Chunk(ctx context.Context, content ChunkContent) ([]ChunkResult, error)

Chunk splits content into text chunks. Returns nil when there is no text.

type TextChunkerOption

type TextChunkerOption func(*TextChunker)

TextChunkerOption configures a TextChunker.

func WithEstimator

func WithEstimator(e SizeEstimator) TextChunkerOption

WithEstimator sets the SizeEstimator used to measure chunk size. Default: HeuristicTokenEstimator.

func WithMaxSize

func WithMaxSize(n int) TextChunkerOption

WithMaxSize sets the maximum chunk size in estimator units. Default: 500.

func WithOverlap

func WithOverlap(n int) TextChunkerOption

WithOverlap sets the number of overlapping units between adjacent chunks. Default: 50.

Directories

Path Synopsis
pgvector module
sqlitevec module

Jump to

Keyboard shortcuts

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