Fixes 02
This commit is contained in:
parent
da1f075556
commit
a8f128c18f
4 changed files with 193 additions and 16 deletions
|
|
@ -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}')"}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
result = response.strip()
|
|
||||||
json_match = re.search(r'\{.*\}', result, re.DOTALL)
|
|
||||||
if json_match:
|
|
||||||
result = json_match.group(0)
|
|
||||||
|
|
||||||
|
# If first attempt fails, try with more explicit prompt
|
||||||
|
if response and not self._isValidJsonResponse(response):
|
||||||
|
logger.warning("First AI validation attempt failed, retrying with explicit JSON-only prompt")
|
||||||
|
explicitPrompt = f"""
|
||||||
|
{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),
|
||||||
|
|
@ -130,6 +229,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")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -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,16 +74,62 @@ Respond with JSON only:
|
||||||
documents=None,
|
documents=None,
|
||||||
options=request_options
|
options=request_options
|
||||||
)
|
)
|
||||||
if response:
|
|
||||||
import re
|
|
||||||
result = response.strip()
|
|
||||||
json_match = re.search(r'\{.*\}', result, re.DOTALL)
|
|
||||||
if json_match:
|
|
||||||
result = json_match.group(0)
|
|
||||||
|
|
||||||
|
# If first attempt fails, try with more explicit prompt
|
||||||
|
if response and not self._isValidJsonResponse(response):
|
||||||
|
logger.debug("First AI intent analysis attempt failed, retrying with explicit JSON-only prompt")
|
||||||
|
explicitPrompt = f"""
|
||||||
|
{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
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue