Pipelines
Pipeline runs steps in order and feeds each step’s output to the next. run_traced also records a content hash of every step’s input and output, a compact fingerprint of the whole run that you can snapshot.
from beacon import Block, Pipeline, extract_json, template
class ExtractIntent(Block): def __init__(self, llm): self.llm = llm
def run(self, message: str) -> dict: prompt = template("Classify as JSON: <msg>{m}</msg>", m=message) return extract_json(self.llm(prompt), schema=INTENT)
def route(intent: dict) -> str: return {"cancel": "retention", "refund": "billing"}.get(intent["intent"], "support")
pipeline = Pipeline([ExtractIntent(llm), route])pipeline.run("I want to cancel") # 'retention'- The first step receives the arguments given to
run; each later step receives the previous output as its single argument. - Steps can be
Blocksubclasses (overriderun) or plain callables, which are wrapped automatically. - Pipelines nest: a
Pipelineis itself aBlock.
Traces
Section titled “Traces”>>> result = pipeline.run_traced("I want to cancel")>>> result.output'retention'>>> result.trace()[{'index': 0, 'block': 'ExtractIntent', 'input_hash': 'b4f6ee8b23e7441b', 'output_hash': '1f1bc8aeb9db2e0b'}, {'index': 1, 'block': 'route', 'input_hash': '1f1bc8aeb9db2e0b', 'output_hash': '052f5628c6167d05'}]Hashes come from hash_content, and are None for values that aren’t JSON-like. With a recorded model response, the trace is fully deterministic:
from beacon.snapshots import assert_snapshot
def test_cancel_flow(): pipeline = Pipeline([ExtractIntent(llm=lambda p: RECORDED), route]) result = pipeline.run_traced("I want to cancel") assert_snapshot("flows/cancel", {"output": result.output, "trace": result.trace()})