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

# ai-pipeline-core v0.14.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 execution DAG and document storage.
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. [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.
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)

## database

Unified database module for execution DAG and document storage.

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

### Classes

```python
class RunScopeInfo:
    """Aggregated metadata for a non-empty document run scope."""

    # Fields
    run_scope: RunScope
    document_count: int
    latest_created_at: datetime
```

### Functions

```python
def __getattr__(name: str) -> Any:
    """Resolve stable package-level re-exports lazily."""

async def download_deployment(source: DatabaseReader, deployment_id: UUID, output_path: Path) -> None:
    """Export a deployment as a FilesystemDatabase snapshot."""

def create_database(*, backend: str='memory', base_path: Path | None=None, settings: Settings | None=None) -> Database:
    """Factory for creating database backends."""

def create_database_from_settings(settings: Settings, base_path: Path | None=None) -> Database:
    """Create the right database backend based on application settings."""
```


## 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)."""
    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."""
    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: Database | None=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: ...
    def run(self, documents: tuple[Document, ...], options: TOptions) -> TResult:
        """Execute the remote deployment."""


class DocumentInput(_InputBase):
    """Document provided to a deployment — inline content or a URL reference."""

    # Fields
    name: str = Field(default='', description="Document filename (e.g. 'task.md'). Auto-derived from URL path if omitted.")  # Document filename (e.g. 'task.md'). Auto-derived from URL path if omitted.
    description: str = Field(default='', description='Human-readable description of this document.')  # Human-readable description of this document.
    class_name: str = Field(default='', description='Document type class name. Required when the pipeline accepts multiple input types.')  # Document type class name. Required when the pipeline accepts multiple input types.
    derived_from: tuple[str, ...] = Field(default=(), description='Content provenance: SHA256 hashes of source documents or URIs.')  # Content provenance: SHA256 hashes of source documents or URIs.
    triggered_by: tuple[str, ...] = Field(default=(), description='Causal provenance: SHA256 hashes of triggering documents.')  # Causal provenance: SHA256 hashes of triggering documents.
    attachments: tuple[AttachmentInput, ...] = Field(default=(), description='Secondary content attached to this document.')  # Secondary content attached to this document.
    STRIP_KEYS: ClassVar[frozenset[str]] = frozenset({'id', 'sha256', 'content_sha256', 'size', 'mime_type'})
```


## 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."""
    def mime_type(self) -> str:
        """Detected MIME type from content and filename. Cached."""


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.
    publicly_visible: ClassVar[bool] = False  # Whether this document type should be displayed in frontend dashboards.
    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, summary: str | 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."""
    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 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."""


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

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

### Functions

```python
def get_inline_summary(document_sha256: DocumentSha256) -> str | None:
    """Get an inline summary registered at document creation time, if any."""

def pop_inline_summary(document_sha256: DocumentSha256) -> str | None:
    """Consume and remove an inline summary for a document, if any."""

def set_inline_summary(document_sha256: DocumentSha256, summary: str) -> None:
    """Set or replace an inline summary for a document."""

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


## 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",
        "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
ConversationContent = str | Document | list[Document]
AnyMessage = Document | ModelResponse[Any] | UserMessage | AssistantMessage | ToolResultMessage
```

### 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 UserMessage:
    """Internal wrapper for user string messages (not a Document)."""

    # Fields
    text: str


class AssistantMessage:
    """Internal wrapper for injected assistant messages (not from an LLM call)."""

    # Fields
    text: str


class ToolResultMessage:
    """Internal wrapper for tool execution results in conversation history."""

    # Fields
    tool_call_id: str
    function_name: str
    content: str


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 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 validate_model_not_empty(cls, v: str) -> str:
        """Reject empty model name."""
    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[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, 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[None]': ...
    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]': ...
    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."""
    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):
    """Base for tool outputs. ``content`` is sent to the LLM as the tool result."""

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


class Tool:
    """Base class for LLM tools with import-time validation."""

    # Fields
    Input: type[BaseModel]
    Output: type[ToolOutput]

    # Methods
    def __init_subclass__(cls, **kwargs: Any) -> None: ...
    def execute(self, input: Any) -> ToolOutput:
        """Execute the tool with validated input and return the result."""


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


## 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."""


class ExecutionLogBuffer:
    """Thread-safe execution log buffer with per-node ordering and summaries."""

    # Methods
    def __init__(self, *, flush_size: int=DEFAULT_LOG_BUFFER_FLUSH_SIZE, max_pending_logs: int=MAX_PENDING_EXECUTION_LOGS, request_flush: Callable[[], None] | None=None) -> None: ...
    def append(self, log: ExecutionLog) -> None:
        """Assign sequence_no, update summaries, and queue the log for flush."""
    def consume_dropped_count(self) -> int:
        """Return and reset the count of logs dropped due to local buffer overflow."""
    def consume_summary(self, node_id: UUID) -> dict[str, int | str]:
        """Return and forget a node summary once its terminal payload has been persisted."""
    def drain(self) -> list[ExecutionLog]:
        """Return all pending logs and clear the buffer."""
    def get_summary(self, node_id: UUID) -> dict[str, int | str]:
        """Return lightweight log counters for a node."""


class ExecutionLogHandler(_logging.Handler):
    """Route execution-scoped logs from the root logger into the active log buffer."""

    # Methods
    def emit(self, record: Any) -> None:
        """Append an execution-scoped log record to the active buffer when configured."""
```

### Functions

```python
def __getattr__(name: str) -> Any:
    """Resolve logging exports lazily to avoid import cycles during startup."""

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

### Functions

```python
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
    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."""
    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."""
    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
    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, 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: ResultPublisher | None=None) -> Generator[ExecutionContext, None, None]:
    """Set up an execution + task context for tests without full deployment wiring."""

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


## 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[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 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 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 a database backend by SHA256."""

    # Fields
    model_config = ConfigDict(frozen=True, populate_by_name=True)
    doc_ref: DocumentSha256 = 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 ToolCallEntry(BaseModel):
    """Typed tool call entry for replay serialization."""

    # Fields
    model_config = ConfigDict(frozen=True)
    id: str
    function_name: str
    arguments: str


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', 'tool_result']
    text: str | None = None  # For user_text and assistant_text entries
    content: str | None = None  # For response and tool_result 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
    tool_call_id: str | None = None  # For tool_result entries
    function_name: str | None = None  # For tool_result entries
    tool_calls: list[ToolCallEntry] | None = None  # For response entries with tool calls


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 = ''
    prompt_documents: tuple[DocumentRef, ...] = ()  # Prompt documents sent via send(Document) or send([Document, ...])
    response_format: str | None = None  # "module:ClassName" path to Pydantic model for send_structured()
    purpose: str | None = None  # Purpose label for the LLM call
    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
    include_date: bool = True
    current_date: str | None = None
    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, database: DatabaseReader) -> 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 PipelineTask invocation."""

    # 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 a PipelineTask class or 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, database: DatabaseReader) -> Any:
        """Resolve document references and re-execute the task."""
    def to_yaml(self) -> str:
        """Serialize to human-editable YAML."""


class FlowReplay(BaseModel):
    """Replay payload for a PipelineFlow 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 database-backed replay scoping
    documents: tuple[DocumentRef, ...] = ()  # Input documents referenced by SHA256
    flow_options: dict[str, Any] = {}  # FlowOptions fields (filtered to known fields at execution)
    flow_params: dict[str, Any] = {}  # PipelineFlow constructor kwargs for replay
    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, database: DatabaseReader) -> 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 = ''
    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.1-flash-lite'
    pubsub_project_id: str = ''
    pubsub_topic_id: str = ''
```
