""" React Mode Prompt Generation Handles prompt templates for react mode action handling. """ from typing import Any, List from modules.datamodels.datamodelChat import PromptBundle, PromptPlaceholder from modules.workflows.processing.shared.placeholderFactory import ( extractUserPrompt, extractUserLanguage, extractAvailableMethods, extractAvailableDocumentsSummary, extractAvailableDocumentsIndex, extractAvailableConnectionsIndex, extractPreviousActionResults, extractLearningsAndImprovements, extractLatestRefinementFeedback, extractWorkflowHistory, ) from modules.workflows.processing.shared.methodDiscovery import methods, getActionParameterList def generateReactPlanSelectionPrompt(services, context: Any) -> PromptBundle: """Define placeholders first, then the template; return PromptBundle.""" placeholders: List[PromptPlaceholder] = [ PromptPlaceholder(label="USER_PROMPT", content=extractUserPrompt(context), summaryAllowed=False), PromptPlaceholder(label="AVAILABLE_DOCUMENTS_SUMMARY", content=extractAvailableDocumentsSummary(services, context), summaryAllowed=True), PromptPlaceholder(label="AVAILABLE_METHODS", content=extractAvailableMethods(services), summaryAllowed=False), # Provide enriched history context for Stage 1 to craft parametersContext PromptPlaceholder(label="WORKFLOW_HISTORY", content=extractWorkflowHistory(services, context), summaryAllowed=True), # Provide deterministic indexes so the planner can choose exact labels PromptPlaceholder(label="AVAILABLE_DOCUMENTS_INDEX", content=extractAvailableDocumentsIndex(services, context), summaryAllowed=True), PromptPlaceholder(label="AVAILABLE_CONNECTIONS_INDEX", content=extractAvailableConnectionsIndex(services), summaryAllowed=False), ] template = """Select exactly one action to advance the task. OBJECTIVE: {{KEY:USER_PROMPT}} AVAILABLE_DOCUMENTS_SUMMARY: {{KEY:AVAILABLE_DOCUMENTS_SUMMARY}} AVAILABLE_METHODS: {{KEY:AVAILABLE_METHODS}} WORKFLOW_HISTORY (reverse-chronological, enriched): {{KEY:WORKFLOW_HISTORY}} AVAILABLE_DOCUMENTS_INDEX: {{KEY:AVAILABLE_DOCUMENTS_INDEX}} AVAILABLE_CONNECTIONS_INDEX: {{KEY:AVAILABLE_CONNECTIONS_INDEX}} REPLY: Return ONLY a JSON object with the following structure (no comments, no extra text): {{ "action": "method.action_name", "actionObjective": "...", "learnings": ["..."], "requiredInputDocuments": ["docList:..."], "requiredConnection": "connection:..." | null, "parametersContext": "concise text that Stage 2 will use to set business parameters" }} EXAMPLE how to assign references from AVAILABLE_DOCUMENTS_INDEX and AVAILABLE_CONNECTIONS_INDEX: "requiredInputDocuments": ["docList:msg_47a7a578-e8f2-4ba8-ac66-0dbff40605e0:round8_task1_action1_results","docItem:5d8b7aee-b546-4487-b6a8-835c86f7b186:AI_Generated_Document_20251006-104256.docx"], "requiredConnection": "connection:msft:p.motsch@valueon.ch:1ae8b8e5-128b-49b8-b1cb-7c632669eeae", RULES: 1. Use EXACT action names from AVAILABLE_METHODS 2. Do NOT output a "parameters" object 3. parametersContext must be short and sufficient for Stage 2 4. Return ONLY JSON - no markdown, no explanations 5. For requiredInputDocuments, use ONLY exact references from AVAILABLE_DOCUMENTS_INDEX (docList:... or docItem:...) 6. For requiredConnection, use ONLY an exact label from AVAILABLE_CONNECTIONS_INDEX """ return PromptBundle(prompt=template, placeholders=placeholders) def generateReactParametersPrompt(services, context: Any, compoundActionName: str) -> PromptBundle: """Define placeholders first, then the template; return PromptBundle. Minimal Stage 2 (no fallback): consumes actionObjective, selectedAction, parametersContext only. Excludes documents/connections/history entirely. """ # derive method/action and parameter list methodName, actionName = (compoundActionName.split('.', 1) if '.' in compoundActionName else (compoundActionName, '')) actionParameterList = getActionParameterList(methodName, actionName, methods) def _formatBusinessParameters(params) -> str: excluded = {"documentList", "connectionReference"} # Case 1: params is a list of dicts or objects with 'name' if isinstance(params, (list, tuple)): entries = [] for p in params: try: if isinstance(p, dict): name = p.get("name") if not name or name in excluded: continue ptype = p.get("type") or p.get("dataType") or "" req = p.get("required") reqTxt = "required" if (req is True or str(req).lower() == "true") else "optional" desc = p.get("description") or p.get("desc") or "" entry = f"- {name} ({ptype}, {reqTxt})" + (f": {desc}" if desc else "") entries.append(entry) else: # Try attribute access name = getattr(p, "name", None) if not name or name in excluded: continue ptype = getattr(p, "type", "") or getattr(p, "dataType", "") req = getattr(p, "required", False) reqTxt = "required" if (req is True or str(req).lower() == "true") else "optional" desc = getattr(p, "description", None) or getattr(p, "desc", None) or "" entry = f"- {name} ({ptype}, {reqTxt})" + (f": {desc}" if desc else "") entries.append(entry) except Exception: continue return "\n".join(entries) # Case 2: params is a string description: filter out lines mentioning excluded names if isinstance(params, str): lines = [ln for ln in params.splitlines() if not any(ex in ln for ex in excluded)] return "\n".join(lines).strip() # Fallback: plain string try: return str(params) except Exception: return "" actionParametersText = _formatBusinessParameters(actionParameterList) # determine action objective if available, else fall back to user prompt if hasattr(context, 'action_objective') and context.action_objective: actionObjective = context.action_objective elif hasattr(context, 'task_step') and context.task_step and getattr(context.task_step, 'objective', None): actionObjective = context.task_step.objective else: actionObjective = extractUserPrompt(context) # Minimal Stage 2 (no fallback) parametersContext = getattr(context, 'parameters_context', None) learningsText = "" try: # If Stage 1 learnings were attached to context, pass them textually if hasattr(context, 'learnings') and context.learnings: if isinstance(context.learnings, (list, tuple)): learningsText = "\n".join(f"- {str(x)}" for x in context.learnings) else: learningsText = str(context.learnings) except Exception: learningsText = "" placeholders: List[PromptPlaceholder] = [ PromptPlaceholder(label="ACTION_OBJECTIVE", content=actionObjective, summaryAllowed=False), PromptPlaceholder(label="SELECTED_ACTION", content=compoundActionName, summaryAllowed=False), PromptPlaceholder(label="PARAMETERS_CONTEXT", content=(parametersContext or ""), summaryAllowed=True), PromptPlaceholder(label="ACTION_PARAMETERS", content=actionParametersText, summaryAllowed=False), PromptPlaceholder(label="LEARNINGS", content=learningsText, summaryAllowed=True), ] template = """You are a parameter generator. Set the parameters for this specific action. CONTEXT AND OBJECTIVE: ----------------- {{KEY:ACTION_OBJECTIVE}} ----------------- SELECTED_ACTION: {{KEY:SELECTED_ACTION}} REPLY (ONLY JSON): {{ "schema": "parameters_v1", "parameters": {{ "paramName": "value" }} }} CONTEXT FOR PARAMETER VALUES: ----------------- {{KEY:PARAMETERS_CONTEXT}} ----------------- LEARNINGS (from prior attempts, if any): {{KEY:LEARNINGS}} REQUIRED PARAMETERS FOR THIS ACTION (use these exact parameter names): {{KEY:ACTION_PARAMETERS}} INSTRUCTIONS: - Use ONLY the parameter names listed in section REQUIRED PARAMETERS FOR THIS ACTION - Fill in appropriate values based on the context and objective - Do NOT invent new parameters - Do NOT include: documentList, connectionReference, history, documents, connections RULES: - Return ONLY JSON (no markdown, no prose) - Use ONLY the exact parameter names listed in REQUIRED PARAMETERS FOR THIS ACTION - Do NOT add any parameters not listed above - Do NOT add nested objects or custom fields """ return PromptBundle(prompt=template, placeholders=placeholders) def generateReactRefinementPrompt(services, context: Any, reviewContent: str) -> PromptBundle: """Define placeholders first, then the template; return PromptBundle.""" placeholders: List[PromptPlaceholder] = [ PromptPlaceholder(label="USER_PROMPT", content=extractUserPrompt(context), summaryAllowed=False), PromptPlaceholder(label="REVIEW_CONTENT", content=reviewContent, summaryAllowed=True), ] template = """Decide the next step based on the observation. OBJECTIVE: {{KEY:USER_PROMPT}} OBSERVATION: {{KEY:REVIEW_CONTENT}} REPLY: Return only a JSON object with your decision: {{ "decision": "continue|stop", "reason": "brief explanation" }} RULES: 1. Use "continue" if objective NOT fulfilled 2. Use "stop" if objective fulfilled 3. Return ONLY JSON - no other text 4. Do NOT use markdown code blocks 5. Do NOT add explanations """ return PromptBundle(prompt=template, placeholders=placeholders)