This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Stages

Catalogue of built-in stages and how to configure them.

For conceptual documentation — what a Stage is, the Stage contract, conditions as a design decision, and child document emission — see Architecture: Stage.

Configuring a Stage

To configure a Stage, provide its class in the config. You can also specify a name (for logging and error messages), conditions, and conditionPolicy:

{
  name: "AddRandomBoolean-First"
  class: "com.kmwllc.lucille.stage.AddRandomBoolean"
  field_name: "rand_bool_1"
  percent_true: 65
}

Each Stage also accepts its own implementation-specific parameters (like field_name and percent_true above). See the individual stage pages below for details.


Conditions

For any Stage, you can specify conditions in its config to control when the Stage processes a Document.

Condition parameters

ParameterRequiredDescription
fieldsYesOne or more field names to evaluate.
valuesNoList of values to match against those fields. If omitted, only field existence is checked.
valuesPathNoPath to a file containing match values, one per line. Use instead of values when the list is large or managed externally. Supports local paths, classpath: resources, and cloud storage URIs (S3, GCS, HTTPS).
operatorNo"must" (default) — condition passes if a match is found. "must_not" — condition passes if no match is found.

values and valuesPath are mutually exclusive — specifying both is an error.

How matching works

With values or valuesPath: The condition passes if any of the listed fields contains any of the listed values. Matching is type-coerced to string — a boolean field true matches the value "true", an integer 10 matches "10". null is a valid value entry and will match a null field value.

Without values or valuesPath: The condition checks field existence only.

  • operator: "must" — passes if all listed fields are present on the document.
  • operator: "must_not" — passes if all listed fields are absent from the document.

conditionPolicy

When a stage has multiple conditions, conditionPolicy in the stage’s root config controls how they combine:

  • "all" (default) — all conditions must be met
  • "any" — at least one condition must be met

Examples

Run a stage only when a field exists:

{
  class: "com.kmwllc.lucille.stage.MyStage"
  conditions: [
    { fields: ["content"] }
  ]
}

Run a stage only when a field matches a value:

{
  name: "print-1"
  class: "com.kmwllc.lucille.stage.Print"
  conditions: [
    { fields: ["city"], values: ["Boston", "New York"] }
  ]
}

Skip a stage when a field is present (must_not existence check):

{
  class: "com.kmwllc.lucille.stage.OpenAIEmbed"
  conditions: [
    { fields: ["embedding"], operator: "must_not" }
  ]
}

Require multiple conditions (all must be met):

{
  class: "com.kmwllc.lucille.stage.OpenAIEmbed"
  conditionPolicy: "all"
  conditions: [
    { fields: ["content"] }
    { fields: ["content_type"], values: ["article"] }
  ]
}

Load match values from a file:

{
  class: "com.kmwllc.lucille.stage.DropDocument"
  conditions: [
    { fields: ["category"], valuesPath: "s3://my-bucket/excluded-categories.txt" }
  ]
}

For the full reference on controlling document fate and connector sequencing — conditions, skipping, dropping, error handling, child documents, and more — see Control Flow.


Stage Catalogue

See All Stages for a complete listing of all available stages organized by category, including their configuration parameters.

Detailed pages are available for more complex stages:

  • ChunkText — Split long text fields into chunks for embedding and RAG pipelines.
  • EmbeddedPython — Run Python code inside the JVM using GraalPy.
  • ExternalPython — Delegate processing to an external Python process via Py4J.
  • PromptOllama — Enrich documents using a locally-running LLM.
  • QueryOpensearch — Execute OpenSearch search templates per document.

Plugin stages (TextExtractor, ApplyOCR, ApplyOpenNLPNameFinders, JlamaEmbed) are listed at the bottom of All Stages with their Maven dependencies.

1 - All Stages

A complete reference of all Stages available in lucille-core, organized by category.

This page lists all Stages available in lucille-core and as optional plugin modules.

All stages share common configuration parameters:

ParameterDescription
classRequired. The fully qualified class name of the Stage.
nameOptional display name used in logging and metrics.
conditionsOptional list of conditions controlling when the Stage executes.
conditionPolicy"any" or "all" (default: "all").

Field Manipulation

CopyFields

com.kmwllc.lucille.stage.CopyFields

Copies one or more source fields to destination fields.

ParameterTypeRequiredDescription
sourceList<String>YesSource field names.
destList<String>YesDestination field names (parallel to source).
updateModeStringNooverwrite, append, or skip. Default: overwrite.
{ class: "com.kmwllc.lucille.stage.CopyFields", source: ["title"], dest: ["title_copy"] }

RenameFields

com.kmwllc.lucille.stage.RenameFields

Renames fields by mapping old names to new names.

ParameterTypeRequiredDescription
fieldMappingMap<String, String>YesMap of old field name → new field name.
{ class: "com.kmwllc.lucille.stage.RenameFields", fieldMapping: { old_field: new_field } }

DeleteFields

com.kmwllc.lucille.stage.DeleteFields

Removes specified fields from a Document.

ParameterTypeRequiredDescription
fieldsList<String>YesField names to delete.

SetStaticValues

com.kmwllc.lucille.stage.SetStaticValues

Sets one or more fields to fixed, static values.

ParameterTypeRequiredDescription
fieldsMap<String, Object>YesMap of field name → static value to set.
updateModeStringNooverwrite, append, or skip. Default: overwrite.
skipDocumentBooleanNoIf true, marks the document as skipped after setting values. The document bypasses downstream stages but still reaches the Indexer. Useful for combining field-setting and skipping in a single stage with one set of conditions. Default: false.
{ class: "com.kmwllc.lucille.stage.SetStaticValues", fields: { source: "my-connector", version: 2 } }

Concatenate

com.kmwllc.lucille.stage.Concatenate

Concatenates the values of multiple source fields into a destination field.

ParameterTypeRequiredDescription
sourceList<String>YesFields whose values will be concatenated.
destStringYesDestination field name.
delimiterStringNoSeparator inserted between values. Default: "".
updateModeStringNooverwrite, append, or skip.

SplitFieldValues

com.kmwllc.lucille.stage.SplitFieldValues

Splits a field value (or multi-valued field) by a delimiter into a list.

ParameterTypeRequiredDescription
fieldsList<String>YesFields to split.
delimiterStringNoDelimiter string. Default: ",".
updateModeStringNooverwrite, append, or skip.

RemoveDuplicateValues

com.kmwllc.lucille.stage.RemoveDuplicateValues

Removes duplicate values from multi-valued fields.

ParameterTypeRequiredDescription
fieldsList<String>YesFields to deduplicate.

RemoveEmptyFields

com.kmwllc.lucille.stage.RemoveEmptyFields

Removes fields whose value is null, an empty string, or an empty list.

ParameterTypeRequiredDescription
fieldsList<String>NoSpecific fields to check. If omitted, all fields are checked.

NormalizeFieldNames

com.kmwllc.lucille.stage.NormalizeFieldNames

Normalizes field names (e.g., lowercasing, replacing spaces with underscores).


DropValues

com.kmwllc.lucille.stage.DropValues

Removes specific values from multi-valued fields.

ParameterTypeRequiredDescription
fieldValuePairsMap<String, List<String>>YesMap of field name → list of values to remove.

Text Processing

TrimWhitespace

com.kmwllc.lucille.stage.TrimWhitespace

Trims leading and trailing whitespace from string fields.

ParameterTypeRequiredDescription
fieldsList<String>YesFields to trim.

RemoveDiacritics

com.kmwllc.lucille.stage.RemoveDiacritics

Normalizes accented characters to their ASCII equivalents (e.g., ée).

ParameterTypeRequiredDescription
fieldsList<String>YesFields to normalize.

TruncateField

com.kmwllc.lucille.stage.TruncateField

Truncates string field values to a maximum length.

ParameterTypeRequiredDescription
fieldsList<String>YesFields to truncate.
maxLengthIntegerYesMaximum allowed length.

Length

com.kmwllc.lucille.stage.Length

Computes the length (number of characters or list elements) of field values and stores the result.

ParameterTypeRequiredDescription
sourceList<String>YesFields to measure.
destList<String>YesFields to write lengths to.

ApplyRegex

com.kmwllc.lucille.stage.ApplyRegex

Applies a regular expression to one or more fields. Can extract capture groups or check for matches.

ParameterTypeRequiredDescription
sourceList<String>YesFields to apply the regex to.
destList<String>NoFields to write extracted groups to (parallel to source).
regexStringYesThe regular expression pattern.
updateModeStringNooverwrite, append, or skip.

ReplacePatterns

com.kmwllc.lucille.stage.ReplacePatterns

Performs pattern-based find-and-replace on string field values.

ParameterTypeRequiredDescription
fieldsList<String>YesFields to process.
patternsMap<String, String>YesMap of regex pattern → replacement string.

Base64Decode

com.kmwllc.lucille.stage.Base64Decode

Decodes a Base64-encoded field value into a byte array or string.

ParameterTypeRequiredDescription
sourceStringYesField containing the Base64-encoded value.
destStringYesField to write the decoded value to.

NormalizeText

com.kmwllc.lucille.stage.NormalizeText

Applies Unicode normalization and optional case folding to text fields.

ParameterTypeRequiredDescription
fieldsList<String>YesFields to normalize.

ExtractFirstCharacter

com.kmwllc.lucille.stage.ExtractFirstCharacter

Extracts the first character of a string field.

ParameterTypeRequiredDescription
sourceStringYesSource field.
destStringYesDestination field.

CreateStaticTeaser

com.kmwllc.lucille.stage.CreateStaticTeaser

Generates a teaser (short excerpt) from a text field.

ParameterTypeRequiredDescription
sourceStringYesSource text field.
destStringYesDestination teaser field.
lengthIntegerNoMaximum teaser length in characters.

HashFieldValueToBucket

com.kmwllc.lucille.stage.HashFieldValueToBucket

Hashes a field’s value and assigns the document to a numbered bucket (useful for deterministic partitioning).

ParameterTypeRequiredDescription
sourceStringYesField whose value is hashed.
destStringYesField to write the bucket number to.
numBucketsIntegerYesTotal number of buckets.

Type Conversion & Parsing

ParseDate

com.kmwllc.lucille.stage.ParseDate

Parses date strings using configurable format patterns and writes an Instant or formatted string.

ParameterTypeRequiredDescription
sourceList<String>YesFields containing date strings.
destList<String>NoDestination fields. Defaults to overwriting source.
formatsList<String>YesDate format patterns to try, in order.
timezoneStringNoTimezone for parsing. Default: UTC.

ParseFloats

com.kmwllc.lucille.stage.ParseFloats

Parses a JSON array string (e.g., "[0.1, 0.2, 0.3]") into a list of floats.

ParameterTypeRequiredDescription
sourceStringYesField containing the JSON array string.
destStringYesDestination field for the parsed list.

ParseFilePath

com.kmwllc.lucille.stage.ParseFilePath

Extracts path components (directory, filename, extension) from a file path field.

ParameterTypeRequiredDescription
sourceStringYesField containing the file path.
directoryDestStringNoDestination for the directory component.
filenameDestStringNoDestination for the filename (without extension).
extensionDestStringNoDestination for the file extension.

ParseJson

com.kmwllc.lucille.stage.ParseJson

Parses a JSON string field into a JsonNode.

ParameterTypeRequiredDescription
sourceStringYesField containing a JSON string.
destStringYesDestination field for the parsed JsonNode.

Timestamp

com.kmwllc.lucille.stage.Timestamp

Writes the current timestamp to a field.

ParameterTypeRequiredDescription
destStringNoDestination field. Default: timestamp.

Document Flow Control

DropDocument

com.kmwllc.lucille.stage.DropDocument

Marks a document as dropped. It will not be sent to the Indexer.

Typically used with conditions to selectively drop documents:

{
  class: "com.kmwllc.lucille.stage.DropDocument"
  conditions: [
    { fields: ["status"], values: ["deleted"] }
  ]
}

SkipDocument

com.kmwllc.lucille.stage.SkipDocument

Marks a document as skipped. It bypasses all downstream Stages but still reaches the Indexer. Used to issue deletes against a search backend.

{
  class: "com.kmwllc.lucille.stage.SkipDocument"
  conditions: [
    { fields: ["is_deleted"], values: ["true"] }
  ]
}

Contains

com.kmwllc.lucille.stage.Contains

Checks whether a field’s value is contained in a configured list. Sets a boolean result field.

ParameterTypeRequiredDescription
fieldStringYesField to check.
valuesList<String>YesValues to look for.
destStringYesDestination boolean field.

EmitNestedChildren

com.kmwllc.lucille.stage.EmitNestedChildren

Extracts a nested array from a Document and emits each array element as an independent child Document.

ParameterTypeRequiredDescription
fieldStringYesField containing the array of nested objects to extract.
keepParentBooleanNoWhether to also emit the parent Document. Default: true.

CreateChildrenStage

com.kmwllc.lucille.stage.CreateChildrenStage

Generates child documents from the current document’s fields using configurable rules.


CollapseChildrenDocuments

com.kmwllc.lucille.stage.CollapseChildrenDocuments

Merges child document field values back onto the parent document.


HTML, XML, and Web

ApplyJSoup

com.kmwllc.lucille.stage.ApplyJSoup

Parses HTML content using JSoup and extracts text or attribute values using CSS selectors.

ParameterTypeRequiredDescription
byteArrayFieldStringYesField containing the HTML as a byte array.
destinationFieldsMapYesMap of destination field name → {type, selector} definition.

Each destination field definition requires:

  • type: "text" (inner text) or "attr" (attribute value).
  • selector: CSS selector.
  • attr: (if type is "attr") the attribute name to extract.
{
  class: "com.kmwllc.lucille.stage.ApplyJSoup"
  byteArrayField: "html_content"
  destinationFields: {
    title: { type: "text", selector: "h1" }
    body:  { type: "text", selector: ".article-body p" }
  }
}

XPathExtractor

com.kmwllc.lucille.stage.XPathExtractor

Evaluates XPath expressions against an XML field.

ParameterTypeRequiredDescription
xmlFieldStringYesField containing XML content.
fieldMappingsMap<String, String>YesMap of destination field → XPath expression.

FetchUri

com.kmwllc.lucille.stage.FetchUri

Fetches the content of a URL stored in a document field and stores the response as a byte array.

ParameterTypeRequiredDescription
sourceStringYesField containing the URL to fetch.
destStringYesField to write the response bytes to.

File Handling

ApplyFileHandlers

com.kmwllc.lucille.stage.ApplyFileHandlers

Applies configured FileHandlers to a byte array field, generating child documents for each extracted record.

ParameterTypeRequiredDescription
fileContentFieldStringNoField containing the file bytes. Default: file_content.
filePathFieldStringNoField containing the file path (used to determine handler). Default: file_path.
fileHandlersObjectYesFileHandler configuration — same structure as the fileHandlers block in FileConnector. At least one handler must be declared.

FetchFileContent

com.kmwllc.lucille.stage.FetchFileContent

Loads the content of a file path (from local filesystem or cloud storage) into a byte array field on the document.

ParameterTypeRequiredDescription
pathFieldStringYesField containing the file path or URI.
destFieldStringNoDestination byte array field. Default: file_content.

ComputeFieldSize

com.kmwllc.lucille.stage.ComputeFieldSize

Measures the byte size of a field value (e.g., a byte array or string) and stores the result.

ParameterTypeRequiredDescription
sourceStringYesField to measure.
destStringYesDestination field for the size in bytes.

TextExtractor

com.kmwllc.lucille.tika.stage.TextExtractor (requires lucille-tika)

Extracts text from over 1,000 file formats — PDF, Microsoft Office documents, HTML, images with embedded OCR, and many more — using Apache Tika. Reads raw bytes from a source field and writes extracted text to a destination field.

Maven dependency:

<dependency>
  <groupId>com.kmwllc</groupId>
  <artifactId>lucille-tika</artifactId>
  <version>${lucille.version}</version>
</dependency>
{
  name: "extract-text"
  class: "com.kmwllc.lucille.tika.stage.TextExtractor"
  source: "file_content"
  dest: "extracted_text"
}

ApplyOCR

com.kmwllc.lucille.ocr.stage.ApplyOCR (requires lucille-ocr)

Performs optical character recognition on image fields using Tesseract. Reads image bytes from a source field and writes the recognized text to a destination field. Requires Tesseract to be installed on the system running Lucille.

Maven dependency:

<dependency>
  <groupId>com.kmwllc</groupId>
  <artifactId>lucille-ocr</artifactId>
  <version>${lucille.version}</version>
</dependency>
{
  name: "ocr"
  class: "com.kmwllc.lucille.ocr.stage.ApplyOCR"
  source: "image_bytes"
  dest: "ocr_text"
  language: "eng"
}

Enrichment & Lookup

DictionaryLookup

com.kmwllc.lucille.stage.DictionaryLookup

Looks up field values in a term dictionary and adds matched entries as field values.

ParameterTypeRequiredDescription
sourceList<String>YesFields whose values are used as lookup keys.
destList<String>YesDestination fields for lookup results.
dictPathStringYesPath to the dictionary file.

QueryDatabase

com.kmwllc.lucille.stage.QueryDatabase

Executes a JDBC prepared statement using document field values as parameters and merges the result onto the document. Useful for per-document database enrichment (e.g., joining a lookup table for each document mid-pipeline).

ParameterTypeRequiredDescription
driverStringYesJDBC driver class name.
connectionStringStringYesJDBC connection URL.
jdbcUserStringYesDatabase username.
jdbcPasswordStringYesDatabase password.
sqlStringNoSQL query with ? placeholders for parameters.
keyFieldsList<String>YesDocument field names whose values are substituted for ? in the SQL, in order.
inputTypesList<String>YesJDBC types for each key field (e.g., "STRING", "INT", "LONG"). Must match keyFields length.
fieldMappingMap<String, String>YesMaps result-set column names to document field names.

ElasticsearchLookup

com.kmwllc.lucille.stage.ElasticsearchLookup

Performs a lookup against an Elasticsearch index and merges matching fields onto the document.


QueryOpensearch

com.kmwllc.lucille.stage.QueryOpensearch

Executes a search template against an OpenSearch index using document field values as parameters. See QueryOpensearch for full documentation.


MatchQuery

com.kmwllc.lucille.stage.MatchQuery

Executes a match query against a configured search backend and enriches the document with matching results.


AI / ML

OpenAIEmbed

com.kmwllc.lucille.stage.OpenAIEmbed

Generates vector embeddings for a text field using the OpenAI Embeddings API.

ParameterTypeRequiredDescription
sourceStringYesField containing the text to embed.
apiKeyStringYesOpenAI API key. Use ${OPENAI_API_KEY} for environment variable substitution.
embedDocumentBooleanYesWhether to embed the main document.
embedChildrenBooleanYesWhether to embed child documents.
destStringNoField to write the embedding vector to. Default: embeddings.
modelNameStringNoOpenAI embedding model. Default: text-embedding-3-small.
dimensionsIntegerNoOutput vector dimensions (only supported by text-embedding-3-* models).

Supported models: text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002

Text is truncated to 8,191 tokens before embedding (the OpenAI API limit). Lucille uses jtokkit for accurate token counting before the API call.

{
  class: "com.kmwllc.lucille.stage.OpenAIEmbed"
  source: "content"
  dest: "content_vector"
  modelName: "text-embedding-3-small"
  apiKey: ${OPENAI_API_KEY}
  embedDocument: true
  embedChildren: true
}

JlamaEmbed

com.kmwllc.lucille.jlama.stage.JlamaEmbed (requires lucille-jlama)

Generates vector embeddings using a quantized LLM running locally inside the JVM via Jlama. No API key or external service required — the model runs directly in the Lucille process. Useful for teams with data-residency or compliance constraints that prevent sending documents to external APIs.

Maven dependency:

<dependency>
  <groupId>com.kmwllc</groupId>
  <artifactId>lucille-jlama</artifactId>
  <version>${lucille.version}</version>
</dependency>
{
  name: "embed"
  class: "com.kmwllc.lucille.jlama.stage.JlamaEmbed"
  source: "content"
  dest: "content_vector"
  modelPath: "/models/my-embedding-model"
}

PromptOllama

com.kmwllc.lucille.stage.PromptOllama

Sends document fields to a locally-running Ollama LLM and merges the JSON response back onto the document. See PromptOllama for full documentation.


EmbeddedPython

com.kmwllc.lucille.stage.EmbeddedPython

Runs per-document Python code inside the JVM using GraalPy. See EmbeddedPython for full documentation.


ExternalPython

com.kmwllc.lucille.stage.ExternalPython

Delegates per-document processing to an external Python process via Py4J. See ExternalPython for full documentation.


ApplyJavascript

com.kmwllc.lucille.stage.ApplyJavascript

Runs a JavaScript snippet per document using GraalVM’s JavaScript engine.

ParameterTypeRequiredDescription
scriptStringNoInline JavaScript code.
scriptPathStringNoPath to a .js file. Exactly one of script or scriptPath must be provided.

ApplyJSONata

com.kmwllc.lucille.stage.ApplyJSONata

Applies a JSONata expression to transform the document’s JSON representation.

ParameterTypeRequiredDescription
expressionStringYesJSONata expression to apply.
destStringNoDestination field for the expression result. If omitted, results are merged onto the document.

ExtractEntitiesFST

com.kmwllc.lucille.stage.ExtractEntitiesFST

Performs named entity recognition using a finite-state transducer dictionary.

ParameterTypeRequiredDescription
sourceStringYesField containing text to extract entities from.
destStringYesDestination field for extracted entity values.
dictionaryPathStringYesPath to the FST dictionary file.

ExtractEntities

com.kmwllc.lucille.stage.ExtractEntities

Extracts entities from text fields using configured rules.


ApplyOpenNLPNameFinders (requires lucille-entity-extraction)

com.kmwllc.lucille.entity.stage.ApplyOpenNLPNameFinders

Performs named entity recognition (NER) using Apache OpenNLP models. Identifies entities such as people, organizations, and locations in text fields.

Maven dependency:

<dependency>
  <groupId>com.kmwllc</groupId>
  <artifactId>lucille-entity-extraction</artifactId>
  <version>${lucille.version}</version>
</dependency>

DetectLanguage

com.kmwllc.lucille.stage.DetectLanguage

Detects the language of a text field and writes the ISO language code to a destination field.

ParameterTypeRequiredDescription
sourceStringYesField containing the text.
destStringYesDestination field for the language code (e.g., "en", "fr").

ChunkText

com.kmwllc.lucille.stage.ChunkText

Splits a long text field into smaller chunks suitable for embedding (RAG pipelines). Each chunk is emitted as a child document. See ChunkText for full documentation.

ParameterTypeRequiredDescription
sourceStringYesField containing the text to chunk.
destStringNoField name for chunk content in child documents. Default: text.
chunkingMethodStringNoStrategy: sentence, paragraph, fixed, or custom. Default: sentence.
regexStringNo*Regex delimiter for custom chunking method.
lengthToSplitIntegerNo*Characters per chunk for fixed chunking method.
chunksToMergeIntegerNoMerge N initial chunks into one final chunk. Default: 1.
chunksToOverlapIntegerNoNumber of chunks to overlap when merging.
overlapPercentageIntegerNoPercentage of neighbouring chunks to add as overlap. Default: 0.
characterLimitIntegerNoHard maximum character count for a final chunk.
preMergeMinChunkLenIntegerNoAppend chunks shorter than this to a neighbour before merging.
preMergeMaxChunkLenIntegerNoTruncate chunks longer than this before merging.
cleanChunksBooleanNoRemove newlines and trim chunks. Default: false.

Chunking methods:

  • sentence — Detects sentence boundaries using OpenNLP.
  • paragraph — Splits on consecutive line breaks (\n\n, \r\n\r\n, etc.).
  • fixed — Splits every lengthToSplit characters.
  • custom — Splits on occurrences of the regex pattern.

Child document fields: Each child document receives id (parent ID + chunk number), parent_id, offset, length, chunk_number, total_chunks, and the chunk content in dest.


RandomVector

com.kmwllc.lucille.stage.RandomVector

Generates a random float vector and sets it on a document field. Useful for testing vector search pipelines.

ParameterTypeRequiredDescription
destStringYesDestination field for the random vector.
dimensionsIntegerYesNumber of dimensions in the vector.

Testing & Debugging

Print

com.kmwllc.lucille.stage.Print

Logs documents in JSON format at INFO level and/or writes them to a file. Place Print anywhere in the pipeline to capture the document state at that point.

ParameterTypeRequiredDescription
shouldLogBooleanNoLog each document as JSON at INFO level. Default: true.
outputFileStringNoPath to a file to write documents to (one JSON object per line). Created if it does not exist.
whitelistList<String>NoIf set, only these fields are included in the output.
blacklistList<String>NoFields to exclude from the output.
overwriteFileBooleanNoOverwrite the output file if it already exists. Default: true.
appendThreadNameBooleanNoAppend the Worker thread name to the output filename, keeping per-thread output separate. Recommended when using multiple worker threads. Default: true.
{
  class: "com.kmwllc.lucille.stage.Print"
  shouldLog: false
  outputFile: "/tmp/pipeline-output.jsonl"
}

Capture and Replay

Print enables a useful development pattern: run a pipeline to capture its output to disk, then replay that output directly into a search backend without re-running the enrichment.

Step 1 — Capture. Add Print to the end of your pipeline with an outputFile. Use a NopIndexer (or set sendEnabled: false on a real indexer) so no data is actually indexed during the capture run:

pipelines: [{
  name: my-pipeline
  stages: [
    { class: "com.kmwllc.lucille.stage.SomeExpensiveEnrichment" ... }
    {
      class: "com.kmwllc.lucille.stage.Print"
      shouldLog: false
      outputFile: "/tmp/captured.jsonl"
    }
  ]
}]
indexer { type: Nop }

Step 2 — Replay. Point a FileConnector at the captured JSONL file using the JSON file handler. Use a minimal or empty pipeline — the captured documents already contain all enriched fields:

connectors: [{
  name: replay
  class: "com.kmwllc.lucille.connector.FileConnector"
  pipeline: replay-pipeline
  paths: ["/tmp/captured.jsonl"]
  fileHandlers: { json: { idField: "id" } }
}]
pipelines: [{
  name: replay-pipeline
  stages: []
}]
indexer { type: OpenSearch }
opensearch { ... }

This lets you iterate on indexer configuration, field mappings, or search backend settings without repeating expensive enrichment (OCR, embedding generation, database lookups) on every attempt.


AddRandomString

com.kmwllc.lucille.stage.AddRandomString

Adds a random alphanumeric string to a field.

ParameterTypeRequiredDescription
field_nameStringNoDestination field. Default: random_string.
lengthIntegerNoString length. Default: 8.

AddRandomInt

com.kmwllc.lucille.stage.AddRandomInt

Adds a random integer to a field.

ParameterTypeRequiredDescription
field_nameStringNoDestination field. Default: random_int.
minIntegerNoMinimum value (inclusive). Default: 0.
maxIntegerNoMaximum value (exclusive). Default: 100.

AddRandomDouble

com.kmwllc.lucille.stage.AddRandomDouble

Adds a random double to a field.

ParameterTypeRequiredDescription
field_nameStringNoDestination field. Default: random_double.
minDoubleNoMinimum value. Default: 0.0.
maxDoubleNoMaximum value. Default: 1.0.

AddRandomDate

com.kmwllc.lucille.stage.AddRandomDate

Adds a random date/timestamp to a field within a configured range.

ParameterTypeRequiredDescription
field_nameStringNoDestination field. Default: random_date.

AddRandomBoolean

com.kmwllc.lucille.stage.AddRandomBoolean

Adds a random boolean to a field.

ParameterTypeRequiredDescription
field_nameStringNoDestination field. Default: random_bool.
percent_trueIntegerNoPercentage chance of true. Default: 50.

AddRandomNestedField

com.kmwllc.lucille.stage.AddRandomNestedField

Adds a nested JSON object with random values to a field. Useful for testing nested document structures.


2 - ChunkText

Split a long text field into smaller overlapping chunks for embedding and RAG pipelines. Each chunk becomes a child document.

The ChunkText Stage splits a long text field into smaller, optionally overlapping segments. Each segment is emitted as a child document that flows independently through all downstream stages and is indexed as its own record. This is the foundation of retrieval-augmented generation (RAG) pipelines in Lucille.

Configuration

{
  class: "com.kmwllc.lucille.stage.ChunkText"
  source: "body"
  dest: "text"
  chunkingMethod: "sentence"
  chunksToMerge: 5
  chunksToOverlap: 1
  cleanChunks: true
}

Configuration Parameters

ParameterTypeRequiredDescription
sourceStringYesField containing the text to chunk.
destStringNoField name for chunk content in child docs. Default: text.
chunkingMethodStringNoChunking strategy. Default: sentence. See below.
regexStringRequired for customRegex pattern to split on.
lengthToSplitIntegerRequired for fixedNumber of characters per chunk.
chunksToMergeIntegerNoHow many initial chunks to merge into one final chunk. Default: 1 (no merging).
chunksToOverlapIntegerNoNumber of chunks from the previous final chunk to prepend to the current one.
overlapPercentageIntegerNoPercentage of the current chunk’s characters to add from its neighbours. Default: 0.
characterLimitIntegerNoHard maximum character count for a final chunk after all merging.
preMergeMinChunkLenIntegerNoInitial chunks shorter than this are appended to a neighbour before merging.
preMergeMaxChunkLenIntegerNoInitial chunks longer than this are truncated before merging.
cleanChunksBooleanNoRemove internal newlines and trim whitespace from each chunk. Default: false.

Chunking Methods

sentence (default)

Detects sentence boundaries using an Apache OpenNLP sentence detector. This produces semantically coherent chunks. Best used with chunksToMerge to combine a few sentences into each final chunk.

{
  class: "com.kmwllc.lucille.stage.ChunkText"
  source: "body"
  chunkingMethod: "sentence"
  chunksToMerge: 4      # 4 sentences per chunk
  chunksToOverlap: 1    # 1 sentence of overlap between chunks
}

paragraph

Splits on consecutive line breaks (\n\n, \r\n\r\n, etc.). Suitable for documents with paragraph structure.

{
  class: "com.kmwllc.lucille.stage.ChunkText"
  source: "body"
  chunkingMethod: "paragraph"
  characterLimit: 1000    # Cap chunks at 1000 characters
}

fixed

Splits every lengthToSplit characters. Simple and predictable, but may cut mid-sentence.

{
  class: "com.kmwllc.lucille.stage.ChunkText"
  source: "body"
  chunkingMethod: "fixed"
  lengthToSplit: 512
}

custom

Splits on occurrences of a regex pattern. Useful when documents have a known structural delimiter.

{
  class: "com.kmwllc.lucille.stage.ChunkText"
  source: "body"
  chunkingMethod: "custom"
  regex: "---+"    # Split on markdown horizontal rules
}

Processing Order

The Stage applies transformations in this order:

  1. Initial chunking by the chosen method.
  2. Cleaning (if cleanChunks: true): strip newlines, trim whitespace.
  3. Pre-merge filtering: drop or truncate chunks based on preMergeMinChunkLen / preMergeMaxChunkLen.
  4. Merging (if chunksToMerge > 1): combine N chunks into each final chunk.
  5. Overlap (if chunksToOverlap or overlapPercentage set): prepend content from adjacent chunks.
  6. Character limiting (if characterLimit set): truncate oversized final chunks.

Child Document Fields

Each chunk becomes a child document with these fields:

FieldDescription
id{parent_id}-chunk-{n} (e.g., doc-42-chunk-0).
parent_idID of the parent document.
chunk_numberZero-based index of this chunk.
total_chunksTotal number of chunks from this parent.
offsetCharacter offset of the chunk’s start in the original text.
lengthNumber of characters in the chunk.
{dest}The chunk text (default field name: text).

All other fields from the parent document are not copied to child documents unless a downstream Stage does so explicitly.

Typical RAG Pipeline

pipelines: [
  {
    name: "rag-pipeline"
    stages: [
      # 1. Extract text from PDF bytes
      {
        class: "com.kmwllc.lucille.tika.stage.TextExtractor"
        source: "file_content"
        dest: "body"
      },
      # 2. Split into sentence-merged chunks
      {
        class: "com.kmwllc.lucille.stage.ChunkText"
        source: "body"
        dest: "chunk_text"
        chunkingMethod: "sentence"
        chunksToMerge: 5
        chunksToOverlap: 1
        cleanChunks: true
        characterLimit: 2000
      },
      # 3. Embed each chunk (only child docs, which carry the chunk text)
      {
        class: "com.kmwllc.lucille.stage.OpenAIEmbed"
        source: "chunk_text"
        dest: "chunk_vector"
        embedDocument: false
        embedChildren: true
        apiKey: ${OPENAI_API_KEY}
      }
    ]
  }
]

Tips

  • Use embedDocument: false, embedChildren: true in OpenAIEmbed to only embed chunks, not the full parent document.
  • Set characterLimit to stay within your embedding model’s token limit. For text-embedding-3-small, 8,191 tokens ≈ roughly 6,000–7,000 characters of English text.
  • Use cleanChunks: true when the source text has formatting artifacts (extra whitespace, embedded newlines from PDF extraction).
  • chunksToOverlap and chunksToMerge work together: if you merge 5 sentences with 1 overlap, each chunk shares its last sentence with the next chunk, helping the model retrieve context that spans a chunk boundary.

3 - PromptOllama

Connect to Ollama Server and send a Document to an LLM for enrichment.

What if you could just, actually, put an LLM on everything?

Ollama

Ollama allows you to run a variety of Large Language Models (LLMs) with minimal setup. You can also create custom models using Modelfiles and system prompts.

The PromptOllama Stage allows you to connect to a running instance of Ollama Server, which communicates with an LLM through a simple API. The Stage sends part (or all) of a Document to the LLM for generic enrichment. You’ll want to create a custom model (with a Modelfile) or provide a System Prompt in the Stage Config that is tailored to your pipeline.

We strongly recommend you have the LLM output only a JSON object for two main reasons: Firstly, LLMs tend to follow instructions better when instructed to do so. Secondly, Lucille can then parse the JSON response and fully integrate it into your Document.

Example

Let’s say you are working with Documents which represent emails, and you want to monitor them for potential signs of fraud. Lucille doesn’t have a DetectFraud Stage (at time of writing), but you can use PromptOllama to add this information with an LLM.

  • Modelfile: Let’s say you created a custom model, fraud_detector, in your instance of Ollama Server. As part of the modelfile, you instruct the model to check the contents for fraud and output a JSON object containing just a boolean value (under fraud). Your Stage would be configured like so:
{
  name: "Ollama-Fraud"
  class: "com.kmwllc.lucille.stage.PromptOllama"
  hostURL: "http://localhost:9200"
  modelName: "fraud_detector"
  fields: ["email_text"]
}
  • System Prompt: You can also just reference a specific LLM directly, and provide a system prompt in the Stage configuration.
{
  name: "Ollama-Fraud"
  class: "com.kmwllc.lucille.stage.PromptOllama"
  hostURL: "http://localhost:9200"
  modelName: "gemma3"
  systemPrompt: "You are to read the text inside \"email_text\" and output a JSON object containing only one field, fraud, a boolean, representing whether the text contains evidence of fraud or not."
  fields: "email_text"
}

Regardless of the approach you choose, the LLM will receive a request that looks like this:

{
  "email_text": "Let's be sure to juice the numbers in our next quarterly earnings report."
}

(Since fields: ["email_text"], any other fields on this Document are not part of the request.)

And the response from the LLM should look like this:

{
  "fraud": true
}

Lucille will then add all key-value pairs in this response JSON into your Document. So, the Document will become:

{
  "id": "emails.csv-85",
  "run-id": "f9538992-5900-459a-90ce-2e8e1a85695c",
  "email_text": "Let's be sure to juice the numbers in our next quarterly earnings report.",
  "fraud": true
}

As you can see, PromptOllama is very versatile, and can be used to enrich your Documents in a lot of ways.

4 - QueryOpensearch

Execute an OpenSearch Template using information from a Document, and add the response to it.

OpenSearch Templates

You can use templates in OpenSearch to repeatedly run a certain query using different parameters. For example, if we have an index full of parks, and we want to search for a certain park, we might use a template like this:

{
  "source": {
    "query": {
      "match_phrase": {
        "park_name": "{{park_to_search}}"
      }
    }
  }
}

In Opensearch, you could then call this template (providing it park_to_search) instead of writing out the full query each time you want to search.

Templates can also have default values. For example, if you want park_to_search to default to “Central Park” when a value is not provided, it would be written as: "park_name": "{{park_to_search}}{{^park_to_search}}Central Park{{/park_to_search}}"

QueryOpensearch Stage

The QueryOpensearch Stage executes a search template using certain fields from a Document as your parameters and adding OpenSearch’s response to the Document. You’ll specify either templateName, the name of a search template you’ve saved, or searchTemplate, the template you want to execute, in your Config.

You’ll also need to specify the names of parameters in your search template. These will need to match the names of fields on your Documents. If your names don’t match, you can use the RenameFields Stage first.

In particular, you have to specify which parameters are required and which are optional. If a required name in requiredParamNames is missing from a Document, an Exception will be thrown, and the template will not be executed. If an optional name in optionalParamNames is missing they (naturally) won’t be part of the template execution, so the default value will be used by OpenSearch.

If a parameter without a default value is missing, OpenSearch doesn’t throw an Exception - it just returns an empty response with zero hits. So, it is very important that requiredParamNames and optionalParamNames are defined very carefully!

5 - EmbeddedPython

Run a document through a Java embedded Graal Python environment.

Why Use It?

EmbeddedPython executes per-document Python code inside the Lucille JVM using GraalPy. Instead of returning a JSON object, your script mutates the current document directly through a Python-friendly proxy bound as doc (and the raw Java document as rawDoc). This avoids ports, subprocesses, venvs, and per-document JSON round trips.

When To Use It

Use EmbeddedPython when you need one or more of the following:

  • Minimal operational overhead (ports, subprocess lifecycle, venv creation, pip installs).
  • No use of any external Python libraries or native dependencies that require a real Python environment.
  • Lightweight field enrichment/transformation.

When To Use ExternalPython Instead

Avoid EmbeddedPython and use ExternalPython when you need one or more of the following:

  • Real Python compatibility (including packages with native dependencies).
  • Dependency management via a requirements.txt installed into a managed venv.
  • Process isolation apart from the JVM.

Example

Input Document

{
  "id": "doc-1",
  "title": "Hello",
  "author": "Test",
  "views": 123
}

Python Script

doc["title"] = doc["title"].upper()

Output Document

{
  "id": "doc-1",
  "title": "HELLO",
  "author": "Test",
  "views": 123
}

Config Parameters

{
 name: "EmbeddedPython-Example"
 class: "com.kmwllc.lucille.stage.EmbeddedPython"

 # Specify exactly one of the following:
 script_path: "/path/to/my_script.py"
 script: "doc['title'] = doc['title'].upper()"
}

script_path can begin with classpath: to load the script as a classpath resource rather than a filesystem path:

script_path: "classpath:scripts/my_script.py"

This lets you package Python scripts inside your JAR as resources (under src/main/resources/). The Lucille process loads them directly from the classpath at runtime, so you don’t need to place files in specific filesystem locations or ensure the process has read access to them.

6 - ExternalPython

Run a document through an external Py4J Python environment.

Why Use It?

ExternalPython delegates per-document processing to an external Python process using Py4J. Lucille serializes the Document into a request, calls a Python function, receives a JSON response, and applies that response back onto the document.

When To Use It

Use ExternalPython when you need one or more of the following:

  • Real Python compatibility (including packages with native dependencies).
  • Dependency management via a requirements.txt installed into a managed venv.
  • Process isolation apart from the JVM.

When To Use EmbeddedPython Instead

Avoid ExternalPython and use EmbeddedPython when you need one or more of the following:

  • Minimal operational overhead (ports, subprocess lifecycle, venv creation, pip installs).
  • No use of any external Python libraries or native dependencies that require a real Python environment.
  • Lightweight field enrichment/transformation.

Restrictions

Your python file must be in one of the following directories that start in the current working directory that is running lucille:

  • ./python
  • ./src/main/resources
  • ./src/test/resources
  • ./src/test/resources/ExternalPythonTest (for testing)

Example

Input Document

{
  "id": "doc-1",
  "title": "Hello",
  "author": "Test",
  "views": 123
}

Python Script

def process_document(doc):
    title = doc["title"]
  
    return {
        "title": title.upper()
    }

Python Returns

{
  "title": "HELLO"
}

Output Document

{
  "id": "doc-1",
  "title": "HELLO"
}

Config Parameters

{
  name: "ExternalPython-Example"
  class: "com.kmwllc.lucille.stage.ExternalPython"

  scriptPath: "/path/to/my_script.py"

  # Optional
  pythonExecutable: "python3"
  requirementsPath: "/path/to/requirements.txt"
  functionName: "process_document"
  port: 25333
}

Example (NumPy)

Input Document

{
  "id": "doc-2",
  "values": [1, 2, 3, 4, 5]
}

Python Script

import numpy as np

def process_document(doc):
    arr = np.array(doc["values"], dtype=float)
  
    return {
        "values": doc["values"],
        "mean": float(np.mean(arr)),
        "stddev": float(np.std(arr))
    }

Output Document

{
  "id": "doc-2",
  "values": [1, 2, 3, 4, 5],
  "mean": 3.0,
  "stddev": 1.41
}

requirements.txt

numpy

Config Parameters

{
  name: "ExternalPython-Numpy"
  class: "com.kmwllc.lucille.stage.ExternalPython"

  scriptPath: "/path/to/my_numpy_script.py"
  requirementsPath: "/path/to/requirements.txt"
  
  # Optional
  pythonExecutable: "python3"
  functionName: "process_document"
  port: 25333
}