Skip to content

Commit 14f831f

Browse files
author
AgentPatterns
committed
feat(examples/python): add fallback-recovery-agent runnable example
1 parent 9883e63 commit 14f831f

8 files changed

Lines changed: 857 additions & 0 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Fallback-Recovery Agent (Python)
2+
3+
Production-style example for the Fallback-Recovery pattern:
4+
5+
- detect and classify tool failures
6+
- retry retriable failures within budget
7+
- switch to fallback tools when primary path is unstable
8+
- keep progress via checkpointed step results
9+
- finish with explicit `stop_reason`, `trace`, and `history`
10+
11+
## Run
12+
13+
```bash
14+
python3 -m venv .venv
15+
source .venv/bin/activate
16+
pip install -r requirements.txt
17+
18+
export OPENAI_API_KEY="sk-..."
19+
# optional:
20+
# export OPENAI_MODEL="gpt-4.1-mini"
21+
# export OPENAI_TIMEOUT_SECONDS="60"
22+
23+
python main.py
24+
```
25+
26+
## Files
27+
28+
- `main.py` - run flow and final output
29+
- `gateway.py` - recovery policy, retries, fallbacks, budgets
30+
- `tools.py` - deterministic primary/fallback tool stubs
31+
- `checkpoint_store.py` - in-memory step checkpointing
32+
- `llm.py` - final brief synthesis
33+
- `context.py` - request and policy hints
34+
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from __future__ import annotations
2+
3+
import time
4+
from dataclasses import dataclass
5+
from typing import Any
6+
7+
8+
@dataclass
9+
class CheckpointRow:
10+
run_id: str
11+
step_id: str
12+
source: str
13+
tool: str
14+
result: dict[str, Any]
15+
saved_at: float
16+
ttl_seconds: float | None = None
17+
18+
19+
class CheckpointStore:
20+
def __init__(self) -> None:
21+
self._rows: dict[tuple[str, str], CheckpointRow] = {}
22+
23+
def get_step(self, *, run_id: str, step_id: str, now: float | None = None) -> CheckpointRow | None:
24+
row = self._rows.get((run_id, step_id))
25+
if row is None:
26+
return None
27+
if row.ttl_seconds is None:
28+
return row
29+
now_ts = time.time() if now is None else float(now)
30+
if (now_ts - row.saved_at) > float(row.ttl_seconds):
31+
# Expired checkpoint: treat as missing.
32+
self._rows.pop((run_id, step_id), None)
33+
return None
34+
return row
35+
36+
def save_step(
37+
self,
38+
*,
39+
run_id: str,
40+
step_id: str,
41+
source: str,
42+
tool: str,
43+
result: dict[str, Any],
44+
) -> None:
45+
self._rows[(run_id, step_id)] = CheckpointRow(
46+
run_id=run_id,
47+
step_id=step_id,
48+
source=source,
49+
tool=tool,
50+
result=result,
51+
saved_at=time.time(),
52+
ttl_seconds=None,
53+
)
54+
55+
def save_step_with_ttl(
56+
self,
57+
*,
58+
run_id: str,
59+
step_id: str,
60+
source: str,
61+
tool: str,
62+
result: dict[str, Any],
63+
ttl_seconds: float,
64+
) -> None:
65+
self._rows[(run_id, step_id)] = CheckpointRow(
66+
run_id=run_id,
67+
step_id=step_id,
68+
source=source,
69+
tool=tool,
70+
result=result,
71+
saved_at=time.time(),
72+
ttl_seconds=float(ttl_seconds),
73+
)
74+
75+
def dump_run(self, *, run_id: str) -> list[dict[str, Any]]:
76+
out: list[dict[str, Any]] = []
77+
for row in self._rows.values():
78+
if row.run_id != run_id:
79+
continue
80+
out.append(
81+
{
82+
"step_id": row.step_id,
83+
"source": row.source,
84+
"tool": row.tool,
85+
"result_keys": sorted(row.result.keys()),
86+
"saved_at": row.saved_at,
87+
}
88+
)
89+
out.sort(key=lambda item: item["saved_at"])
90+
return out
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from __future__ import annotations
2+
3+
from typing import Any
4+
5+
6+
def build_operations_context(*, report_date: str, region: str) -> dict[str, Any]:
7+
return {
8+
"goal": "Prepare a customer-safe operations update for the payments incident.",
9+
"request": {
10+
"report_date": report_date,
11+
"region": region.upper(),
12+
},
13+
"policy_hints": {
14+
"max_retries": 1,
15+
"max_fallbacks": 2,
16+
"allow_cached_fallback": True,
17+
"avoid_absolute_guarantees": True,
18+
},
19+
}
20+

0 commit comments

Comments
 (0)