1111
1212
1313class ConfirmationStrategy (ABC ):
14- """Strategy for generating confirmation messages during human-in-the-loop flows."""
14+ """Strategy for generating confirmation messages during human-in-the-loop flows.
1515
16+ Subclasses must define the message properties. The methods use those properties
17+ by default, but can be overridden for complete customization.
18+ """
19+
20+ @property
21+ @abstractmethod
22+ def approval_header (self ) -> str :
23+ """Header for approval accepted message. Must be overridden."""
24+ ...
25+
26+ @property
27+ @abstractmethod
28+ def approval_footer (self ) -> str :
29+ """Footer for approval accepted message. Must be overridden."""
30+ ...
31+
32+ @property
33+ @abstractmethod
34+ def rejection_message (self ) -> str :
35+ """Message when user rejects. Must be overridden."""
36+ ...
37+
38+ @property
1639 @abstractmethod
40+ def state_confirmed_message (self ) -> str :
41+ """Message when state is confirmed. Must be overridden."""
42+ ...
43+
44+ @property
45+ @abstractmethod
46+ def state_rejected_message (self ) -> str :
47+ """Message when state is rejected. Must be overridden."""
48+ ...
49+
1750 def on_approval_accepted (self , steps : list [dict [str , Any ]]) -> str :
1851 """Generate message when user approves function execution.
1952
53+ Default implementation uses header/footer properties.
54+ Override for complete customization.
55+
2056 Args:
2157 steps: List of approved steps with 'description', 'status', etc.
2258
2359 Returns:
2460 Message to display to user
2561 """
26- ...
62+ enabled_steps = [s for s in steps if s .get ("status" ) == "enabled" ]
63+ message_parts = [self .approval_header .format (count = len (enabled_steps ))]
64+ for i , step in enumerate (enabled_steps , 1 ):
65+ message_parts .append (f"{ i } . { step ['description' ]} \n " )
66+ message_parts .append (self .approval_footer )
67+ return "" .join (message_parts )
2768
28- @abstractmethod
2969 def on_approval_rejected (self , steps : list [dict [str , Any ]]) -> str :
3070 """Generate message when user rejects function execution.
3171
@@ -35,141 +75,143 @@ def on_approval_rejected(self, steps: list[dict[str, Any]]) -> str:
3575 Returns:
3676 Message to display to user
3777 """
38- ...
78+ return self . rejection_message
3979
40- @abstractmethod
4180 def on_state_confirmed (self ) -> str :
4281 """Generate message when user confirms predictive state changes.
4382
4483 Returns:
4584 Message to display to user
4685 """
47- ...
86+ return self . state_confirmed_message
4887
49- @abstractmethod
5088 def on_state_rejected (self ) -> str :
5189 """Generate message when user rejects predictive state changes.
5290
5391 Returns:
5492 Message to display to user
5593 """
56- ...
94+ return self . state_rejected_message
5795
5896
5997class DefaultConfirmationStrategy (ConfirmationStrategy ):
60- """Generic confirmation messages suitable for most agents.
61-
62- This preserves the original behavior from v1.
63- """
98+ """Generic confirmation messages suitable for most agents."""
6499
65- def on_approval_accepted (self , steps : list [dict [str , Any ]]) -> str :
66- """Generate generic approval message with step list."""
67- enabled_steps = [s for s in steps if s .get ("status" ) == "enabled" ]
68-
69- message_parts = [f"Executing { len (enabled_steps )} approved steps:\n \n " ]
70-
71- for i , step in enumerate (enabled_steps , 1 ):
72- message_parts .append (f"{ i } . { step ['description' ]} \n " )
73-
74- message_parts .append ("\n All steps completed successfully!" )
100+ @property
101+ def approval_header (self ) -> str :
102+ return "Executing {count} approved steps:\n \n "
75103
76- return "" .join (message_parts )
104+ @property
105+ def approval_footer (self ) -> str :
106+ return "\n All steps completed successfully!"
77107
78- def on_approval_rejected ( self , steps : list [ dict [ str , Any ]]) -> str :
79- """Generate generic rejection message."""
108+ @ property
109+ def rejection_message ( self ) -> str :
80110 return "No problem! What would you like me to change about the plan?"
81111
82- def on_state_confirmed ( self ) -> str :
83- """Generate generic state confirmation message."""
112+ @ property
113+ def state_confirmed_message ( self ) -> str :
84114 return "Changes confirmed and applied successfully!"
85115
86- def on_state_rejected ( self ) -> str :
87- """Generate generic state rejection message."""
116+ @ property
117+ def state_rejected_message ( self ) -> str :
88118 return "No problem! What would you like me to change?"
89119
90120
91121class TaskPlannerConfirmationStrategy (ConfirmationStrategy ):
92122 """Domain-specific confirmation messages for task planning agents."""
93123
94- def on_approval_accepted (self , steps : list [dict [str , Any ]]) -> str :
95- """Generate task-specific approval message."""
96- enabled_steps = [s for s in steps if s .get ("status" ) == "enabled" ]
97-
98- message_parts = ["Executing your requested tasks:\n \n " ]
99-
100- for i , step in enumerate (enabled_steps , 1 ):
101- message_parts .append (f"{ i } . { step ['description' ]} \n " )
124+ @property
125+ def approval_header (self ) -> str :
126+ return "Executing your requested tasks:\n \n "
102127
103- message_parts .append ("\n All tasks completed successfully!" )
128+ @property
129+ def approval_footer (self ) -> str :
130+ return "\n All tasks completed successfully!"
104131
105- return "" .join (message_parts )
106-
107- def on_approval_rejected (self , steps : list [dict [str , Any ]]) -> str :
108- """Generate task-specific rejection message."""
132+ @property
133+ def rejection_message (self ) -> str :
109134 return "No problem! Let me revise the plan. What would you like me to change?"
110135
111- def on_state_confirmed ( self ) -> str :
112- """Task planners typically don't use state confirmation."""
136+ @ property
137+ def state_confirmed_message ( self ) -> str :
113138 return "Tasks confirmed and ready to execute!"
114139
115- def on_state_rejected ( self ) -> str :
116- """Task planners typically don't use state confirmation."""
140+ @ property
141+ def state_rejected_message ( self ) -> str :
117142 return "No problem! How should I adjust the task list?"
118143
119144
120145class RecipeConfirmationStrategy (ConfirmationStrategy ):
121146 """Domain-specific confirmation messages for recipe agents."""
122147
123- def on_approval_accepted (self , steps : list [dict [str , Any ]]) -> str :
124- """Generate recipe-specific approval message."""
125- enabled_steps = [s for s in steps if s .get ("status" ) == "enabled" ]
126-
127- message_parts = ["Updating your recipe:\n \n " ]
148+ @property
149+ def approval_header (self ) -> str :
150+ return "Updating your recipe:\n \n "
128151
129- for i , step in enumerate (enabled_steps , 1 ):
130- message_parts .append (f"{ i } . { step ['description' ]} \n " )
131-
132- message_parts .append ("\n Recipe updated successfully!" )
133-
134- return "" .join (message_parts )
152+ @property
153+ def approval_footer (self ) -> str :
154+ return "\n Recipe updated successfully!"
135155
136- def on_approval_rejected ( self , steps : list [ dict [ str , Any ]]) -> str :
137- """Generate recipe-specific rejection message."""
156+ @ property
157+ def rejection_message ( self ) -> str :
138158 return "No problem! What ingredients or steps should I change?"
139159
140- def on_state_confirmed ( self ) -> str :
141- """Generate recipe-specific state confirmation message."""
160+ @ property
161+ def state_confirmed_message ( self ) -> str :
142162 return "Recipe changes applied successfully!"
143163
144- def on_state_rejected ( self ) -> str :
145- """Generate recipe-specific state rejection message."""
164+ @ property
165+ def state_rejected_message ( self ) -> str :
146166 return "No problem! What would you like me to adjust in the recipe?"
147167
148168
149169class DocumentWriterConfirmationStrategy (ConfirmationStrategy ):
150170 """Domain-specific confirmation messages for document writing agents."""
151171
152- def on_approval_accepted (self , steps : list [dict [str , Any ]]) -> str :
153- """Generate document-specific approval message."""
154- enabled_steps = [s for s in steps if s .get ("status" ) == "enabled" ]
155-
156- message_parts = ["Applying your edits:\n \n " ]
157-
158- for i , step in enumerate (enabled_steps , 1 ):
159- message_parts .append (f"{ i } . { step ['description' ]} \n " )
160-
161- message_parts .append ("\n Document updated successfully!" )
172+ @property
173+ def approval_header (self ) -> str :
174+ return "Applying your edits:\n \n "
162175
163- return "" .join (message_parts )
176+ @property
177+ def approval_footer (self ) -> str :
178+ return "\n Document updated successfully!"
164179
165- def on_approval_rejected ( self , steps : list [ dict [ str , Any ]]) -> str :
166- """Generate document-specific rejection message."""
180+ @ property
181+ def rejection_message ( self ) -> str :
167182 return "No problem! Which changes should I keep or modify?"
168183
169- def on_state_confirmed ( self ) -> str :
170- """Generate document-specific state confirmation message."""
184+ @ property
185+ def state_confirmed_message ( self ) -> str :
171186 return "Document edits applied!"
172187
173- def on_state_rejected ( self ) -> str :
174- """Generate document-specific state rejection message."""
188+ @ property
189+ def state_rejected_message ( self ) -> str :
175190 return "No problem! What should I change about the document?"
191+
192+
193+ def apply_confirmation_strategy (
194+ strategy : ConfirmationStrategy | None ,
195+ accepted : bool ,
196+ steps : list [dict [str , Any ]],
197+ ) -> str :
198+ """Apply a confirmation strategy to generate a message.
199+
200+ This helper consolidates the pattern used in multiple orchestrators.
201+
202+ Args:
203+ strategy: Strategy to use, or None for default
204+ accepted: Whether the user approved
205+ steps: List of steps (may be empty for state confirmations)
206+
207+ Returns:
208+ Generated message string
209+ """
210+ if strategy is None :
211+ strategy = DefaultConfirmationStrategy ()
212+
213+ if not steps :
214+ # State confirmation (no steps)
215+ return strategy .on_state_confirmed () if accepted else strategy .on_state_rejected ()
216+ # Step-based approval
217+ return strategy .on_approval_accepted (steps ) if accepted else strategy .on_approval_rejected (steps )
0 commit comments