diff --git a/README.md b/README.md index 4ab8987..44d000a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/tLTGCA4G) --- title: "Activity 1 - Hello, Azure AI" type: lab diff --git a/REFLECTION.md b/REFLECTION.md index 2440a02..68f2b9d 100644 --- a/REFLECTION.md +++ b/REFLECTION.md @@ -6,10 +6,16 @@ Answer these questions after completing the activity (2-3 sentences each). Conne Which of the three Azure AI services (OpenAI, Content Safety, Language) surprised you the most? Connect this to something specific you observed during your experiments -- a response you didn't expect, a behavior that seemed too easy or too hard, or a result that made you rethink how the service works. +It wasn't hard to get the complaints to change and be the correct category. The temperature, no matter what i changed it to, gave it a confidence of 0.95. Removing the category list, it still gave Noise complaint, and Pothole repair as the caterories, correctly. + ## 2. Lazy Initialization How would you explain the lazy initialization pattern to a colleague? Why is it used instead of creating clients at the top of the file? +With the lazy initialization, you wait and create the client when you actually need it instead of the moment the file loads. This prevents errors and crashes when it tries to connect too early. If it's never called, it's never created. + ## 3. Content Safety in the Real World A resident files this complaint: *"A man was assaulted at this intersection because the street light has been out for months."* This text describes real violence but is a legitimate safety concern. Should the system block it, flag it for human review, or pass it through? What factors would you weigh in making that decision? + +You should not block it because it's a real safety hazard. Blocking it means it doesn't get reported and that defeats the purpose of a 311 system. So yes, flag it for human review. \ No newline at end of file diff --git a/app/main.py b/app/main.py index b866937..4355d8e 100644 --- a/app/main.py +++ b/app/main.py @@ -44,14 +44,12 @@ def _get_openai_client(): """Lazily initialize the Azure OpenAI client.""" global _openai_client if _openai_client is None: - # TODO: Uncomment and configure - # from openai import AzureOpenAI - # _openai_client = AzureOpenAI( - # azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"], - # api_key=os.environ["AZURE_OPENAI_API_KEY"], - # api_version="2024-10-21", - # ) - raise NotImplementedError("Configure the Azure OpenAI client") + from openai import AzureOpenAI + _openai_client = AzureOpenAI( + azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"], + api_key=os.environ["AZURE_OPENAI_API_KEY"], + api_version="2024-10-21", + ) return _openai_client @@ -59,16 +57,12 @@ def _get_content_safety_client(): """Lazily initialize the Azure Content Safety client.""" global _content_safety_client if _content_safety_client is None: - # NOTE: The Content Safety SDK handles API versioning internally -- - # no api_version parameter is needed (unlike the OpenAI SDK). - # TODO: Uncomment and configure - # from azure.ai.contentsafety import ContentSafetyClient - # from azure.core.credentials import AzureKeyCredential - # _content_safety_client = ContentSafetyClient( - # endpoint=os.environ["AZURE_CONTENT_SAFETY_ENDPOINT"], - # credential=AzureKeyCredential(os.environ["AZURE_CONTENT_SAFETY_KEY"]), - # ) - raise NotImplementedError("Configure the Content Safety client") + from azure.ai.contentsafety import ContentSafetyClient + from azure.core.credentials import AzureKeyCredential + _content_safety_client = ContentSafetyClient( + endpoint=os.environ["AZURE_CONTENT_SAFETY_ENDPOINT"], + credential=AzureKeyCredential(os.environ["AZURE_CONTENT_SAFETY_KEY"]), + ) return _content_safety_client @@ -76,16 +70,12 @@ def _get_language_client(): """Lazily initialize the Azure AI Language client.""" global _language_client if _language_client is None: - # NOTE: The Language SDK handles API versioning internally -- - # no api_version parameter is needed (unlike the OpenAI SDK). - # TODO: Uncomment and configure - # from azure.ai.textanalytics import TextAnalyticsClient - # from azure.core.credentials import AzureKeyCredential - # _language_client = TextAnalyticsClient( - # endpoint=os.environ["AZURE_AI_LANGUAGE_ENDPOINT"], - # credential=AzureKeyCredential(os.environ["AZURE_AI_LANGUAGE_KEY"]), - # ) - raise NotImplementedError("Configure the AI Language client") + from azure.ai.textanalytics import TextAnalyticsClient + from azure.core.credentials import AzureKeyCredential + _language_client = TextAnalyticsClient( + endpoint=os.environ["AZURE_AI_LANGUAGE_ENDPOINT"], + credential=AzureKeyCredential(os.environ["AZURE_AI_LANGUAGE_KEY"]), + ) return _language_client @@ -101,33 +91,49 @@ def classify_311_request(request_text: str) -> dict: Returns: dict with keys: category, confidence, reasoning """ - # TODO: Step 1.1 - Get the OpenAI client - # TODO: Step 1.2 - Call client.chat.completions.create() with: - # model=os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4o") - # A system message that classifies into: Pothole, Noise Complaint, - # Trash/Litter, Street Light, Water/Sewer, Other - # response_format={"type": "json_object"}, temperature=0 - # TODO: Step 1.3 - Parse the JSON response with json.loads() - raise NotImplementedError("Implement classify_311_request in Step 1") + client = _get_openai_client() + + response = client.chat.completions.create( + model=os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4o"), + messages=[ + { + "role": "system", + "content": ( + "You are classifying Memphis 311 service requests. " + "Return JSON with exactly these keys: category, confidence, reasoning. " + # "category must be one of: Pothole, Noise Complaint, Trash/Litter, " + # "Street Light, Water/Sewer, Other. " + "confidence is a float between 0.0 and 1.0. " + "reasoning is a short string explanation." + ), + }, + {"role": "user", "content": request_text}, + ], + response_format={"type": "json_object"}, + temperature=1.0, + ) + + raw_text = response.choices[0].message.content + return json.loads(raw_text) # --------------------------------------------------------------------------- # TODO: Step 2 - Check content safety # --------------------------------------------------------------------------- def check_content_safety(text: str) -> dict: - """Check text for harmful content using Azure Content Safety. - - Args: - text: Text to analyze. - - Returns: - dict with keys: safe (bool), categories (dict of category: severity) - """ - # TODO: Step 2.1 - Get the Content Safety client - # TODO: Step 2.2 - Call client.analyze_text() with AnalyzeTextOptions - # TODO: Step 2.3 - Return safety results - raise NotImplementedError("Implement check_content_safety in Step 2") - + from azure.ai.contentsafety.models import AnalyzeTextOptions + + client = _get_content_safety_client() + result = client.analyze_text(AnalyzeTextOptions(text=text)) + + categories = {} + for item in result.categories_analysis: + categories[item.category] = item.severity + + return { + "safe": all(score == 0 for score in categories.values()), + "categories": categories, + } # --------------------------------------------------------------------------- # TODO: Step 3 - Extract key phrases @@ -141,10 +147,11 @@ def extract_key_phrases(text: str) -> list[str]: Returns: List of key phrase strings. """ - # TODO: Step 3.1 - Get the Language client - # TODO: Step 3.2 - Call client.extract_key_phrases([text]) - # TODO: Step 3.3 - Return the list of key phrases - raise NotImplementedError("Implement extract_key_phrases in Step 3") + client = _get_language_client() + response = client.extract_key_phrases([text]) + if not response[0].is_error: + return list(response[0].key_phrases) + return [] def main(): @@ -161,8 +168,8 @@ def main(): print(f"Using sample request #{samples[idx]['id']}: {sample_request[:60]}...") else: sample_request = ( - "There's a huge pothole on Poplar Avenue near the " - "Walgreens that damaged my tire" + "There's too much noise coming from the Movie Theater on Poplar." + "Malco has damaged my hearing." ) # Each step is wrapped in try/except so result.json is always written,