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

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

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

## Reading Order

1. [database](database.md) — Unified database module for the span-based schema.
2. [deployment](deployment.md) — Pipeline deployment utilities for unified, type-safe deployments.
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. [logger](logger.md) — Logging infrastructure for AI Pipeline Core.
7. [observability](observability.md) — Observability system for AI pipelines.
8. [pipeline](pipeline.md) — Pipeline framework primitives.
9. [prompt_compiler](prompt_compiler.md) — Prompt compiler for type-safe, validated prompt specifications.
10. [replay](replay.md) — Generic replay and experimentation entry points.
11. [settings](settings.md)

## database

Unified database module for the span-based schema.

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

### Types & Constants

```python
Database = _MemoryDatabase | FilesystemDatabase | ClickHouseDatabase
```

### Classes

```python
class DatabaseReader(Protocol):
    """Read protocol for the span/document/blob/log schema."""

    # Methods
    async def find_documents_by_name(self, names: list[str], *, document_type: str | None=None) -> dict[str, DocumentRecord]: ...
        """Find document records by exact name match."""
    async def get_all_document_shas_for_tree(self, root_deployment_id: UUID) -> set[str]: ...
        """Collect all document SHA256s referenced anywhere in a deployment tree."""
    async def get_blob(self, content_sha256: str) -> _BlobRecord | None: ...
        """Retrieve a blob by content SHA256."""
    async def get_blobs_batch(self, content_sha256s: list[str]) -> dict[str, _BlobRecord]: ...
        """Retrieve blobs keyed by content SHA256."""
    async def get_cached_completion(self, cache_key: str, *, max_age: timedelta | None=None) -> SpanRecord | None: ...
        """Find a completed span matching the cache key within the max age window."""
    async def get_child_spans(self, parent_span_id: UUID) -> list[SpanRecord]: ...
        """Retrieve direct child spans ordered by sequence number."""
    async def get_deployment_by_run_id(self, run_id: str) -> SpanRecord | None: ...
        """Find the newest deployment span for a run ID."""
    async def get_deployment_cost_totals(self, root_deployment_id: UUID) -> CostTotals: ...
        """Aggregate cost (all spans) and token totals (llm_round only) for a deployment tree."""
    async def get_deployment_logs(self, deployment_id: UUID, *, level: str | None=None, category: str | None=None) -> list[LogRecord]: ...
        """Retrieve logs for an entire deployment."""
    async def get_deployment_logs_batch(self, deployment_ids: list[UUID], *, level: str | None=None, category: str | None=None) -> list[LogRecord]: ...
        """Retrieve logs for multiple deployments in one operation."""
    async def get_deployment_span_count(self, root_deployment_id: UUID, *, kinds: list[str] | None=None) -> int: ...
        """Count spans in a deployment tree, optionally filtering by span kind."""
    async def get_deployment_tree(self, root_deployment_id: UUID) -> list[SpanRecord]: ...
        """Retrieve every span in a deployment tree as a flat list."""
    async def get_document(self, document_sha256: str) -> DocumentRecord | None: ...
        """Retrieve a document record by SHA256."""
    async def get_document_with_content(self, document_sha256: str) -> HydratedDocument | None: ...
        """Load document metadata plus primary content and attachment blobs."""
    async def get_documents_batch(self, sha256s: list[str]) -> dict[str, DocumentRecord]: ...
        """Retrieve multiple document records keyed by SHA256."""
    async def get_span(self, span_id: UUID) -> SpanRecord | None: ...
        """Retrieve a span by its ID."""
    async def get_span_logs(self, span_id: UUID, *, level: str | None=None, category: str | None=None) -> list[LogRecord]: ...
        """Retrieve logs for a specific span."""
    async def get_spans_referencing_document(self, document_sha256: str, *, kinds: list[str] | None=None) -> list[SpanRecord]: ...
        """Find spans that reference a SHA in document or blob input/output arrays."""
    async def list_deployments(self, limit: int, *, status: str | None=None) -> list[SpanRecord]: ...
        """List deployment spans ordered by newest start time first."""


class SpanKind(StrEnum):
    """Discriminator for span-based execution records."""

    # Fields
    DEPLOYMENT = 'deployment'
    FLOW = 'flow'
    TASK = 'task'
    OPERATION = 'operation'
    CONVERSATION = 'conversation'
    LLM_ROUND = 'llm_round'
    TOOL_CALL = 'tool_call'


class SpanStatus(StrEnum):
    """Lifecycle status for span-based execution records."""

    # Fields
    RUNNING = 'running'
    COMPLETED = 'completed'
    FAILED = 'failed'
    CACHED = 'cached'
    SKIPPED = 'skipped'


class SpanRecord:
    """Row from the span-oriented execution table."""

    # Fields
    span_id: UUID
    parent_span_id: UUID | None
    deployment_id: UUID
    root_deployment_id: UUID
    run_id: str
    kind: str
    name: str
    sequence_no: int
    deployment_name: str = ''
    description: str = ''
    status: str = SpanStatus.RUNNING
    started_at: datetime = field(default_factory=_utcnow)
    ended_at: datetime | None = None
    version: int = 1
    cache_key: str = ''
    previous_conversation_id: UUID | None = None
    cost_usd: float = 0.0
    error_type: str = ''
    error_message: str = ''
    input_document_shas: tuple[str, ...] = ()
    output_document_shas: tuple[str, ...] = ()
    target: str = ''
    receiver_json: str = ''
    input_json: str = ''
    output_json: str = ''
    error_json: str = ''
    meta_json: str = ''
    metrics_json: str = ''
    input_blob_shas: tuple[str, ...] = ()
    output_blob_shas: tuple[str, ...] = ()

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


class DocumentRecord:
    """Row from the content-addressed documents table."""

    # Fields
    document_sha256: DocumentSha256
    content_sha256: str
    document_type: str
    name: str
    description: str = ''
    mime_type: str = ''
    size_bytes: int = 0
    summary: str = ''
    derived_from: tuple[str, ...] = ()
    triggered_by: tuple[str, ...] = ()
    attachment_names: tuple[str, ...] = ()
    attachment_descriptions: tuple[str, ...] = ()
    attachment_content_sha256s: tuple[str, ...] = ()
    attachment_mime_types: tuple[str, ...] = ()
    attachment_size_bytes: tuple[int, ...] = ()
    publicly_visible: bool = False

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


class CostTotals:
    """Aggregated cost and token totals for a deployment. Cost includes all span kinds, token counts include llm_round spans only."""

    # Fields
    cost_usd: float = 0.0
    tokens_input: int = 0
    tokens_output: int = 0
    tokens_cache_read: int = 0
    tokens_reasoning: int = 0


class HydratedDocument:
    """Document metadata with loaded primary and attachment blob content."""

    # Fields
    record: DocumentRecord
    content: bytes
    attachment_contents: dict[str, bytes] = field(default_factory=dict)

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


## deployment

Pipeline deployment utilities for unified, type-safe deployments.

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

### Classes

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

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


class FlowAction(StrEnum):
    """Directive action for dynamic flow control."""

    # Fields
    CONTINUE = 'continue'
    SKIP = 'skip'


class FlowDirective:
    """Flow planning directive returned by plan_next_flow()."""

    # Fields
    action: FlowAction = FlowAction.CONTINUE
    reason: str = ''


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

    # Fields
    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_flows(self, options: TOptions) -> Sequence[PipelineFlow]: ...
        """Build flow instances for this run."""
    def build_partial_result(self, run_id: str, documents: tuple[Document, ...], options: TOptions) -> TResult: ...
        """Build a result for partial pipeline runs (--start/--end that don't reach the last step)."""
    @staticmethod
    def build_result(run_id: str, documents: tuple[Document, ...], options: TOptions) -> TResult: ...
        """Extract typed result from pipeline documents."""
    def plan_next_flow(self, flow_class: type[PipelineFlow], plan: Sequence[PipelineFlow], output_documents: tuple[Document, ...]) -> FlowDirective: ...
        """Optionally skip future instances of a flow class."""
    async def run(self, run_id: str, documents: Sequence[Document], options: TOptions, publisher: ResultPublisher | None=None, start_step: int=1, end_step: int | None=None, parent_execution_id: UUID | None=None, database: Any=None) -> TResult: ...
        """Execute flows with resume, per-flow uploads, and step control."""
    def run_cli(self, initializer: Callable[[TOptions], tuple[str, tuple[Document, ...]]] | None=None, cli_mixin: type | None=None) -> None: ...
        """Execute pipeline from CLI with positional working_directory and --start/--end 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 database."""


class RemoteDeployment(Generic[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]]
    deployment_class: ClassVar[str] = ''

    # Methods
    @property
    def deployment_path(self) -> str: ...
        """Full Prefect deployment path: '{flow_name}/{deployment_name}'."""
    def __init_subclass__(cls, **kwargs: Any) -> None: ...
    async def run(self, documents: tuple[Document, ...], options: TOptions) -> TResult: ...
        """Execute the remote deployment."""
```


## 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)
```

### 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 mime_type(self) -> str: ...
        """Detected MIME type from content and filename. Cached."""
    @property
    def size(self) -> int: ...
        """Content size in bytes."""
    @property
    def text(self) -> str: ...
        """Content decoded as UTF-8. Raises ValueError if not text."""


class Document(BaseModel):
    """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.
    publicly_visible: ClassVar[bool] = False  # Whether this document type should be displayed in frontend dashboards.
    name: str
    description: str | None = None
    summary: str = ''
    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, summary: str='', 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 approximate_tokens_count(self) -> int: ...
        """Approximate token count across primary content and attachments."""
    @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 content_sha256(self) -> str: ...
        """SHA256 hash of raw content bytes only. Used for content deduplication."""
    @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 mime_type(self) -> str: ...
        """Detected MIME type. Extension-based for known formats, content analysis for others. Cached."""
    @property
    def parsed(self) -> TContent: ...
        """Content parsed against the declared generic type parameter. Cached."""
    @property
    def sha256(self) -> DocumentSha256: ...
        """Full SHA256 identity hash (name + content + derived_from + triggered_by + attachments). BASE32 encoded, cached."""
    @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, triggered_by: Sequence[Document], derived_from: Sequence[Document] | None=None, description: str | None=None, summary: str='', attachments: tuple[Attachment, ...] | None=None) -> Self: ...
        """Create a document triggered by other documents (causal provenance)."""
    @classmethod
    def create_external(cls, *, name: str, content: str | bytes | dict[str, Any] | list[Any] | BaseModel, from_sources: Sequence[str], triggered_by: Sequence[Document] | None=None, description: str | None=None, summary: str='', attachments: tuple[Attachment, ...] | None=None) -> Self: ...
        """Create a document from external URI sources (websites, APIs, MCP responses)."""
    @classmethod
    def create_root(cls, *, name: str, content: str | bytes | dict[str, Any] | list[Any] | BaseModel, reason: str, description: str | None=None, summary: str='', attachments: tuple[Attachment, ...] | None=None) -> Self: ...
        """Create a root document (pipeline input) with no provenance."""
    @classmethod
    def derive(cls, *, name: str, content: str | bytes | dict[str, Any] | list[Any] | BaseModel, derived_from: Sequence[Document], triggered_by: Sequence[Document] | None=None, description: str | None=None, summary: str='', attachments: tuple[Attachment, ...] | None=None) -> Self: ...
        """Create a document derived from other documents (content provenance)."""
    @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."""
    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 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 has_derived_from(self, source: Document | str) -> bool: ...
        """Check if a source (Document or string) is in this document's derived_from."""
    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 serialize_model(self) -> dict[str, Any]: ...
        """Serialize to JSON-compatible dict for storage/transmission. Roundtrips with from_dict()."""


class DocumentValidationError(Exception):
    """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."""
```

### 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_all(documents: Sequence[Any], doc_type: type[T]) -> list[T]: ...
    """Find all documents of the given type in a sequence."""

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

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


## exceptions

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

### Classes

```python
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."""


class EmptyResponseError(LLMError):
    """Model returned empty content (no text and no tool calls). Retried with LiteLLM cache disabled."""


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


## 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",
        "gemini-3.1-flash-lite",
        # Search models
        "gemini-3-flash-search",
        "gpt-5-mini-search",
        "grok-4.1-fast-search",
        "sonar-pro-search",
    ]
    | str
)
MAX_TOOL_ROUNDS_DEFAULT = 10
```

### 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 result from last send() call."""
    @property
    def reasoning_content(self) -> str: ...
        """Reasoning content from last send() call (if model supports it)."""
    @property
    def tool_call_records(self) -> tuple[ToolCallRecord, ...]: ...
        """All tool call records from this Conversation's send() call (across all rounds)."""
    @property
    def usage(self) -> TokenUsage: ...
        """Token usage from last send() call."""
    @classmethod
    def __codec_load__(cls, state: dict[str, Any]) -> Self: ...
        """Reconstruct a Conversation from codec state."""
    @classmethod
    def validate_model_not_empty(cls, v: str) -> str: ...
        """Reject empty model name."""
    def __codec_state__(self) -> dict[str, Any]: ...
        """Return codec state including Pydantic private fields."""
    async def send(self, content: ConversationContent, *, tools: list[Tool] | None=None, tool_choice: Literal['auto', 'required', 'none'] | None=None, max_tool_rounds: int=MAX_TOOL_ROUNDS_DEFAULT, purpose: str | None=None, expected_cost: float | None=None) -> Conversation[str]: ...
        """Send message, returns NEW Conversation with response."""
    async def send_spec(self, spec: PromptSpec[str], *, documents: Sequence[Document] | None=None, include_input_documents: bool=True, tools: list[Tool] | None=None, tool_choice: Literal['auto', 'required', 'none'] | None=None, max_tool_rounds: int=MAX_TOOL_ROUNDS_DEFAULT, purpose: str | None=None, expected_cost: float | None=None) -> Conversation[str]: ...
    async def send_spec(self, spec: PromptSpec[U], *, documents: Sequence[Document] | None=None, include_input_documents: bool=True, tools: list[Tool] | None=None, tool_choice: Literal['auto', 'required', 'none'] | None=None, max_tool_rounds: int=MAX_TOOL_ROUNDS_DEFAULT, purpose: str | None=None, expected_cost: float | None=None) -> Conversation[U]: ...
    async def send_spec(self, spec: PromptSpec[Any], *, documents: Sequence[Document] | None=None, include_input_documents: bool=True, tools: list[Tool] | None=None, tool_choice: Literal['auto', 'required', 'none'] | None=None, max_tool_rounds: int=MAX_TOOL_ROUNDS_DEFAULT, purpose: str | None=None, expected_cost: float | None=None) -> Conversation[Any]: ...
        """Send a PromptSpec to the LLM."""
    async def send_structured(self, content: ConversationContent, response_format: type[U], *, tools: list[Tool] | None=None, tool_choice: Literal['auto', 'required', 'none'] | None=None, max_tool_rounds: int=MAX_TOOL_ROUNDS_DEFAULT, purpose: str | None=None, expected_cost: float | None=None) -> Conversation[U]: ...
        """Send message expecting structured response, returns NEW Conversation[U] with .parsed."""
    def tool_calls_for(self, tool_cls: type[Tool]) -> tuple[ToolCallRecord, ...]: ...
        """Filter tool call records by tool class."""
    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."""


class ToolOutput(BaseModel):
    """Container for serialized tool output sent to the LLM and caller metadata."""

    # Fields
    model_config = ConfigDict(frozen=True)
    content: str
    data: Any = None


class Tool:
    """Base class for LLM tools with import-time validation and a sealed lifecycle."""

    # Fields
    Input: type[BaseModel]
    Output: type[BaseModel]
    name: ClassVar[str]
    retries: ClassVar[int] = 0
    retry_delay_seconds: ClassVar[float] = 2.0
    timeout_seconds: ClassVar[int] = 120
    max_response_bytes: ClassVar[int | None] = None
    handled_exceptions: ClassVar[tuple[type[Exception], ...]] = ()

    # Methods
    def __init_subclass__(cls, **kwargs: Any) -> None: ...
    async def execute(self, input: BaseModel) -> ToolOutput: ...
        """Execute tool lifecycle (retry, timeout, errors, serialization)."""
    def handle_error(self, error: Exception) -> ToolOutput: ...
        """Format a handled exception into a caller-facing ToolOutput."""
    async def run(self, input: Any) -> BaseModel: ...
        """Override this method with your tool logic. Return self.Output(...)."""


class ToolCallRecord:
    """Record of a single tool call execution within the tool loop."""

    # Fields
    tool: type[Tool]
    input: BaseModel
    output: ToolOutput
    round: int
```

### Functions

```python
def to_snake_case(name: str) -> str: ...
    """Convert PascalCase to snake_case, handling consecutive capitals."""

def generate_tool_schema(tool: Tool) -> dict[str, Any]: ...
    """Generate OpenAI Chat Completions tool schema from a Tool instance."""
```


## logger

Logging infrastructure for AI Pipeline Core.

Read [Full guide](logger.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
TraceDatabase = Database | FilesystemDatabase | ClickHouseDatabase
```

### Functions

```python
async def show_deployment(reader: DatabaseReader, deployment_id: UUID) -> str: ...
    """Render a deployment summary for ai-trace show."""

def main(argv: list[str] | None=None) -> int: ...
    """Run the ai-trace CLI."""
```


## pipeline

Pipeline framework primitives.

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

### Classes

```python
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(BaseModel):
    """Base configuration for pipeline flows."""

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


class PipelineFlow:
    """Base class for pipeline flows."""

    # Fields
    name: ClassVar[str]
    estimated_minutes: ClassVar[float] = 1.0
    BASE_COST_USD: ClassVar[float] = 0.0
    input_document_types: ClassVar[list[type[Document]]] = []
    output_document_types: ClassVar[list[type[Document]]] = []
    task_graph: ClassVar[list[tuple[str, str]]] = []

    # Methods
    def __init__(self, **kwargs: Any) -> None: ...
        """Constructor for per-flow instance configuration."""
    @classmethod
    def expected_tasks(cls) -> list[str]: ...
        """Return expected task names extracted from run() AST."""
    def __init_subclass__(cls, **kwargs: Any) -> None: ...
    def get_params(self) -> dict[str, Any]: ...
        """Return constructor params for flow plan serialization."""
    async def run(self, documents: tuple[Any, ...], options: Any) -> tuple[Any, ...]: ...
        """Execute the flow."""


class TaskHandle:
    """Handle for an executing pipeline task."""

    # Fields
    task_class: type[Any] | None
    input_arguments: Mapping[str, Any]

    # Methods
    @property
    def done(self) -> bool: ...
        """Whether the underlying task has finished."""
    def __await__(self): ...
    def cancel(self) -> None: ...
        """Cancel the underlying task."""
    async def result(self) -> T: ...
        """Await the underlying task result."""


class TaskBatch:
    """Collected task results and handles that did not complete successfully."""

    # Fields
    completed: list[tuple[Document[Any], ...]]
    incomplete: list[TaskHandle[tuple[Document[Any], ...]]]


class PipelineTask:
    """Base class for pipeline tasks."""

    # Fields
    name: ClassVar[str]
    estimated_minutes: ClassVar[float] = 1.0
    retries: ClassVar[int] = 0
    retry_delay_seconds: ClassVar[int] = 20
    timeout_seconds: ClassVar[int | None] = None
    cacheable: ClassVar[bool] = False
    cache_version: ClassVar[int] = 1
    cache_ttl_seconds: ClassVar[int | None] = None
    BASE_COST_USD: ClassVar[float] = 0.0
    expected_cost: ClassVar[float | None] = None
    input_document_types: ClassVar[list[type[Document]]] = []
    output_document_types: ClassVar[list[type[Document]]] = []

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

### Functions

```python
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]: ...
    """Acquire a concurrency/rate-limit slot for an operation."""

def get_run_id() -> str: ...
    """Return the current run ID from the active execution context."""

def pipeline_test_context(run_id: str='test-run', publisher: Any | None=None, cache_ttl: timedelta | None=None) -> Generator[ExecutionContext]: ...
    """Set up an execution + task context for tests without full deployment wiring."""

def add_cost(amount: float, reason: str='') -> None: ...
    """Attach a cost to the current execution span."""

async def collect_tasks(*handles: TaskAwaitableGroup, deadline_seconds: float | None=None) -> TaskBatch: ...
    """Await task handles with an optional deadline and split completed/incomplete."""

async def as_task_completed(*handles: TaskAwaitableGroup) -> AsyncIterator[TaskHandle[tuple[Document[Any], ...]]]: ...
    """Yield task handles in completion order."""

async def run_tasks_until(task_cls: type[Any], argument_groups: Sequence[tuple[tuple[Any, ...], dict[str, Any]]], *, deadline_seconds: float | None=None) -> TaskBatch: ...
    """Launch ``task_cls.run(*args, **kwargs)`` for each argument group and collect the handles."""

async def traced_operation(name: str, description: str='') -> AsyncGenerator[None]: ...
    """Create a tracked operation span. Outside deployment context this is a no-op."""
```


## 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):
    """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 main(argv: list[str] | None=None) -> int: ...
    """CLI entry point for prompt compiler operations (``ai-prompt-compiler`` command)."""

def render_text(spec: PromptSpec[Any], *, 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[Any]) -> list[tuple[str, str]]: ...
    """Return XML-tagged message blocks for multi-line, structured, and list fields."""

def render_preview(spec_class: type[PromptSpec[Any]], *, 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 StructuredField(*, description: str, **kwargs: Any) -> Any: ...
    """Declare a BaseModel field on a PromptSpec."""

def ListField(*, description: str, **kwargs: Any) -> Any: ...
    """Declare a list field on a PromptSpec."""
```


## replay

Generic replay and experimentation entry points.

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

### Classes

```python
class ExperimentResult:
    # Fields
    source_span_id: UUID
    replay_run_id: str
    replay_root_span_id: UUID | None
    original: OriginalOutput
    result: Any
    duration_seconds: float
    cost_usd: float
    model_used: str
    tokens: TokenSummary | None
    recording_degraded: bool = False


class ExperimentOverrides:
    # Fields
    model: str | None = None
    model_options: dict[str, Any] | None = None
    tools: Mapping[str, Tool] | None = None
    response_format: type[BaseModel] | None = None
```

### Functions

```python
def infer_db_path(replay_file: Path) -> Path: ...
    """Walk up from a replay file to find a snapshot root."""

def main(argv: list[str] | None=None) -> int: ...
    """Run the replay CLI."""

async def execute_span(span_id: UUID, *, source_db: DatabaseReader, sink_db: DatabaseWriter | None=None) -> Any: ...
    """Replay one recorded span against live code."""

async def experiment_span(span_id: UUID, *, source_db: DatabaseReader, sink_db: DatabaseWriter | None=None, overrides: ExperimentOverrides | None=None) -> ExperimentResult: ...

async def experiment_batch(span_ids: Sequence[UUID], source_db: DatabaseReader, *, overrides: ExperimentOverrides | None=None, concurrency: int=5, sink_db: DatabaseWriter | None=None) -> list[ExperimentResult]: ...

async def find_experiment_span_ids(database: DatabaseReader, deployment_id: UUID, *, kind: str | None=None, purpose: str | None=None, task_class: str | None=None) -> list[UUID]: ...
```


## 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 = ""
    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
    doc_summary_enabled: bool = True
    doc_summary_model: str = "gemini-3.1-flash-lite"
    pubsub_project_id: str = ""
    pubsub_topic_id: str = ""
    lmnr_project_api_key: str = ""
```
