diff --git a/.env b/.env index 97344d7..b7e101c 100644 --- a/.env +++ b/.env @@ -1,5 +1,7 @@ # Ollama configuration -LLM_OLLAMA_BASE_URL=http://192.168.0.140:11434 +# LLM_OLLAMA_BASE_URL=http://192.168.0.140:11434 +# LLM_OLLAMA_BASE_URL=http://192.168.0.122:11434 +LLM_OLLAMA_BASE_URL="https://api-amer-sandbox-gbl-mdm-hub.pfizer.com/ollama" LLM_OLLAMA_MODEL=phi4-mini:latest # LLM_OLLAMA_MODEL=smollm:360m # LLM_OLLAMA_MODEL=qwen3:0.6b @@ -8,7 +10,7 @@ LLM_OLLAMA_MODEL=phi4-mini:latest LOG_LEVEL=DEBUG # Ollama API Key (required when using Ollama mode) # Langfuse configuration -LANGFUSE_ENABLED=true +LANGFUSE_ENABLED=false LANGFUSE_PUBLIC_KEY="pk-lf-17dfde63-93e2-4983-8aa7-2673d3ecaab8" LANGFUSE_SECRET_KEY="sk-lf-ba41a266-6fe5-4c90-a483-bec8a7aaa321" LANGFUSE_HOST="https://cloud.langfuse.com" \ No newline at end of file diff --git a/app/handlers.py b/app/handlers.py index 1e48b6b..c26c6d5 100644 --- a/app/handlers.py +++ b/app/handlers.py @@ -6,49 +6,65 @@ from llm.models import JiraWebhookPayload from shared_store import requests_queue, ProcessingRequest from loguru import logger -router = APIRouter( - prefix="/api", - tags=["API"] +jira_router = APIRouter( + prefix="/jira", + tags=["Jira"] ) -webhook_router = APIRouter( - prefix="/webhooks", - tags=["Webhooks"] +queue_router = APIRouter( + prefix="/queue", + tags=["Queue"] ) -@router.post("/jira_webhook", status_code=201) -async def receive_jira_webhook(payload: JiraWebhookPayload): - """Handle incoming Jira webhook and store request""" +@jira_router.post("/sendRequest", status_code=201) +async def send_jira_request(payload: JiraWebhookPayload): + """Send requests to add to queue for further processing""" request_id = requests_queue.add_request(payload.model_dump()) return {"request_id": request_id} -@router.get("/pending_requests") -async def get_pending_requests(): - """Return all pending requests""" +class GetResponseRequest(BaseModel): + issueKey: str + +@jira_router.post("/getResponse") +async def get_jira_response(request: GetResponseRequest): + """Get response attribute provided by ollama for a given issueKey.""" + matched_request = requests_queue.get_latest_completed_by_issue_key(request.issueKey) + if not matched_request: + raise HTTPException(status_code=404, detail=f"No completed request found for issueKey: {request.issueKey}") + return matched_request.response if matched_request.response else "No response yet" + +# @queue_router.get("/{issueKey}") +# async def get_queue_element_by_issue_key(issueKey: str): +# """Get the element with specific issueKey. Return latest which was successfully processed by ollama. Skip pending or failed.""" +# matched_request = requests_queue.get_latest_completed_by_issue_key(issueKey) +# if not matched_request: +# raise HTTPException(status_code=404, detail=f"No completed request found for issueKey: {issueKey}") +# return matched_request + +@queue_router.get("/getAll") +async def get_all_requests_in_queue(): + """Gets all requests""" + all_requests = requests_queue.get_all_requests() + return {"requests": all_requests} + +@queue_router.get("/getPending") +async def get_pending_requests_in_queue(): + """Gets all requests waiting to be processed""" all_requests = requests_queue.get_all_requests() pending = [req for req in all_requests if req.status == "pending"] return {"requests": pending} -@router.delete("/requests/{request_id}") -async def delete_specific_request(request_id: int): - """Delete specific request by ID""" - if requests_queue.delete_request_by_id(request_id): - return {"deleted": True} - raise HTTPException(status_code=404, detail="Request not found") - -@router.delete("/requests") -async def delete_all_requests(): - """Clear all requests""" +@queue_router.delete("/clearAll") +async def clear_all_requests_in_queue(): + """Clear all the requests from the queue""" requests_queue.clear_all_requests() return {"status": "cleared"} -@router.get("/requests/{request_id}/response") -async def get_request_response(request_id: int): - """Get response for specific request""" - matched_request = requests_queue.get_request_by_id(request_id) - if not matched_request: - raise HTTPException(status_code=404, detail="Request not found") - return matched_request.response if matched_request.response else "No response yet" +# Original webhook_router remains unchanged for now, as it's not part of the /jira or /queue prefixes +webhook_router = APIRouter( + prefix="/webhooks", + tags=["Webhooks"] +) @webhook_router.post("/jira") async def handle_jira_webhook(): diff --git a/config/application.yml b/config/application.yml index 6f1372b..dbbb4a3 100644 --- a/config/application.yml +++ b/config/application.yml @@ -21,8 +21,8 @@ llm: # Settings for Ollama ollama: # Can be overridden by OLLAMA_BASE_URL - base_url: "http://192.168.0.140:11434" - # base_url: "https://api-amer-sandbox-gbl-mdm-hub.pfizer.com/ollama" + #base_url: "http://192.168.0.140:11434" + base_url: "https://api-amer-sandbox-gbl-mdm-hub.pfizer.com/ollama" # Can be overridden by OLLAMA_MODEL @@ -47,7 +47,7 @@ langfuse: processor: # Interval in seconds between polling for new Jira analysis requests # Can be overridden by PROCESSOR_POLL_INTERVAL_SECONDS environment variable - poll_interval_seconds: 30 + poll_interval_seconds: 10 # Maximum number of retries for failed Jira analysis requests # Can be overridden by PROCESSOR_MAX_RETRIES environment variable diff --git a/jira_webhook_llm.py b/jira_webhook_llm.py index 4c0ed44..6010a00 100644 --- a/jira_webhook_llm.py +++ b/jira_webhook_llm.py @@ -24,7 +24,7 @@ from loguru import logger from shared_store import RequestStatus, requests_queue, ProcessingRequest from llm.models import JiraWebhookPayload from llm.chains import analysis_chain, validate_response -from app.handlers import router, webhook_router # Import from unified handlers +from app.handlers import jira_router, queue_router, webhook_router # Import new routers from config import settings async def process_single_jira_request(request: ProcessingRequest): @@ -124,7 +124,8 @@ def create_app(): # Include routers _app.include_router(webhook_router) - _app.include_router(router) + _app.include_router(jira_router) + _app.include_router(queue_router) # Add health check endpoint @_app.get("/health") diff --git a/llm/prompt_tests.py b/llm/prompt_tests.py deleted file mode 100644 index 159cbae..0000000 --- a/llm/prompt_tests.py +++ /dev/null @@ -1,29 +0,0 @@ -import unittest -from llm.chains import load_prompt_template, validate_response -from llm.models import AnalysisFlags - -class PromptTests(unittest.TestCase): - def test_prompt_loading(self): - """Test that prompt template loads correctly""" - try: - template = load_prompt_template() - self.assertIsNotNone(template) - self.assertIn("issueKey", template.input_variables) - except Exception as e: - self.fail(f"Prompt loading failed: {str(e)}") - - def test_response_validation(self): - """Test response validation logic""" - valid_response = { - "hasMultipleEscalations": False, - "customerSentiment": "neutral" - } - invalid_response = { - "customerSentiment": "neutral" - } - - self.assertTrue(validate_response(valid_response)) - self.assertFalse(validate_response(invalid_response)) - -if __name__ == "__main__": - unittest.main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f820755..b51f9e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ fastapi==0.111.0 -pydantic==2.7.1 +pydantic==2.7.4 pydantic-settings>=2.0.0 langchain>=0.1.0 langchain-ollama>=0.1.0 diff --git a/shared_store.py b/shared_store.py index 0ad3b12..6059049 100644 --- a/shared_store.py +++ b/shared_store.py @@ -66,6 +66,20 @@ class RequestQueue: with self._processing_lock: return next((req for req in self._requests if req.id == request_id), None) + def get_latest_completed_by_issue_key(self, issue_key: str) -> Optional[ProcessingRequest]: + """ + Retrieves the latest successfully completed request for a given issue key. + Skips pending or failed requests. + """ + with self._processing_lock: + completed_requests = [ + req for req in self._requests + if req.payload.get("issueKey") == issue_key and req.status == RequestStatus.COMPLETED + ] + # Sort by completed_at to get the latest, if available + completed_requests.sort(key=lambda req: req.completed_at if req.completed_at else datetime.min, reverse=True) + return completed_requests[0] if completed_requests else None + def delete_request_by_id(self, request_id: int) -> bool: """Deletes a specific request by its ID.""" with self._processing_lock: