Spaces:
Sleeping
Sleeping
File size: 8,578 Bytes
116524e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | # 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 |
|