<!-- Auto-generated by ai_pipeline_core.docs_generator — DO NOT EDIT MANUALLY -->

# ai-pipeline-core v0.12.4 — API Reference

Auto-generated API reference. Do not edit manually. Run: `make docs-ai-build`

## Reading Order

1. [deployment](deployment.md) — Pipeline deployment utilities for unified, type-safe deployments.
2. [document_store](document_store.md) — Document store protocol and backends for AI pipeline flows.
3. [documents](documents.md) — Document system for AI pipeline flows.
4. [exceptions](exceptions.md)
5. [llm](llm.md) — Large Language Model integration via LiteLLM proxy.
6. [logging](logging.md) — Logging infrastructure for AI Pipeline Core.
7. [observability](observability.md) — Observability system for AI pipelines.
8. [pipeline](pipeline.md) — Pipeline framework primitives — decorators, flow options, and concurrency limits.
9. [prompt_compiler](prompt_compiler.md) — Prompt compiler for type-safe, validated prompt specifications.
10. [replay](replay.md) — First-class replay system for AI pipeline debugging.
11. [settings](settings.md)

## deployment

Pipeline deployment utilities for unified, type-safe deployments.

Read [Full guide](deployment.md) for detailed informations how to use it

### Types & Constants

```python
RunResponse = Annotated[
    PendingRun | ProgressRun | CompletedRun | FailedRun,
    Discriminator("type"),
]
ProgressCallback = Callable[[float, str], Awaitable[None]]
```

### Classes

```python
class DeploymentResult(BaseModel):
    """Base class for deployment results."""

    # Fields
    success: bool
    error: str | None = None
    documents: tuple[OutputDocument, ...] = ()
    model_config = ConfigDict(frozen=True)


class PipelineDeployment(Generic[TOptions, TResult]):
    """Base class for pipeline deployments with three execution modes."""

    # Fields
    flows: ClassVar[list[Any]]
    name: ClassVar[str]
    options_type: ClassVar[type[FlowOptions]]
    result_type: ClassVar[type[DeploymentResult]]
    pubsub_service_type: ClassVar[str] = ''
    cache_ttl: ClassVar[timedelta | None] = timedelta(hours=24)
    concurrency_limits: ClassVar[Mapping[str, PipelineLimit]] = MappingProxyType({})

    # Methods
    def __init_subclass__(cls, **kwargs: Any) -> None: ...
    def as_prefect_flow(self) -> Callable[..., Any]:
        """Generate a Prefect flow for production deployment via ``ai-pipeline-deploy`` CLI."""
    def build_partial_result(self, run_id: str, documents: list[Document], options: TOptions) -> TResult:
        """Build a result for partial pipeline runs (--start/--end that don't reach the last step)."""
    def build_result(run_id: str, documents: list[Document], options: TOptions) -> TResult:
        """Extract typed result from pipeline documents loaded from DocumentStore."""
    def run(self, run_id: str, documents: Sequence[Document], options: TOptions, publisher: ResultPublisher | None=None, start_step: int=1, end_step: int | None=None, task_result_store: TaskResultStore | None=None, parent_execution_id: UUID | None=None, parent_span_id: str | None=None) -> TResult:
        """Execute flows with resume, per-flow uploads, and step control."""
    def run_cli(self, initializer: Callable[[TOptions], tuple[str, list[Document]]] | None=None, trace_name: str | None=None, cli_mixin: type[BaseSettings] | None=None) -> None:
        """Execute pipeline from CLI with positional working_directory and --start/--end/--no-trace flags."""
    def run_local(self, run_id: str, documents: Sequence[Document], options: TOptions, publisher: ResultPublisher | None=None, output_dir: Path | None=None) -> TResult:
        """Run locally with Prefect test harness and in-memory document store."""


class RunState(StrEnum):
    """Pipeline run lifecycle state."""

    # Fields
    PENDING = 'PENDING'
    RUNNING = 'RUNNING'
    COMPLETED = 'COMPLETED'
    FAILED = 'FAILED'
    CRASHED = 'CRASHED'
    CANCELLED = 'CANCELLED'


class FlowStatus(StrEnum):
    """Individual flow step status within a pipeline run."""

    # Fields
    STARTED = 'started'
    COMPLETED = 'completed'
    CACHED = 'cached'
    PROGRESS = 'progress'


class PendingRun(_RunBase):
    """Pipeline queued or running but no progress reported yet."""

    # Fields
    type: Literal['pending'] = 'pending'


class ProgressRun(_RunBase):
    """Pipeline running with step-level progress data."""

    # Fields
    type: Literal['progress'] = 'progress'
    step: int
    total_steps: int
    flow_name: str
    status: FlowStatus
    progress: float  # overall 0.0-1.0
    step_progress: float  # within step 0.0-1.0
    message: str


class DeploymentResultData(BaseModel):
    """Typed result payload — always has success + optional error."""

    # Fields
    success: bool
    error: str | None = None
    model_config = ConfigDict(frozen=True, extra='allow')


class CompletedRun(_RunBase):
    """Pipeline finished (Prefect COMPLETED). Check result.success for business outcome."""

    # Fields
    type: Literal['completed'] = 'completed'
    result: DeploymentResultData


class FailedRun(_RunBase):
    """Pipeline crashed — execution error, not business logic."""

    # Fields
    type: Literal['failed'] = 'failed'
    error: str
    result: DeploymentResultData | None = None


class RemoteDeployment(Generic[TDoc, TOptions, TResult]):
    """Typed client for calling a remote PipelineDeployment via Prefect."""

    # Fields
    name: ClassVar[str]
    options_type: ClassVar[type[FlowOptions]]
    result_type: ClassVar[type[DeploymentResult]]
    trace_level: ClassVar[TraceLevel] = 'always'
    trace_cost: ClassVar[float | None] = None

    # Methods
    @property
    def deployment_path(self) -> str:
        """Full Prefect deployment path: '{flow_name}/{deployment_name}'."""
    def __init_subclass__(cls, **kwargs: Any) -> None: ...
    def run(self, run_id: str, documents: list[TDoc], options: TOptions, on_progress: ProgressCallback | None=None) -> TResult:
        """Execute the remote deployment via Prefect."""
    def run_traced(self, run_id: str, documents: list[TDoc], options: TOptions, output_dir: Path, on_progress: ProgressCallback | None=None) -> TResult:
        """Execute with local .trace/ debug tracing at output_dir/.trace."""


class OutputDocument(BaseModel):
    """Document metadata in deployment results. Binary content is None."""

    # Fields
    sha256: DocumentSha256
    name: str
    class_name: str
    mime_type: str
    size: int
    description: str = ''
    content: str | None = None
    attachments: tuple[OutputAttachment, ...] = ()
    model_config = ConfigDict(frozen=True)


class StartedEvent:
    """Pipeline execution started."""

    # Fields
    run_id: str
    flow_run_id: str
    run_scope: str


class ProgressEvent:
    """Flow-level or intra-flow progress."""

    # Fields
    run_id: str
    flow_run_id: str
    flow_name: str
    step: int
    total_steps: int
    progress: float
    step_progress: float
    status: FlowStatus
    message: str


class CompletedEvent:
    """Pipeline completed successfully."""

    # Fields
    run_id: str
    flow_run_id: str
    result: dict[str, Any]
    chain_context: dict[str, Any]
    actual_cost: float


class FailedEvent:
    """Pipeline execution failed."""

    # Fields
    run_id: str
    flow_run_id: str
    error_code: ErrorCode
    error_message: str


class ResultPublisher(Protocol):
    """Publishes pipeline lifecycle events to external consumers."""

    # Methods
    def close(self) -> None:
        """Release resources held by the publisher."""
    def publish_completed(self, event: CompletedEvent) -> None:
        """Publish a pipeline completed event."""
    def publish_failed(self, event: FailedEvent) -> None:
        """Publish a pipeline failed event."""
    def publish_heartbeat(self, run_id: str) -> None:
        """Publish a heartbeat signal."""
    def publish_progress(self, event: ProgressEvent) -> None:
        """Publish a flow progress event."""
    def publish_started(self, event: StartedEvent) -> None:
        """Publish a pipeline started event."""


class NoopPublisher:
    """Discards all lifecycle events. Default publisher for CLI and run_local."""

    # Methods
    def close(self) -> None:
        """No resources to release."""
    def publish_completed(self, event: CompletedEvent) -> None:
        """Accept and discard a completed event."""
    def publish_failed(self, event: FailedEvent) -> None:
        """Accept and discard a failed event."""
    def publish_heartbeat(self, run_id: str) -> None:
        """Accept and discard a heartbeat."""
    def publish_progress(self, event: ProgressEvent) -> None:
        """Accept and discard a progress event."""
    def publish_started(self, event: StartedEvent) -> None:
        """Accept and discard a started event."""


class MemoryPublisher:
    """Records all lifecycle events in-memory for test assertions."""

    # Methods
    def __init__(self) -> None: ...
    def close(self) -> None:
        """No resources to release."""
    def publish_completed(self, event: CompletedEvent) -> None:
        """Record a completed event."""
    def publish_failed(self, event: FailedEvent) -> None:
        """Record a failed event."""
    def publish_heartbeat(self, run_id: str) -> None:
        """Record a heartbeat."""
    def publish_progress(self, event: ProgressEvent) -> None:
        """Record a progress event."""
    def publish_started(self, event: StartedEvent) -> None:
        """Record a started event."""
```

### Functions

```python
async def progress_update(fraction: float, message: str='') -> None:
    """Report intra-flow progress (0.0-1.0). No-op without context."""

async def run_remote_deployment(deployment_name: str, parameters: dict[str, Any], on_progress: ProgressCallback | None=None, run_id: str | None=None) -> Any:
    """Run a remote Prefect deployment with optional progress callback."""
```


## document_store

Document store protocol and backends for AI pipeline flows.

Read [Full guide](document_store.md) for detailed informations how to use it

### Classes

```python
class FlowCompletion:
    """Record of a successful flow execution for resume cache."""

    # Fields
    flow_name: str
    input_sha256s: tuple[str, ...]
    output_sha256s: tuple[str, ...]
    stored_at: datetime


class DocumentNode:
    """Lightweight document metadata without content or attachments."""

    # Fields
    sha256: DocumentSha256
    class_name: str
    name: str
    description: str = ''
    derived_from: tuple[str, ...] = ()
    triggered_by: tuple[str, ...] = ()
    summary: str = ''


class DocumentReader(Protocol):
    """Read-only protocol for application code that consumes documents."""

    # Methods
    def check_existing(self, sha256s: list[DocumentSha256]) -> set[DocumentSha256]:
        """Return the subset of sha256s that already exist in the store."""
    def find_by_source(self, source_values: list[str], document_type: type[Document], *, max_age: timedelta | None=None) -> dict[str, Document]:
        """Find most recent document per source value, matched against ``derived_from`` entries. max_age filters on ``stored_at`` timestamp."""
    def get_flow_completion(self, run_scope: RunScope, flow_name: str, *, max_age: timedelta | None=None) -> FlowCompletion | None:
        """Get the completion record for a flow, or None if not found / expired. max_age filters on ``stored_at`` timestamp."""
    def has_documents(self, run_scope: RunScope, document_type: type[Document], *, max_age: timedelta | None=None) -> bool:
        """Check if any documents of this type exist in the run scope. max_age filters on ``stored_at`` timestamp."""
    def load(self, run_scope: RunScope, document_types: list[type[Document]]) -> list[Document]:
        """Load all documents of the given types from a run scope."""
    def load_by_sha256s(self, sha256s: list[DocumentSha256], document_type: type[_D], run_scope: RunScope | None=None) -> dict[DocumentSha256, _D]:
        """Batch-load full documents by SHA256."""
    def load_nodes_by_sha256s(self, sha256s: list[DocumentSha256]) -> dict[DocumentSha256, DocumentNode]:
        """Batch-load lightweight metadata for documents by SHA256."""
    def load_scope_metadata(self, run_scope: RunScope) -> list[DocumentNode]:
        """Load lightweight metadata for ALL documents in a run scope."""
    def load_summaries(self, document_sha256s: list[DocumentSha256]) -> dict[DocumentSha256, str]:
        """Load summaries by SHA256."""
```

### Functions

```python
def get_document_store() -> DocumentStore | None:
    """Get the process-global document store singleton (framework-internal, returns full DocumentStore)."""
```


## documents

Document system for AI pipeline flows.

Read [Full guide](documents.md) for detailed informations how to use it

### Types & Constants

```python
DocumentSha256 = NewType("DocumentSha256", str)
RunScope = NewType("RunScope", str)
```

### Classes

```python
class Attachment(BaseModel):
    """Immutable binary attachment for multi-part documents."""

    # Fields
    model_config = ConfigDict(frozen=True, extra='forbid')
    SERIALIZE_METADATA_KEYS: ClassVar[frozenset[str]] = frozenset({'mime_type', 'size'})
    name: str
    content: bytes
    description: str | None = None

    # Methods
    @property
    def is_image(self) -> bool:
        """True if MIME type starts with image/."""
    @property
    def is_pdf(self) -> bool:
        """True if MIME type is application/pdf."""
    @property
    def is_text(self) -> bool:
        """True if MIME type indicates text content."""
    @property
    def size(self) -> int:
        """Content size in bytes."""
    @property
    def text(self) -> str:
        """Content decoded as UTF-8. Raises ValueError if not text."""
    @classmethod
    def validate_content(cls, v: Any) -> bytes:
        """Convert content to bytes."""
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Reject path traversal, reserved suffixes, whitespace issues."""
    def mime_type(self) -> str:
        """Detected MIME type from content and filename. Cached."""
    def serialize_content(self, v: bytes) -> str:
        """Serialize content: plain string for text, data URI (RFC 2397) for binary."""


class Document(BaseModel, Generic[TContent]):
    """Immutable base class for all pipeline documents. Cannot be instantiated directly — must be subclassed."""

    # Fields
    MAX_CONTENT_SIZE: ClassVar[int] = 25 * 1024 * 1024  # Maximum allowed total size in bytes (default 25MB).
    FILES: ClassVar[type[StrEnum] | None] = None  # Allowed filenames enum. Define as nested ``class FILES(StrEnum)`` or assign an external StrEnum subclass.
    name: str
    description: str | None = None
    content: bytes
    derived_from: tuple[str, ...] = ()  # Content provenance: documents and references this document's content was directly
    triggered_by: tuple[DocumentSha256, ...] = ()  # Causal provenance: documents that triggered this document's creation without directly
    attachments: tuple[Attachment, ...] = ()
    model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True, extra='forbid')

    # Methods
    def __init__(self, *, name: str, content: bytes, description: str | None=None, derived_from: tuple[str, ...] | None=None, triggered_by: tuple[DocumentSha256, ...] | None=None, attachments: tuple[Attachment, ...] | None=None) -> None:
        """Initialize with raw bytes content. Most users should use `create()` or `derive()` instead."""
    @property
    def content_documents(self) -> tuple[str, ...]:
        """Document SHA256 hashes from derived_from (filtered by is_document_sha256)."""
    @property
    def content_references(self) -> tuple[str, ...]:
        """Non-hash reference strings from derived_from (URLs, file paths, etc.)."""
    @property
    def id(self) -> str:
        """First 6 chars of sha256. Used as short document identifier in LLM context."""
    @property
    def is_image(self) -> bool:
        """True if MIME type starts with image/."""
    @property
    def is_pdf(self) -> bool:
        """True if MIME type is application/pdf."""
    @property
    def is_text(self) -> bool:
        """True if MIME type indicates text content."""
    @property
    def size(self) -> int:
        """Total size of content + attachments in bytes."""
    @property
    def text(self) -> str:
        """Content decoded as UTF-8. Raises ValueError if not text."""
    @classmethod
    def create(cls, *, name: str, content: str | bytes | dict[str, Any] | list[Any] | BaseModel, description: str | None=None, derived_from: tuple[str, ...] | None=None, triggered_by: tuple[DocumentSha256, ...] | None=None, attachments: tuple[Attachment, ...] | None=None) -> Self:
        """Create a document with automatic content-to-bytes conversion."""
    @classmethod
    def create_root(cls, *, name: str, content: str | bytes | dict[str, Any] | list[Any] | BaseModel, reason: str, description: str | None=None, attachments: tuple[Attachment, ...] | None=None) -> Self:
        """Create a root document (pipeline input) with no provenance."""
    @classmethod
    def derive(cls, *, from_documents: tuple['Document', ...], name: str, content: str | bytes | dict[str, Any] | list[Any] | BaseModel, triggered_by: tuple['Document', ...]=(), description: str | None=None, attachments: tuple[Attachment, ...] | None=None) -> Self:
        """Create a document derived from other documents. The 95% API path."""
    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> Self:
        """Deserialize from dict produced by serialize_model(). Roundtrip guarantee."""
    @classmethod
    def get_content_type(cls) -> type[BaseModel] | None:
        """Return the declared content type from the generic parameter, or None."""
    @classmethod
    def get_expected_files(cls) -> list[str] | None:
        """Return allowed filenames from FILES enum, or None if unrestricted."""
    @classmethod
    def validate_content(cls, v: Any, info: ValidationInfo) -> bytes:
        """Convert content to bytes. Enforces MAX_CONTENT_SIZE."""
    @classmethod
    def validate_derived_from(cls, v: tuple[str, ...]) -> tuple[str, ...]:
        """derived_from must be document SHA256 hashes or URLs."""
    @classmethod
    def validate_file_name(cls, name: str) -> None:
        """Validate filename against FILES enum. Override only for custom validation beyond FILES."""
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Reject path traversal, whitespace issues, reserved suffixes. Must match FILES enum if defined."""
    @classmethod
    def validate_triggered_by(cls, v: tuple[DocumentSha256, ...]) -> tuple[DocumentSha256, ...]:
        """triggered_by must be valid document SHA256 hashes."""
    def __copy__(self) -> Self:
        """Blocked: copy.copy() is not supported for Documents."""
    def __deepcopy__(self, _memo: dict[int, Any] | None=None) -> Self:
        """Blocked: copy.deepcopy() is not supported for Documents."""
    def __init_subclass__(cls, **kwargs: Any) -> None:
        """Validate subclass at definition time. Cannot start with 'Test', cannot add custom fields."""
    def __reduce__(self) -> Any:
        """Blocked: pickle serialization is not supported for Documents."""
    def __reduce_ex__(self, _protocol: object) -> Any:
        """Blocked: pickle serialization is not supported for Documents."""
    def approximate_tokens_count(self) -> int:
        """Approximate token count (tiktoken gpt-4 encoding). Images=TOKENS_PER_IMAGE, PDFs/other=1024."""
    def as_json(self) -> Any:
        """Parse content as JSON."""
    def as_pydantic_model(self, model_type: type[TModel]) -> TModel: ...
    def as_pydantic_model(self, model_type: type[list[TModel]]) -> list[TModel]: ...
    def as_pydantic_model(self, model_type: type[TModel] | type[list[TModel]]) -> TModel | list[TModel]:
        """Parse JSON/YAML content and validate against a Pydantic model. Supports single and list types."""
    def as_yaml(self) -> Any:
        """Parse content as YAML via ruamel.yaml."""
    def content_sha256(self) -> str:
        """SHA256 hash of raw content bytes only. Used for content deduplication."""
    def has_derived_from(self, source: 'Document | str') -> bool:
        """Check if a source (Document or string) is in this document's derived_from."""
    def mime_type(self) -> str:
        """Detected MIME type. Extension-based for known formats, content analysis for others. Cached."""
    def model_copy(self, *args: Any, **kwargs: Any) -> Self:
        """Blocked: model_copy bypasses Document validation and lifecycle tracking."""
    def parse(self, type_: type[Any]) -> Any:
        """Parse content to the requested type. Reverses create() conversion. Extension-based dispatch, no guessing."""
    def parsed(self) -> TContent:
        """Content parsed against the declared generic type parameter. Cached."""
    def retype(self, new_type: type[TDocument], *, preserve_provenance: bool, update: dict[str, Any] | None=None) -> TDocument:
        """Convert to a different Document subclass."""
    def serialize_content(self, v: bytes) -> str:
        """Serialize content: plain string for text, data URI (RFC 2397) for binary."""
    def serialize_model(self) -> dict[str, Any]:
        """Serialize to JSON-compatible dict for storage/transmission. Roundtrips with from_dict()."""
    def sha256(self) -> DocumentSha256:
        """Full SHA256 identity hash (name + content + derived_from + triggered_by + attachments). BASE32 encoded, cached."""
    def validate_no_provenance_overlap(self) -> Self:
        """Reject documents where the same SHA256 appears in both derived_from and triggered_by."""
    def validate_total_size(self) -> Self:
        """Validate that total document size (content + attachments) is within limits."""


class RunContext:
    """Immutable context for a pipeline run, carried via ContextVar."""

    # Fields
    run_scope: RunScope
    execution_id: UUID | None = None


class TaskDocumentContext:
    """Tracks documents created within a single pipeline task or flow execution."""

    # Fields
    created: set[DocumentSha256] = field(default_factory=set)

    # Methods
    def deduplicate(documents: list[Document]) -> list[Document]:
        """Deduplicate documents by SHA256, preserving first occurrence order."""
    def finalize(self, returned_docs: list[Document]) -> list[DocumentSha256]:
        """Detect orphaned documents -- created but not returned."""
    def register_created(self, doc: Document) -> None:
        """Register a document as created in this task/flow context."""
    def validate_provenance(self, documents: list[Document], existing_sha256s: set[DocumentSha256]) -> list[str]:
        """Validate provenance (derived_from and triggered_by) for returned documents."""
```

### Functions

```python
def sanitize_url(url: str) -> str:
    """Sanitize URL or query string for use as a filename (max 100 chars)."""

def is_document_sha256(value: str) -> bool:
    """Check if a string is a valid base32-encoded SHA256 hash (52 chars, A-Z2-7, sufficient entropy)."""

def ensure_extension(name: str, ext: str) -> str:
    """Ensure a filename has the given extension, adding it only if missing."""

def replace_extension(name: str, ext: str) -> str:
    """Replace the file extension (or add one if missing)."""

def find_document(documents: Sequence[Any], doc_type: type[T]) -> T:
    """Find a document of the given type in a sequence."""

def get_run_context() -> RunContext | None:
    """Get the current run context, or None if not set."""

def set_run_context(ctx: RunContext) -> Token[RunContext | None]:
    """Set the run context. Returns a token for restoring the previous value."""

def reset_run_context(token: Token[RunContext | None]) -> None:
    """Reset the run context to its previous value using a token from set_run_context."""
```


## exceptions

Read [Full guide](exceptions.md) for detailed informations how to use it

### Classes

```python
class PipelineCoreError(Exception):
    """Base exception for all AI Pipeline Core errors."""


class DocumentValidationError(PipelineCoreError):
    """Raised when document validation fails."""


class DocumentSizeError(DocumentValidationError):
    """Raised when document content exceeds MAX_CONTENT_SIZE limit."""


class DocumentNameError(DocumentValidationError):
    """Raised when document name contains path traversal, reserved suffixes, or invalid format."""


class LLMError(PipelineCoreError):
    """Raised when LLM generation fails after all retries, including timeouts and provider errors."""


class OutputDegenerationError(LLMError):
    """LLM output contains degeneration patterns (e.g., token repetition loops). Triggers retry with cache disabled."""
```


## llm

Large Language Model integration via LiteLLM proxy.

Read [Full guide](llm.md) for detailed informations how to use it

### Types & Constants

```python
type ModelName = (
    Literal[
        # Core models
        "gemini-3-pro",
        "gpt-5.1",
        # Small models
        "gemini-3-flash",
        "gpt-5-mini",
        "grok-4.1-fast",
        # Search models
        "gemini-3-flash-search",
        "gpt-5-mini-search",
        "grok-4.1-fast-search",
        "sonar-pro-search",
    ]
    | str
)
ConversationContent = str | Document | list[Document]
```

### Classes

```python
class Citation:
    """A URL citation returned by search-enabled models."""

    # Fields
    title: str
    url: str
    start_index: int
    end_index: int


class TokenUsage(BaseModel):
    """Token usage statistics from an LLM call."""

    # Fields
    model_config = ConfigDict(frozen=True)
    prompt_tokens: int  # Input tokens consumed by prompt and context
    completion_tokens: int  # Output tokens generated by the model
    total_tokens: int  # prompt_tokens + completion_tokens
    cached_tokens: int = 0  # Prompt tokens served from provider cache
    reasoning_tokens: int = 0  # Tokens used for internal model reasoning


class ModelOptions(BaseModel):
    """Configuration options for LLM generation requests."""

    # Fields
    model_config = ConfigDict(frozen=True, extra='forbid')
    temperature: float | None = None  # Leave unset (provider decides) unless you have a specific reason
    system_prompt: str | None = None  # Prepended to messages; substitutor instructions appended when active
    search_context_size: Literal['low', 'medium', 'high'] | None = None
    reasoning_effort: Literal['low', 'medium', 'high'] | None = None
    retries: int = 3  # Cache auto-disabled on retry; degeneration detection triggers auto-retry
    retry_delay_seconds: int = 20  # Fixed delay between retries
    timeout: int = 600
    cache_ttl: str | None = '300s'
    service_tier: Literal['auto', 'default', 'flex', 'scale', 'priority'] | None = None
    max_completion_tokens: int | None = None  # Defaults to provider behavior (~30K typical)
    stop: tuple[str, ...] | None = None
    verbosity: Literal['low', 'medium', 'high'] | None = None
    stream: bool = False
    usage_tracking: bool = True
    user: str | None = None
    metadata: Mapping[str, str] | None = None
    extra_body: Mapping[str, Any] | None = None

    # Methods
    def to_openai_completion_kwargs(self) -> dict[str, Any]:
        """Convert options to OpenAI API completion parameters."""


class Conversation(BaseModel, Generic[T]):
    """Immutable conversation state for LLM interactions."""

    # Fields
    model_config = ConfigDict(frozen=True)
    model: str
    context: tuple[Document, ...] = ()
    messages: tuple[_AnyMessage, ...] = ()
    model_options: ModelOptions | None = None
    enable_substitutor: bool = True
    extract_result_tags: bool = False
    include_date: bool = True
    current_date: str | None = None

    # Methods
    @property
    def approximate_tokens_count(self) -> int:
        """Approximate token count for all context and messages."""
    @property
    def citations(self) -> tuple[Citation, ...]:
        """Citations from last send() call (for search-enabled models)."""
    @property
    def content(self) -> str:
        """Response text from last send() call."""
    @property
    def cost(self) -> float | None:
        """Cost from last send() call (if available)."""
    @property
    def parsed(self) -> T | None:
        """Parsed Pydantic model from last send_structured() call."""
    @property
    def reasoning_content(self) -> str:
        """Reasoning content from last send() call (if model supports it)."""
    @property
    def usage(self) -> TokenUsage:
        """Token usage from last send() call."""
    @classmethod
    def validate_model_not_empty(cls, v: str) -> str:
        """Reject empty model name."""
    def send(self, content: ConversationContent, *, purpose: str | None=None, expected_cost: float | None=None) -> 'Conversation[None]':
        """Send message, returns NEW Conversation with response."""
    def send_spec(self, spec: PromptSpec[str], *, documents: Sequence[Document] | None=None, include_input_documents: bool=True, purpose: str | None=None, expected_cost: float | None=None) -> 'Conversation[None]': ...
    def send_spec(self, spec: PromptSpec[U], *, documents: Sequence[Document] | None=None, include_input_documents: bool=True, purpose: str | None=None, expected_cost: float | None=None) -> 'Conversation[U]': ...
    def send_spec(self, spec: PromptSpec[Any], *, documents: Sequence[Document] | None=None, include_input_documents: bool=True, purpose: str | None=None, expected_cost: float | None=None) -> 'Conversation[Any]':
        """Send a PromptSpec to the LLM."""
    def send_structured(self, content: ConversationContent, response_format: type[U], *, purpose: str | None=None, expected_cost: float | None=None) -> 'Conversation[U]':
        """Send message expecting structured response, returns NEW Conversation[U] with .parsed."""
    def with_assistant_message(self, content: str) -> 'Conversation[T]':
        """Return NEW Conversation with an injected assistant turn in messages."""
    def with_context(self, *docs: Document) -> 'Conversation[T]':
        """Return NEW Conversation with documents added to the cacheable context prefix."""
    def with_document(self, doc: Document) -> 'Conversation[T]':
        """Return NEW Conversation with document appended to messages (dynamic suffix, not cached)."""
    def with_documents(self, docs: Sequence[Document]) -> 'Conversation[T]':
        """Return NEW Conversation with multiple documents appended to messages (not cached)."""
    def with_model(self, model: str) -> 'Conversation[T]':
        """Return NEW Conversation with a different model, preserving all state."""
    def with_model_options(self, options: ModelOptions) -> 'Conversation[T]':
        """Return NEW Conversation with updated model options."""
    def with_substitutor(self, enabled: bool=True) -> 'Conversation[T]':
        """Return NEW Conversation with content protection enabled/disabled."""
```


## logging

Logging infrastructure for AI Pipeline Core.

Read [Full guide](logging.md) for detailed informations how to use it

### Classes

```python
class LoggingConfig:
    """Manages logging configuration for the pipeline."""

    # Methods
    def __init__(self, config_path: Path | None=None):
        """Initialize logging configuration."""
    def apply(self) -> None:
        """Apply the logging configuration."""
    def load_config(self) -> dict[str, Any]:
        """Load logging configuration from file or defaults."""
```

### Functions

```python
def setup_logging(config_path: Path | None=None, level: str | None=None) -> None:
    """Setup logging for the AI Pipeline Core library."""

def get_pipeline_logger(name: str) -> logging.Logger:
    """Get a logger for pipeline components."""
```


## observability

Observability system for AI pipelines.

Read [Full guide](observability.md) for detailed informations how to use it

### Types & Constants

```python
TraceLevel = Literal["always", "debug", "off"]
```

### Classes

```python
class TraceInfo(BaseModel):
    """Container for propagating trace context through the pipeline."""

    # Fields
    session_id: str | None = None
    user_id: str | None = None
    metadata: dict[str, str] = Field(default_factory=dict)
    tags: list[str] = Field(default_factory=list)
```

### Functions

```python
def main(argv: list[str] | None=None) -> int:
    """CLI entry point for trace download and inspection."""

def trace(func: Callable[P, R] | None=None, *, level: TraceLevel='always', name: str | None=None, session_id: str | None=None, user_id: str | None=None, metadata: dict[str, Any] | None=None, tags: list[str] | None=None, span_type: str | None=None, ignore_input: bool=False, ignore_output: bool=False, ignore_inputs: list[str] | None=None, input_formatter: Callable[..., str] | None=None, output_formatter: Callable[..., str] | None=None, ignore_exceptions: bool=False, preserve_global_context: bool=True, trim_documents: bool=True) -> Callable[[Callable[P, R]], Callable[P, R]] | Callable[P, R]:
    """Add Laminar observability tracing to any function."""

def set_trace_cost(cost: float | str) -> None:
    """Set cost attributes for the current trace span."""
```


## pipeline

Pipeline framework primitives — decorators, flow options, and concurrency limits.

Read [Full guide](pipeline.md) for detailed informations how to use it

### Types & Constants

```python
type RetryConditionCallable = Callable[[Any, Any, Any], bool]
type StateHookCallable = Callable[[Any, Any, Any], None]
type TaskRunNameValueOrCallable = str | Callable[[], str]
```

### Classes

```python
class TaskLike(Protocol[R_co]):
    """Protocol for type-safe Prefect task representation."""

    # Fields
    submit: Callable[..., Any]
    map: Callable[..., Any]
    name: str | None
    estimated_minutes: int

    # Methods
    def __call__(self, *args: Any, **kwargs: Any) -> Coroutine[Any, Any, R_co]: ...
    def __getattr__(self, name: str) -> Any: ...


class FlowLike(Protocol[FO_contra]):
    """Protocol for decorated flow objects returned by @pipeline_flow."""

    # Fields
    name: str | None
    input_document_types: list[type[Document]]
    output_document_types: list[type[Document]]
    estimated_minutes: int
    stub: bool

    # Methods
    def __call__(self, run_id: str, documents: Sequence[Document], flow_options: FO_contra) -> Coroutine[Any, Any, list[Document]]: ...
    def __getattr__(self, name: str) -> Any: ...


class LimitKind(StrEnum):
    """Kind of concurrency/rate limit."""

    # Fields
    CONCURRENT = 'concurrent'
    PER_MINUTE = 'per_minute'
    PER_HOUR = 'per_hour'


class PipelineLimit:
    """Concurrency/rate limit configuration."""

    # Fields
    limit: int
    kind: LimitKind = LimitKind.CONCURRENT
    timeout: int = 600

    # Methods
    def __post_init__(self) -> None: ...


class FlowOptions(BaseSettings):
    """Base configuration for pipeline flows. Uses pydantic-settings."""

    # Fields
    model_config = SettingsConfigDict(frozen=True, extra='forbid')
```

### Functions

```python
def pipeline_task(__fn: Callable[..., Coroutine[Any, Any, R_co]] | None=None, /, *, trace_level: TraceLevel='always', trace_ignore_input: bool=False, trace_ignore_output: bool=False, trace_ignore_inputs: list[str] | None=None, trace_input_formatter: Callable[..., str] | None=None, trace_output_formatter: Callable[..., str] | None=None, trace_cost: float | None=None, expected_cost: float | None=None, trace_trim_documents: bool=True, estimated_minutes: int=1, name: str | None=None, description: str | None=None, tags: Iterable[str] | None=None, version: str | None=None, cache_policy: CachePolicy | type[NotSet]=NotSet, cache_key_fn: Callable[[TaskRunContext, dict[str, Any]], str | None] | None=None, cache_expiration: datetime.timedelta | None=None, task_run_name: TaskRunNameValueOrCallable | None=None, retries: int | None=None, retry_delay_seconds: int | float | list[float] | Callable[[int], list[float]] | None=None, retry_jitter_factor: float | None=None, persist_result: bool | None=None, result_storage: ResultStorage | str | None=None, result_serializer: ResultSerializer | str | None=None, result_storage_key: str | None=None, cache_result_in_memory: bool=True, timeout_seconds: int | float | None=None, log_prints: bool | None=False, refresh_cache: bool | None=None, on_completion: list[StateHookCallable] | None=None, on_failure: list[StateHookCallable] | None=None, retry_condition_fn: RetryConditionCallable | None=None, viz_return_value: bool | None=None, asset_deps: list[str | Asset] | None=None) -> TaskLike[R_co] | Callable[[Callable[..., Coroutine[Any, Any, R_co]]], TaskLike[R_co]]:
    """Decorate an async function as a traced Prefect task with document auto-save."""

def pipeline_flow(*, trace_level: TraceLevel='always', trace_ignore_input: bool=False, trace_ignore_output: bool=False, trace_ignore_inputs: list[str] | None=None, trace_input_formatter: Callable[..., str] | None=None, trace_output_formatter: Callable[..., str] | None=None, trace_cost: float | None=None, expected_cost: float | None=None, trace_trim_documents: bool=True, estimated_minutes: int=1, stub: bool=False, name: str | None=None, version: str | None=None, flow_run_name: Callable[[], str] | str | None=None, retries: int | None=None, retry_delay_seconds: int | float | None=None, task_runner: TaskRunner[PrefectFuture[Any]] | None=None, description: str | None=None, timeout_seconds: int | float | None=None, validate_parameters: bool=True, persist_result: bool | None=None, result_storage: ResultStorage | str | None=None, result_serializer: ResultSerializer | str | None=None, cache_result_in_memory: bool=True, log_prints: bool | None=None, on_completion: list[FlowStateHook[Any, Any]] | None=None, on_failure: list[FlowStateHook[Any, Any]] | None=None, on_cancellation: list[FlowStateHook[Any, Any]] | None=None, on_crashed: list[FlowStateHook[Any, Any]] | None=None, on_running: list[FlowStateHook[Any, Any]] | None=None) -> Callable[[Callable[..., Coroutine[Any, Any, Sequence[Document]]]], FlowLike[Any]]:
    """Decorate an async function as a traced Prefect flow with annotation-driven document types."""

async def safe_gather(*coroutines: Coroutine[Any, Any, T], label: str='', raise_if_all_fail: bool=True) -> list[T]:
    """Execute coroutines in parallel, returning successes and logging failures."""

async def safe_gather_indexed(*coroutines: Coroutine[Any, Any, T], label: str='', raise_if_all_fail: bool=True) -> list[T | None]:
    """Execute coroutines in parallel, preserving positional correspondence."""

async def pipeline_concurrency(name: str, *, timeout: int | None=None) -> AsyncGenerator[None, None]:
    """Acquire a concurrency/rate-limit slot for an operation."""
```


## prompt_compiler

Prompt compiler for type-safe, validated prompt specifications.

Read [Full guide](prompt_compiler.md) for detailed informations how to use it

### Types & Constants

```python
RESULT_TAG = "result"
RESULT_OPEN = f"<{RESULT_TAG}>"
RESULT_CLOSE = f"</{RESULT_TAG}>"
MAX_TASK_CHARS = 2000
MAX_TASK_LINES = 40
```

### Classes

```python
class Role:
    """Base class for LLM role definitions."""

    # Fields
    text: ClassVar[str]

    # Methods
    def __init_subclass__(cls, **kwargs: Any) -> None: ...


class Rule:
    """Base class for behavioral constraints."""

    # Fields
    text: ClassVar[str]

    # Methods
    def __init_subclass__(cls, **kwargs: Any) -> None: ...


class OutputRule:
    """Base class for output formatting constraints."""

    # Fields
    text: ClassVar[str]

    # Methods
    def __init_subclass__(cls, **kwargs: Any) -> None: ...


class Guide:
    """Base class for reference material / methodology guides."""

    # Fields
    template: ClassVar[str]

    # Methods
    @classmethod
    def render(cls) -> str:
        """Return the cached template file content."""
    def __init_subclass__(cls, **kwargs: Any) -> None: ...


class PromptSpec(BaseModel, Generic[OutputT]):
    """Base class for all prompt specifications."""

    # Fields
    model_config = ConfigDict(frozen=True, extra='forbid')
    input_documents: ClassVar[tuple[type[Document], ...]]
    role: ClassVar[type[Role] | None]
    task: ClassVar[str]
    guides: ClassVar[tuple[type[Guide], ...]]
    rules: ClassVar[tuple[type[Rule], ...]]
    output_rules: ClassVar[tuple[type[OutputRule], ...]]
    output_structure: ClassVar[str | None]

    # Methods
    def __init_subclass__(cls, *, follows: type['PromptSpec'] | None=None, **kwargs: Any) -> None: ...
```

### Functions

```python
def render_text(spec: PromptSpec, *, documents: Sequence[Document] | None=None, include_input_documents: bool=True) -> str:
    """Render a PromptSpec instance to prompt text."""

def render_multi_line_messages(spec: PromptSpec) -> list[tuple[str, str]]:
    """Return XML-tagged message blocks for multi-line fields."""

def render_preview(spec_class: type[PromptSpec], *, include_input_documents: bool=True) -> str:
    """Render a spec CLASS with placeholder values for dynamic fields."""

def MultiLineField(*, description: str, **kwargs: Any) -> Any:
    """Declare a multi-line field on a PromptSpec."""

def main(argv: list[str] | None=None) -> int:
    """CLI entry point for prompt compiler operations (``ai-prompt-compiler`` command)."""
```


## replay

First-class replay system for AI pipeline debugging.

Read [Full guide](replay.md) for detailed informations how to use it

### Classes

```python
class DocumentRef(BaseModel):
    """Reference to a document stored in LocalDocumentStore by SHA256."""

    # Fields
    model_config = ConfigDict(frozen=True, populate_by_name=True)
    doc_ref: str = Field(alias='$doc_ref')  # Full SHA256 hash of the document
    class_name: str  # Document subclass name for type resolution
    name: str  # Original document name


class HistoryEntry(BaseModel):
    """Single entry in a conversation's message history."""

    # Fields
    model_config = ConfigDict(frozen=True, populate_by_name=True)
    type: Literal['user_text', 'assistant_text', 'response', 'document']
    text: str | None = None  # For user_text and assistant_text entries
    content: str | None = None  # For response entries
    doc_ref: str | None = Field(None, alias='$doc_ref')  # SHA256 for document entries
    class_name: str | None = None  # Document class for document entries
    name: str | None = None  # Document name for document entries


class ConversationReplay(BaseModel):
    """Replay payload for a Conversation.send() / send_structured() call."""

    # Fields
    model_config = ConfigDict(frozen=True, populate_by_name=True)
    version: int = 1
    payload_type: Literal['conversation'] = 'conversation'
    model: str
    model_options: dict[str, Any] = {}  # ModelOptions fields (reasoning_effort, cache_ttl, etc.)
    prompt: str
    response_format: str | None = None  # "module:ClassName" path to Pydantic model for send_structured()
    purpose: str | None = None  # Laminar span label for tracing
    context: tuple[DocumentRef, ...] = ()  # Context documents resolved by SHA256 at execution
    history: tuple[HistoryEntry, ...] = ()  # Prior conversation turns
    enable_substitutor: bool = True  # URL/token protection via URLSubstitutor
    extract_result_tags: bool = False  # Extract content between <result> tags
    original: dict[str, Any] = {}  # Cost/tokens from original execution, for comparison

    # Methods
    @classmethod
    def from_yaml(cls, text: str) -> Self:
        """Deserialize from YAML text."""
    def execute(self, store_base: Path) -> Any:
        """Resolve document references and re-execute the LLM call."""
    def to_yaml(self) -> str:
        """Serialize to human-editable YAML."""


class TaskReplay(BaseModel):
    """Replay payload for a @pipeline_task call."""

    # Fields
    model_config = ConfigDict(frozen=True, populate_by_name=True)
    version: int = 1
    payload_type: Literal['pipeline_task'] = 'pipeline_task'
    function_path: str  # "module:qualname" path to the task function
    arguments: dict[str, Any] = {}  # Documents as $doc_ref, BaseModels as dicts, primitives as-is
    original: dict[str, Any] = {}  # Cost/tokens from original execution, for comparison

    # Methods
    @classmethod
    def from_yaml(cls, text: str) -> Self:
        """Deserialize from YAML text."""
    def execute(self, store_base: Path) -> Any:
        """Resolve document references and re-execute the task function."""
    def to_yaml(self) -> str:
        """Serialize to human-editable YAML."""


class FlowReplay(BaseModel):
    """Replay payload for a @pipeline_flow call."""

    # Fields
    model_config = ConfigDict(frozen=True, populate_by_name=True)
    version: int = 1
    payload_type: Literal['pipeline_flow'] = 'pipeline_flow'
    function_path: str  # "module:qualname" path to the flow function
    run_id: str  # Unique run identifier for document store scoping
    documents: tuple[DocumentRef, ...] = ()  # Input documents referenced by SHA256
    flow_options: dict[str, Any] = {}  # FlowOptions fields (filtered to known fields at execution)
    original: dict[str, Any] = {}  # Cost/tokens from original execution, for comparison

    # Methods
    @classmethod
    def from_yaml(cls, text: str) -> Self:
        """Deserialize from YAML text."""
    def execute(self, store_base: Path) -> Any:
        """Resolve document references and re-execute the flow function."""
    def to_yaml(self) -> str:
        """Serialize to human-editable YAML."""
```

### Functions

```python
def main(argv: list[str] | None=None) -> int:
    """CLI entry point for replay operations."""
```


## settings

Read [Full guide](settings.md) for detailed informations how to use it

### Classes

```python
class Settings(BaseSettings):
    """Base configuration for AI Pipeline applications."""

    # Fields
    model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8', extra='ignore', frozen=True)  # Settings are immutable after initialization
    openai_base_url: str = ''
    openai_api_key: str = ''
    prefect_api_url: str = ''
    prefect_api_key: str = ''
    prefect_api_auth_string: str = ''
    prefect_work_pool_name: str = 'default'
    prefect_work_queue_name: str = 'default'
    prefect_gcs_bucket: str = ''
    lmnr_project_api_key: str = ''
    lmnr_debug: str = ''
    gcs_service_account_file: str = ''  # Path to GCS service account JSON file
    clickhouse_host: str = ''
    clickhouse_port: int = 8443
    clickhouse_database: str = 'default'
    clickhouse_user: str = 'default'
    clickhouse_password: str = ''
    clickhouse_secure: bool = True
    clickhouse_connect_timeout: int = 10
    clickhouse_send_receive_timeout: int = 30
    tracking_enabled: bool = True  # When False, disables ClickHouse tracking even if ClickHouse is configured
    doc_summary_enabled: bool = True
    doc_summary_model: str = 'gemini-3-flash'
    pubsub_project_id: str = ''
    pubsub_topic_id: str = ''
```
