418 lines
20 KiB
Python
418 lines
20 KiB
Python
"""
|
||
Placeholder-based prompt factory for dynamic AI calls.
|
||
This module provides prompt templates with placeholders that can be filled dynamically.
|
||
"""
|
||
|
||
import json
|
||
from typing import Dict, Any
|
||
from modules.workflows.processing.promptFactory import (
|
||
_getAvailableDocuments,
|
||
_getPreviousRoundContext,
|
||
getMethodsList,
|
||
getEnhancedDocumentContext,
|
||
_getConnectionReferenceList,
|
||
methods
|
||
)
|
||
|
||
|
||
def createTaskPlanningPromptTemplate() -> str:
|
||
"""Create task planning prompt template with placeholders."""
|
||
return """You are a task planning AI that analyzes user requests and creates structured, self-contained task plans with user-friendly feedback messages.
|
||
|
||
USER REQUEST: {{KEY:USER_PROMPT}}
|
||
|
||
AVAILABLE DOCUMENTS: {{KEY:AVAILABLE_DOCUMENTS}}
|
||
|
||
PREVIOUS WORKFLOW ROUNDS CONTEXT:
|
||
{{KEY:WORKFLOW_HISTORY}}
|
||
|
||
INSTRUCTIONS:
|
||
1. Analyze the user request, available documents, and previous workflow rounds context
|
||
2. If the user request appears to be a follow-up (like "try again", "versuche es nochmals", "retry", etc.),
|
||
use the PREVIOUS WORKFLOW ROUNDS CONTEXT to understand what the user wants to retry or continue
|
||
3. Group related topics and sequential steps into single, comprehensive tasks
|
||
4. Focus on business outcomes, not technical operations
|
||
5. Make each task self-contained: clearly state what to do and what outputs are expected
|
||
6. Ensure proper handover between tasks (later actions will use your task outputs)
|
||
7. Detect the language of the user request and include it in languageUserDetected
|
||
8. Generate user-friendly messages for each task in the user's request language
|
||
9. Return a JSON object with the exact structure shown below
|
||
|
||
TASK GROUPING PRINCIPLES:
|
||
- COMBINE RELATED TOPICS: Group related subjects, sequential steps, or workflow-structured activities into single tasks
|
||
- SEQUENTIAL WORKFLOWS: If the user says "first do this, then that, then that" → create ONE task that handles the entire sequence
|
||
- SIMILAR CONTENT: If multiple items deal with the same subject matter → combine into ONE comprehensive task
|
||
- ONLY SPLIT WHEN DIFFERENT: Create separate tasks ONLY when the user explicitly wants different, independent things
|
||
|
||
EXAMPLES OF GOOD TASK GROUPING:
|
||
|
||
COMBINE INTO ONE TASK:
|
||
- "Analyze the documents, extract key insights, and create a summary report" → ONE task: "Analyze documents and create comprehensive summary report"
|
||
- "First check my emails, then respond to urgent ones, then organize my inbox" → ONE task: "Process and organize email inbox with priority responses"
|
||
- "Review the budget, analyze spending patterns, and suggest cost-cutting measures" → ONE task: "Comprehensive budget analysis with optimization recommendations"
|
||
- "Create a business strategy, develop marketing plan, and prepare presentation" → ONE task: "Develop complete business strategy with marketing plan and presentation"
|
||
|
||
SPLIT INTO MULTIPLE TASKS:
|
||
- "Create a business strategy for Q4" AND "Check my emails for messages from my assistant" → TWO separate tasks (different subjects)
|
||
- "Analyze customer feedback" AND "Prepare quarterly financial report" → TWO separate tasks (different business areas)
|
||
- "Review project timeline" AND "Update employee handbook" → TWO separate tasks (unrelated activities)
|
||
|
||
TASK PLANNING PRINCIPLES:
|
||
- Break down complex requests into logical, sequential steps
|
||
- Focus on business value and outcomes
|
||
- Keep tasks at a meaningful level of abstraction (not implementation details)
|
||
- Each task should produce results that can be used by subsequent tasks
|
||
- Ensure clear dependencies and handovers between tasks
|
||
- Provide clear, actionable user messages in the user's request language
|
||
- Group related activities to minimize task fragmentation
|
||
- Only create multiple tasks when dealing with truly different, independent objectives
|
||
- Make task objectives action-oriented and specific (include scope, data sources to consider, and output intent at high level)
|
||
- Write success_criteria as measurable acceptance criteria focusing on outputs (what artifacts or insights will exist and how they are validated)
|
||
|
||
FOLLOW-UP PROMPT HANDLING:
|
||
- If the user request is a follow-up (e.g., "try again", "versuche es nochmals", "retry", "continue", "proceed"),
|
||
analyze the PREVIOUS WORKFLOW ROUNDS CONTEXT to understand what failed or was incomplete
|
||
- Use the previous round's user requests and task outcomes to determine what the user wants to retry
|
||
- If previous rounds failed due to missing documents, and documents are now available,
|
||
create tasks that use the newly available documents to accomplish the original request
|
||
- Maintain the same business objective from previous rounds but adapt to current available resources
|
||
|
||
SPECIFIC SCENARIO HANDLING:
|
||
- If previous round failed with "documents missing" error and current round has documents available,
|
||
the user likely wants to retry the same operation with the newly provided documents
|
||
- Example: Previous round "speichere mir die 3 dokumente im sharepoint unter xxx" failed due to missing documents,
|
||
current round "versuche es nochmals" with documents should retry the SharePoint save operation
|
||
- Always check if the current request is a retry by looking for retry keywords and previous round context
|
||
|
||
REQUIRED JSON STRUCTURE:
|
||
{{
|
||
"overview": "Brief description of the overall plan",
|
||
"languageUserDetected": "en", // Language code detected from user request (en, de, fr, it, es, etc.)
|
||
"userMessage": "User-friendly message explaining the task plan in user's request language",
|
||
"tasks": [
|
||
{{
|
||
"id": "task_1",
|
||
"objective": "Clear business objective this task accomplishes (combining related activities)",
|
||
"dependencies": ["task_0"], // IDs of tasks that must complete first
|
||
"success_criteria": ["criteria1", "criteria2"],
|
||
"estimated_complexity": "low|medium|high",
|
||
"userMessage": "User-friendly message explaining what this task will accomplish in user's request language"
|
||
}}
|
||
]
|
||
}}
|
||
|
||
EXAMPLES OF GOOD TASK OBJECTIVES (COMBINING RELATED ACTIVITIES):
|
||
- "Analyze documents and extract key insights for business communication"
|
||
- "Create professional business communication incorporating analyzed information"
|
||
- "Execute business communication using specified channels and document outcomes"
|
||
- "Develop comprehensive business strategy with implementation roadmap and success metrics"
|
||
|
||
EXAMPLES OF WELL-FORMED SUCCESS CRITERIA (OUTPUT-FOCUSED):
|
||
- "Deliver a prioritized list of 10–20 candidates with justification"
|
||
- "Provide a structured JSON with fields: company, ticker, rationale, metrics"
|
||
- "Produce a presentation outline with 5 sections and bullet points per section"
|
||
- "Include data sources and date stamped references for traceability"
|
||
|
||
EXAMPLES OF GOOD SUCCESS CRITERIA:
|
||
- "Key insights extracted and ready for business use"
|
||
- "Professional communication created with clear business value"
|
||
- "Business communication successfully delivered and documented"
|
||
- "All outcomes properly documented and accessible"
|
||
|
||
EXAMPLES OF BAD TASK OBJECTIVES:
|
||
- "Read the PDF file" (too granular - should be "Analyze document content")
|
||
- "Convert data to CSV" (implementation detail - should be "Structure data for analysis")
|
||
- "Send email" (too specific - should be "Deliver business communication")
|
||
|
||
LANGUAGE DETECTION:
|
||
- Analyze the user request text to identify the language
|
||
- Use standard language codes: en (English), de (German), fr (French), it (Italian), es (Spanish), etc.
|
||
- If the language cannot be determined, use "en" as default
|
||
- Include the detected language in the languageUserDetected field
|
||
|
||
NOTE: Respond with ONLY the JSON object. Do not include any explanatory text."""
|
||
|
||
|
||
def createActionDefinitionPromptTemplate() -> str:
|
||
"""Create action definition prompt template with placeholders."""
|
||
return """You are an action planning AI that generates specific, executable actions for task steps.
|
||
|
||
TASK OBJECTIVE: {{KEY:USER_PROMPT}}
|
||
|
||
AVAILABLE DOCUMENTS: {{KEY:AVAILABLE_DOCUMENTS}}
|
||
|
||
WORKFLOW HISTORY: {{KEY:WORKFLOW_HISTORY}}
|
||
|
||
AVAILABLE METHODS: {{KEY:AVAILABLE_METHODS}}
|
||
|
||
USER LANGUAGE: {{KEY:USER_LANGUAGE}}
|
||
|
||
INSTRUCTIONS:
|
||
- Generate actions to accomplish this task step using available documents, connections, and previous results
|
||
- Use docItem for single documents and docList for groups of documents as shown in AVAILABLE DOCUMENTS
|
||
- If there are no documents available, do not create document extraction actions. Select methods strictly based on the task objective; choose web actions when external information is required. Otherwise, generate a status/information report requesting needed inputs.
|
||
- Always pass documentList as a LIST of references (docItem and/or docList) - this list CANNOT be empty for document extraction actions
|
||
- For referencing documents from previous actions, use the format "round{current_round}_task{current_task}_action{action_number}_{descriptive_label}"
|
||
- Each action must be self-contained and executable with the provided parameters
|
||
- For document extraction, ensure prompts are specific and detailed
|
||
- Include validation steps in extraction prompts where relevant
|
||
- If this is a retry, learn from previous failures and improve the approach
|
||
- Address specific issues mentioned in previous review feedback
|
||
- When specifying expectedDocumentFormats, ensure AI prompts explicitly request pure data without markdown formatting
|
||
- Generate user-friendly messages for each action in the user's language
|
||
|
||
PARAMETER COMPLETENESS REQUIREMENTS:
|
||
- Every parameter must contain all information needed to execute without implicit context
|
||
- Use explicit, concrete values (units, languages, formats, limits, date ranges, IDs) when applicable
|
||
- For search-like parameters (if any method requires a query), derive the query from the task objective AND ALL success criteria dimensions. Include:
|
||
- Key entities and domain terms from the objective
|
||
- All distinct facets from success_criteria (e.g., valuation AND AI potential AND know-how needs)
|
||
- Geography/localization (e.g., Schweiz/Suisse/Switzerland; use multilingual synonyms when helpful)
|
||
- Time horizon or recency if relevant
|
||
- Boolean operators and synonyms to increase precision (use AND/OR, quotes, parentheses)
|
||
- Avoid single-topic or generic queries focused only on one facet (e.g., pure valuation metrics)
|
||
- When facets are truly distinct, create 1–3 focused actions with precise queries rather than one vague catch-all
|
||
- Document list parameters must reference only existing labels or prior action outputs; do not reference future outputs
|
||
|
||
DOCUMENT ROUTING GUIDANCE:
|
||
- Each action should produce documents with a clear resultLabel for routing
|
||
- Use consistent naming: "round{current_round}_task{current_task}_action{action_number}_{descriptive_label}"
|
||
- Ensure document flow: Action A produces documents that Action B can consume
|
||
- Document labels should be descriptive of content, not just "results" or "output"
|
||
- Consider what subsequent actions will need and structure outputs accordingly
|
||
|
||
REQUIRED JSON STRUCTURE:
|
||
{{
|
||
"actions": [
|
||
{{
|
||
"method": "method_name",
|
||
"action": "action_name",
|
||
"parameters": {{}},
|
||
"resultLabel": "round{current_round}_task{current_task}_action{action_number}_{descriptive_label}",
|
||
"description": "Brief description of what this action accomplishes",
|
||
"userMessage": "User-friendly message explaining what this action will do in user's language"
|
||
}}
|
||
]
|
||
}}
|
||
|
||
IMPORTANT NOTES:
|
||
- Respond with ONLY the JSON object. Do not include any explanatory text.
|
||
- Before creating any document extraction action, verify that AVAILABLE DOCUMENTS contains actual document references.
|
||
- Always include a user-friendly userMessage for each action in the user's language.
|
||
- The examples above show German user messages as reference - adapt the language to match the USER LANGUAGE specified above."""
|
||
|
||
|
||
def createActionSelectionPromptTemplate() -> str:
|
||
"""Create action selection prompt template with placeholders."""
|
||
return """Select exactly one action to advance the task.
|
||
|
||
OBJECTIVE: {{KEY:USER_PROMPT}}
|
||
AVAILABLE DOCUMENTS: {{KEY:AVAILABLE_DOCUMENTS}}
|
||
USER LANGUAGE: {{KEY:USER_LANGUAGE}}
|
||
|
||
MINIMAL TOOL CATALOG (method -> action -> [parameterNames]):
|
||
{{KEY:AVAILABLE_METHODS}}
|
||
|
||
BUSINESS RULES:
|
||
- Pick exactly one action per step.
|
||
- Derive choice from objective and success criteria.
|
||
- Prefer user language.
|
||
- Keep it minimal; avoid provider specifics.
|
||
|
||
RESPONSE FORMAT (JSON only):
|
||
{{"action":{{"method":"web","name":"search"}}}}"""
|
||
|
||
|
||
def createActionParameterPromptTemplate() -> str:
|
||
"""Create action parameter prompt template with placeholders."""
|
||
return """Provide only the required parameters for this action.
|
||
|
||
SELECTED ACTION: {{KEY:SELECTED_ACTION}}
|
||
ACTION SIGNATURE: {{KEY:ACTION_SIGNATURE}}
|
||
OBJECTIVE: {{KEY:USER_PROMPT}}
|
||
AVAILABLE DOCUMENTS: {{KEY:AVAILABLE_DOCUMENTS}}
|
||
USER LANGUAGE: {{KEY:USER_LANGUAGE}}
|
||
|
||
RULES:
|
||
- Return only the parameters object.
|
||
- Include user language if relevant.
|
||
- Reference documents only by exact labels available.
|
||
- Avoid unnecessary fields; host applies defaults.
|
||
- Use the ACTION SIGNATURE above to understand what parameters are required.
|
||
- Convert the objective into appropriate parameter values as needed.
|
||
|
||
RESPONSE FORMAT (JSON only):
|
||
{{"parameters":{{}}}}"""
|
||
|
||
|
||
def createRefinementPromptTemplate() -> str:
|
||
"""Create refinement prompt template with placeholders."""
|
||
return """Decide next step based on observation.
|
||
|
||
OBJECTIVE: {{KEY:USER_PROMPT}}
|
||
OBSERVATION:
|
||
{{KEY:REVIEW_CONTENT}}
|
||
|
||
RULES:
|
||
- If criteria are met or no further action helps, decide stop.
|
||
- Else decide continue.
|
||
|
||
RESPONSE FORMAT (JSON only):
|
||
{{"decision":"continue","reason":"Need more data"}}"""
|
||
|
||
|
||
def createResultReviewPromptTemplate() -> str:
|
||
"""Create result review prompt template with placeholders."""
|
||
return """You are a result validation AI that reviews task execution outcomes and determines success, retry needs, or failure.
|
||
|
||
TASK OBJECTIVE: {{KEY:USER_PROMPT}}
|
||
|
||
EXECUTION RESULTS:
|
||
{{KEY:REVIEW_CONTENT}}
|
||
|
||
VALIDATION CRITERIA:
|
||
- Review each action's success/failure status
|
||
- Check if required documents were produced
|
||
- Validate document quality and completeness
|
||
- Assess if success criteria were met
|
||
- Identify any missing or incomplete outputs
|
||
- Determine if retry would help or if task should be marked as failed
|
||
|
||
REQUIRED JSON STRUCTURE:
|
||
{{
|
||
"status": "success|retry|failed",
|
||
"reason": "Detailed explanation of the validation decision",
|
||
"improvements": ["specific improvement 1", "specific improvement 2"],
|
||
"quality_score": 8, // 1-10 scale
|
||
"met_criteria": ["criteria1", "criteria2"],
|
||
"unmet_criteria": ["criteria3", "criteria4"],
|
||
"confidence": 0.85, // 0.0-1.0 scale
|
||
"userMessage": "User-friendly message explaining the validation result"
|
||
}}
|
||
|
||
VALIDATION PRINCIPLES:
|
||
- Be thorough but fair in assessment
|
||
- Focus on business value and outcomes
|
||
- Consider both technical execution and business results
|
||
- Provide specific, actionable improvement suggestions
|
||
- Use quality scores to track progress across retries
|
||
- Clearly identify which success criteria were met vs. unmet
|
||
- Set appropriate confidence levels based on evidence quality
|
||
|
||
NOTE: Respond with ONLY the JSON object. Do not include any explanatory text."""
|
||
|
||
|
||
# Helper functions to extract content for placeholders
|
||
|
||
def extractUserPrompt(context) -> str:
|
||
"""Extract user prompt from context."""
|
||
if hasattr(context, 'task_step') and context.task_step:
|
||
return context.task_step.objective or 'No request specified'
|
||
return 'No request specified'
|
||
|
||
|
||
def extractAvailableDocuments(context) -> str:
|
||
"""Extract available documents from context."""
|
||
if hasattr(context, 'workflow') and context.workflow:
|
||
return _getAvailableDocuments(context.workflow)
|
||
return "No documents available"
|
||
|
||
|
||
def extractWorkflowHistory(service, context) -> str:
|
||
"""Extract workflow history from context."""
|
||
if hasattr(context, 'workflow') and context.workflow:
|
||
return _getPreviousRoundContext(service, context.workflow) or "No previous workflow rounds - this is the first round."
|
||
return "No previous workflow rounds - this is the first round."
|
||
|
||
|
||
def extractAvailableMethods(service) -> str:
|
||
"""Extract available methods for action planning."""
|
||
methodList = getMethodsList(service)
|
||
method_actions = {}
|
||
for sig in methodList:
|
||
if '.' in sig:
|
||
method, rest = sig.split('.', 1)
|
||
action = rest.split('(')[0]
|
||
method_actions.setdefault(method, []).append((action, sig))
|
||
|
||
# Create a structured JSON format for better AI parsing
|
||
available_methods_json = {}
|
||
for method, actions in method_actions.items():
|
||
available_methods_json[method] = {}
|
||
# Get the method instance for accessing docstrings
|
||
method_instance = methods.get(method, {}).get('instance') if methods else None
|
||
|
||
for action, sig in actions:
|
||
# Parse the signature to extract parameters
|
||
if '(' in sig and ')' in sig:
|
||
# Extract parameters from signature
|
||
params_start = sig.find('(')
|
||
params_end = sig.find(')')
|
||
params_str = sig[params_start+1:params_end]
|
||
|
||
# Parse parameters directly from the docstring - much simpler and more reliable!
|
||
parameters = []
|
||
|
||
# Get the actual function's docstring
|
||
if method_instance and hasattr(method_instance, action):
|
||
func = getattr(method_instance, action)
|
||
if hasattr(func, '__doc__') and func.__doc__:
|
||
docstring = func.__doc__
|
||
|
||
# Parse Parameters section from docstring
|
||
lines = docstring.split('\n')
|
||
in_parameters = False
|
||
for i, line in enumerate(lines):
|
||
original_line = line
|
||
line = line.strip()
|
||
|
||
if line.startswith('Parameters:'):
|
||
in_parameters = True
|
||
continue
|
||
elif line.startswith('Returns:') or line.startswith('Raises:') or line.startswith('Note:') or line.startswith('Example:') or line.startswith('Examples:'):
|
||
in_parameters = False
|
||
continue
|
||
elif in_parameters and line and not line.startswith('-') and not line.startswith('*'):
|
||
# This is a parameter line
|
||
if ':' in line:
|
||
param_name = line.split(':')[0].strip()
|
||
param_desc = line.split(':', 1)[1].strip()
|
||
parameters.append(f"{param_name}: {param_desc}")
|
||
|
||
available_methods_json[method][action] = parameters
|
||
else:
|
||
available_methods_json[method][action] = []
|
||
|
||
return json.dumps(available_methods_json, indent=2, ensure_ascii=False)
|
||
|
||
|
||
def extractUserLanguage(service) -> str:
|
||
"""Extract user language from service."""
|
||
return service.user.language if service and service.user else 'en'
|
||
|
||
|
||
def extractReviewContent(context) -> str:
|
||
"""Extract review content from context."""
|
||
if hasattr(context, 'action_results') and context.action_results:
|
||
# Build result summary
|
||
result_summary = ""
|
||
for i, result in enumerate(context.action_results):
|
||
result_summary += f"\nRESULT {i+1}:\n"
|
||
result_summary += f" Success: {result.success}\n"
|
||
if result.error:
|
||
result_summary += f" Error: {result.error}\n"
|
||
|
||
if result.documents:
|
||
result_summary += f" Documents: {len(result.documents)} document(s)\n"
|
||
for doc in result.documents:
|
||
doc_name = getattr(doc, 'documentName', 'Unknown')
|
||
doc_mime = getattr(doc, 'mimeType', 'Unknown')
|
||
result_summary += f" - {doc_name} ({doc_mime})\n"
|
||
else:
|
||
result_summary += f" Documents: None\n"
|
||
|
||
return result_summary
|
||
elif hasattr(context, 'observation') and context.observation:
|
||
return json.dumps(context.observation, ensure_ascii=False)
|
||
else:
|
||
return "No review content available"
|