0.2.0 - conf for sandbox
Some checks failed
CI/CD Pipeline / test (push) Has been cancelled

This commit is contained in:
Ireneusz Bachanowicz 2025-07-22 18:24:02 +02:00
parent 9e698e40f9
commit 6a57d91b7e
7 changed files with 66 additions and 65 deletions

15
.env
View File

@ -1,20 +1,21 @@
# Ollama configuration # 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=http://192.168.0.122:11434
# LLM_OLLAMA_BASE_URL="https://api-amer-sandbox-gbl-mdm-hub.pfizer.com/ollama" LLM_OLLAMA_BASE_URL="https://api-amer-sandbox-gbl-mdm-hub.pfizer.com/ollama"
LLM_OLLAMA_MODEL=phi4-mini:latest LLM_OLLAMA_MODEL=phi4-mini:latest
# LLM_OLLAMA_MODEL=smollm:360m # LLM_OLLAMA_MODEL=smollm:360m
# LLM_OLLAMA_MODEL=qwen3:0.6b # LLM_OLLAMA_MODEL=qwen3:0.6b
# LLM_OLLAMA_MODEL=qwen3:1.7b # LLM_OLLAMA_MODEL=qwen3:1.7b
# LLM_OLLAMA_MODEL=qwen3:8b
# Logging configuration # Logging configuration
LOG_LEVEL=DEBUG LOG_LEVEL=DEBUG
# Ollama API Key (required when using Ollama mode) # Ollama API Key (required when using Ollama mode)
# Langfuse configuration # Langfuse configuration
LANGFUSE_ENABLED=true LANGFUSE_ENABLED=false
LANGFUSE_PUBLIC_KEY="pk-lf-17dfde63-93e2-4983-8aa7-2673d3ecaab8" LANGFUSE_PUBLIC_KEY="pk-lf-"
LANGFUSE_SECRET_KEY="sk-lf-ba41a266-6fe5-4c90-a483-bec8a7aaa321" LANGFUSE_SECRET_KEY="sk-lf-"
LANGFUSE_HOST="https://cloud.langfuse.com" LANGFUSE_HOST="https://cloud.langfuse.com"
# Gemini configuration # Gemini configuration
LLM_GEMINI_API_KEY="AIzaSyDl12gxyTf2xCaTbT6OMJg0I-Rc82Ib77c" LLM_GEMINI_API_KEY=""
LLM_GEMINI_MODEL="gemini-2.5-flash" LLM_GEMINI_MODEL="gemini-2.5-flash"
LLM_MODE=gemini LLM_MODE=ollama

View File

@ -42,11 +42,11 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
COPY config ./config COPY config ./config
# Copy your application source code. # Copy your application source code.
COPY jira_webhook_llm.py . COPY main.py .
COPY config.py . COPY config.py .
# Expose the port your application listens on. # Expose the port your application listens on.
EXPOSE 8000 EXPOSE 8000
# Define the command to run your application. # Define the command to run your application.
CMD ["uvicorn", "jira-webhook-llm:app", "--host", "0.0.0.0", "--port", "8000"] CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@ -3,7 +3,7 @@ llm:
# The mode to run the application in. # The mode to run the application in.
# Can be 'openai' or 'ollama'. # Can be 'openai' or 'ollama'.
# This can be overridden by the LLM_MODE environment variable. # This can be overridden by the LLM_MODE environment variable.
mode: gemini # Change mode to gemini mode: ollama # Change mode to gemini
# Settings for OpenAI-compatible APIs (like OpenRouter) # Settings for OpenAI-compatible APIs (like OpenRouter)
openai: openai:
@ -26,7 +26,7 @@ llm:
# It's HIGHLY recommended to set this via an environment variable # It's HIGHLY recommended to set this via an environment variable
# instead of saving it in this file. # instead of saving it in this file.
# Can be overridden by GEMINI_API_KEY # Can be overridden by GEMINI_API_KEY
api_key: "AIzaSyDl12gxyTf2xCaTbT6OMJg0I-Rc82Ib77c" # Move from openai api_key: ""
# Can be overridden by GEMINI_MODEL # Can be overridden by GEMINI_MODEL
# model: "gemini-2.5-flash" # model: "gemini-2.5-flash"
@ -38,7 +38,7 @@ llm:
# Settings for Ollama # Settings for Ollama
ollama: ollama:
# Can be overridden by OLLAMA_BASE_URL # Can be overridden by OLLAMA_BASE_URL
#base_url: "http://192.168.0.140:11434" # base_url: "http://192.168.0.122:11434"
base_url: "https://api-amer-sandbox-gbl-mdm-hub.pfizer.com/ollama" base_url: "https://api-amer-sandbox-gbl-mdm-hub.pfizer.com/ollama"
@ -47,17 +47,18 @@ llm:
# model: "qwen3:1.7b" # model: "qwen3:1.7b"
# model: "smollm:360m" # model: "smollm:360m"
# model: "qwen3:0.6b" # model: "qwen3:0.6b"
# model: "qwen3:8b"
# Langfuse configuration for observability and analytics # Langfuse configuration for observability and analytics
langfuse: langfuse:
# Enable or disable Langfuse integration # Enable or disable Langfuse integration
# Can be overridden by LANGFUSE_ENABLED environment variable # Can be overridden by LANGFUSE_ENABLED environment variable
enabled: true enabled: false
# Langfuse API credentials # Langfuse API credentials
# It's HIGHLY recommended to set these via environment variables # It's HIGHLY recommended to set these via environment variables
# instead of saving them in this file # instead of saving them in this file
public_key: "pk-lf-17dfde63-93e2-4983-8aa7-2673d3ecaab8" public_key: "pk-lf-"
secret_key: "sk-lf-ba41a266-6fe5-4c90-a483-bec8a7aaa321" secret_key: "sk-lf-"
host: "https://cloud.langfuse.com" host: "https://cloud.langfuse.com"
# Processor configuration # Processor configuration
@ -68,7 +69,7 @@ processor:
# Maximum number of retries for failed Jira analysis requests # Maximum number of retries for failed Jira analysis requests
# Can be overridden by PROCESSOR_MAX_RETRIES environment variable # Can be overridden by PROCESSOR_MAX_RETRIES environment variable
max_retries: 5 max_retries: 0
# Initial delay in seconds before the first retry attempt (exponential backoff) # Initial delay in seconds before the first retry attempt (exponential backoff)
# Can be overridden by PROCESSOR_INITIAL_RETRY_DELAY_SECONDS environment variable # Can be overridden by PROCESSOR_INITIAL_RETRY_DELAY_SECONDS environment variable

View File

@ -8,7 +8,7 @@ services:
# Service for your FastAPI application # Service for your FastAPI application
jira-webhook-llm: jira-webhook-llm:
image: artifactory.pfizer.com/mdmhub-docker-dev/mdmtools/ollama/jira-webhook-llm:0.1.8 image: artifactory.pfizer.com/mdmhub-docker-dev/mdmtools/ollama/jira-webhook-llm:0.2.0
ports: ports:
- "8000:8000" - "8000:8000"
environment: environment:
@ -30,4 +30,4 @@ services:
# Command to run your FastAPI application using Uvicorn # Command to run your FastAPI application using Uvicorn
# --host 0.0.0.0 is crucial for the app to be accessible from outside the container # --host 0.0.0.0 is crucial for the app to be accessible from outside the container
# --reload is good for development; remove for production # --reload is good for development; remove for production
command: uvicorn jira-webhook-llm:app --host 0.0.0.0 --port 8000 command: uvicorn main:app --host 0.0.0.0 --port 8000

View File

@ -32,7 +32,6 @@ if settings.llm.mode == 'openai':
llm = ChatOpenAI( llm = ChatOpenAI(
model=settings.llm.openai_model if settings.llm.openai_model else "", # Ensure model is str model=settings.llm.openai_model if settings.llm.openai_model else "", # Ensure model is str
temperature=0.7, temperature=0.7,
max_tokens=2000,
api_key=settings.llm.openai_api_key, # type: ignore # Suppress Pylance error due to SecretStr type mismatch api_key=settings.llm.openai_api_key, # type: ignore # Suppress Pylance error due to SecretStr type mismatch
base_url=settings.llm.openai_api_base_url base_url=settings.llm.openai_api_base_url
) )
@ -52,7 +51,8 @@ elif settings.llm.mode == 'ollama':
llm = OllamaLLM( llm = OllamaLLM(
model=settings.llm.ollama_model, model=settings.llm.ollama_model,
base_url=base_url base_url=base_url,
num_ctx=32000
# Removed streaming, timeout, max_retries as they are not valid parameters for OllamaLLM # Removed streaming, timeout, max_retries as they are not valid parameters for OllamaLLM
) )
@ -92,7 +92,6 @@ elif settings.llm.mode == 'gemini': # New: Add Gemini initialization
llm = ChatGoogleGenerativeAI( llm = ChatGoogleGenerativeAI(
model=settings.llm.gemini_model, model=settings.llm.gemini_model,
temperature=0.7, temperature=0.7,
max_tokens=2000,
google_api_key=settings.llm.gemini_api_key google_api_key=settings.llm.gemini_api_key
) )
@ -131,7 +130,7 @@ if llm is None:
llm_runnable: Runnable = llm # type: ignore llm_runnable: Runnable = llm # type: ignore
# Set up Output Parser for structured JSON # Set up Output Parser for structured JSON
parser = JsonOutputParser(pydantic_object=AnalysisFlags) parser = JsonOutputParser()
# Load prompt template from file # Load prompt template from file
def load_prompt_template(version="v1.2.0"): def load_prompt_template(version="v1.2.0"):
@ -222,8 +221,8 @@ def validate_response(response: Union[dict, str], issue_key: str = "N/A") -> boo
AnalysisFlags.model_validate(response) AnalysisFlags.model_validate(response)
return True return True
except Exception as e: except Exception as e:
logger.error(f"[{issue_key}] Pydantic validation error: {e}. Invalid response: {response}") logger.warning(f"[{issue_key}] Pydantic validation failed: {e}. Continuing with raw response: {response}")
return False return True # Allow processing even if validation fails
except Exception as e: except Exception as e:
logger.error(f"[{issue_key}] Unexpected error during response validation: {e}. Response: {response}") logger.error(f"[{issue_key}] Unexpected error during response validation: {e}. Response: {response}")
return False return False

View File

@ -1,60 +1,58 @@
SYSTEM: SYSTEM:
You are a precise AI assistant that analyzes Jira tickets and outputs a JSON object. You are an expert AI assistant that analyzes Jira tickets and outputs a concise summary in a valid JSON format.
Your task is to analyze the provided Jira ticket data and generate a JSON object based on the rules below. Your output MUST be a single JSON object and nothing else.
Your output MUST be ONLY the JSON object, with no additional text or explanations.
## JSON Output Schema ## JSON Output Schema
{format_instructions} {format_instructions}
## Field-by-Field Instructions ## Field-by-Field Instructions
### `hasMultipleEscalations` (boolean) 1. **`issueCategory` (string)**
- Set to `true` ONLY if the user has made multiple requests for help from the "MDM HUB team" without getting a response. * Classify the core problem. Choose ONE: "technical_issue", "data_request", "access_problem", "general_question", "other".
- A normal back-and-forth conversation is NOT an escalation.
### `customerSentiment` (string: "neutral", "frustrated", "calm") 2. **`area` (string)**
- Set to `"frustrated"` if the user mentions blockers, deadlines, or uses urgent language (e.g., "urgent", "asap", "blocked"). * Classify the technical domain. Choose the BEST fit from the following options:
- Set to `"calm"` if the language is polite and patient. * `"Direct Channel"`
- Set to `"neutral"` otherwise. * `"Streaming Channel"`
* `"Java Batch Channel"`
* `"ETL Batch Channel"`
* `"DCR Service"`
* `"API Gateway"`
* `"Callback Service"`
* `"Publisher"`
* `"Reconciliation"`
* `"Snowflake"`
* `"Authentication"`
* `"Other"`
### `issueCategory` (string: "technical_issue", "data_request", "access_problem", "general_question", "other") 3. **`customerSentiment` (string)**
- `"technical_issue"`: Errors, bugs, system failures, API problems. * Analyze the user's tone.
- `"data_request"`: Asking for data exports, reports, or information retrieval. * `"frustrated"`: User mentions blockers, deadlines, or uses urgent language ("ASAP", "urgent", "blocked").
- `"access_problem"`: User cannot log in, has permission issues. * `"neutral"`: Default, non-emotional tone.
- `"general_question"`: "How do I..." or other general inquiries.
- `"other"`: If it doesn't fit any other category.
### `area` (string) 4. **`isEscalated` (boolean)**
- Classify the ticket into ONE of the following areas based on keywords: * Set to `true` if the user explicitly states they are escalating, mentions previous unanswered requests, or if the tone is highly frustrated and urgent.
- `"Direct Channel"`: "REST API", "API Gateway", "Create/Update HCP/HCO" * Set to `false` otherwise.
- `"Streaming Channel"`: "Kafka", "SQS", "Reltio events", "Snowflake"
- `"Java Batch Channel"`: "Batch", "File loader", "Airflow" 5. **`oneSentenceSummary` (string)**
- `"ETL Batch Channel"`: "ETL", "Informatica" * A single paragraph in concise English that summarizes the discussion.
- `"DCR Service"`: "DCR", "PforceRx", "OneKey", "Veeva"
- `"API Gateway"`: "Kong", "authentication", "routing"
- `"Callback Service"`: "Callback", "HCO names", "ranking"
- `"Publisher"`: "Publisher", "routing rules"
- `"Reconciliation"`: "Reconciliation", "sync"
- `"Snowflake"`: "Snowflake", "Data Mart", "SQL"
- `"Authentication"`: "PingFederate", "OAuth2", "Key-Auth"
- `"Other"`: If it doesn't fit any other category.
## Example ## Example
### Input: ### Input:
- Summary: "DCR Rejected by OneKey" - Summary: "DCR Rejected by OneKey"
- Description: "Our DCR for PforceRx was rejected by OneKey. Can the MDM HUB team investigate?" - Description: "Our DCR for PforceRx was rejected by OneKey. Can the MDM HUB team investigate? This is blocking our weekly report."
- Comment: "" - Comment: ""
### Output: ### Output:
```json ```json
{{ {{
"Hasmultipleescalations": false, "issueCategory": "technical_issue",
"CustomerSentiment": "neutral", "area": "Application Service",
"IssueCategory": "technical_issue", "customerSentiment": "frustrated",
"Area": "DCR Service" "isEscalated": false,
"oneSentenceSummary": "A DCR for PforceRx was rejected by OneKey, which is blocking a weekly report."
}} }}
```
USER: USER:
Analyze the following Jira ticket: Analyze the following Jira ticket:

View File

@ -57,11 +57,13 @@ class JiraWebhookPayload(BaseModel):
updated: Optional[str] = None updated: Optional[str] = None
class AnalysisFlags(BaseModel): class AnalysisFlags(BaseModel):
hasMultipleEscalations: bool = Field(alias="Hasmultipleescalations", description="Is there evidence of multiple escalation attempts?") model_config = ConfigDict(alias_generator=lambda x: ''.join(word.capitalize() if i > 0 else word for i, word in enumerate(x.split('_'))), populate_by_name=True)
customerSentiment: Optional[CustomerSentiment] = Field(alias="CustomerSentiment", description="Overall customer sentiment (e.g., 'neutral', 'frustrated', 'calm').")
# New: Add category and area fields issueCategory: IssueCategory = Field(..., description="The primary category of the Jira ticket.")
issueCategory: IssueCategory = Field(alias="IssueCategory", description="The primary category of the Jira ticket.") area: Area = Field(..., description="The technical area of the MDM HUB related to the issue.")
area: Area = Field(alias="Area", description="The technical area of the MDM HUB related to the issue.") customerSentiment: Optional[CustomerSentiment] = Field(..., description="Overall customer sentiment (e.g., 'neutral', 'frustrated', 'calm').")
isEscalated: bool = Field(..., description="Is there evidence of multiple escalation attempts?")
oneSentenceSummary: str = Field(..., description="A single paragraph in concise English that summarizes the discussion.")
class JiraAnalysisResponse(BaseModel): class JiraAnalysisResponse(BaseModel):