This commit is contained in:
ValueOn AG 2025-10-15 00:36:00 +02:00
parent da1f075556
commit a8f128c18f
4 changed files with 193 additions and 16 deletions

View file

@ -251,6 +251,10 @@ class MethodOutlook(MethodBase):
if '@' in filter_text and '.' in filter_text and ' ' not in filter_text and not filter_text.startswith('from:'): if '@' in filter_text and '.' in filter_text and ' ' not in filter_text and not filter_text.startswith('from:'):
return {"$filter": f"from/fromAddress/address eq '{filter_text}'"} return {"$filter": f"from/fromAddress/address eq '{filter_text}'"}
# Handle OData filter conditions (contains 'eq', 'ne', 'gt', 'lt', etc.)
if any(op in filter_text.lower() for op in [' eq ', ' ne ', ' gt ', ' lt ', ' ge ', ' le ', ' and ', ' or ']):
return {"$filter": filter_text}
# Handle text content - search in subject # Handle text content - search in subject
return {"$filter": f"contains(subject,'{filter_text}')"} return {"$filter": f"contains(subject,'{filter_text}')"}

View file

@ -931,7 +931,8 @@ class MethodSharepoint(MethodBase):
return ActionResult.isFailure(error="pathQuery must start with '/' and include site name with syntax /site:<Site Display Name>/... e.g. /site:KM LayerFinance/Documents/Work") return ActionResult.isFailure(error="pathQuery must start with '/' and include site name with syntax /site:<Site Display Name>/... e.g. /site:KM LayerFinance/Documents/Work")
# Check if pathQuery contains search terms (words without proper path structure) # Check if pathQuery contains search terms (words without proper path structure)
if not pathQuery.startswith('/site:') and not pathQuery.startswith('/Documents') and not pathQuery.startswith('/Shared Documents'): valid_path_prefixes = ['/site:', '/Documents', '/documents', '/Shared Documents', '/shared documents']
if not any(pathQuery.startswith(prefix) for prefix in valid_path_prefixes):
return ActionResult.isFailure(error=f"Invalid pathQuery '{pathQuery}'. This appears to be search terms, not a valid SharePoint path. Use findDocumentPath action first to search for folders, then use the returned folder path as pathQuery.") return ActionResult.isFailure(error=f"Invalid pathQuery '{pathQuery}'. This appears to be search terms, not a valid SharePoint path. Use findDocumentPath action first to search for folders, then use the returned folder path as pathQuery.")
# For pathQuery, we need to discover sites to find the specific one # For pathQuery, we need to discover sites to find the specific one
@ -1627,7 +1628,8 @@ class MethodSharepoint(MethodBase):
return ActionResult.isFailure(error="pathQuery must start with '/' and include site name with syntax /site:<Site Display Name>/... e.g. /site:KM LayerFinance/Documents/Work") return ActionResult.isFailure(error="pathQuery must start with '/' and include site name with syntax /site:<Site Display Name>/... e.g. /site:KM LayerFinance/Documents/Work")
# Check if pathQuery contains search terms (words without proper path structure) # Check if pathQuery contains search terms (words without proper path structure)
if not pathQuery.startswith('/site:') and not pathQuery.startswith('/Documents') and not pathQuery.startswith('/Shared Documents'): valid_path_prefixes = ['/site:', '/Documents', '/documents', '/Shared Documents', '/shared documents']
if not any(pathQuery.startswith(prefix) for prefix in valid_path_prefixes):
return ActionResult.isFailure(error=f"Invalid pathQuery '{pathQuery}'. This appears to be search terms, not a valid SharePoint path. Use findDocumentPath action first to search for folders, then use the returned folder path as pathQuery.") return ActionResult.isFailure(error=f"Invalid pathQuery '{pathQuery}'. This appears to be search terms, not a valid SharePoint path. Use findDocumentPath action first to search for folders, then use the returned folder path as pathQuery.")
# For pathQuery, we need to discover sites to find the specific one # For pathQuery, we need to discover sites to find the specific one

View file

@ -46,6 +46,53 @@ class ContentValidator:
"improvementSuggestions": [f"NEXT STEP: Fix validation error - {error}. Check system logs for more details and retry the operation."] "improvementSuggestions": [f"NEXT STEP: Fix validation error - {error}. Check system logs for more details and retry the operation."]
} }
def _isValidJsonResponse(self, response: str) -> bool:
"""Checks if response contains valid JSON structure"""
try:
import re
# Look for JSON with expected structure
json_match = re.search(r'\{[^{}]*"overallSuccess"[^{}]*\}', response, re.DOTALL)
if json_match:
json.loads(json_match.group(0))
return True
return False
except:
return False
def _extractFallbackValidationResult(self, response: str) -> Dict[str, Any]:
"""Extracts validation result from malformed AI response"""
try:
import re
# Extract key values using regex patterns
overall_success = re.search(r'"overallSuccess"\s*:\s*(true|false)', response, re.IGNORECASE)
quality_score = re.search(r'"qualityScore"\s*:\s*([0-9.]+)', response)
gap_analysis = re.search(r'"gapAnalysis"\s*:\s*"([^"]*)"', response)
# Determine overall success from context if not found
if not overall_success:
# Look for positive/negative indicators in the text
if any(word in response.lower() for word in ['success', 'complete', 'fulfilled', 'satisfied']):
overall_success = True
elif any(word in response.lower() for word in ['failed', 'incomplete', 'missing', 'error']):
overall_success = False
else:
overall_success = False
return {
"overallSuccess": overall_success.group(1).lower() == 'true' if overall_success else False,
"qualityScore": float(quality_score.group(1)) if quality_score else 0.5,
"validationDetails": [{
"documentName": "AI Validation (Fallback)",
"gapAnalysis": gap_analysis.group(1) if gap_analysis else "Unable to parse detailed analysis",
"successCriteriaMet": [False] # Conservative fallback
}],
"improvementSuggestions": ["NEXT STEP: AI response was malformed - retry the operation for better results"]
}
except Exception as e:
logger.error(f"Fallback extraction failed: {str(e)}")
return None
async def _validateWithAI(self, documents: List[Any], intent: Dict[str, Any]) -> Dict[str, Any]: async def _validateWithAI(self, documents: List[Any], intent: Dict[str, Any]) -> Dict[str, Any]:
"""AI-based comprehensive validation - single main function""" """AI-based comprehensive validation - single main function"""
try: try:
@ -81,7 +128,10 @@ Perform comprehensive validation:
5. Identify specific gaps and issues 5. Identify specific gaps and issues
6. Provide actionable next steps 6. Provide actionable next steps
Respond with JSON only: CRITICAL: Respond with ONLY the JSON object below. Do not include any explanatory text, analysis, or other content before or after the JSON.
IMPORTANT: Even if the content is binary files (like .docx, .pdf, etc.), you must still respond with JSON only. Do not explain that files are binary - just validate based on file names and types.
{{ {{
"overallSuccess": true/false, "overallSuccess": true/false,
"qualityScore": 0.0-1.0, "qualityScore": 0.0-1.0,
@ -110,14 +160,63 @@ Respond with JSON only:
documents=None, documents=None,
options=request_options options=request_options
) )
if response:
import re # If first attempt fails, try with more explicit prompt
result = response.strip() if response and not self._isValidJsonResponse(response):
json_match = re.search(r'\{.*\}', result, re.DOTALL) logger.warning("First AI validation attempt failed, retrying with explicit JSON-only prompt")
if json_match: explicitPrompt = f"""
result = json_match.group(0) {validationPrompt}
IMPORTANT: You must respond with ONLY valid JSON. No explanations, no analysis, no text before or after. Just the JSON object.
"""
response = await self.services.ai.callAi(
prompt=explicitPrompt,
documents=None,
options=request_options
)
if not response or not response.strip():
logger.warning("AI validation returned empty response")
return self._createFailedValidationResult("AI validation failed - empty response")
# Clean and extract JSON from response
result = response.strip()
logger.debug(f"AI validation response length: {len(result)}")
# Try to find JSON in the response with multiple strategies
import re
# Strategy 1: Look for JSON in markdown code blocks
json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', result, re.DOTALL)
if json_match:
result = json_match.group(1)
logger.debug(f"Extracted JSON from markdown code block: {result[:200]}...")
else:
# Strategy 2: Look for JSON object with proper structure
json_match = re.search(r'\{[^{}]*"overallSuccess"[^{}]*\}', result, re.DOTALL)
if not json_match:
# Strategy 3: Look for any JSON object
json_match = re.search(r'\{.*\}', result, re.DOTALL)
if not json_match:
logger.debug(f"No JSON found in AI response, trying fallback extraction: {result[:200]}...")
logger.debug(f"Full AI response: {result}")
# Try fallback extraction for text responses
fallback_result = self._extractFallbackValidationResult(result)
if fallback_result:
logger.info("Using fallback text extraction for validation")
return fallback_result
logger.warning("All AI validation attempts failed - no JSON found and fallback extraction failed")
return self._createFailedValidationResult("AI validation failed - no JSON in response")
else:
result = json_match.group(0)
logger.debug(f"Extracted JSON directly: {result[:200]}...")
try:
aiResult = json.loads(result) aiResult = json.loads(result)
logger.info("AI validation JSON parsed successfully")
return { return {
"overallSuccess": aiResult.get("overallSuccess", False), "overallSuccess": aiResult.get("overallSuccess", False),
@ -129,6 +228,18 @@ Respond with JSON only:
}]), }]),
"improvementSuggestions": aiResult.get("improvementSuggestions", []) "improvementSuggestions": aiResult.get("improvementSuggestions", [])
} }
except json.JSONDecodeError as json_error:
logger.warning(f"All AI validation attempts failed - invalid JSON: {str(json_error)}")
logger.debug(f"JSON content: {result}")
# Try to extract key information from malformed response
fallbackResult = self._extractFallbackValidationResult(result)
if fallbackResult:
logger.info("Using fallback validation result from malformed JSON")
return fallbackResult
return self._createFailedValidationResult(f"AI validation failed - invalid JSON: {str(json_error)}")
return self._createFailedValidationResult("AI validation failed - no response") return self._createFailedValidationResult("AI validation failed - no response")

View file

@ -48,7 +48,8 @@ Analyze the user's intent and determine:
3. What quality requirements they have (accuracy, completeness, format) 3. What quality requirements they have (accuracy, completeness, format)
4. What specific success criteria define completion 4. What specific success criteria define completion
Respond with JSON only: CRITICAL: Respond with ONLY the JSON object below. Do not include any explanatory text, analysis, or other content before or after the JSON.
{{ {{
"primaryGoal": "The main objective the user wants to achieve", "primaryGoal": "The main objective the user wants to achieve",
"dataType": "numbers|text|documents|analysis|code|unknown", "dataType": "numbers|text|documents|analysis|code|unknown",
@ -73,15 +74,61 @@ Respond with JSON only:
documents=None, documents=None,
options=request_options options=request_options
) )
if response:
import re # If first attempt fails, try with more explicit prompt
result = response.strip() if response and not self._isValidJsonResponse(response):
json_match = re.search(r'\{.*\}', result, re.DOTALL) logger.debug("First AI intent analysis attempt failed, retrying with explicit JSON-only prompt")
if json_match: explicitPrompt = f"""
result = json_match.group(0) {analysisPrompt}
IMPORTANT: You must respond with ONLY valid JSON. No explanations, no analysis, no text before or after. Just the JSON object.
"""
response = await self.services.ai.callAi(
prompt=explicitPrompt,
documents=None,
options=request_options
)
if not response or not response.strip():
logger.warning("AI intent analysis returned empty response")
return None
# Clean and extract JSON from response
result = response.strip()
logger.debug(f"AI intent analysis response length: {len(result)}")
# Try to find JSON in the response with multiple strategies
import re
# Strategy 1: Look for JSON in markdown code blocks
json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', result, re.DOTALL)
if json_match:
result = json_match.group(1)
logger.debug(f"Extracted JSON from markdown code block: {result[:200]}...")
else:
# Strategy 2: Look for JSON object with proper structure
json_match = re.search(r'\{[^{}]*"primaryGoal"[^{}]*\}', result, re.DOTALL)
if not json_match:
# Strategy 3: Look for any JSON object
json_match = re.search(r'\{.*\}', result, re.DOTALL)
if not json_match:
logger.warning(f"All AI intent analysis attempts failed - no JSON found in response: {result[:200]}...")
logger.debug(f"Full AI response: {result}")
return None
result = json_match.group(0)
logger.debug(f"Extracted JSON directly: {result[:200]}...")
try:
aiResult = json.loads(result) aiResult = json.loads(result)
logger.info("AI intent analysis JSON parsed successfully")
return aiResult return aiResult
except json.JSONDecodeError as json_error:
logger.warning(f"All AI intent analysis attempts failed - invalid JSON: {str(json_error)}")
logger.debug(f"JSON content: {result}")
return None
return None return None
@ -118,3 +165,16 @@ Respond with JSON only:
"successCriteria": ["Delivers what the user requested"], "successCriteria": ["Delivers what the user requested"],
"confidenceScore": 0.1 "confidenceScore": 0.1
} }
def _isValidJsonResponse(self, response: str) -> bool:
"""Checks if response contains valid JSON structure"""
try:
import re
# Look for JSON with expected structure
json_match = re.search(r'\{[^{}]*"primaryGoal"[^{}]*\}', response, re.DOTALL)
if json_match:
json.loads(json_match.group(0))
return True
return False
except:
return False