logic-engine / docs /guides /composing-pipelines.md
ghostdrive1's picture
Upload folder using huggingface_hub
116524e verified
|
Raw
History Blame Contribute Delete
8.58 kB
# Composing Custom Pipelines
ACE is built on a composable pipeline engine. Every runner (`ACE`, `BrowserUse`,
`LangChain`, `ClaudeCode`, `TraceAnalyser`) is a thin wrapper around a `Pipeline`
made of steps. You can compose your own pipelines by mixing and matching these
steps — or writing custom ones.
## Three Levels of ACE
| Level | Pattern | Control |
|-------|---------|---------|
| **Zero-config** | `ACELiteLLM.from_model("gpt-4o-mini")` | Roles + pipeline auto-created |
| **Role customisation** | `ACE.from_roles(agent=..., reflector=..., ...)` | Custom roles, pipeline auto-composed |
| **Pipeline composition** | `Pipeline([AgentStep(...), ...])` | Full control over step ordering |
This guide covers **Level 3** — composing pipelines directly.
## Anatomy of an ACE Pipeline
Every ACE pipeline is a sequence of steps, each with a `requires`/`provides`
contract that declares what context fields it reads and writes:
```
AgentStep ─────> EvaluateStep ─────> ReflectStep ─────> UpdateStep
provides: provides: provides: provides:
agent_output trace reflections skill_manager_output
(also mutates skillbook
via the SM's tools)
```
The pipeline validates these contracts at construction time — if a step requires
a field that no earlier step provides, you'll get an error immediately.
## Composing from Steps
All pipeline classes and ACE steps are importable from `ace`:
```python
from ace import (
# Pipeline engine
Pipeline, Branch, MergeStrategy, StepProtocol, SampleResult,
# ACE context
ACEStepContext, SkillbookView,
# Roles
Agent, Reflector, SkillManager,
# Steps
AgentStep, EvaluateStep, learning_tail,
# Types
Sample, Skillbook, SimpleEnvironment,
)
skillbook = Skillbook()
pipe = Pipeline([
AgentStep(Agent("gpt-4o-mini"), skillbook),
EvaluateStep(SimpleEnvironment()),
*learning_tail(Reflector("gpt-4o-mini"), SkillManager("gpt-4o-mini"), skillbook),
])
```
## Using `learning_tail()`
The `learning_tail()` helper returns the standard learning step sequence:
```python
from ace import learning_tail, Reflector, SkillManager, Skillbook
steps = learning_tail(
Reflector(llm),
SkillManager(llm),
Skillbook(),
dedup_manager=my_dedup_manager, # optional
checkpoint_dir="/tmp/checkpoints", # optional
)
# Returns: [ReflectStep, UpdateStep,
# DeduplicateStep, CheckpointStep]
```
Use it when building custom integrations that provide their own execute step but
want the standard learning pipeline.
## Inspecting Runner Presets with `build_steps()`
Every runner has a `build_steps()` classmethod that returns the step list it
would use internally. This lets you inspect, modify, and recompose:
```python
from ace import ACE, Pipeline, ACERunner, Skillbook
# Get the default steps
steps = ACE.build_steps(
agent=my_agent,
reflector=my_reflector,
skill_manager=my_skill_manager,
environment=my_env,
)
# Insert a custom step after EvaluateStep
steps.insert(2, MyLoggingStep())
# Build your own pipeline and runner
skillbook = Skillbook()
pipe = Pipeline(steps)
runner = ACERunner(pipeline=pipe, skillbook=skillbook)
results = runner.run(samples)
```
All runners support `build_steps()`: `ACE`, `BrowserUse`, `ClaudeCode`,
`LangChain`, and `TraceAnalyser`.
## Writing Custom Steps
A step is any object satisfying `StepProtocol` — no base class needed:
```python
from ace import ACEStepContext
class MyLoggingStep:
requires = frozenset({"agent_output"})
provides = frozenset()
def __call__(self, ctx: ACEStepContext) -> ACEStepContext:
print(f"Agent answered: {ctx.agent_output.final_answer}")
return ctx
```
Key rules:
- `requires`: frozenset of context field names this step reads
- `provides`: frozenset of context field names this step writes
- `__call__`: receives and returns `ACEStepContext` (use `ctx.replace(...)` for updates)
- Steps should be stateless — no internal counters
## Mixing Integrations
You can compose steps from different integrations into one pipeline. For example,
combining a browser-use execute step with custom learning:
```python
from ace import Pipeline, learning_tail, Reflector, SkillManager, Skillbook
from ace.integrations.browser_use import BrowserExecuteStep, BrowserToTrace
skillbook = Skillbook()
pipe = Pipeline([
BrowserExecuteStep(browser_llm),
BrowserToTrace(),
MyCustomFilterStep(), # your custom step
*learning_tail(Reflector(llm), SkillManager(llm), skillbook),
])
```
Integration steps live in `ace.integrations` since they have
framework-specific dependencies.
## Running the Pipeline
### With a runner
The simplest way to run a custom pipeline is through `ACERunner`:
```python
from ace import ACERunner, Sample, Skillbook
runner = ACERunner(pipeline=pipe, skillbook=skillbook)
results = runner.run(
[Sample(question="What is 2+2?", ground_truth="4")],
epochs=1,
)
```
### Directly
You can also run the pipeline directly by constructing contexts yourself:
```python
from ace import Pipeline, ACEStepContext, SkillbookView, Sample, Skillbook
ctx = ACEStepContext(
sample=Sample(question="What is 2+2?", ground_truth="4"),
skillbook=SkillbookView(skillbook),
)
results = pipe.run([ctx])
pipe.wait_for_background() # wait for async learning steps
```
## Branching (Parallel Steps)
The pipeline engine supports parallel branches for steps that can run
concurrently:
```python
from ace import Pipeline, Branch, MergeStrategy
pipe = Pipeline([
AgentStep(agent, skillbook),
Branch(
[EvaluateStep(env_a), EvaluateStep(env_b)],
merge=MergeStrategy.LAST,
),
*learning_tail(reflector, skill_manager, skillbook),
])
```
See the [Pipeline Engine docs](../pipeline/branching.md) for full branching
and merge strategy details.
## Using RRStep (Recursive Reflector)
`RRStep` satisfies both `StepProtocol` and `ReflectorLike`, so it can be used
in two ways:
### As a drop-in reflector replacement
Pass it anywhere a `Reflector` is expected:
```python
from ace import ACELiteLLM
from ace.rr import RRStep, RRConfig
ace = ACELiteLLM.from_model("gpt-4o-mini", reflector=RRStep("gpt-4o-mini", config=RRConfig(max_requests=10)))
```
### As a pipeline step
Place it directly in a pipeline (it provides `reflections`):
```python
from ace import Pipeline, learning_tail, SkillManager, Skillbook
from ace.rr import RRStep, RRConfig
skillbook = Skillbook()
rr = RRStep("gpt-4o-mini", config=RRConfig(max_requests=15))
pipe = Pipeline([
MyExecuteStep(),
MyToTrace(),
rr, # replaces ReflectStep — provides "reflections"
*learning_tail(None, SkillManager("gpt-4o-mini"), skillbook)[1:], # skip ReflectStep
])
```
### With recursion enabled
Allow the RR to decompose large batch inputs via recursive child sessions:
```python
from ace.rr import RRStep, RRConfig
rr = RRStep(
"gpt-4o",
config=RRConfig(max_requests=40, max_depth=1), # depth=1 allows one level of recursion
)
```
## Available Steps
All steps are importable from `ace`:
| Step | Purpose |
|------|---------|
| `AgentStep` | Execute Agent role |
| `EvaluateStep` | Run TaskEnvironment evaluation |
| `ReflectStep` | Run Reflector role (async boundary) |
| `UpdateStep` | Run the agentic SkillManager; its tools mutate the skillbook directly |
| `DeduplicateStep` | Merge near-duplicate skills |
| `CheckpointStep` | Save skillbook to disk |
| `LoadTracesStep` | Load JSONL trace files |
| `ExportSkillbookMarkdownStep` | Export skillbook as markdown |
| `ObservabilityStep` | Generic observability hook |
| `PersistStep` | Persist step output |
| `OpikStep` | Log traces to Opik |
| `RRStep` | Recursive Reflector |
Integration steps (in `ace.integrations`):
| Step | Integration |
|------|-------------|
| `BrowserExecuteStep` / `BrowserToTrace` | browser-use |
| `LangChainExecuteStep` / `LangChainToTrace` | LangChain |
| `ClaudeCodeExecuteStep` / `ClaudeCodeToTrace` | Claude Code |
| `ClaudeSDKExecuteStep` / `ClaudeSDKToTrace` | Anthropic Python SDK |
| `OpenClawToTraceStep` | OpenClaw |