17
17
import static org .opensearch .ml .engine .algorithms .agent .AgentUtils .getMcpToolSpecs ;
18
18
import static org .opensearch .ml .engine .algorithms .agent .AgentUtils .getMlToolSpecs ;
19
19
import static org .opensearch .ml .engine .algorithms .agent .MLChatAgentRunner .LLM_INTERFACE ;
20
+ import static org .opensearch .ml .engine .algorithms .agent .MLChatAgentRunner .MAX_ITERATION ;
20
21
import static org .opensearch .ml .engine .algorithms .agent .MLChatAgentRunner .saveTraceData ;
21
22
import static org .opensearch .ml .engine .algorithms .agent .PromptTemplate .DEFAULT_PLANNER_PROMPT ;
22
23
import static org .opensearch .ml .engine .algorithms .agent .PromptTemplate .DEFAULT_PLANNER_PROMPT_TEMPLATE ;
@@ -90,16 +91,19 @@ public class MLPlanExecuteAndReflectAgentRunner implements MLAgentRunner {
90
91
private String plannerWithHistoryPromptTemplate ;
91
92
92
93
// defaults
93
- private static final String DEFAULT_SYSTEM_PROMPT = "Always respond in JSON format." ;
94
- private static final String DEFAULT_REACT_SYSTEM_PROMPT = "You are a helpful assistant." ;
94
+ private static final String DEFAULT_PLANNER_SYSTEM_PROMPT =
95
+ "You are part of an OpenSearch cluster. When you deliver your final result, include a comprehensive report. This report MUST:\\ n1. List every analysis or step you performed.\\ n2. Summarize the inputs, methods, tools, and data used at each step.\\ n3. Include key findings from all intermediate steps — do NOT omit them.\\ n4. Clearly explain how the steps led to your final conclusion.\\ n5. Return the full analysis and conclusion in the 'result' field, even if some of this was mentioned earlier.\\ n\\ nThe final response should be fully self-contained and detailed, allowing a user to understand the full investigation without needing to reference prior messages. Always respond in JSON format." ;
96
+ private static final String DEFAULT_EXECUTOR_SYSTEM_PROMPT =
97
+ "You are a dedicated helper agent working as part of a plan‑execute‑reflect framework. Your role is to receive a discrete task, execute all necessary internal reasoning or tool calls, and return a single, final response that fully addresses the task. You must never return an empty response. If you are unable to complete the task or retrieve meaningful information, you must respond with a clear explanation of the issue or what was missing. Under no circumstances should you end your reply with a question or ask for more information. If you search any index, always include the raw documents in the final result instead of summarizing the content. This is critical to give visibility into what the query retrieved." ;
95
98
private static final String DEFAULT_NO_ESCAPE_PARAMS = "tool_configs,_tools" ;
96
99
private static final String DEFAULT_MAX_STEPS_EXECUTED = "20" ;
97
100
private static final int DEFAULT_MESSAGE_HISTORY_LIMIT = 10 ;
101
+ private static final String DEFAULT_REACT_MAX_ITERATIONS = "20" ;
98
102
99
103
// fields
100
104
public static final String PROMPT_FIELD = "prompt" ;
101
105
public static final String USER_PROMPT_FIELD = "user_prompt" ;
102
- public static final String REACT_SYSTEM_PROMPT_FIELD = "react_system_prompt " ;
106
+ public static final String EXECUTOR_SYSTEM_PROMPT_FIELD = "executor_system_prompt " ;
103
107
public static final String STEPS_FIELD = "steps" ;
104
108
public static final String COMPLETED_STEPS_FIELD = "completed_steps" ;
105
109
public static final String PLANNER_PROMPT_FIELD = "planner_prompt" ;
@@ -109,18 +113,21 @@ public class MLPlanExecuteAndReflectAgentRunner implements MLAgentRunner {
109
113
public static final String SYSTEM_PROMPT_FIELD = "system_prompt" ;
110
114
public static final String QUESTION_FIELD = "question" ;
111
115
public static final String MEMORY_ID_FIELD = "memory_id" ;
116
+ public static final String PARENT_INTERACTION_ID_FIELD = "parent_interaction_id" ;
112
117
public static final String TENANT_ID_FIELD = "tenant_id" ;
113
118
public static final String RESULT_FIELD = "result" ;
114
119
public static final String RESPONSE_FIELD = "response" ;
115
120
public static final String STEP_RESULT_FIELD = "step_result" ;
116
- public static final String REACT_AGENT_ID_FIELD = "reAct_agent_id" ;
117
- public static final String REACT_AGENT_MEMORY_ID_FIELD = "reAct_agent_memory_id" ;
121
+ public static final String EXECUTOR_AGENT_ID_FIELD = "executor_agent_id" ;
122
+ public static final String EXECUTOR_AGENT_MEMORY_ID_FIELD = "executor_agent_memory_id" ;
123
+ public static final String EXECUTOR_AGENT_PARENT_INTERACTION_ID_FIELD = "executor_agent_parent_interaction_id" ;
118
124
public static final String NO_ESCAPE_PARAMS_FIELD = "no_escape_params" ;
119
125
public static final String DEFAULT_PROMPT_TOOLS_FIELD = "tools_prompt" ;
120
126
public static final String MAX_STEPS_EXECUTED_FIELD = "max_steps" ;
121
127
public static final String PLANNER_PROMPT_TEMPLATE_FIELD = "planner_prompt_template" ;
122
128
public static final String REFLECT_PROMPT_TEMPLATE_FIELD = "reflect_prompt_template" ;
123
129
public static final String PLANNER_WITH_HISTORY_TEMPLATE_FIELD = "planner_with_history_template" ;
130
+ public static final String EXECUTOR_MAX_ITERATIONS_FIELD = "executor_max_iterations" ;
124
131
125
132
public MLPlanExecuteAndReflectAgentRunner (
126
133
Client client ,
@@ -154,9 +161,7 @@ private void setupPromptParameters(Map<String, String> params) {
154
161
155
162
String userPrompt = params .get (QUESTION_FIELD );
156
163
params .put (USER_PROMPT_FIELD , userPrompt );
157
-
158
- String userSystemPrompt = params .getOrDefault (SYSTEM_PROMPT_FIELD , "" );
159
- params .put (SYSTEM_PROMPT_FIELD , userSystemPrompt + DEFAULT_SYSTEM_PROMPT );
164
+ params .put (SYSTEM_PROMPT_FIELD , params .getOrDefault (SYSTEM_PROMPT_FIELD , DEFAULT_PLANNER_SYSTEM_PROMPT ));
160
165
161
166
if (params .get (PLANNER_PROMPT_FIELD ) != null ) {
162
167
this .plannerPrompt = params .get (PLANNER_PROMPT_FIELD );
@@ -329,6 +334,8 @@ private void executePlanningLoop(
329
334
parentInteractionId ,
330
335
finalResult ,
331
336
completedSteps .get (completedSteps .size () - 2 ),
337
+ allParams .get (EXECUTOR_AGENT_MEMORY_ID_FIELD ),
338
+ allParams .get (EXECUTOR_AGENT_PARENT_INTERACTION_ID_FIELD ),
332
339
finalListener
333
340
);
334
341
return ;
@@ -353,22 +360,31 @@ private void executePlanningLoop(
353
360
354
361
if (parseLLMOutput .get (RESULT_FIELD ) != null ) {
355
362
String finalResult = parseLLMOutput .get (RESULT_FIELD );
356
- saveAndReturnFinalResult ((ConversationIndexMemory ) memory , parentInteractionId , finalResult , null , finalListener );
363
+ saveAndReturnFinalResult (
364
+ (ConversationIndexMemory ) memory ,
365
+ parentInteractionId ,
366
+ allParams .get (EXECUTOR_AGENT_MEMORY_ID_FIELD ),
367
+ allParams .get (EXECUTOR_AGENT_PARENT_INTERACTION_ID_FIELD ),
368
+ finalResult ,
369
+ null ,
370
+ finalListener
371
+ );
357
372
} else {
358
373
// todo: optimize double conversion of steps (string to list to string)
359
374
List <String > steps = Arrays .stream (parseLLMOutput .get (STEPS_FIELD ).split (", " )).toList ();
360
375
addSteps (steps , allParams , STEPS_FIELD );
361
376
362
377
String stepToExecute = steps .getFirst ();
363
- String reActAgentId = allParams .get (REACT_AGENT_ID_FIELD );
378
+ String reActAgentId = allParams .get (EXECUTOR_AGENT_ID_FIELD );
364
379
Map <String , String > reactParams = new HashMap <>();
365
380
reactParams .put (QUESTION_FIELD , stepToExecute );
366
- if (allParams .containsKey (REACT_AGENT_MEMORY_ID_FIELD )) {
367
- reactParams .put (MEMORY_ID_FIELD , allParams .get (REACT_AGENT_MEMORY_ID_FIELD ));
381
+ if (allParams .containsKey (EXECUTOR_AGENT_MEMORY_ID_FIELD )) {
382
+ reactParams .put (MEMORY_ID_FIELD , allParams .get (EXECUTOR_AGENT_MEMORY_ID_FIELD ));
368
383
}
369
384
370
- reactParams .put (SYSTEM_PROMPT_FIELD , allParams .getOrDefault (REACT_SYSTEM_PROMPT_FIELD , DEFAULT_REACT_SYSTEM_PROMPT ));
385
+ reactParams .put (SYSTEM_PROMPT_FIELD , allParams .getOrDefault (EXECUTOR_SYSTEM_PROMPT_FIELD , DEFAULT_EXECUTOR_SYSTEM_PROMPT ));
371
386
reactParams .put (LLM_RESPONSE_FILTER , allParams .get (LLM_RESPONSE_FILTER ));
387
+ reactParams .put (MAX_ITERATION , allParams .getOrDefault (EXECUTOR_MAX_ITERATIONS_FIELD , DEFAULT_REACT_MAX_ITERATIONS ));
372
388
373
389
AgentMLInput agentInput = AgentMLInput
374
390
.AgentMLInputBuilder ()
@@ -387,13 +403,18 @@ private void executePlanningLoop(
387
403
388
404
// Process tensors in a single stream
389
405
reactResult .getMlModelOutputs ().stream ().flatMap (output -> output .getMlModelTensors ().stream ()).forEach (tensor -> {
390
- if (MEMORY_ID_FIELD .equals (tensor .getName ())) {
391
- results .put (MEMORY_ID_FIELD , tensor .getResult ());
392
- } else {
393
- Map <String , ?> dataMap = tensor .getDataAsMap ();
394
- if (dataMap != null && dataMap .containsKey (RESPONSE_FIELD )) {
395
- results .put (STEP_RESULT_FIELD , (String ) dataMap .get (RESPONSE_FIELD ));
396
- }
406
+ switch (tensor .getName ()) {
407
+ case MEMORY_ID_FIELD :
408
+ results .put (MEMORY_ID_FIELD , tensor .getResult ());
409
+ break ;
410
+ case PARENT_INTERACTION_ID_FIELD :
411
+ results .put (PARENT_INTERACTION_ID_FIELD , tensor .getResult ());
412
+ break ;
413
+ default :
414
+ Map <String , ?> dataMap = tensor .getDataAsMap ();
415
+ if (dataMap != null && dataMap .containsKey (RESPONSE_FIELD )) {
416
+ results .put (STEP_RESULT_FIELD , (String ) dataMap .get (RESPONSE_FIELD ));
417
+ }
397
418
}
398
419
});
399
420
@@ -404,11 +425,16 @@ private void executePlanningLoop(
404
425
// Only add memory_id to params if it exists and is not empty
405
426
String reActMemoryId = results .get (MEMORY_ID_FIELD );
406
427
if (reActMemoryId != null && !reActMemoryId .isEmpty ()) {
407
- allParams .put (REACT_AGENT_MEMORY_ID_FIELD , reActMemoryId );
428
+ allParams .put (EXECUTOR_AGENT_MEMORY_ID_FIELD , reActMemoryId );
429
+ }
430
+
431
+ String reActParentInteractionId = results .get (PARENT_INTERACTION_ID_FIELD );
432
+ if (reActParentInteractionId != null && !reActParentInteractionId .isEmpty ()) {
433
+ allParams .put (EXECUTOR_AGENT_PARENT_INTERACTION_ID_FIELD , reActParentInteractionId );
408
434
}
409
435
410
- completedSteps .add (stepToExecute );
411
- completedSteps .add (results .get (STEP_RESULT_FIELD ));
436
+ completedSteps .add (String . format ( " \n Step: %s \n " , stepToExecute ) );
437
+ completedSteps .add (String . format ( " \n Step Result: %s \n " , results .get (STEP_RESULT_FIELD ) ));
412
438
413
439
saveTraceData (
414
440
(ConversationIndexMemory ) memory ,
@@ -524,6 +550,8 @@ private void addSteps(List<String> steps, Map<String, String> allParams, String
524
550
private void saveAndReturnFinalResult (
525
551
ConversationIndexMemory memory ,
526
552
String parentInteractionId ,
553
+ String reactAgentMemoryId ,
554
+ String reactParentInteractionId ,
527
555
String finalResult ,
528
556
String input ,
529
557
ActionListener <Object > finalListener
@@ -536,7 +564,12 @@ private void saveAndReturnFinalResult(
536
564
}
537
565
538
566
memory .getMemoryManager ().updateInteraction (parentInteractionId , updateContent , ActionListener .wrap (res -> {
539
- List <ModelTensors > finalModelTensors = createModelTensors (memory .getConversationId (), parentInteractionId );
567
+ List <ModelTensors > finalModelTensors = createModelTensors (
568
+ memory .getConversationId (),
569
+ parentInteractionId ,
570
+ reactAgentMemoryId ,
571
+ reactParentInteractionId
572
+ );
540
573
finalModelTensors
541
574
.add (
542
575
ModelTensors
@@ -553,7 +586,12 @@ private void saveAndReturnFinalResult(
553
586
}));
554
587
}
555
588
556
- private static List <ModelTensors > createModelTensors (String sessionId , String parentInteractionId ) {
589
+ private static List <ModelTensors > createModelTensors (
590
+ String sessionId ,
591
+ String parentInteractionId ,
592
+ String reactAgentMemoryId ,
593
+ String reactParentInteractionId
594
+ ) {
557
595
List <ModelTensors > modelTensors = new ArrayList <>();
558
596
modelTensors
559
597
.add (
@@ -563,7 +601,13 @@ private static List<ModelTensors> createModelTensors(String sessionId, String pa
563
601
List
564
602
.of (
565
603
ModelTensor .builder ().name (MLAgentExecutor .MEMORY_ID ).result (sessionId ).build (),
566
- ModelTensor .builder ().name (MLAgentExecutor .PARENT_INTERACTION_ID ).result (parentInteractionId ).build ()
604
+ ModelTensor .builder ().name (MLAgentExecutor .PARENT_INTERACTION_ID ).result (parentInteractionId ).build (),
605
+ ModelTensor .builder ().name (EXECUTOR_AGENT_MEMORY_ID_FIELD ).result (reactAgentMemoryId ).build (),
606
+ ModelTensor
607
+ .builder ()
608
+ .name (EXECUTOR_AGENT_PARENT_INTERACTION_ID_FIELD )
609
+ .result (reactParentInteractionId )
610
+ .build ()
567
611
)
568
612
)
569
613
.build ()
0 commit comments