25
25
"""
26
26
The following code demonstrates a simple plan and execute process using the Semantic Kernel Process Framework.
27
27
It defines a multi-step workflow that leverages OpenAI's Responses API and integrated web search tools to generate,
28
- refine, and execute a plan based on a user's query.
28
+ refine, and execute a plan based on a user's query. An OpenAI api key is required to run this sample. The Azure OpenAI
29
+ Responses API does not yet support the web search tool.
29
30
30
31
The process is composed of several steps:
31
32
45
46
"""
46
47
47
48
48
- # Define a method to get a response from the OpenAI agent
49
+ #
50
+ # 1) Helper to run OpenAI agent
51
+ #
49
52
async def run_openai_agent (instructions : str , prompt : str , agent_name : str = "GenericAgent" ) -> str :
50
53
client , model = OpenAIResponsesAgent .setup_resources ()
51
54
agent_tools = [OpenAIResponsesAgent .configure_web_search_tool ()]
@@ -62,7 +65,7 @@ async def run_openai_agent(instructions: str, prompt: str, agent_name: str = "Ge
62
65
63
66
64
67
#
65
- # 1 ) Global Events
68
+ # 2 ) Global Events
66
69
#
67
70
class PlanExecuteEvents (str , Enum ):
68
71
StartProcess = "StartProcess"
@@ -75,7 +78,7 @@ class PlanExecuteEvents(str, Enum):
75
78
76
79
77
80
#
78
- # 2 ) Planner Step
81
+ # 3 ) Planner Step
79
82
#
80
83
class PlannerStepState :
81
84
times_called : int = 0
@@ -111,7 +114,7 @@ async def create_plan(self, user_request: str, context: KernelProcessStepContext
111
114
112
115
113
116
#
114
- # 3 ) Replan Step
117
+ # 4 ) Replan Step
115
118
#
116
119
class ReplanStepState :
117
120
times_called : int = 0
@@ -147,7 +150,7 @@ async def refine_plan(self, payload: dict, context: KernelProcessStepContext) ->
147
150
148
151
149
152
#
150
- # 4 ) Execute Step
153
+ # 5 ) Execute Step
151
154
#
152
155
class ExecuteStepState :
153
156
current_index : int = 0
@@ -163,19 +166,16 @@ async def activate(self, state: KernelProcessStepState[ExecuteStepState]):
163
166
@kernel_function (name = EXECUTE_PLAN )
164
167
async def execute_plan (self , payload : dict , context : KernelProcessStepContext ) -> dict :
165
168
plan = payload ["plan" ]
166
- partials = payload .get ("partials" , []) # May be empty on first run
169
+ partials = payload .get ("partials" , [])
167
170
168
171
if self .state .current_index >= len (plan ):
169
- # No more tasks
170
172
return {
171
173
"partial_result" : "All tasks done" ,
172
174
"plan" : plan ,
173
175
"current_index" : self .state .current_index ,
174
176
}
175
177
176
178
current_task = plan [self .state .current_index ]
177
-
178
- # Incorporate partial context into the prompt:
179
179
prompt = (
180
180
f"So far we have these partial results:\n \n { chr (10 ).join (partials )} \n \n Now your task is: { current_task } "
181
181
)
@@ -185,7 +185,6 @@ async def execute_plan(self, payload: dict, context: KernelProcessStepContext) -
185
185
prompt = prompt ,
186
186
agent_name = "ExecuteAgent" ,
187
187
)
188
-
189
188
partial_result = response .strip ()
190
189
191
190
executed_index = self .state .current_index
@@ -199,7 +198,7 @@ async def execute_plan(self, payload: dict, context: KernelProcessStepContext) -
199
198
200
199
201
200
#
202
- # 5 ) Decision Step
201
+ # 6 ) Decision Step
203
202
#
204
203
class DecisionStepState (KernelBaseModel ):
205
204
partials : list [str ] = Field (default_factory = list )
@@ -223,38 +222,41 @@ async def make_decision(self, data: dict, context: KernelProcessStepContext):
223
222
if partial_result and partial_result .lower () != "all tasks done" :
224
223
self .state .partials .append (partial_result )
225
224
226
- # ( A) If "All tasks done"
225
+ # A) If "All tasks done"
227
226
if partial_result .strip ().lower ().startswith ("all tasks done" ):
228
- final_text = "[FINAL] All tasks completed.\n \n === Aggregated Partial Results ===\n " + "\n \n " .join (
229
- self .state .partials
230
- )
231
- await context .emit_event (PlanExecuteEvents .PlanFinished , data = final_text )
227
+ # No more tasks
228
+ final_text = "\n " .join (self .state .partials )
229
+ payload = {
230
+ "type" : "final" ,
231
+ "content" : final_text .strip (),
232
+ }
233
+ await context .emit_event (PlanExecuteEvents .PlanFinished , data = payload )
232
234
return
233
235
234
- # ( B) If physically done all tasks
236
+ # B) If physically done all tasks
235
237
if current_index >= len (plan ):
236
- final_text = "[FINAL] No more tasks.\n \n === Aggregated Partial Results ===\n " + "\n \n " .join (
237
- self .state .partials
238
- )
239
- await context .emit_event (PlanExecuteEvents .PlanFinished , data = final_text )
238
+ final_text = "\n " .join (self .state .partials )
239
+ payload = {
240
+ "type" : "final" ,
241
+ "content" : final_text .strip (),
242
+ }
243
+ await context .emit_event (PlanExecuteEvents .PlanFinished , data = payload )
240
244
return
241
245
242
- # ( C) Let LLM decide: continue or replan
246
+ # C) Otherwise: let LLM decide whether to continue or replan
243
247
prompt = (
244
248
"We have a plan with remaining tasks.\n "
245
249
"PARTIAL RESULTS SO FAR:\n " + "\n " .join (self .state .partials ) + "\n \n "
246
250
"Decide: 'continue' with the next step or 'replan' if something is invalid. "
247
251
"DO NOT say 'finish' if tasks remain. Return only 'continue' or 'replan'."
248
252
)
249
-
250
253
response = await run_openai_agent (
251
254
instructions = "Plan orchestrator with web-search if needed. Only respond 'continue' or 'replan'." ,
252
255
prompt = prompt ,
253
256
agent_name = "DecisionAgent" ,
254
257
)
255
258
256
259
decision = response .strip ().lower ()
257
-
258
260
print (f"[DecisionStep] LLM decision: { decision } " )
259
261
self .state .last_decision = decision
260
262
@@ -276,10 +278,11 @@ async def make_decision(self, data: dict, context: KernelProcessStepContext):
276
278
277
279
278
280
#
279
- # 6 ) Output Step
281
+ # 7 ) Output Step
280
282
#
281
- class OutputStepState :
282
- last_message : str = ""
283
+ class OutputStepState (KernelBaseModel ):
284
+ debug_history : list [str ] = Field (default_factory = list )
285
+ final_answer : str = ""
283
286
284
287
285
288
class OutputStep (KernelProcessStep [OutputStepState ]):
@@ -290,13 +293,22 @@ async def activate(self, state: KernelProcessStepState[OutputStepState]):
290
293
self .state = state .state
291
294
292
295
@kernel_function (name = SHOW_MESSAGE )
293
- async def show_message (self , message : str | list [str ]):
294
- self .state .last_message = message
295
- print (f"[OutputStep] { message } " )
296
+ async def show_message (self , message : dict ):
297
+ """Handles either debug messages or final messages."""
298
+ msg_type = message .get ("type" , "debug" )
299
+ content = message .get ("content" , "" )
300
+
301
+ if msg_type == "debug" :
302
+ self .state .debug_history .append (content )
303
+ print (content )
304
+ else :
305
+ # final
306
+ self .state .final_answer = content
307
+ print ("[OutputStep] Storing final result:" , content )
296
308
297
309
298
310
#
299
- # 7 ) Build the Process
311
+ # 8 ) Build the Process
300
312
#
301
313
def build_process () -> KernelProcess :
302
314
builder = ProcessBuilder (name = "GeneralPlanAndExecute" )
@@ -311,7 +323,7 @@ def build_process() -> KernelProcess:
311
323
# 1) Start => Planner
312
324
builder .on_input_event (PlanExecuteEvents .StartProcess ).send_event_to (target = planner , parameter_name = "user_request" )
313
325
314
- # 2) Planner => Executor + Output
326
+ # 2) Planner => Executor + Output (debug)
315
327
planner .on_function_result (PlannerStep .CREATE_PLAN ).send_event_to (target = executor , parameter_name = "payload" )
316
328
317
329
planner .on_function_result (PlannerStep .CREATE_PLAN ).send_event_to (
@@ -339,7 +351,7 @@ async def main():
339
351
340
352
# Provide any user question
341
353
user_question = "Where was the quarterback of the winning team in the 2014 Super Bowl born?"
342
- print (f"Starting process with: { user_question } " )
354
+ print (f"Starting process with: ' { user_question } ' " )
343
355
344
356
process = build_process ()
345
357
async with await start_local_process (
@@ -351,38 +363,47 @@ async def main():
351
363
visibility = KernelProcessEventVisibility .Public ,
352
364
),
353
365
) as process_context :
354
- # Finally, get the Output
366
+ # Retrieve final state
355
367
process_state = await process_context .get_state ()
356
368
output_step_state : KernelProcessStepState [OutputStepState ] = next (
357
369
(s .state for s in process_state .steps if s .state .name == "OutputStep" ), None
358
370
)
359
- final_msg = output_step_state .state .last_message if output_step_state else "No final message."
360
- print (f"\n \n [Final State] => { final_msg } " )
371
+
372
+ if output_step_state :
373
+ # Final user-facing answer:
374
+ final_answer = output_step_state .state .final_answer .strip ()
375
+
376
+ print ("\n [Final State]:" )
377
+ print (final_answer )
378
+ else :
379
+ print ("[Final State]: No final message." )
361
380
362
381
"""
363
- Sample Output:
382
+ Starting process with: 'Where was the quarterback of the winning team in the 2014 Super Bowl born?'
383
+ [PlannerStep] Created plan: ['Identify the Winning Team:** Find out which team won the 2014 Super Bowl.
384
+ [NFL.com](https://www.nfl.com/)', 'Locate the Quarterback:** Determine who was the quarterback for the winning
385
+ team during that game.', 'Research Birthplace:** Search for the birthplace of the identified quarterback.
386
+ [Wikipedia](https://www.wikipedia.org/)'] (times_called=1)
364
387
365
- Starting process with: Where was the quarterback of the winning team in the 2014 Super Bowl born?
366
- [PlannerStep] Created plan: ['1. Identify the winning team and quarterback of the 2014 Super Bowl.', '2. Find the
367
- birthplace of that quarterback.'] (times_called=1)
368
- [OutputStep] {'plan': ['1. Identify the winning team and quarterback of the 2014 Super Bowl.', '2. Find the
369
- birthplace of that quarterback.']}
370
388
[DecisionStep] LLM decision: continue
371
389
[DecisionStep] LLM decision: continue
372
- [OutputStep] [FINAL] All tasks completed.
373
-
374
- === Aggregated Partial Results ===
375
- The winning team of the 2014 Super Bowl (Super Bowl XLVIII) was the Seattle Seahawks. The quarterback for the
376
- Seahawks at that time was Russell Wilson.
377
-
378
- Russell Wilson was born in Cincinnati, Ohio.
379
-
380
-
381
- [Final State] => [FINAL] All tasks completed.
382
-
383
- === Aggregated Partial Results ===
384
- The winning team of the 2014 Super Bowl (Super Bowl XLVIII) was the Seattle Seahawks. The quarterback for the
385
- Seahawks at that time was Russell Wilson.
390
+ [DecisionStep] LLM decision: continue
391
+ [OutputStep] Storing final result: The Seattle Seahawks won Super Bowl XLVIII in 2014, defeating the Denver Broncos
392
+ 43-8. The Seahawks' defense dominated the game, forcing four turnovers and limiting the Broncos' high-scoring
393
+ offense. Linebacker Malcolm Smith was named Super Bowl MVP after returning an interception 69 yards for a
394
+ touchdown. ([nfl.com](https://www.nfl.com/news/seattle-seahawks-d-dominates-manning-denver-broncos-to-win-supe-0ap2000000323056?utm_source=openai))
395
+ The quarterback for the Seattle Seahawks during Super Bowl XLVIII was Russell Wilson.
396
+ Russell Wilson, the quarterback for the Seattle Seahawks during Super Bowl XLVIII, was born on November 29, 1988,
397
+ in Cincinnati, Ohio. ([britannica.com](https://www.britannica.com/facts/Russell-Wilson?utm_source=openai))
398
+
399
+ [Final State]:
400
+ The Seattle Seahawks won Super Bowl XLVIII in 2014, defeating the Denver Broncos 43-8. The Seahawks' defense
401
+ dominated the game, forcing four turnovers and limiting the Broncos' high-scoring offense. Linebacker Malcolm
402
+ Smith was named Super Bowl MVP after returning an interception 69 yards for a touchdown.
403
+ ([nfl.com](https://www.nfl.com/news/seattle-seahawks-d-dominates-manning-denver-broncos-to-win-supe-0ap2000000323056?utm_source=openai))
404
+ The quarterback for the Seattle Seahawks during Super Bowl XLVIII was Russell Wilson.
405
+ Russell Wilson, the quarterback for the Seattle Seahawks during Super Bowl XLVIII, was born on November 29, 1988,
406
+ in Cincinnati, Ohio. ([britannica.com](https://www.britannica.com/facts/Russell-Wilson?utm_source=openai))
386
407
"""
387
408
388
409
0 commit comments