logic-engine / docs /pipeline /api-reference.md
ghostdrive1's picture
Upload folder using huggingface_hub
116524e verified
|
Raw
History Blame Contribute Delete
12.8 kB
# API Reference
Complete reference for all public classes, methods, and enums in the pipeline engine.
---
## `pipeline.context`
### `StepContext`
Frozen dataclass passed from step to step. The pipeline engine only reads `sample` and `metadata` β€” domain-specific fields are added by subclassing.
```python
@dataclass(frozen=True)
class StepContext:
sample: Any = None
metadata: MappingProxyType = field(
default_factory=lambda: MappingProxyType({})
)
```
| Method | Signature | Description |
|--------|-----------|-------------|
| `replace` | `(**changes: Any) -> StepContext` | Return a new context with the given fields replaced. Uses `dataclasses.replace` internally. |
**Behavior:**
- `metadata` is auto-coerced from `dict` to `MappingProxyType` in `__post_init__`
- Subclasses inherit `.replace()` β€” it works on all fields including subclass-defined ones
---
## `pipeline.protocol`
### `StepProtocol`
Structural protocol that every step (and Pipeline/Branch) must satisfy.
```python
@runtime_checkable
class StepProtocol(Protocol):
requires: AbstractSet[str]
provides: AbstractSet[str]
def __call__(self, ctx: StepContext) -> StepContext: ...
```
| Attribute | Type | Description |
|-----------|------|-------------|
| `requires` | `AbstractSet[str]` | Metadata keys the step reads |
| `provides` | `AbstractSet[str]` | Metadata keys the step writes |
| `__call__` | `(StepContext) -> StepContext` | Execute the step |
**Notes:**
- `AbstractSet[str]` accepts both `set` and `frozenset`
- `@runtime_checkable` enables `isinstance(step, StepProtocol)` checks
---
### `SampleResult`
Outcome for one sample after the pipeline has run.
```python
@dataclass
class SampleResult:
sample: Any
output: StepContext | None
error: Exception | None
failed_at: str | None
cause: Exception | None = None
```
| Field | Type | Description |
|-------|------|-------------|
| `sample` | `Any` | The original input sample |
| `output` | `StepContext \| None` | Final context (`None` if any step failed) |
| `error` | `Exception \| None` | The exception (`None` if succeeded) |
| `failed_at` | `str \| None` | Class name of the step that raised (`None` if succeeded) |
| `cause` | `Exception \| None` | Inner exception for `BranchError` failures (default `None`) |
**Notes:**
- Mutable β€” background threads update it in-place when background steps complete
- For background steps, `output`/`error` may be `None` until `wait_for_background()` completes
---
## `pipeline.pipeline`
### `Pipeline`
Ordered sequence of steps. Satisfies `StepProtocol` β€” can be nested inside other pipelines.
#### Constructor
```python
Pipeline(steps: list | None = None, hooks: list[PipelineHook] | None = None)
```
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `steps` | `list \| None` | `None` | Optional initial list of steps |
| `hooks` | `list[PipelineHook] \| None` | `None` | Observation-only hooks fired around each foreground step |
Validates step ordering and infers contracts at construction time.
#### Attributes
| Attribute | Type | Description |
|-----------|------|-------------|
| `requires` | `frozenset[str]` | Fields the pipeline needs from external context (auto-inferred) |
| `provides` | `frozenset[str]` | Fields the pipeline writes (auto-inferred, union of all steps) |
#### Methods
##### `then`
```python
def then(self, step: object) -> Pipeline
```
Append a step and return `self` for chaining. Validates ordering immediately.
| Parameter | Type | Description |
|-----------|------|-------------|
| `step` | `object` | Any object satisfying `StepProtocol` |
**Returns:** `self` (for method chaining)
**Raises:** `PipelineOrderError` if the step requires a field produced by a later step
---
##### `branch`
```python
def branch(
self,
*pipelines: object,
merge: MergeStrategy | Callable = MergeStrategy.RAISE_ON_CONFLICT,
) -> Pipeline
```
Append a `Branch` step and return `self` for chaining. Shorthand for `.then(Branch(*pipelines, merge=merge))`.
**Returns:** `self` (for method chaining)
---
##### `run`
```python
def run(
self,
contexts: Iterable[StepContext],
workers: int = 1,
on_sample_done: Callable[[SampleResult], None] | None = None,
cancel_token: CancellationToken | None = None,
) -> list[SampleResult]
```
Process contexts through the pipeline (sync entry point).
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `contexts` | `Iterable[StepContext]` | β€” | Input contexts to process |
| `workers` | `int` | `1` | Max concurrent samples in foreground steps |
| `on_sample_done` | `Callable \| None` | `None` | Callback after each sample's foreground steps complete (or fail). Must not block. |
| `cancel_token` | `CancellationToken \| None` | `None` | Cancellation signal. Checked before each step and each new sample. Pass a fresh token per invocation. |
**Returns:** `list[SampleResult]` β€” one result per input context
**Notes:** Calls `asyncio.run(self.run_async(...))` internally. For background steps, call `wait_for_background()` after this returns. When `cancel_token` is provided, also sets `cancel_token_var` so code inside steps (e.g. LLM clients) can read it.
---
##### `run_async`
```python
async def run_async(
self,
contexts: Iterable[StepContext],
workers: int = 1,
on_sample_done: Callable[[SampleResult], None] | None = None,
cancel_token: CancellationToken | None = None,
) -> list[SampleResult]
```
Async entry point. Use `await pipe.run_async(contexts)` from coroutine contexts.
Same parameters and return type as `run()`.
---
##### `__call__`
```python
def __call__(self, ctx: StepContext) -> StepContext
```
Run all steps sequentially on a single context. Used when the pipeline is nested as a step inside another pipeline.
**Notes:** `async_boundary` markers are ignored in this mode β€” all steps run to completion.
---
##### `wait_for_background`
```python
def wait_for_background(self, timeout: float | None = None) -> None
```
Block until all background tasks complete.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `timeout` | `float \| None` | `None` | Max seconds to wait. `None` = wait indefinitely. |
**Raises:** `TimeoutError` if timeout elapses before completion
---
##### `background_stats`
```python
def background_stats(self) -> dict[str, int]
```
Return a snapshot of background task progress. Thread-safe.
**Returns:** `{"active": int, "completed": int}`
---
## `pipeline.branch`
### `MergeStrategy`
Enum of built-in merge strategies for `Branch` outputs.
```python
class MergeStrategy(Enum):
RAISE_ON_CONFLICT = "raise_on_conflict"
LAST_WRITE_WINS = "last_write_wins"
NAMESPACED = "namespaced"
```
| Value | Behavior |
|-------|----------|
| `RAISE_ON_CONFLICT` | Raises `ValueError` if two branches write different values to the same named field. Metadata merges with last-writer-wins. |
| `LAST_WRITE_WINS` | Last branch's value wins for every conflicting field. |
| `NAMESPACED` | Each branch's output stored at `metadata["branch_N"]`. No conflict possible. |
---
### `Branch`
Runs multiple pipelines in parallel, then merges their outputs. Satisfies `StepProtocol`.
#### Constructor
```python
Branch(
*pipelines: object,
merge: MergeStrategy | Callable = MergeStrategy.RAISE_ON_CONFLICT,
)
```
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `*pipelines` | `object` | β€” | Child pipelines to run in parallel (at least one required) |
| `merge` | `MergeStrategy \| Callable` | `RAISE_ON_CONFLICT` | Merge strategy or custom `fn(list[StepContext]) -> StepContext` |
**Raises:** `ValueError` if no pipelines are provided
#### Attributes
| Attribute | Type | Description |
|-----------|------|-------------|
| `requires` | `frozenset[str]` | Union of all children's requires |
| `provides` | `frozenset[str]` | Union of all children's provides |
| `pipelines` | `list` | The child pipelines |
#### Methods
##### `__call__`
```python
def __call__(self, ctx: StepContext) -> StepContext
```
Sync fan-out via `ThreadPoolExecutor`. All branches run to completion before any failure is raised.
**Raises:** `BranchError` if any branch fails
---
##### `__call_async__`
```python
async def __call_async__(self, ctx: StepContext) -> StepContext
```
Async fan-out via `asyncio.gather`. Sync children are wrapped with `asyncio.to_thread`.
**Raises:** `BranchError` if any branch fails
---
## `pipeline.protocol` β€” Hooks
### `PipelineHook`
Observation-only protocol fired around each foreground step. Hooks cannot modify context β€” both methods return `None`.
```python
@runtime_checkable
class PipelineHook(Protocol):
def before_step(self, step_name: str, ctx: StepContext) -> None: ...
def after_step(self, step_name: str, ctx: StepContext) -> None: ...
```
| Method | Parameters | Description |
|--------|-----------|-------------|
| `before_step` | `step_name: str, ctx: StepContext` | Called before each foreground step executes |
| `after_step` | `step_name: str, ctx: StepContext` | Called after each foreground step completes |
**Notes:**
- `step_name` is `type(step).__name__` β€” hooks know what ran but cannot inspect or mutate the step instance
- Hooks fire for foreground steps only β€” background steps (after `async_boundary`) do not trigger hooks
- If a hook raises, the pipeline logs the error and continues β€” a broken hook never kills the pipeline
- For `Branch` steps, hooks fire once for `"Branch"` as a whole, not for inner steps
---
## `pipeline.errors`
### `CancellationToken`
Thread-safe cancellation signal. Create a fresh token per `run()` invocation.
```python
class CancellationToken:
def cancel(self) -> None: ...
@property
def is_cancelled(self) -> bool: ...
```
| Method / Property | Description |
|-------------------|-------------|
| `cancel()` | Signal cancellation. Thread-safe, idempotent. |
| `is_cancelled` | `True` after `cancel()` has been called. |
---
### `cancel_token_var`
`ContextVar` set by `Pipeline.run_async()` so code inside steps (e.g. LLM clients) can read the current cancel token without parameter changes.
```python
cancel_token_var: ContextVar[CancellationToken | None] # default: None
```
**Notes:**
- Set before steps run, reset after `run_async()` completes
- `asyncio.to_thread()` copies contextvars automatically β€” visible in sync steps too
- Read with `cancel_token_var.get(None)` β€” returns `None` when no pipeline is running
---
### `PipelineCancelled`
```python
class PipelineCancelled(Exception): ...
```
A `cancel_token` was triggered. Surfaces in `SampleResult.error` β€” never propagated to the caller of `run()`. Callers check `isinstance(result.error, PipelineCancelled)` to distinguish cancellation from step failures.
---
### `PipelineOrderError`
```python
class PipelineOrderError(Exception): ...
```
A step requires a field that no earlier step provides (but a later step does). Raised at **construction time**.
---
### `PipelineConfigError`
```python
class PipelineConfigError(Exception): ...
```
Invalid pipeline wiring. Raised at **construction time**. Examples:
- More than one `async_boundary = True` step in the same pipeline
- An `async_boundary = True` step inside a `Branch` child
---
### `BranchError`
```python
class BranchError(Exception):
failures: list[BaseException]
```
One or more branch pipelines failed. All branches always run to completion before this is raised. Raised at **runtime**.
| Attribute | Type | Description |
|-----------|------|-------------|
| `failures` | `list[BaseException]` | One exception per failed branch |
---
## Step class attributes
Optional attributes a step class can declare to control pipeline behavior:
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| `requires` | `set[str] \| frozenset[str]` | *(required)* | Metadata keys the step reads |
| `provides` | `set[str] \| frozenset[str]` | *(required)* | Metadata keys the step writes |
| `async_boundary` | `bool` | `False` | Marks the foreground/background split point |
| `max_workers` | `int` | `1` | Max concurrent background threads for this step class |