Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/slackBotFunction/app/services/ai_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"""

from app.services.bedrock import query_bedrock
from app.services.query_reformulator import reformulate_query

# from app.services.query_reformulator import reformulate_query
from app.core.config import get_logger
from app.core.types import AIProcessorResponse

Expand All @@ -16,10 +17,10 @@
def process_ai_query(user_query: str, session_id: str | None = None) -> AIProcessorResponse:
"""shared AI processing logic for both slack and direct invocation"""
# reformulate: improves vector search quality in knowledge base
reformulated_query = reformulate_query(user_query)
# reformulated_query = reformulate_query(user_query)

# session_id enables conversation continuity across multiple queries
kb_response = query_bedrock(reformulated_query, session_id)
kb_response = query_bedrock(user_query, session_id)

logger.info(
"response from bedrock",
Expand Down
2 changes: 1 addition & 1 deletion packages/slackBotFunction/app/services/bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def query_bedrock(user_query: str, session_id: str = None) -> RetrieveAndGenerat
"knowledgeBaseId": config.KNOWLEDGEBASE_ID,
"modelArn": prompt_template.get("model_id", config.RAG_MODEL_ID),
"retrievalConfiguration": {
"vectorSearchConfiguration": {"numberOfResults": 5, "overrideSearchType": "SEMANTIC"}
"vectorSearchConfiguration": {"numberOfResults": 3, "overrideSearchType": "SEMANTIC"}
},
"generationConfiguration": {
"guardrailConfiguration": {
Expand Down
24 changes: 16 additions & 8 deletions packages/slackBotFunction/app/slack/slack_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,22 @@ def command_handler(body: Dict[str, Any], command: Dict[str, Any], client: WebCl
session_pull_request_id = extract_test_command_params(command.get("text")).get("pr")
if session_pull_request_id:
logger.info(f"Command in pull request session {session_pull_request_id} from user {user_id}")
forward_to_pull_request_lambda(
body=body,
event={**command, "channel": command.get("channel_id")},
pull_request_id=session_pull_request_id,
event_id=f"/command-{time.time()}",
store_pull_request_id=False,
type="command",
)
try:
forward_to_pull_request_lambda(
body=body,
event={**command, "channel": command.get("channel_id")},
pull_request_id=session_pull_request_id,
event_id=f"/command-{time.time()}",
store_pull_request_id=False,
type="command",
)
except Exception as e:
logger.error(f"Failed to forward to PR lambda: {e}", extra={"error": traceback.format_exc()})
client.chat_postEphemeral(
channel=command.get("channel_id"),
user=user_id,
text=f"Failed to forward to PR {session_pull_request_id}. Does the stack exist?",
)
return

try:
Expand Down
2 changes: 1 addition & 1 deletion packages/slackBotFunction/app/utils/handler_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def extract_test_command_params(text: str) -> Dict[str, str]:
pr_pattern = rf"{prefix}\s*(\d+)\b"
q_pattern = r"\bq-?(\d+)(?:-(\d+))?"

pr_match = re.match(pr_pattern, text, flags=re.IGNORECASE)
pr_match = re.search(pr_pattern, text, flags=re.IGNORECASE)
if pr_match:
params["pr"] = pr_match.group(1)

Expand Down
65 changes: 31 additions & 34 deletions packages/slackBotFunction/tests/test_ai_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
class TestAIProcessor:

@patch("app.services.ai_processor.query_bedrock")
@patch("app.services.ai_processor.reformulate_query")
def test_process_ai_query_without_session(self, mock_reformulate, mock_bedrock):
def test_process_ai_query_without_session(self, mock_bedrock):
"""new conversation: no session context passed to bedrock"""
mock_reformulate.return_value = "reformulated: How to authenticate EPS API?"
# mock_reformulate.return_value = "reformulated: How to authenticate EPS API?"
mock_bedrock.return_value = {
"output": {"text": "To authenticate with EPS API, you need..."},
"sessionId": "new-session-abc123",
Expand All @@ -26,14 +25,13 @@ def test_process_ai_query_without_session(self, mock_reformulate, mock_bedrock):
assert result["citations"][0]["title"] == "EPS Authentication Guide"
assert "kb_response" in result

mock_reformulate.assert_called_once_with("How to authenticate EPS API?")
mock_bedrock.assert_called_once_with("reformulated: How to authenticate EPS API?", None)
# mock_reformulate.assert_called_once_with("How to authenticate EPS API?")
mock_bedrock.assert_called_once_with("How to authenticate EPS API?", None)

@patch("app.services.ai_processor.query_bedrock")
@patch("app.services.ai_processor.reformulate_query")
def test_process_ai_query_with_session(self, mock_reformulate, mock_bedrock):
def test_process_ai_query_with_session(self, mock_bedrock):
"""conversation continuity: existing session maintained across queries"""
mock_reformulate.return_value = "reformulated: What about rate limits?"
# mock_reformulate.return_value = "reformulated: What about rate limits?"
mock_bedrock.return_value = {
"output": {"text": "EPS API has rate limits of..."},
"sessionId": "existing-session-456",
Expand All @@ -47,39 +45,39 @@ def test_process_ai_query_with_session(self, mock_reformulate, mock_bedrock):
assert result["citations"] == []
assert "kb_response" in result

mock_reformulate.assert_called_once_with("What about rate limits?")
mock_bedrock.assert_called_once_with("reformulated: What about rate limits?", "existing-session-456")
# mock_reformulate.assert_called_once_with("What about rate limits?")
mock_bedrock.assert_called_once_with("What about rate limits?", "existing-session-456")

@patch("app.services.ai_processor.query_bedrock")
@patch("app.services.ai_processor.reformulate_query")
def test_process_ai_query_reformulate_error(self, mock_reformulate, mock_bedrock):
def test_process_ai_query_reformulate_error(self, mock_bedrock):
"""graceful degradation: reformulation failure bubbles up"""
mock_reformulate.side_effect = Exception("Query reformulation failed")
# Test disabled as reformulation is currently bypassed
pass
# mock_reformulate.side_effect = Exception("Query reformulation failed")

with pytest.raises(Exception) as exc_info:
process_ai_query("How to authenticate EPS API?")
# with pytest.raises(Exception) as exc_info:
# process_ai_query("How to authenticate EPS API?")

assert "Query reformulation failed" in str(exc_info.value)
mock_bedrock.assert_not_called()
# assert "Query reformulation failed" in str(exc_info.value)
# mock_bedrock.assert_not_called()

@patch("app.services.ai_processor.query_bedrock")
@patch("app.services.ai_processor.reformulate_query")
def test_process_ai_query_bedrock_error(self, mock_reformulate, mock_bedrock):
def test_process_ai_query_bedrock_error(self, mock_bedrock):
"""bedrock service failure: error propagated to caller"""
mock_reformulate.return_value = "reformulated query"
# mock_reformulate.return_value = "reformulated query"
mock_bedrock.side_effect = Exception("Bedrock service error")

with pytest.raises(Exception) as exc_info:
process_ai_query("How to authenticate EPS API?")

assert "Bedrock service error" in str(exc_info.value)
mock_reformulate.assert_called_once()
# mock_reformulate.assert_called_once()

@patch("app.services.ai_processor.query_bedrock")
@patch("app.services.ai_processor.reformulate_query")
def test_process_ai_query_missing_citations(self, mock_reformulate, mock_bedrock):
def test_process_ai_query_missing_citations(self, mock_bedrock):
"""bedrock response incomplete: citations default to empty list"""
"""bedrock response incomplete: citations default to empty list"""
mock_reformulate.return_value = "reformulated query"
# mock_reformulate.return_value = "reformulated query"
mock_bedrock.return_value = {
"output": {"text": "Response without citations"},
"sessionId": "session-123",
Expand All @@ -93,10 +91,10 @@ def test_process_ai_query_missing_citations(self, mock_reformulate, mock_bedrock
assert result["citations"] == [] # safe default when bedrock omits citations

@patch("app.services.ai_processor.query_bedrock")
@patch("app.services.ai_processor.reformulate_query")
def test_process_ai_query_missing_session_id(self, mock_reformulate, mock_bedrock):
def test_process_ai_query_missing_session_id(self, mock_bedrock):
"""bedrock response incomplete: session_id properly handles None"""
mock_reformulate.return_value = "reformulated query"
"""bedrock response incomplete: session_id properly handles None"""
# mock_reformulate.return_value = "reformulated query"
mock_bedrock.return_value = {
"output": {"text": "Response without session"},
"citations": [],
Expand All @@ -110,10 +108,9 @@ def test_process_ai_query_missing_session_id(self, mock_reformulate, mock_bedroc
assert result["citations"] == []

@patch("app.services.ai_processor.query_bedrock")
@patch("app.services.ai_processor.reformulate_query")
def test_process_ai_query_empty_query(self, mock_reformulate, mock_bedrock):
def test_process_ai_query_empty_query(self, mock_bedrock):
"""edge case: empty query still processed through full pipeline"""
mock_reformulate.return_value = ""
# mock_reformulate.return_value = ""
mock_bedrock.return_value = {
"output": {"text": "Please provide a question"},
"sessionId": "session-empty",
Expand All @@ -123,14 +120,14 @@ def test_process_ai_query_empty_query(self, mock_reformulate, mock_bedrock):
result = process_ai_query("")

assert result["text"] == "Please provide a question"
mock_reformulate.assert_called_once_with("")
# mock_reformulate.assert_called_once_with("")
mock_bedrock.assert_called_once_with("", None)

@patch("app.services.ai_processor.query_bedrock")
@patch("app.services.ai_processor.reformulate_query")
def test_process_ai_query_includes_raw_response(self, mock_reformulate, mock_bedrock):
def test_process_ai_query_includes_raw_response(self, mock_bedrock):
"""slack needs raw bedrock data: kb_response preserved for session handling"""
"""slack needs raw bedrock data: kb_response preserved for session handling"""
mock_reformulate.return_value = "reformulated query"
# mock_reformulate.return_value = "reformulated query"
raw_response = {
"output": {"text": "Test response"},
"sessionId": "test-123",
Expand Down