Text Chunking
Split extracted text into overlapping, structure-aware chunks ready to embed and index for RAG. Four strategies support different document types — text splits on whitespace/punctuation, Markdown preserves structure and code blocks, YAML maintains section hierarchy, and semantic chunking uses embeddings to detect topic shifts.
Strategies
Section titled “Strategies”- Text — splits on whitespace/punctuation boundaries
- Markdown — structure-aware; preserves headings, lists, and code blocks
- YAML — section-aware; preserves YAML document structure
- Semantic — topic-aware; splits at natural document boundaries
Semantic Chunking
Section titled “Semantic Chunking”Set chunker_type to "semantic". Uses an embedding model for topic detection when one is configured; otherwise falls back to structural heuristics.
config = ExtractionConfig( chunking=ChunkingConfig(chunker_type="semantic"))Behavior:
- Without embeddings — Uses structural heuristics: detects headers (ALL CAPS, numbered sections) and paragraph boundaries
- With embeddings — Compares consecutive paragraphs via embeddings to detect topic shifts, merging paragraphs below the
topic_threshold(default: 0.75)
Use topic_threshold to control sensitivity: lower values (0.1–0.3) detect more topic boundaries (more, smaller chunks); higher values (0.7–0.9) detect fewer (fewer, larger chunks). Only applies when an embedding model is configured.
Markdown Tables
Section titled “Markdown Tables”When chunker_type is "markdown", set table_chunking to control how tables that exceed the chunk size are split:
split(default) — split at row boundaries; continuation chunks do not repeat the headerrepeat_header— prepend the header row and separator to every continuation chunk so each chunk is self-contained
Configuration
Section titled “Configuration”import asynciofrom xberg import ExtractInput, ExtractionConfig, ChunkingConfig, extract
async def main() -> None: config: ExtractionConfig = ExtractionConfig( chunking=ChunkingConfig( max_chars=1000, max_overlap=200, ) ) result = await extract(ExtractInput.from_uri("document.pdf"), config) print(f"Chunks: {len(result.chunks or [])}") for chunk in result.chunks or []: print(f"Length: {len(chunk.content)}")
asyncio.run(main())import asynciofrom xberg import ExtractInput, ExtractionConfig, ChunkingConfig, extract
async def main() -> None: config: ExtractionConfig = ExtractionConfig( chunking=ChunkingConfig( chunker_type="markdown", max_chars=500, max_overlap=50, sizing_type="tokenizer", sizing_model="Xenova/gpt-4o", ) ) result = await extract(ExtractInput.from_uri("document.md"), config) for chunk in result.chunks or []: heading_context = chunk.metadata.get("heading_context") if heading_context: headings = heading_context.get("headings", []) for h in headings: print(f"Heading L{h['level']}: {h['text']}") print(f"Content: {chunk.content[:100]}...")
asyncio.run(main())import asynciofrom xberg import ExtractInput, ExtractionConfig, ChunkingConfig, extract
async def main() -> None: config: ExtractionConfig = ExtractionConfig( chunking=ChunkingConfig(chunker_type="semantic") ) result = await extract(ExtractInput.from_uri("document.pdf"), config) for chunk in result.chunks or []: print(f"Content: {chunk.content[:100]}...")
asyncio.run(main())import asynciofrom xberg import ExtractInput, ExtractionConfig, ChunkingConfig, extract
async def main() -> None: config: ExtractionConfig = ExtractionConfig( chunking=ChunkingConfig( chunker_type="markdown", max_chars=500, max_overlap=50, prepend_heading_context=True, ) ) result = await extract(ExtractInput.from_uri("document.md"), config) for chunk in result.chunks or []: # Each chunk's content is prefixed with its heading breadcrumb print(f"Content: {chunk.content[:100]}...")
asyncio.run(main())import { extract } from "@xberg-io/xberg";
const config = { chunking: { maxChars: 1000, maxOverlap: 200, },};
const result = await extract({ kind: "uri", uri: "document.pdf" }, config);console.log(`Total chunks: ${result.chunks?.length ?? 0}`);import { extract } from "@xberg-io/xberg";
const config = { chunking: { chunkerType: "markdown", maxChars: 500, maxOverlap: 50, sizingType: "tokenizer", sizingModel: "Xenova/gpt-4o", },};
const result = await extract({ kind: "uri", uri: "document.md" }, config);for (const chunk of result.chunks ?? []) { const headings = chunk.metadata?.headingContext?.headings ?? []; for (const heading of headings) { console.log(`Heading L${heading.level}: ${heading.text}`); } console.log(`Content: ${chunk.content.slice(0, 100)}...`);}import { extract } from "@xberg-io/xberg";
const config = { chunking: { chunkerType: "semantic", },};
const result = await extract({ kind: "uri", uri: "document.pdf" }, config);for (const chunk of result.chunks ?? []) { console.log(`Content: ${chunk.content.slice(0, 100)}...`);}import { extract } from "@xberg-io/xberg";
const config = { chunking: { chunkerType: "markdown", maxChars: 500, maxOverlap: 50, prependHeadingContext: true, },};
const result = await extract({ kind: "uri", uri: "document.md" }, config);for (const chunk of result.chunks ?? []) { // Each chunk's content is prefixed with its heading breadcrumb console.log(`Content: ${chunk.content.slice(0, 100)}...`);}use xberg::{ExtractionConfig, ChunkingConfig};
let config = ExtractionConfig { chunking: Some(ChunkingConfig { max_characters: 1000, overlap: 200, embedding: None, }), ..Default::default()};use xberg::{ExtractionConfig, ChunkingConfig, ChunkerType};
let config = ExtractionConfig { chunking: Some(ChunkingConfig { chunker_type: ChunkerType::Semantic, ..Default::default() }), ..Default::default()};use xberg::{ExtractionConfig, ChunkingConfig, ChunkerType};
let config = ExtractionConfig { chunking: Some(ChunkingConfig { max_characters: 500, overlap: 50, chunker_type: ChunkerType::Markdown, prepend_heading_context: true, ..Default::default() }), ..Default::default()};package main
import ( "fmt"
"github.com/xberg-io/xberg/packages/go")
func main() { maxChars := uint(1000) overlap := uint(200) config := xberg.ExtractionConfig{ Chunking: &xberg.ChunkingConfig{ MaxCharacters: &maxChars, Overlap: &overlap, }, }
fmt.Printf("Config: MaxCharacters=%d, Overlap=%d\n", *config.Chunking.MaxCharacters, *config.Chunking.Overlap)}package main
import ( "fmt" "log"
"github.com/xberg-io/xberg/packages/go")
func main() { maxChars := uint(500) overlap := uint(50) model := "Xenova/gpt-4o" chunkerType := xberg.ChunkerTypeMarkdown
config := xberg.ExtractionConfig{ Chunking: &xberg.ChunkingConfig{ MaxCharacters: &maxChars, Overlap: &overlap, ChunkerType: &chunkerType, Sizing: xberg.ChunkSizing{ Type: "tokenizer", Model: &model, }, }, }
input := xberg.ExtractInputFromURI("document.md") result, err := xberg.Extract(*input, config) if err != nil { log.Fatalf("extract failed: %v", err) }
for _, chunk := range result.Results[0].Chunks { if chunk.Metadata.HeadingContext != nil { for _, heading := range chunk.Metadata.HeadingContext.Headings { fmt.Printf("Heading L%d: %s\n", heading.Level, heading.Text) } } fmt.Printf("Content: %.100s...\n", chunk.Content) }}package main
import ( "fmt" "log"
"github.com/xberg-io/xberg/packages/go")
func main() { maxChars := uint(500) overlap := uint(50) chunkerType := xberg.ChunkerTypeMarkdown
config := xberg.ExtractionConfig{ Chunking: &xberg.ChunkingConfig{ MaxCharacters: &maxChars, Overlap: &overlap, ChunkerType: &chunkerType, PrependHeadingContext: true, }, }
input := xberg.ExtractInputFromURI("document.md") result, err := xberg.Extract(*input, config) if err != nil { log.Fatalf("extract failed: %v", err) }
for _, chunk := range result.Results[0].Chunks { // Each chunk's content is prefixed with its heading breadcrumb fmt.Printf("Content: %.100s...\n", chunk.Content) }}import io.xberg.ExtractionConfig;import io.xberg.ExtractInputKind;import io.xberg.ExtractionResult;import io.xberg.ExtractedDocument;import io.xberg.ChunkingConfig;
ExtractionConfig config = ExtractionConfig.builder() .chunking(ChunkingConfig.builder() .maxChars(1000) .maxOverlap(200) .build()) .build();import io.xberg.Xberg;import io.xberg.ExtractInput;import io.xberg.ExtractInputKind;import io.xberg.ExtractionConfig;import io.xberg.ExtractionResult;import io.xberg.ExtractedDocument;import io.xberg.ChunkingConfig;
ExtractionConfig config = ExtractionConfig.builder() .chunking(ChunkingConfig.builder() .chunkerType("markdown") .maxChars(500) .maxOverlap(50) .sizingTokenizer("Xenova/gpt-4o") .build()) .build();ExtractionResult output = Xberg.extract( ExtractInput.builder().withKind(ExtractInputKind.Uri).withUri("document.md").build(), config);ExtractedDocument result = output.results().get(0);result.chunks().forEach(chunk -> { var headingContext = chunk.metadata().headingContext(); if (headingContext.isPresent()) { System.out.println("Headings:"); headingContext.get().headings().forEach(heading -> System.out.println(" Level " + heading.level() + ": " + heading.text()) ); }});import io.xberg.Xberg;import io.xberg.ExtractInput;import io.xberg.ExtractInputKind;import io.xberg.ExtractionConfig;import io.xberg.ExtractionResult;import io.xberg.ExtractedDocument;import io.xberg.ChunkingConfig;
ExtractionConfig config = ExtractionConfig.builder() .chunking(ChunkingConfig.builder() .prependHeadingContext(true) .build()) .build();ExtractionResult output = Xberg.extract( ExtractInput.builder().withKind(ExtractInputKind.Uri).withUri("document.md").build(), config);ExtractedDocument result = output.results().get(0);// Each chunk's content is prefixed with its heading breadcrumbresult.chunks().forEach(chunk -> System.out.println(chunk.content().substring(0, Math.min(100, chunk.content().length()))));using Xberg;
class Program{ static async Task Main() { var config = new ExtractionConfig { Chunking = new ChunkingConfig { MaxChars = 1000, MaxOverlap = 200, Embedding = new EmbeddingConfig { Model = EmbeddingModelType.Preset("all-minilm-l6-v2"), Normalize = true, BatchSize = 32 } } };
try { var result = (await XbergConverter.ExtractAsync(ExtractInput.FromUri( "document.pdf"), config )).Results[0].ConfigureAwait(false);
Console.WriteLine($"Chunks: {result.Chunks.Count}"); foreach (var chunk in result.Chunks) { Console.WriteLine($"Content length: {chunk.Content.Length}"); if (chunk.Embedding != null) { Console.WriteLine($"Embedding dimensions: {chunk.Embedding.Length}"); } } } catch (XbergException ex) { Console.WriteLine($"Error: {ex.Message}"); } }}using Xberg;
class Program{ static async Task Main() { var config = new ExtractionConfig { Chunking = new ChunkingConfig { MaxChars = 500, MaxOverlap = 50, Sizing = new ChunkSizingConfig { Type = "tokenizer", Model = "Xenova/gpt-4o" } } };
try { var result = (await XbergConverter.ExtractAsync(ExtractInput.FromUri( "document.md"), config )).Results[0].ConfigureAwait(false);
foreach (var chunk in result.Chunks) { if (chunk.HeadingContext?.Headings != null) { Console.WriteLine("Headings:"); foreach (var heading in chunk.HeadingContext.Headings) { Console.WriteLine($" Level {heading.Level}: {heading.Text}"); } } } } catch (XbergException ex) { Console.WriteLine($"Error: {ex.Message}"); } }}using Xberg;
class Program{ static async Task Main() { var config = new ExtractionConfig { Chunking = new ChunkingConfig { MaxChars = 500, MaxOverlap = 50, PrependHeadingContext = true } };
try { var result = (await XbergConverter.ExtractAsync(ExtractInput.FromUri( "document.md"), config )).Results[0].ConfigureAwait(false);
foreach (var chunk in result.Chunks) { // Each chunk's content is prefixed with its heading breadcrumb Console.WriteLine(chunk.Content[..Math.Min(100, chunk.Content.Length)]); } } catch (XbergException ex) { Console.WriteLine($"Error: {ex.Message}"); } }}require 'xberg'
config = Xberg::ExtractionConfig.new( chunking: Xberg::ChunkingConfig.new( max_characters: 1000, overlap: 200 ))require 'xberg'
config = Xberg::ExtractionConfig.new( chunking: Xberg::ChunkingConfig.new( chunker_type: "markdown", max_characters: 500, overlap: 50, sizing_type: "tokenizer", sizing_model: "Xenova/gpt-4o" ))
input = Xberg::ExtractInput.new(uri: "document.md")result = Xberg.extract(input, config)
result.results.first.chunks.each do |chunk| if chunk.metadata.heading_context puts "Headings:" chunk.metadata.heading_context.headings.each do |heading| puts " #{' ' * (heading.level - 1) * 2}Level #{heading.level}: #{heading.text}" end endendrequire 'xberg'
config = Xberg::ExtractionConfig.new( chunking: Xberg::ChunkingConfig.new( chunker_type: "markdown", max_characters: 500, overlap: 50, prepend_heading_context: true ))
input = Xberg::ExtractInput.new(uri: "document.md")result = Xberg.extract(input, config)
result.results.first.chunks.each do |chunk| # Each chunk's content is prefixed with its heading breadcrumb puts chunk.content[0, 100]endimport { initWasm, extract } from "@xberg-io/xberg-wasm";
await initWasm();
const config = { chunking: { maxChars: 1000, chunkOverlap: 100, },};
const bytes = new Uint8Array(buffer);const result = await extract({ kind: "bytes", bytes, mimeType: "application/pdf" }, config);
result.chunks?.forEach((chunk, idx) => { console.log(`Chunk ${idx}: ${chunk.content.substring(0, 50)}...`); console.log(`Tokens: ${chunk.metadata?.token_count}`);});import { initWasm, extract } from "@xberg-io/xberg-wasm";
await initWasm();
const config = { chunking: { chunkerType: "markdown", maxChars: 2000, // Note: Token-based sizing is not available in WASM builds. // Use character-based sizing instead. },};
const bytes = new Uint8Array(buffer);const result = await extract({ kind: "bytes", bytes, mimeType: "text/markdown" }, config);
result.chunks?.forEach((chunk, idx) => { console.log(`Chunk ${idx}: ${chunk.content.substring(0, 50)}...`);
if (chunk.metadata?.headingContext?.headings) { console.log("Headings:"); chunk.metadata.headingContext.headings.forEach((h) => { console.log(` Level ${h.level}: ${h.text}`); }); }});import { initWasm, extract } from "@xberg-io/xberg-wasm";
await initWasm();
const config = { chunking: { chunkerType: "markdown", maxChars: 2000, prependHeadingContext: true, },};
const bytes = new Uint8Array(buffer);const result = await extract({ kind: "bytes", bytes, mimeType: "text/markdown" }, config);
result.chunks?.forEach((chunk, idx) => { // Each chunk's content is prefixed with its heading breadcrumb console.log(`Chunk ${idx}: ${chunk.content.substring(0, 80)}...`);});Chunk Output
Section titled “Chunk Output”Each chunk in result.chunks contains:
| Field | Description |
|---|---|
content |
Chunk text |
metadata.byte_start / byte_end |
Byte offsets in the original text |
metadata.chunk_index / total_chunks |
Position in sequence |
metadata.token_count |
Token count (when embeddings enabled) |
metadata.heading_context |
Active heading hierarchy (Markdown chunker only) |
metadata.heading_path |
Flattened RAG-shaped heading breadcrumb (e.g., ["Title", "Section", "Subsection"]) for vector database retrieval and context. |
metadata.page_spans |
Per-page coordinates the chunk covers — see Per-Page Chunk Coordinates below. |
metadata.node_ids |
Ids of the covered document.nodes[] entries — see Linking Chunks to Document Nodes below. |
metadata.classifications |
Multi-label classification result — see Chunk Classification below. |
embedding |
Embedding vector (when configured) |
Chunks can be sized by token count instead of characters — enable the chunking-tokenizers feature and set sizing to tokenizer.
Per-Page Chunk Coordinates
Section titled “Per-Page Chunk Coordinates”metadata.page_spans is a list of { page, bbox? } entries, one per page the chunk overlaps, in page order — the first and last entries’ page fields equal metadata.first_page/metadata.last_page. Use it to drive viewer highlighting (draw a box on the source page for a given chunk) without re-deriving page geometry from content offsets.
- Populated whenever page-boundary provenance is available — the same condition under which
first_page/last_pageare populated. bboxon each entry is additionally populated wheninclude_document_structureis enabled, as the union of that page’s body-layer node bounding boxes found within the chunk.- Empty (and omitted from JSON output) when page-boundary provenance is unavailable.
{ "content": "...", "metadata": { "first_page": 2, "last_page": 3, "page_spans": [ { "page": 2, "bbox": { "x0": 72.0, "y0": 120.5, "x1": 540.0, "y1": 640.2 } }, { "page": 3 } ] }}Cross-page fragment stitching for page_spans mirrors the same-page-only scope of table identity — each page’s span is reported independently.
Linking Chunks to Document Nodes
Section titled “Linking Chunks to Document Nodes”metadata.node_ids is a list of the id values of the document.nodes[] (DocumentStructure) entries whose text the chunk covers, deduplicated and in document node-traversal order. Join chunk.metadata.node_ids against document.nodes[].id to trace a chunk back to the structural nodes it came from.
- Populated only when document structure is available (the PDF extraction path) — plain-text and structure-less documents get an empty
node_ids. - Membership is a best-effort verbatim-text-containment check (a node’s text found within the chunk), gated on a minimum node-text length, so very short nodes may not be linked. It is not a byte-exact mapping.
Chunk Classification
Section titled “Chunk Classification”Classify each chunk against a caller-supplied, description-backed label set — the chunk-level analogue of Page Classification. Set ExtractionConfig.chunk_classification to a ChunkClassificationConfig:
| Field | Type | Default | Description |
|---|---|---|---|
definitions |
list[{label, description}] |
— | Label taxonomy. Unlike page classification’s bare label list, every label carries a description injected into the prompt, so the model can disambiguate similarly named labels in large taxonomies. Must contain at least one entry. |
llm |
LlmConfig |
— | LLM configuration used for classification. |
batch_size |
int |
10 |
Number of chunks grouped into a single classification request. Larger batches amortize the fixed cost of the definitions block (repeated verbatim per request) across more chunks, at the risk of exceeding context limits for large taxonomies. |
max_concurrency |
int |
4 |
Maximum number of in-flight batch requests, bounded to avoid rate-limiting the configured LLM provider. |
prompt_template |
str | None |
None |
Minijinja template. Receives {{ definitions }} (rendered label + description list) and {{ chunks }} (numbered chunk texts in the batch). None uses the built-in default. |
Unlike page classification, chunk classification is always multi-label — a chunk may match zero, one, or many definitions. Leaving chunk_classification unset (the default) disables the post-processor entirely and makes no LLM calls.
from xberg import ( ChunkClassificationConfig, ChunkClassificationDefinition, ChunkingConfig, ExtractInput, ExtractionConfig, LlmConfig, extract,)
config = ExtractionConfig( chunking=ChunkingConfig(chunker_type="markdown"), chunk_classification=ChunkClassificationConfig( definitions=[ ChunkClassificationDefinition( label="pricing", description="Discusses cost, fees, or subscription tiers.", ), ChunkClassificationDefinition( label="liability", description="Discusses indemnification, warranties, or limitation of liability.", ), ], llm=LlmConfig(model="openai/gpt-4o-mini"), batch_size=10, max_concurrency=4, ),)output = await extract(ExtractInput(kind="uri", uri="contract.pdf"), config=config)result = output.results[0]for chunk in result.chunks or []: for label in chunk.metadata.classifications: print(label.label, label.confidence)use xberg::{ ChunkClassificationConfig, ChunkClassificationDefinition, ChunkingConfig, ExtractInput, ExtractionConfig, LlmConfig, extract,};
let config = ExtractionConfig { chunking: Some(ChunkingConfig { chunker_type: "markdown".into(), ..Default::default() }), chunk_classification: Some(ChunkClassificationConfig { prompt_template: None, definitions: vec![ ChunkClassificationDefinition { label: "pricing".into(), description: "Discusses cost, fees, or subscription tiers.".into(), }, ChunkClassificationDefinition { label: "liability".into(), description: "Discusses indemnification, warranties, or limitation of liability.".into(), }, ], llm: LlmConfig { model: "openai/gpt-4o-mini".into(), ..Default::default() }, batch_size: 10, max_concurrency: 4, }), ..Default::default()};let output = extract(ExtractInput::from_uri("contract.pdf"), &config).await?;let result = &output.results[0];for chunk in result.chunks.iter().flatten() { for label in &chunk.metadata.classifications { println!("{} {:?}", label.label, label.confidence); }}[chunking]chunker_type = "markdown"
[[chunk_classification.definitions]]label = "pricing"description = "Discusses cost, fees, or subscription tiers."
[[chunk_classification.definitions]]label = "liability"description = "Discusses indemnification, warranties, or limitation of liability."
[chunk_classification.llm]model = "openai/gpt-4o-mini"
batch_size = 10max_concurrency = 4Token Sizing with Your Own Tokenizer (Plugin Variant)
Section titled “Token Sizing with Your Own Tokenizer (Plugin Variant)”Token budgets only protect the embedder when they are counted with the tokenizer the embedder actually uses. When that tokenizer isn’t available as a HuggingFace tokenizer.json (llama.cpp/GGUF vocabularies, SentencePiece models, custom vocabs), plug it in — Xberg calls back into the registered backend to count tokens instead of loading one from the Hub. The chunking-tokenizers feature is still required (it gates the tokenizer sizing variant itself); language bindings ship with it enabled.
- Register the backend once at startup via
xberg::plugins::register_tokenizer_backend(Arc::new(MyTokenizer)). The backend implementsTokenizerBackend(aPlugin-inheriting trait with a synchronouscount_tokens(text) -> usize— it runs inside the splitter’s boundary search, so keep it cheap). - Reference it by name in the chunking config:
{ "sizing": { "type": "tokenizer", "model": "my-tokenizer" } }. A registered name takes precedence over a HuggingFace id; unregistered names fall back to the Hub as before. max_charactersis then the chunk budget in that backend’s tokens.
Language bindings register through the same API — implement the trait’s methods (name, initialize, shutdown, count_tokens) on a host-language object and pass it to register_tokenizer_backend.
RAG Pipeline Example
Section titled “RAG Pipeline Example”import asynciofrom xberg import ( ExtractInput, extract, ExtractionConfig, ChunkingConfig, EmbeddingConfig, EmbeddingModelType,)
async def main() -> None: config: ExtractionConfig = ExtractionConfig( chunking=ChunkingConfig( max_chars=500, max_overlap=50, embedding=EmbeddingConfig( model=EmbeddingModelType.preset("balanced"), normalize=True, batch_size=16 ) ) ) result = await extract(ExtractInput.from_uri("research_paper.pdf"), config)
chunks_with_embeddings: list = [] for chunk in result.chunks or []: if chunk.embedding: chunks_with_embeddings.append({ "content": chunk.content[:100], "embedding_dims": len(chunk.embedding) })
print(f"Chunks with embeddings: {len(chunks_with_embeddings)}")
asyncio.run(main())import { extract } from "@xberg-io/xberg";
const config = { chunking: { maxChars: 500, maxOverlap: 50, embedding: { preset: "balanced", }, },};
const result = await extract({ kind: "uri", uri: "research_paper.pdf" }, config);
if (result.chunks) { for (const chunk of result.chunks) { console.log(`Chunk ${chunk.metadata.chunkIndex + 1}/${chunk.metadata.totalChunks}`); console.log(`Position: ${chunk.metadata.charStart}-${chunk.metadata.charEnd}`); console.log(`Content: ${chunk.content.slice(0, 100)}...`); if (chunk.embedding) { console.log(`Embedding: ${chunk.embedding.length} dimensions`); } }}use xberg::{extract, ExtractionConfig, ExtractInput, ChunkingConfig, EmbeddingConfig};
let config = ExtractionConfig { chunking: Some(ChunkingConfig { max_characters: 500, overlap: 50, embedding: Some(EmbeddingConfig { model: "balanced".to_string(), normalize: true, ..Default::default() }), ..Default::default() }), ..Default::default()};
let output = extract(ExtractInput::from_uri("research_paper.pdf"), &config).await?;let result = &output.results[0];
if let Some(chunks) = &result.chunks { for chunk in chunks { println!("Chunk {}/{}", chunk.metadata.chunk_index + 1, chunk.metadata.total_chunks ); println!("Position: {}-{}", chunk.metadata.byte_start, chunk.metadata.byte_end ); println!("Content: {}...", &chunk.content[..100.min(chunk.content.len())]); if let Some(embedding) = &chunk.embedding { println!("Embedding: {} dimensions", embedding.len()); } }}package main
import ( "fmt" "log"
"github.com/xberg-io/xberg/packages/go")
func main() { maxChars := 500 maxOverlap := 50 normalize := true batchSize := int32(16)
cfg := xberg.ExtractionConfig{ Chunking: &xberg.ChunkingConfig{ MaxChars: &maxChars, MaxOverlap: &maxOverlap, Embedding: &xberg.EmbeddingConfig{ Model: xberg.EmbeddingModelType_Preset("all-mpnet-base-v2"), Normalize: &normalize, BatchSize: &batchSize, }, }, }
input := xberg.ExtractInputFromURI("research_paper.pdf") result, err := xberg.Extract(*input, cfg) if err != nil { log.Fatalf("RAG extraction failed: %v", err) }
chunks := result.Results[0].Chunks fmt.Printf("Found %d chunks for RAG pipeline\n", len(chunks))
for i := 0; i < len(chunks) && i < 3; i++ { chunk := chunks[i] content := chunk.Content if len(content) > 80 { content = content[:80] } fmt.Printf("Chunk %d: %s...\n", i, content) }}import io.xberg.Xberg;import io.xberg.ExtractInputKind;import io.xberg.ExtractionResult;import io.xberg.ExtractedDocument;import io.xberg.ExtractionConfig;import io.xberg.ExtractInput;import io.xberg.ChunkingConfig;import io.xberg.EmbeddingConfig;import io.xberg.EmbeddingModelType;import java.util.List;
ExtractionConfig config = ExtractionConfig.builder() .chunking(ChunkingConfig.builder() .maxChars(500) .maxOverlap(50) .embedding(EmbeddingConfig.builder() .model(EmbeddingModelType.preset("all-mpnet-base-v2")) .normalize(true) .batchSize(16) .build()) .build()) .build();try { ExtractionResult output = Xberg.extract( ExtractInput.builder().withKind(ExtractInputKind.Uri).withUri("research_paper.pdf").build(), config ); ExtractedDocument result = output.results().get(0); List<Object> chunks = result.chunks() != null ? result.chunks() : List.of(); System.out.println("Found " + chunks.size() + " chunks for RAG pipeline"); for (int i = 0; i < Math.min(3, chunks.size()); i++) { Object chunk = chunks.get(i); System.out.println("Chunk " + i + ": " + chunk.toString().substring(0, Math.min(80, chunk.toString().length())) + "..."); }} catch (Exception ex) { System.err.println("RAG extraction failed: " + ex.getMessage());}using Xberg;using System.Collections.Generic;using System.Linq;
class RagPipelineExample{ static async Task Main() { var config = new ExtractionConfig { Chunking = new ChunkingConfig { MaxChars = 500, MaxOverlap = 50, Embedding = new EmbeddingConfig { Model = EmbeddingModelType.Preset("all-mpnet-base-v2"), Normalize = true, BatchSize = 16 } } };
try { var result = (await XbergConverter.ExtractAsync(ExtractInput.FromUri( "research_paper.pdf"), config )).Results[0].ConfigureAwait(false);
var vectorStore = await BuildVectorStoreAsync(result.Chunks) .ConfigureAwait(false);
var query = "machine learning optimization"; var relevantChunks = await SearchAsync(vectorStore, query) .ConfigureAwait(false);
Console.WriteLine($"Found {relevantChunks.Count} relevant chunks"); foreach (var chunk in relevantChunks.Take(3)) { Console.WriteLine($"Content: {chunk.Content[..80]}..."); Console.WriteLine($"Similarity: {chunk.Similarity:F3}\n"); } } catch (XbergException ex) { Console.WriteLine($"Error: {ex.Message}"); } }
static async Task<List<VectorEntry>> BuildVectorStoreAsync( IEnumerable<Chunk> chunks) { return await Task.Run(() => { return chunks.Select(c => new VectorEntry { Content = c.Content, Embedding = c.Embedding?.ToArray() ?? Array.Empty<float>(), Similarity = 0f }).ToList(); }).ConfigureAwait(false); }
static async Task<List<VectorEntry>> SearchAsync( List<VectorEntry> store, string query) { return await Task.Run(() => { return store .OrderByDescending(e => e.Similarity) .ToList(); }).ConfigureAwait(false); }
class VectorEntry { public string Content { get; set; } = string.Empty; public float[] Embedding { get; set; } = Array.Empty<float>(); public float Similarity { get; set; } }}require 'xberg'
config = Xberg::ExtractionConfig.new( chunking: Xberg::ChunkingConfig.new( max_characters: 500, overlap: 50, embedding: Xberg::EmbeddingConfig.new( model: Xberg::EmbeddingModelType.new( type: 'preset', name: 'all-mpnet-base-v2' ), normalize: true, batch_size: 16 ) ))
input = Xberg::ExtractInput.new(uri: 'research_paper.pdf')result = Xberg.extract(input, config)
vector_store = build_vector_store(result.results.first.chunks)query = 'machine learning optimization'relevant_chunks = search_vector_store(vector_store, query)
puts "Found #{relevant_chunks.length} relevant chunks"relevant_chunks.take(3).each do |chunk| puts "Content: #{chunk[:content][0..80]}..." puts "Similarity: #{chunk[:similarity]&.round(3)}\n"end
def build_vector_store(chunks) chunks.map.with_index do |chunk, idx| { id: idx, content: chunk.content, embedding: chunk.embedding, similarity: 0.0 } endend
def search_vector_store(store, query) store.sort_by { |entry| entry[:similarity] }.reverseendSee also
Section titled “See also”- Embeddings — generate vectors for semantic search
- Page Classification — the page-level analogue of chunk classification
- Output Formats — table identity and anchor markers
- Configuration Reference — all chunking options