Spaces:
Sleeping
Sleeping
| # Quick Start | |
| Build and run your first pipeline in under 30 lines. | |
| --- | |
| ## Define two steps | |
| Every step needs three things: `requires`, `provides`, and a `__call__` method. | |
| ```python | |
| from types import MappingProxyType | |
| from pipeline import Pipeline, StepContext | |
| class Tokenize: | |
| """Split text into tokens and count words.""" | |
| requires = frozenset() | |
| provides = frozenset({"tokens", "word_count"}) | |
| def __call__(self, ctx: StepContext) -> StepContext: | |
| tokens = str(ctx.sample).split() | |
| return ctx.replace( | |
| metadata=MappingProxyType({ | |
| **ctx.metadata, | |
| "tokens": tokens, | |
| "word_count": len(tokens), | |
| }) | |
| ) | |
| class Uppercase: | |
| """Convert tokens to uppercase.""" | |
| requires = frozenset({"tokens"}) | |
| provides = frozenset({"upper_tokens"}) | |
| def __call__(self, ctx: StepContext) -> StepContext: | |
| upper = [t.upper() for t in ctx.metadata["tokens"]] | |
| return ctx.replace( | |
| metadata=MappingProxyType({**ctx.metadata, "upper_tokens": upper}) | |
| ) | |
| ``` | |
| --- | |
| ## Build and run | |
| Chain steps with `.then()` and run with a list of contexts: | |
| ```python | |
| pipe = Pipeline().then(Tokenize()).then(Uppercase()) | |
| results = pipe.run([ | |
| StepContext(sample="hello world"), | |
| StepContext(sample="pipeline engine demo"), | |
| ]) | |
| ``` | |
| The pipeline validates ordering at construction time β if `Uppercase` came before `Tokenize`, you'd get a `PipelineOrderError` immediately, not at runtime. | |
| --- | |
| ## Inspect results | |
| Every sample produces exactly one `SampleResult`: | |
| ```python | |
| for r in results: | |
| if r.error: | |
| print(f"Failed at {r.failed_at}: {r.error}") | |
| else: | |
| print(f"Sample: {r.sample}") | |
| print(f"Tokens: {r.output.metadata['upper_tokens']}") | |
| print(f"Count: {r.output.metadata['word_count']}") | |
| ``` | |
| ``` | |
| Sample: hello world | |
| Tokens: ['HELLO', 'WORLD'] | |
| Count: 2 | |
| Sample: pipeline engine demo | |
| Tokens: ['PIPELINE', 'ENGINE', 'DEMO'] | |
| Count: 3 | |
| ``` | |
| --- | |
| ## Add parallelism with Branch | |
| Run independent steps simultaneously with `Branch`: | |
| ```python | |
| from pipeline import MergeStrategy | |
| class Reverse: | |
| """Reverse each token.""" | |
| requires = frozenset({"tokens"}) | |
| provides = frozenset({"reversed_tokens"}) | |
| def __call__(self, ctx: StepContext) -> StepContext: | |
| rev = [t[::-1] for t in ctx.metadata["tokens"]] | |
| return ctx.replace( | |
| metadata=MappingProxyType({**ctx.metadata, "reversed_tokens": rev}) | |
| ) | |
| pipe = ( | |
| Pipeline() | |
| .then(Tokenize()) | |
| .branch( | |
| Pipeline().then(Uppercase()), # runs in parallel | |
| Pipeline().then(Reverse()), # runs in parallel | |
| merge=MergeStrategy.RAISE_ON_CONFLICT, | |
| ) | |
| ) | |
| results = pipe.run([StepContext(sample="fork join")]) | |
| meta = results[0].output.metadata | |
| print(meta["upper_tokens"]) # ['FORK', 'JOIN'] | |
| print(meta["reversed_tokens"]) # ['krof', 'nioj'] | |
| ``` | |
| Both branches write to different fields (`upper_tokens` vs `reversed_tokens`), so `RAISE_ON_CONFLICT` passes through without raising. | |
| --- | |
| ## Fire-and-forget with async_boundary | |
| Some steps are slow and don't need to block the caller. Mark a step with `async_boundary = True` to hand everything from that point onward to a background thread β `run()` returns immediately after the foreground steps. | |
| ```python | |
| import time | |
| class SlowScore: | |
| """Expensive scoring that runs in the background.""" | |
| requires = frozenset({"tokens"}) | |
| provides = frozenset({"score"}) | |
| async_boundary = True # everything from here runs in background | |
| max_workers = 3 # up to 3 background threads | |
| def __call__(self, ctx: StepContext) -> StepContext: | |
| time.sleep(0.5) # simulate slow work | |
| score = ctx.metadata["word_count"] * 10 | |
| return ctx.replace( | |
| metadata=MappingProxyType({**ctx.metadata, "score": score}) | |
| ) | |
| pipe = Pipeline().then(Tokenize()).then(SlowScore()) | |
| # Returns immediately β only Tokenize runs in the foreground | |
| results = pipe.run([ | |
| StepContext(sample="hello world"), | |
| StepContext(sample="background processing demo"), | |
| ]) | |
| # Background scoring still running... | |
| print(pipe.background_stats()) # {'active': 2, 'completed': 0} | |
| # Block until background work finishes | |
| pipe.wait_for_background(timeout=10.0) | |
| # Now results are fully populated | |
| for r in results: | |
| print(f"{r.sample}: score={r.output.metadata['score']}") | |
| ``` | |
| ``` | |
| hello world: score=20 | |
| background processing demo: score=30 | |
| ``` | |
| See [Execution Model](execution.md) for the full concurrency model β `workers` vs `max_workers`, async steps, and boundary rules. | |
| --- | |
| ## Try it interactively | |
| All the examples on this page (and more) are available as a runnable Jupyter notebook: | |
| [:material-notebook: Open the Pipeline Demo Notebook](https://github.com/kayba-ai/agentic-context-engine/blob/main/examples/pipeline_ex/pipeline_demo.ipynb){ .md-button } | |
| --- | |
| ## Next steps | |
| - [**Core Concepts**](core-concepts.md) β Understand the contract system and how validation works | |
| - [**Execution Model**](execution.md) β Learn about async steps, `async_boundary`, and workers | |
| - [**Branching & Parallelism**](branching.md) β Deep dive into merge strategies and error handling | |
| - [**Building Custom Steps**](custom-steps.md) β Dependency injection, testing, and common patterns | |