-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: introduce UI Improvements and experimental compare feature #24
Merged
+5,405
−927
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
9f2b2c7
feat: split processes
aymenfurter 734c5bb
feat: add future
aymenfurter 90d379b
feat: add graspologic
aymenfurter 46c9b7c
feat: pin versions
aymenfurter 174e978
feat: pin versions
aymenfurter cf77040
feat: bump image version
aymenfurter 0b058d7
Merge branch 'main' of https://github.com/aymenfurter/smartrag
aymenfurter de600ff
fix: missing queues
aymenfurter 2594c2e
fix: missing queues
aymenfurter c1487c0
feat: allow graphrag to be used
aymenfurter 68545c0
feat: timeline improvements
aymenfurter 5c03087
feat: UI Improvements
aymenfurter e5ef465
feat: UI Improvements
aymenfurter ac19956
feat: initial commit of compare feature
aymenfurter 4c43f92
feat: various
aymenfurter 909cccb
feat: compare
aymenfurter 3d0f51e
feat: compare cleanup
aymenfurter 6268499
feat: cleanup
aymenfurter e2d3edf
fix: instructor
aymenfurter 8df808b
feat: update image
aymenfurter a190209
feat: update smartrag
aymenfurter e4eda23
feat: move to separate package
aymenfurter 92eeee4
Merge branch 'feat/compare-feature' of https://github.com/aymenfurter…
aymenfurter 87b91f5
Fix code scanning alert no. 20: Information exposure through an excep…
aymenfurter File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import json | ||
import logging | ||
from typing import Dict, Any, AsyncGenerator | ||
from flask import Response, jsonify | ||
from .comparison_service import ComparisonService | ||
from .utils import convert_async_to_sync | ||
from .comparison_models import ComparisonRequest | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
async def handle_comparison_request(data: Dict[str, Any], user_id: str) -> AsyncGenerator[str, None]: | ||
"""Handle different phases of comparison process.""" | ||
try: | ||
service = ComparisonService() | ||
request = ComparisonRequest(**data) | ||
|
||
if request.phase == "generate": | ||
async for event in service.generate_requirements(data, user_id): | ||
yield event | ||
elif request.phase == "execute": | ||
async for event in service.execute_comparison(data, user_id): | ||
yield event | ||
else: | ||
yield json.dumps({ | ||
"type": "error", | ||
"content": f"Invalid phase specified: {request.phase}" | ||
}) + "\n" | ||
|
||
except Exception as e: | ||
logger.error(f"Error in handle_comparison_request: {str(e)}") | ||
yield json.dumps({ | ||
"type": "error", | ||
"content": f"Request processing error: {str(e)}" | ||
}) + "\n" | ||
|
||
async def compare_indexes(data: Dict[str, Any], user_id: str) -> Response: | ||
"""Entry point for comparison functionality.""" | ||
try: | ||
return Response( | ||
convert_async_to_sync(handle_comparison_request(data, user_id)), | ||
content_type='application/x-ndjson' | ||
) | ||
except Exception as e: | ||
logger.error(f"Error in compare_indexes: {str(e)}") | ||
return jsonify({ | ||
"error": "Comparison failed", | ||
"details": "An internal error has occurred. Please try again later." | ||
}), 500 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import json | ||
import logging | ||
from typing import Dict, Any, List, AsyncGenerator | ||
from openai import AzureOpenAI | ||
|
||
from app.query.graphrag_query import GraphRagQuery | ||
from app.integration.graphrag_config import GraphRagConfig | ||
from .comparison_models import ComparisonRequest, Requirement, ComparisonResult, SourceResult, CitationInfo | ||
from .response_processor import ResponseProcessor | ||
from .comparison_index_validator import validate_index_access | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
class ComparisonExecutor: | ||
def __init__(self, config: Dict[str, Any], client: AzureOpenAI, response_processor: ResponseProcessor): | ||
self.config = config | ||
self.client = client | ||
self.response_processor = response_processor | ||
|
||
async def execute(self, data: Dict[str, Any], user_id: str) -> AsyncGenerator[str, None]: | ||
try: | ||
request = ComparisonRequest(**data) | ||
if not request.requirements: | ||
raise ValueError("No requirements provided for execution") | ||
|
||
for requirement in request.requirements: | ||
yield await self._process_requirement(requirement, request, user_id) | ||
|
||
except Exception as e: | ||
logger.error(f"Error executing comparison: {str(e)}") | ||
yield json.dumps({"type": "error", "content": str(e)}) + "\n" | ||
|
||
async def _process_requirement(self, requirement: Dict[str, Any], request: ComparisonRequest, user_id: str) -> str: | ||
req_obj = Requirement(**requirement) | ||
result = ComparisonResult( | ||
requirement=req_obj, | ||
sources={} | ||
) | ||
|
||
for index_name in request.indexes: | ||
try: | ||
source_result = await self._process_index(index_name, req_obj, user_id) | ||
result.sources[index_name] = source_result | ||
|
||
except Exception as e: | ||
logger.error(f"Error querying {index_name} for requirement '{req_obj.description}': {str(e)}") | ||
result.sources[index_name] = SourceResult( | ||
response=f"Error: {str(e)}", | ||
simplified_value=None, | ||
citations=[] | ||
) | ||
|
||
return json.dumps({ | ||
"type": "comparison_result", | ||
"content": result.dict() | ||
}) + "\n" | ||
|
||
async def _process_index(self, index_name: str, requirement: Requirement, user_id: str) -> SourceResult: | ||
container_name, data_source = await validate_index_access(user_id, index_name, self.config) | ||
|
||
config = GraphRagConfig(index_name, user_id, False) | ||
graph_rag = GraphRagQuery(config) | ||
|
||
query = ( | ||
f"Regarding this requirement: {requirement.description}\n" | ||
f"What is the current status or value? Provide a clear, specific answer." | ||
) | ||
|
||
response, context = await graph_rag.global_query(query) | ||
|
||
reviewed_response, citations = await self.response_processor.process_citations( | ||
response, | ||
context, | ||
index_name, | ||
data_source | ||
) | ||
|
||
simplified_value = await self.response_processor.simplify_response( | ||
requirement.metric_type, | ||
query, | ||
response | ||
) | ||
|
||
citation_infos = [ | ||
CitationInfo( | ||
text=citation.get('text', ''), | ||
document_id=citation['file'], | ||
content=citation.get('content', ''), | ||
index_name=index_name | ||
) | ||
for citation in citations | ||
] | ||
|
||
return SourceResult( | ||
response=response, | ||
simplified_value=simplified_value, | ||
citations=citation_infos | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import logging | ||
from typing import Dict, Any, Tuple | ||
|
||
from app.integration.index_manager import create_index_manager, ContainerNameTooLongError | ||
from app.integration.azure_aisearch import create_data_source | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
async def validate_index_access(user_id: str, index_name: str, config: Dict[str, Any]) -> Tuple[str, Dict[str, Any]]: | ||
"""Validate index access and return container name and data source.""" | ||
try: | ||
index_manager = create_index_manager(user_id, index_name, False) | ||
|
||
if not index_manager.user_has_access(): | ||
raise ValueError("Unauthorized access") | ||
|
||
container_name = index_manager.get_ingestion_container() | ||
|
||
data_source = create_data_source( | ||
config['SEARCH_SERVICE_ENDPOINT'], | ||
config['SEARCH_SERVICE_API_KEY'], | ||
container_name | ||
) | ||
|
||
return container_name, data_source | ||
|
||
except ContainerNameTooLongError as e: | ||
raise ValueError(f"Container name too long: {str(e)}") | ||
except Exception as e: | ||
raise ValueError(f"Error accessing index: {str(e)}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from typing import Dict, Any, List, Optional | ||
from pydantic import BaseModel, Field | ||
|
||
class Requirement(BaseModel): | ||
description: str = Field(..., description="The detailed description of what needs to be compared") | ||
metric_type: str = Field(..., description="Type of metric: 'yes_no' or 'numeric'") | ||
metric_unit: Optional[str] = Field(None, description="Unit for numeric metrics (e.g., 'hours', '%', 'CHF')") | ||
|
||
class RequirementList(BaseModel): | ||
requirements: List[Requirement] = Field(..., description="List of requirements to compare") | ||
|
||
class CitationInfo(BaseModel): | ||
text: str = Field(..., description="The cited text") | ||
document_id: str = Field(..., description="Source document identifier") | ||
content: str = Field(..., description="Full context of the citation") | ||
index_name: str = Field(..., description="Name of the index this citation is from") | ||
|
||
class ComparisonRequest(BaseModel): | ||
phase: str = Field(..., description="Phase of comparison: 'generate', 'refine', or 'execute'") | ||
num_requirements: int = Field(default=10, description="Number of requirements to generate") | ||
role: str = Field(default="auditor", description="Role performing the comparison") | ||
comparison_subject: str = Field(default="employment conditions", description="Subject being compared") | ||
comparison_target: str = Field(default="Hospital", description="Target entity type being compared") | ||
indexes: List[str] = Field(..., min_items=2, max_items=2, description="Exactly 2 indexes to compare") | ||
is_restricted: bool = Field(default=True, description="Whether the indexes are restricted") | ||
requirements: Optional[List[Dict[str, Any]]] = Field(None, description="Requirements for refine/execute phase") | ||
feedback: Optional[str] = Field(None, description="Feedback for refinement phase") | ||
|
||
class SimplifiedResponse(BaseModel): | ||
value: Optional[str] = Field(None, description="Simplified value: 'Yes', 'No', or numeric value with unit") | ||
|
||
class SourceResult(BaseModel): | ||
response: str = Field(..., description="Detailed response from the data source") | ||
simplified_value: Optional[str] = Field(None, description="Simplified value extracted from the detailed response") | ||
citations: List[CitationInfo] = Field(default_factory=list, description="List of citations related to the response") | ||
|
||
class ComparisonResult(BaseModel): | ||
requirement: Requirement = Field(..., description="The requirement being compared") | ||
sources: Dict[str, SourceResult] = Field(..., description="Responses from each data source") |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Copilot Autofix AI 3 months ago
To fix the problem, we need to ensure that detailed exception messages are not returned to the user. Instead, we should log the detailed error message on the server and return a generic error message to the user. This can be achieved by modifying the exception handling code to log the error and return a generic message.
Specifically, we will:
_compare
method to log the detailed error message.