-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathstep_function_inheritance_demo.py
More file actions
121 lines (100 loc) · 5.31 KB
/
Copy pathstep_function_inheritance_demo.py
File metadata and controls
121 lines (100 loc) · 5.31 KB
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
#!/usr/bin/env python3
"""
Copyright (c) 2025 SignalWire
This file is part of the SignalWire SDK.
Licensed under the MIT License.
See LICENSE file in the project root for full license information.
"""
"""
Step Function Inheritance Demo
This example exists to teach one specific gotcha: the per-step `functions`
whitelist INHERITS from the previous step when omitted.
Why this matters
----------------
A common mistake when building multi-step agents is to assume each step
starts with a fresh tool set. It does not. The runtime only resets the
active set when a step explicitly declares its `functions` field. If you
forget set_functions() on a later step, the previous step's tools quietly
remain available.
This file shows three step-shaped patterns side by side:
1. step_lookup — explicitly whitelists `lookup_account`
2. step_inherit — has NO set_functions() call. Inherits step_lookup's
whitelist, so `lookup_account` is still callable here.
This is rarely what you want.
3. step_explicit — explicitly whitelists `process_payment`. The previous
inherited `lookup_account` is now disabled, and only
`process_payment` is active.
4. step_disabled — explicitly disables ALL user functions with []
(or "none"). Internal tools like next_step still work.
Best practice
-------------
Call set_functions() on EVERY step that should differ from the previous
one. Treat omission as an explicit decision to inherit, not a default.
Run this file just to see the rendered SWML — there are no real webhook
endpoints behind the tools, this is purely a documentation example.
"""
import json
from signalwire import AgentBase
class StepFunctionInheritanceAgent(AgentBase):
def __init__(self):
super().__init__(name="step_function_inheritance_demo", route="/")
# Register three SWAIG tools so we have something to whitelist.
# In a real agent these would call out to webhooks; here they're stubs.
self.define_tool(
name="lookup_account",
description="Look up customer account details by account number",
parameters={"account_number": {"type": "string"}},
handler=lambda args, raw_data: "looked up",
)
self.define_tool(
name="process_payment",
description="Process a payment for the current customer",
parameters={"amount": {"type": "number"}},
handler=lambda args, raw_data: "payment processed",
)
self.define_tool(
name="send_receipt",
description="Email a receipt to the customer",
parameters={"email": {"type": "string"}},
handler=lambda args, raw_data: "sent",
)
# Build the contexts.
contexts = self.define_contexts()
ctx = contexts.add_context("default")
# ── Step 1: explicit whitelist ─────────────────────────────────
# `lookup_account` is the only tool active in this step.
ctx.add_step("step_lookup").set_text(
"Greet the customer and ask for their account number. "
"Use lookup_account to fetch their details."
).set_functions(["lookup_account"]).set_valid_steps(["step_inherit"])
# ── Step 2: NO set_functions() call → inheritance ──────────────
# Because we didn't call set_functions(), this step inherits the
# active set from step_lookup. `lookup_account` is STILL callable
# here, even though we never asked for it. Most of the time this
# is a bug. To break the inheritance, call set_functions() with an
# explicit list (even if it's empty).
ctx.add_step("step_inherit").set_text(
"Confirm the customer's identity. (No set_functions() here, "
"so lookup_account is still active — this is the inheritance "
"trap.)"
).set_valid_steps(["step_explicit"])
# ── Step 3: explicit replacement ───────────────────────────────
# Whitelist replaces the inherited set. lookup_account is now
# inactive; only process_payment is active.
ctx.add_step("step_explicit").set_text(
"Take the customer's payment. Use process_payment. "
"lookup_account is no longer available."
).set_functions(["process_payment"]).set_valid_steps(["step_disabled"])
# ── Step 4: explicit disable-all ───────────────────────────────
# Pass [] (or "none") to lock out every user-defined tool.
# Internal navigation tools (next_step) are unaffected.
ctx.add_step("step_disabled").set_text(
"Thank the customer and wrap up. No tools are needed here, "
"so we lock everything down with set_functions([])."
).set_functions([]).set_end(True)
if __name__ == "__main__":
agent = StepFunctionInheritanceAgent()
# Render and pretty-print the resulting SWML so you can see exactly
# which steps have a `functions` key in the output and which don't.
swml = agent.render_document()
print(json.dumps(json.loads(swml), indent=2))