229 lines
9.5 KiB
Python
229 lines
9.5 KiB
Python
"""
|
|
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}}
|
|
|
|
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 above
|
|
- Fill in appropriate values based on the context and objective
|
|
- Do NOT invent new parameters
|
|
- Do NOT include: documentList, connectionReference, history, documents, connections
|
|
|
|
REPLY (ONLY JSON):
|
|
{{
|
|
"schema": "parameters_v1",
|
|
"parameters": {{
|
|
"paramName": "value"
|
|
}}
|
|
}}
|
|
|
|
RULES:
|
|
- Return ONLY JSON (no markdown, no prose)
|
|
- Use only the parameters listed in REQUIRED PARAMETERS FOR THIS ACTION
|
|
"""
|
|
|
|
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)
|