Native Concepts
Pipelex includes several built-in native concepts that cover common data types in AI methods. These concepts come with predefined structures and are automatically available in all pipelines—no setup required.
For an introduction to concepts, see Define Your Concepts.
What Are Native Concepts?
Native concepts are ready-to-use building blocks for AI methods. They represent common data types you'll frequently work with: text, images, documents, numbers, and combinations thereof.
Key characteristics:
- Pre-defined: Built into Pipelex, no need to declare them
- Structured: Each has a corresponding Python data model
- Universal: Available across all pipelines and domains
- Extensible: You can refine them to create more specific concepts
Available Native Concepts
Here are all the native concepts you can use out of the box:
| Concept | Description | Content Class Name |
|---|---|---|
Text |
A text | TextContent |
Markdown |
A text written in Markdown, which refines Text |
MarkdownContent |
Image |
An image file | ImageContent |
Document |
A document: PDF, Office (Word, PowerPoint, Excel), web page, or a text file such as Markdown or CSV | DocumentContent |
TextAndImages |
Text with its associated images | TextAndImagesContent |
Number |
A number | NumberContent |
YesNo |
The answer to a yes/no question, with an optional probability | YesNoContent |
Choice |
One option picked out of a declared set | ChoiceContent |
Rating |
A position on an ordered scale of described levels | RatingContent |
Date |
A calendar date, optionally with a time of day | DateContent |
Time |
A time of day, optionally with a UTC offset | TimeContent |
Page |
A document page with text, images, and optional page view | PageContent |
Dynamic |
A dynamic concept that adapts to context | DynamicContent |
JSON |
A JSON object | JSONContent |
Html |
HTML content with inner HTML and optional CSS class | HtmlContent |
SearchResult |
A web search result with answer and sources | SearchResultContent |
Composite |
A named composition of contents | CompositeContent |
Anything |
Any type of content | No specific implementation |
Choosing among Anything, JSON and Dynamic
Three natives hold data whose shape your method does not describe, and each takes a different kind of input. Choose in this order:
- Data you already have as JSON objects: name it. Declare a concept that refines
JSON, with a plain-language description. It takes the object exactly as you have it, with no data model to write, and its name and description tell an agent or a reader what the object means, which a bareJSONdoes not. When you later want the fields validated, turn it into a concept with a structure. JSONorJSON[]when the data has no name worth giving it.JSONis a JSON object, not any JSON value; a list of objects isJSON[].Anythingwhen the pipe is generic over its input: it passes a value along or renders it, whatever it is. It is not the place to put JSON data.Dynamicis not recommended for inputs for now: what aDynamicinput holds is still being settled.
For example, a pipe summarizing orders from a shop's API names them:
[concept.Order]
description = "A customer order, as the shop's API returns it"
refines = "JSON"
[pipe.summarize_order]
type = "PipeLLM"
description = "Summarize a customer order"
inputs = { order = "Order" }
output = "Text"
prompt = """
Summarize this order in two sentences:
@order
"""
The input is the object itself, {"order": {"id": 42, "items": [{"sku": "A-1", "quantity": 2}]}}, and a list of orders would be declared Order[].
Native Concept Structures
Each native concept has a corresponding Python structure that defines its data model. Understanding these structures helps you work with the data they contain.
TextContent
The simplest native concept:
class TextContent(StuffContent):
text: str
Use for: Plain text outputs, summaries, descriptions, etc.
MarkdownContent
A text written in Markdown. Markdown refines Text, and its content class keeps the single field of TextContent, which holds the Markdown source:
class MarkdownContent(TextContent):
text: str
What sets it apart from Text is how it is shown: the HTML view, an HTML template and a PDF laid out without a template format a Markdown value and show a Text value as it is, so a stray # or 1. in a plain text never turns into a heading or a list there. The terminal's pretty view is the exception: it renders both through Markdown.
As a PipeLLM output, output = "Markdown" gives a report whose headings, bold text, lists, tables and links are formatted downstream. The LLM writes free text, exactly as it does for Text:
[pipe.write_inspection_report]
type = "PipeLLM"
description = "Write site notes up as an inspection report"
inputs = { notes = "Text" }
output = "Markdown"
prompt = """
Write these site notes up as an inspection report in Markdown, with a heading for each area inspected.
@notes
"""
Read the report from a Python caller via pipe_output.main_stuff_as_markdown.text.
Refinement. A Markdown value is accepted wherever a Text is: a pipe whose input is Text takes a Markdown report, and its prompt receives the source. The reverse does not hold: a Text value, or a concept that refines Text, is not accepted where a Markdown is required, since nothing says it was written as Markdown. To name a specific kind of report, declare a concept with refines = "Markdown".
As an input, Markdown takes a string, as Text does: "report": "# Roof\n\n- two cracked tiles", or the envelope form {"concept": "Markdown", "content": "# Roof"}.
How it renders:
- In a prompt, and in the plain and Markdown views, it is its source as it is.
pipelex run --save-main-stuffwrites that source verbatim asmain_stuff.md. - Its HTML view converts the Markdown to HTML rather than escaping it, so
main_stuff.htmland the HTML tab ofmain_stuff_viewer.htmlshow the formatted report. Raw HTML inside the source is shown as text rather than passed through, and only URLs with a scheme, such ashttps://example.com, become links, so a file name likeREADME.mdstays text. - The pretty view in the terminal renders it as Markdown.
- Its JSON form is
{"text": "..."}, the same as aText.
Inside an HTML template, such as a PipeCompose template with category = "html", {{ report }} prints a Markdown value as its converted HTML with no filter, while a Text value keeps being escaped. A Markdown field of a structure converts the same way, as in {{ digest.summary }}. {{ report.text }} is the Markdown source, escaped like any string. The $report sigil and {{ report | format }} print the converted HTML too, unless the template names another format, as in {{ report | format("plain") }}, which prints the Markdown source, escaped. The @report sigil wraps the source in tags for a prompt and is not meant for HTML templates.
In a document, a PipeDocGen step that prints a PDF without a template formats a Markdown value, with its headings, lists, tables, code and links, and prints a Text value as plain paragraphs.
Use for: Reports, summaries and write-ups an LLM writes to be read formatted, in an HTML page or a PDF.
ImageContent
Represents an image with optional metadata:
class ImageContent(StuffContent):
url: str
public_url: str | None = None
source_prompt: str | None = None
source_negative_prompt: str | None = None
caption: str | None = None
mime_type: str | None = None
width: int | None = None
height: int | None = None
filename: str | None = None
Fields:
url: Location of the image (a storage URI, an HTTP(S) URL, or a base64 data URL)public_url: A URL a viewer can open, which the runtime fills for every image input it normalizes, passed alone, in a list or in a structured field: the storage provider's link for a stored file (apipelex-storage://reference, adata:URL or an uploaded local file), and the URL itself for anhttp(s)one unless the input names another. A stored file's link is signed when signed URLs are configured, so it expires: a template writing{{ image.public_url }}into HTML produces a report that stops showing the image once the link has expired.source_prompt/source_negative_prompt: The prompts used to generate the image (if applicable)caption: Descriptive text for the imagemime_type: The MIME type of the image. For every image input, the runtime sets it at the start of the run from the file's own bytes, which win over a declared type (see File formats are checked before the run)width/height: Pixel dimensions — present together or not at allfilename: Optional original filename
Use for: Photos, generated images, diagrams, screenshots.
An Image must hold an image file, such as PNG, JPEG or WebP. A run whose Image input, or an input of a concept refining Image, holds a file whose bytes say it is something else, a PDF for one, is refused before it starts. A scanned letter that arrives as a PDF belongs in a Document input.
DocumentContent
Represents a document: a PDF, an Office file (Word, PowerPoint, Excel), a web page, or a text file such as Markdown, CSV, plain text, WebVTT captions or an email message.
class DocumentContent(StuffContent):
url: str
public_url: str | None = None
mime_type: str | None = None
filename: str | None = None
title: str | None = None
snippet: str | None = None
Fields:
url: Location of the document file, storage URL, or web page URLpublic_url: A URL a viewer can open, filled by the runtime for every document input it normalizes, exactly as forImageContent: the storage provider's link for a stored file, which expires when signed URLs are configured, and the URL itself for anhttp(s)onemime_type: The MIME type of the document (e.g., "application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"). For every document input, the runtime sets it at the start of the run from the file's own bytes, and keeps the declared type only when the bytes do not identify the file, as for plain text, Markdown or HTMLfilename: Optional filename of the documenttitle: Optional title of the document or sourcesnippet: Optional text snippet or excerpt from the document
Use for: Contracts, invoices, reports, presentations, web pages, search source citations, any document file.
Which document formats a method can take depends on the models that read them: a PipeExtract step reads the formats its extract model declares, and a PipeLLM step the document formats its model declares. A Word document works with an extract model that reads Word files, and fails with one that reads only PDF.
File formats are checked before the run
At the start of every run, before any pipe runs, the runtime establishes the format of each image and document input, including the items of a list and the fields of a structured input:
- A file given as a
data:URL, a local path or apipelex-storage://reference is identified from its first bytes. The identified type replaces any declared one, so adata:image/pngURL holding a PDF is a PDF. An Office file whose bytes show only that it is a zip archive is known by its declared type or by its file name's extension (.docx,.pptx,.xlsx), and otherwise its format is unknown. - A file the bytes do not identify, such as plain text, Markdown, CSV or HTML, keeps the type it was declared with.
- An
http(s)URL is not fetched before the run, so its format is the type it was declared with, and only that type is checked. Anhttp(s)URL with no declared type is not checked: the provider receives it as it is.
The run is then refused, with an input error naming the input, when:
- an
Imageinput holds a file that is not an image, whatever reads it; - a document input is certain to reach a step whose model does not read its format: for example, Word transcripts batched into a
PipeExtractwhose extract model reads only PDF. The error names each input, its format, the step, the model and the formats that model reads.
The check follows each input through the steps of the method: the items of a batch, the fields of a structured input, and what a binding step or a dotted batch_over binds, so a Word transcript at cases[1].transcripts[0], batched through batch_over = "cases.transcripts" over a list of cases, is refused before the run. A list a binding gathers from a field that is not itself a list, such as cases.attachment over a list of cases, is not followed, and a step that reads its items refuses a file when it gets to it.
A step reached only through a PipeCondition, or a step that may be skipped because an optional input is absent, may never run, so the run is not refused on its account. If it does run, it refuses the file itself, with the same kind of input error. The same holds for files a run produces along the way.
NumberContent
Represents numeric values:
class NumberContent(StuffContent):
number: Union[int, float]
Use for: Counts, calculations, metrics, scores.
YesNoContent
Represents the answer to a yes/no question — a boolean verdict, and the probability that the answer is yes when its producer reports one:
class YesNoContent(StuffContent):
yes_no: bool
probability: float | None = None # from 0 to 1
Renders as yes or no when injected into a prompt, whatever the probability. Especially handy as a PipeLLM output for judgments — output = "YesNo" makes the model return a typed boolean instead of free text answering "yes"/"no":
[pipe.judge_is_urgent]
type = "PipeLLM"
description = "Decide whether a message is urgent"
inputs = { message = "Text" }
output = "YesNo"
prompt = "Is the following message urgent? Answer yes or no.\n\n$message"
Read the verdict from a Python caller via pipe_output.main_stuff_as_yes_no.yes_no.
Use for: Yes/no judgments, boolean classifications, presence/absence checks, pass/fail verdicts.
The verdict natives: YesNo, Choice and Rating
YesNo, Choice and Rating are verdicts, and they follow one rule: a verdict native requires its verdict and nothing else. The verdict — the boolean, the option key, the level — is what every producer can state. Every measure of uncertainty beside it is optional and defined by what it means, never by how a producer computes it, so a producer that measures less fills in less, and one that measures nothing still writes a valid verdict. An unreported measure is absent: a bare yes is never a probability of 1.
class ChoiceContent(StuffContent):
choice: str # the key of the selected option
confidence: float | None = None # from 0 to 1
probabilities: dict[str, float] | None = None # keyed by option key
class RatingContent(StuffContent):
level: int # the index of the selected level, 0 being the first
label: str | None = None # the selected level's label, when the scale declares labels
confidence: float | None = None # from 0 to 1
probabilities: dict[str, float] | None = None # keyed by level index written as text: "0", "1", ...
position: float | None = None # a continuous position, from 0 to the last level's index
A Rating's label is not a measure. The MTHDS standard defines it as the name of the selected level, when the scale declares labels. A PipeJudge whose levels carry labels fills it with the label of the level it chose, copied from its declaration, and one whose levels carry none leaves it absent; a PipeLLM whose output is Rating, or any other producer, may fill it like any optional member. The level stays the verdict either way.
A Choice renders as its key, and a Rating as its label when it has one and as its level otherwise, so $team in a later prompt reads billing, and $severity reads Workaround available for a rating carrying that label and 1 for one carrying none. Branch on them with a PipeCondition: expression = "team.choice" routes by the key, and expression = "'severe' if severity.level >= 2 else 'mild'" by the level, which every Rating has, labelled or not.
A measure is only as good as its producer. A language model asked to fill a verdict — a PipeLLM whose output is YesNo, Choice or Rating — sees the uncertainty members, optional and described, and may fill them with its own estimate of its verdict. That estimate is worth having, and it is not the probability a dedicated judging model measures over the options it was given: a Choice written by a PipeLLM has no declared option set behind it, so its probabilities are keyed by whatever the model wrote. When a PipeCondition gates on a probability, read which producer wrote it, and guard against its absence: approved.probability is not none and approved.probability >= 0.8.
Where a caller supplies a verdict, and where a producer reports one. An input declared YesNo takes the bare verdict, true or false, and builds a YesNo with no probability; send its object form inside the explicit envelope when you have a probability to pass on. A result is read the other way: a YesNo a pipe produces is an object carrying yes_no and, when reported, probability, at every depth of the result. A Choice or Rating input is provided in its envelope, {"concept": "native.Choice", "content": {"choice": "billing"}}, since a bare "billing" would read as a text.
Use for: Choice for routing and classification over a declared set of options, Rating for grading on an ordered scale whose levels are described, and both wherever a method branches on a verdict and may weigh how sure its producer was.
DateContent
Represents a calendar date, as precise as its source states — a required date plus an optional time of day:
class DateContent(StuffContent):
date: datetime.date
time: datetime.time | None = None
The time is present only when the source states one — the concept never invents a time, so an LLM extracting "delivery by March 15" produces a date with no time, while a ticket's "7 Jul 2026, 15:40" keeps the time (and its UTC offset, when stated). It renders as ISO 8601 truncated to the stated precision (2026-07-07, or 2026-07-07T15:40:00+02:00) when injected into a prompt. As a PipeLLM output, output = "Date" makes the model return the two-field structure:
[pipe.extract_departure]
type = "PipeLLM"
description = "Extract the scheduled departure from a ticket"
inputs = { ticket = "Ticket" }
output = "Date"
prompt = "Extract the scheduled departure from this ticket: $ticket"
As a pipeline input, a top-level TOML date or datetime literal maps to Date directly (departure = 2026-07-07T15:40:00+02:00); read it from a Python caller via pipe_output.main_stuff_as_date.
TimeContent
Represents a time of day, as precise as its source states — never attached to an invented date:
class TimeContent(StuffContent):
time: datetime.time
The UTC offset is kept on the time when the source states one, exactly as Date handles its optional time. It renders as ISO 8601 (15:40:00, or 15:40:00+02:00) when injected into a prompt. As a pipeline input, a top-level TOML time-of-day literal maps to Time directly (opening = 09:00:00).
Use for: Opening hours, schedules, times of day with no specific date.
Use for: Issue dates, due dates, dates of birth, departures, effective/termination dates — any date found on a document. For a date value without any date attached (a bare time of day), degrade to Text.
TextAndImagesContent
Combines text with one or more images:
class TextAndImagesContent(StuffContent):
text: Optional[TextContent]
images: Optional[List[ImageContent]]
raw_html: Optional[str]
Fields:
text: The text contentimages: A list of images extracted from the contentraw_html: The raw HTML of the fetched page, when requested viainclude_raw_html
Use for: Rich content combining text and visuals, extracted document content, reports with diagrams.
PageContent
Represents a document page with both content and visual representation:
class PageContent(StructuredContent):
text_and_images: TextAndImagesContent
page_view: Optional[ImageContent] = None
Fields:
- text_and_images: The extracted text and embedded images from the page
- page_view: A screenshot or rendering of the entire page
Use for: Document pages extracted by PipeExtract, individual pages from multi-page documents.
DynamicContent
A flexible concept that adapts to context:
class DynamicContent(StuffContent):
# Dynamic content that can adapt to context
# Structure is flexible and determined at runtime
pass
Use for: Methods where the content structure isn't known in advance.
JSONContent
A concept that represents a JSON object. This enables pipes to receive as input or to output any JSON object.
class JSONContent(StuffContent):
json_obj: dict[str, Any]
A JSON input takes a JSON object as it is, {"a": 1}, and its inputs template shows the bare object; JSON[] takes a list of objects. JSON is a JSON object, not any JSON value, so a string or a number there is refused. The explicit form carries the content form: {"concept": "JSON", "content": {"json_obj": {"a": 1}}}.
Use for: Data you already have as JSON objects, preferably through a concept that refines JSON (see Choosing among Anything, JSON and Dynamic).
HtmlContent
Represents HTML content with styling:
class HtmlContent(StuffContent):
inner_html: str
css_class: str | None = None
Fields:
inner_html: The inner HTML of the contentcss_class: The CSS class applied to the wrapping element, optional — content that needs no wrapper simply omits it, and the HTML rendering then returns the rawinner_htmlwith no wrappingdiv
Use for: Rendered HTML fragments, styled content blocks, HTML-based reports.
SearchResultContent
Represents the result of a web search query. Produced by PipeSearch:
class SearchResultContent(StuffContent):
answer: str
sources: list[DocumentContent] = Field(default_factory=empty_list_factory_of(DocumentContent))
Fields:
answer: The synthesized answer text from the searchsources: A list ofDocumentContentsource citations (each withurl,title, andsnippet)
Use for: Web search results, research findings, information retrieval with citations.
CompositeContent
An untyped named composition of contents. Produced by PipeParallel when its output is Composite:
class CompositeContent(StuffContent):
model_config = ConfigDict(extra="allow")
Each named sub-content is a top-level field of the composite — there is no wrapper key, so the serialized shape matches what a bespoke structured concept with the same field names would produce.
Use for: Combining parallel branch results without declaring a bespoke concept.
Anything
Anything has no content class of its own: an Anything value is held in the content class matching what it is.
- As an input, it takes any JSON value but an array or null. A string, a number, a boolean or an object becomes text, a number, a yes/no or a JSON object, and the input keeps the
Anythingconcept. A typed envelope,{"concept": "Text", "content": "hi"}, hands it a specific concept, which it keeps, since every concept satisfiesAnything.Anything[]takes a list of such values. - As an output, it promises nothing narrower: a pipe declared to output
Anythingmay produce any concept.
Use for: Pipes that are generic over their input, passing a value along or rendering it whatever it is.
Using Native Concepts
Native concepts can be used directly in your pipeline definitions without any additional setup:
In Pipe Inputs
[pipe.analyze_document]
type = "PipeLLM"
description = "Analyze a document"
inputs = { document = "Document" }
output = "Text"
prompt = """
Analyze this document and provide a summary:
@document
"""
In Pipe Outputs
[pipe.process_image]
type = "PipeLLM"
description = "Describe an image"
inputs = { photo = "Image" }
output = "Text"
prompt = "Describe what you see in this image: $photo"
With Page Content
The Page concept is particularly useful with PipeExtract:
[pipe.extract_pages]
type = "PipeExtract"
description = "Extract content from a document"
inputs = { document = "Document" }
output = "Page[]"
This extracts each page with both its text/images and a visual representation.
In Complex Methods
[pipe.create_report]
type = "PipeSequence"
description = "Generate a report with text and images"
inputs = { data = "Text" }
output = "TextAndImages"
steps = [
{ pipe = "analyze_data", result = "analysis" },
{ pipe = "create_charts", result = "charts" },
{ pipe = "combine_content", result = "report" }
]
Refining Native Concepts
You can create more specific concepts by refining native ones—for example, creating an Invoice concept that refines Document or a ProductPhoto that refines Image. This gives you semantic clarity while inheriting the native concept's structure.
For complete details on refinement syntax, type compatibility, limitations, best practices, and future features, see Refining Concepts.
When to Use Native Concepts
Use native concepts directly when:
- ✅ Working with simple, unstructured data
- ✅ The native structure is sufficient for your needs
- ✅ You want maximum interoperability across pipes
- ✅ Prototyping and quick experiments
Refine native concepts when:
- ✅ You need semantic specificity (e.g.,
InvoicevsDocument) - ✅ You want to add custom structure on top of the base structure
- ✅ Building domain-specific methods
- ✅ Need type safety for specific document types
Common Patterns
Text Processing
[pipe.summarize]
type = "PipeLLM"
description = "Summarize any text"
inputs = { content = "Text" }
output = "Text"
prompt = """
Summarize this content:
@content
"""
Document Extraction
[pipe.extract_pages]
type = "PipeExtract"
description = "Extract content from a document"
inputs = { document = "Document" }
output = "Page[]"
[pipe.analyze_pages]
type = "PipeLLM"
description = "Analyze pages"
inputs = { pages = "Page[]" }
output = "Text"
prompt = """Analyze those pages:
@pages
"""
[pipe.extract_and_analyze]
type = "PipeSequence"
description = "Extract and analyze a document"
inputs = { document = "Document" }
output = "Text"
steps = [
{ pipe = "extract_pages", result = "pages" },
{ pipe = "analyze_pages", result = "analysis" }
]
Multi-Modal Processing
[pipe.analyze_with_context]
type = "PipeLLM"
description = "Analyze image with text context"
inputs = { image = "Image", context = "Text" }
output = "Text"
prompt = """
Given this context: $context
Analyze this image: $image
"""
Web Search
[pipe.search_topic]
type = "PipeSearch"
description = "Search the web for information"
inputs = { topic = "Text" }
output = "SearchResult"
model = "$standard"
prompt = "What is $topic?"
Related Documentation
- Define Your Concepts - Learn about concept semantics
- Inline Structures - Add structure to refined concepts
- Python StructuredContent Classes - Advanced customization
- MTHDS Language Tutorial - Use native concepts in pipelines