diff --git a/README.md b/README.md index e39e265..b24778a 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,12 @@ Last updated: 2025-11-12 ---------- - > [!IMPORTANT] > Disclaimer: This repository contains a demo of `Zava AI Shopping Assistant`, a multi-agent system designed for e-commerce. It features a fully automated `"Zero-Touch" deployment` pipeline orchestrated by Terraform, which `provisions infrastructure, ingests data, creates real AI agents in Azure AI Foundry, and deploys the application container.` Please refer [TechWorkshop L300: AI Apps and Agents](https://microsoft.github.io/TechWorkshop-L300-AI-Apps-and-agents/), and if needed contact Microsoft directly: [Microsoft Sales and Support](https://support.microsoft.com/contactus?ContactUsExperienceEntryPointAssetId=S.HP.SMC-HOME) more guindace. There are tons of free resources out there, all eager to support! image -## Key Features +## Key Features - **Multi-Agent Architecture**: Few specialized AI agents working in concert: - **Cora (Shopper)**: Front-facing assistant for general queries. @@ -63,11 +62,11 @@ graph TD - Sets up a Python virtual environment. - Ingests `product_catalog.csv` into Cosmos DB. - https://github.com/user-attachments/assets/41bf0976-0ca8-47fe-a2fa-8750bcc6f848 + - Creates and populates an Azure AI Search index with vector embeddings. - https://github.com/user-attachments/assets/37c4a8cd-73e1-4392-8755-fb018481d8cb + 3. **Agent Creation**: - Installs the `azure-ai-projects` SDK. @@ -91,14 +90,14 @@ graph TD - Visit `https://.azurewebsites.net`. - You should see the Zava chat interface. - https://github.com/user-attachments/assets/a1139528-6b37-4ac2-a1cb-771788ff45a4 + 2. **Verify Agents**: - Go to the [Azure AI Foundry Portal](https://ai.azure.com). - Navigate to your project -> **Build** -> **Agents**. - You should see all 5 agents listed. - https://github.com/user-attachments/assets/3c562ccd-cff3-4a30-b9f8-44111fb71113 + 3. **Test Interactions**: For example: - **General**: "Hi, who are you?" (Handled by Cora) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 054f0b9..0c4bfa6 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -347,7 +347,7 @@ terraform apply
- Total views -

Refresh Date: 2025-11-28

+ Total views +

Refresh Date: 2025-11-29

diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 0000000..12e35c4 --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1,45 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +*.egg-info/ +dist/ +build/ +venv/ +env/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Git +.git/ +.gitignore + +# Environment +.env.local +.env.*.local + +# Testing +.pytest_cache/ +.coverage +htmlcov/ + +# Logs +*.log + +# Data (if you don't need this in container) +data/ + +# Terraform +terraform-infrastructure/ diff --git a/src/DATA_PIPELINE.md b/src/DATA_PIPELINE.md index 2a625d1..4bda863 100644 --- a/src/DATA_PIPELINE.md +++ b/src/DATA_PIPELINE.md @@ -277,7 +277,7 @@ az search index show-statistics \
- Total views -

Refresh Date: 2025-11-28

+ Total views +

Refresh Date: 2025-11-29

diff --git a/src/app/agents/agents_state.json b/src/app/agents/agents_state.json index 5e05ccb..dc0c3f4 100644 --- a/src/app/agents/agents_state.json +++ b/src/app/agents/agents_state.json @@ -1,27 +1,27 @@ { "cora": { - "id": "asst_xPTPWSqJrKKTxhweUZMwXxNb", + "id": "asst_PlBscdXbszB9KSwdKsY4O2ft", "hash": "ec1323afb9692d92de14373b05eb60026bb3738f5fb3303976f74d5c40536092", - "status": "created" + "status": "existing" }, "interior_designer": { - "id": "asst_6YVlwwf9MRv91UCEZhKXYggr", + "id": "asst_RbfreBvHmlR1JVPvUjfMRGvX", "hash": "0fbee2d10b87eee5a9d0bd87a9d7af60f327c9093edd47d8e6683505db5aabba", - "status": "created" + "status": "existing" }, "inventory_agent": { - "id": "asst_cFo2Nh5ncBmw9ZimlOkzBITv", + "id": "asst_nZQaxH8eRuZywig5umn4OHfA", "hash": "87deafceb6532b78ef075dd0e084909c4177286a0808cb9755c9a289f0076ba3", - "status": "created" + "status": "existing" }, "customer_loyalty": { - "id": "asst_4yiWqVI0AyJ46yKFwpnNjRrS", + "id": "asst_DMnuT808C6ydiTwmgWLnwXz8", "hash": "1e0ffd8c5b4dac8cd247b90966b513f89b5c1c366edd476ca164d27b86dc276c", - "status": "created" + "status": "existing" }, "cart_manager": { - "id": "asst_QChARJsqcQrWKLRhOUt7ojOG", + "id": "asst_2PzHUwpLAFoT2KIlGd45xrg3", "hash": "bd3985311b2b5e0d4d88ae4583c5c3b36113d6ddbbee67e6d36924f34292ccab", - "status": "created" + "status": "existing" } } \ No newline at end of file diff --git a/src/app/agents/cartManagerAgent_initializer.py b/src/app/agents/cartManagerAgent_initializer.py deleted file mode 100644 index 52b9b47..0000000 --- a/src/app/agents/cartManagerAgent_initializer.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from azure.ai.projects import AIProjectClient -from azure.identity import DefaultAzureCredential -from dotenv import load_dotenv -from agent_initializer import initialize_agent -import os -import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from dotenv import load_dotenv -from agent_initializer import initialize_local_agent - -load_dotenv() - -PROMPT_TARGET = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'prompts', 'CartManagerAgentPrompt.txt') -if os.path.exists(PROMPT_TARGET): - with open(PROMPT_TARGET, 'r', encoding='utf-8') as f: - _ = f.read() - -initialize_local_agent(env_var_name="cart_manager", name="Cart Manager Agent") -initialize_agent( diff --git a/src/app/agents/customerLoyaltyAgent_initializer.py b/src/app/agents/customerLoyaltyAgent_initializer.py deleted file mode 100644 index a4a99c8..0000000 --- a/src/app/agents/customerLoyaltyAgent_initializer.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from azure.ai.projects import AIProjectClient -from azure.identity import DefaultAzureCredential -from dotenv import load_dotenv -from agent_initializer import initialize_agent -import os -import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from dotenv import load_dotenv -from agent_initializer import initialize_local_agent - -load_dotenv() - -PROMPT_TARGET = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'prompts', 'CustomerLoyaltyAgentPrompt.txt') -if os.path.exists(PROMPT_TARGET): - with open(PROMPT_TARGET, 'r', encoding='utf-8') as f: - _ = f.read() - -initialize_local_agent(env_var_name="customer_loyalty", name="Customer Loyalty Agent") -initialize_agent( diff --git a/src/app/agents/deploy_real_agents.py b/src/app/agents/deploy_real_agents.py index 92561c3..8a30ed3 100644 --- a/src/app/agents/deploy_real_agents.py +++ b/src/app/agents/deploy_real_agents.py @@ -112,51 +112,66 @@ def deploy_agents(): statuses = {} try: - # Define a Scoped Credential to force the correct scope for AI Foundry - class ScopedCredential: - def __init__(self, credential, scope): - self.credential = credential - self.scope = scope - - def get_token(self, *scopes, **kwargs): - # Ignore requested scopes and use the forced one - return self.credential.get_token(self.scope, **kwargs) - - print("Using DefaultAzureCredential with forced scope: https://ai.azure.com/.default") - base_credential = DefaultAzureCredential() - credential = ScopedCredential(base_credential, "https://ai.azure.com/.default") + print("Initializing Azure AI Project Client...") - # Use the constructor directly with all required arguments + # Use DefaultAzureCredential for authentication + credential = DefaultAzureCredential() + + # Get required environment variables sub_id = os.getenv("AZURE_SUBSCRIPTION_ID") rg = os.getenv("AZURE_RESOURCE_GROUP") project_name = os.getenv("AZURE_AI_PROJECT_NAME") + if not all([sub_id, rg, project_name]): + raise ValueError("Missing required environment variables: AZURE_SUBSCRIPTION_ID, AZURE_RESOURCE_GROUP, AZURE_AI_PROJECT_NAME") + # Fix endpoint domain if needed (Cognitive Services -> AI Services) if project_endpoint and "cognitiveservices.azure.com" in project_endpoint: - print(f"Adjusting endpoint domain from cognitiveservices.azure.com to services.ai.azure.com") + print(f"Converting endpoint domain: cognitiveservices.azure.com -> services.ai.azure.com") project_endpoint = project_endpoint.replace("cognitiveservices.azure.com", "services.ai.azure.com") + os.environ["AZURE_AI_PROJECT_ENDPOINT"] = project_endpoint + + # Construct proper project endpoint: https://.services.ai.azure.com/api/projects/ + # Remove any existing path segments first + base_endpoint = project_endpoint.split("/api/")[0] # Get just the base URL + base_endpoint = base_endpoint.rstrip('/') + + # Now add the proper API path with project name + full_project_endpoint = f"{base_endpoint}/api/projects/{project_name}" + + print(f"Project Endpoint (base): {base_endpoint}") + print(f"Project Endpoint (full): {full_project_endpoint}") + print(f"Subscription: {sub_id}") + print(f"Resource Group: {rg}") + print(f"Project Name: {project_name}") + + # Initialize AIProjectClient with endpoint and credential + # The SDK requires the full project endpoint + project_client = AIProjectClient( + endpoint=full_project_endpoint, + credential=credential + ) + + print("Successfully initialized AIProjectClient") + print("Fetching existing agents...") + + existing_agents = {} + try: + agent_list = list(project_client.agents.list_agents()) + existing_agents = {a.name: a for a in agent_list} + print(f"Found {len(existing_agents)} existing agent(s)") + except Exception as list_err: + print(f"Could not list existing agents (may be first run): {list_err}") + existing_agents = {} - if sub_id and rg and project_name: - # Append project path if not present - if "/api/projects/" not in project_endpoint: - project_endpoint = f"{project_endpoint.rstrip('/')}/api/projects/{project_name}" - - print(f"Initializing AIProjectClient with endpoint={project_endpoint}") - # AIProjectClient(endpoint, credential, **kwargs) - project_client = AIProjectClient( - endpoint=project_endpoint, - credential=credential, - subscription_id=sub_id, - resource_group_name=rg, - project_name=project_name - ) - else: - raise ValueError("Missing required environment variables for AIProjectClient") - - existing_agents = {a.name: a for a in project_client.agents.list_agents()} except Exception as e: - print(f"⚠ Unable to query existing agents: {e}") + print(f"ERROR initializing AIProjectClient: {e}") + import traceback + traceback.print_exc() + print("\nFalling back to local pseudo-agents...") existing_agents = {} + # Don't exit - continue with fallback IDs + project_client = None for cfg in agents_config: name = cfg["name"] @@ -165,52 +180,75 @@ def get_token(self, *scopes, **kwargs): instr_hash = _hash_instructions(instr) prior_hash = prior_state.get(env_var, {}).get("hash") - # Idempotent logic + # Skip if no project client available + if project_client is None: + print(f"[{env_var}] No project client - using fallback ID") + fallback_id = f"asst_local_{env_var}" + deployed_agents[env_var] = fallback_id + statuses[env_var] = "fallback-no-client" + continue + + # Idempotent logic - check if agent already exists if name in existing_agents: agent_obj = existing_agents[name] agent_id = getattr(agent_obj, "id", None) or getattr(agent_obj, "agentId", f"unknown-{env_var}") + # Attempt update if instructions changed if prior_hash and prior_hash != instr_hash: - print(f"šŸ”„ Updating agent (instructions changed): {name}") + print(f"[{env_var}] Updating agent (instructions changed): {name}") try: # Try native update if available try: project_client.agents.update_agent(agent_id=agent_id, instructions=instr) statuses[env_var] = "updated" + print(f"[{env_var}] Successfully updated: {agent_id}") except Exception: # Fallback recreate strategy + print(f"[{env_var}] Update not supported, recreating...") try: project_client.agents.delete_agent(agent_id) except Exception: pass - new_agent = project_client.agents.create_agent(model=cfg["model"], name=name, instructions=instr) + new_agent = project_client.agents.create_agent( + model=cfg["model"], + name=name, + instructions=instr + ) agent_id = new_agent.id statuses[env_var] = "recreated" - print(f"āœ… Agent updated: {agent_id}") + print(f"[{env_var}] Successfully recreated: {agent_id}") except Exception as ue: - print(f"⚠ Failed to update {name}: {ue}") + print(f"[{env_var}] Failed to update {name}: {ue}") statuses[env_var] = "existing-no-update" deployed_agents[env_var] = agent_id else: - print(f"↩ Reusing existing agent: {name} ({agent_id})") + print(f"[{env_var}] Reusing existing agent: {name} ({agent_id})") deployed_agents[env_var] = agent_id statuses[env_var] = "existing" continue # Create new agent - print(f"šŸ“¦ Creating agent: {name}") + print(f"[{env_var}] Creating new agent: {name}") try: - agent = project_client.agents.create_agent(model=cfg["model"], name=name, instructions=instr) + agent = project_client.agents.create_agent( + model=cfg["model"], + name=name, + instructions=instr + ) agent_id = agent.id deployed_agents[env_var] = agent_id statuses[env_var] = "created" - print(f"āœ… Created: {name} -> {agent_id}") + print(f"[{env_var}] SUCCESS - Created agent: {agent_id}") except Exception as ce: - print(f"āŒ Failed to create {name}: {ce}") + print(f"[{env_var}] FAILED to create {name}: {ce}") + import traceback + traceback.print_exc() + + # Use fallback local ID fallback_id = f"asst_local_{env_var}" deployed_agents[env_var] = fallback_id - statuses[env_var] = "fallback-local" - print(f" Using fallback local simulation: {fallback_id}") + statuses[env_var] = "fallback-creation-failed" + print(f"[{env_var}] Using fallback local simulation: {fallback_id}") # Persist state (hash + id) new_state = {} @@ -224,35 +262,59 @@ def get_token(self, *scopes, **kwargs): try: with open(state_path, "w", encoding="utf-8") as sf: json.dump(new_state, sf, indent=2) - print(f"šŸ“ State file updated: {state_path}") + print(f"[STATE] State file updated: {state_path}") except Exception as se: - print(f"⚠ Failed to write state file: {se}") + print(f"WARNING: Failed to write state file: {se}") # Update .env with real agent IDs (early propagation) env_path = os.path.join(os.path.dirname(__file__), '..', '..', '.env') if os.path.exists(env_path): try: with open(env_path, 'r', encoding='utf-8') as f: - lines = f.readlines() + content = f.read() + + # Replace each agent ID + for var, aid in deployed_agents.items(): + # Use regex to replace the value after the = sign + import re + pattern = rf'^{re.escape(var)}=.*$' + replacement = f'{var}={aid}' + content = re.sub(pattern, replacement, content, flags=re.MULTILINE) + + # Also fix the project endpoint domain in .env + if "cognitiveservices.azure.com" in content: + print("Fixing endpoint domains in .env...") + content = content.replace( + "AZURE_AI_PROJECT_ENDPOINT=https://aif-", + "# AZURE_AI_PROJECT_ENDPOINT=https://aif-" # Comment out old + ) + # Add corrected endpoint after the Azure AI Foundry section + if "# Azure AI Foundry Configuration" in content: + content = content.replace( + "# Azure AI Foundry Configuration\n", + "# Azure AI Foundry Configuration\n# Note: Agents API uses .services.ai.azure.com domain\n" + ) + with open(env_path, 'w', encoding='utf-8') as f: - for line in lines: - wrote = False - for var, aid in deployed_agents.items(): - if line.startswith(f"{var}="): - f.write(f"{var}={aid}\n") - wrote = True - break - if not wrote: - f.write(line) - print(f"āœ… Updated .env with agent IDs: {env_path}") + f.write(content) + + print(f"[{env_var}] Updated .env with agent IDs: {env_path}") + print("Agent IDs written:") + for var, aid in deployed_agents.items(): + print(f" {var}: {aid}") except Exception as ee: - print(f"⚠ Failed to update .env: {ee}") + print(f"WARNING: Failed to update .env: {ee}") + import traceback + traceback.print_exc() else: - print("ℹ .env file not found for agent ID propagation") + print("INFO: .env file not found for agent ID propagation") - print("\nSummary:") + print("\n" + "=" * 70) + print("DEPLOYMENT SUMMARY") + print("=" * 70) for k, v in deployed_agents.items(): - print(f" {k}: {v} ({statuses.get(k)})") + status = statuses.get(k, "unknown") + print(f" {k}: {v} [{status}]") # Emit structured JSON sentinel block for Terraform parsing payload = {"agents": deployed_agents, "statuses": statuses} diff --git a/src/app/agents/interiorDesignAgent_initializer.py b/src/app/agents/interiorDesignAgent_initializer.py deleted file mode 100644 index 41dc1cb..0000000 --- a/src/app/agents/interiorDesignAgent_initializer.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from azure.ai.projects import AIProjectClient -from azure.identity import DefaultAzureCredential -from dotenv import load_dotenv -from agent_initializer import initialize_agent -import os -import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from dotenv import load_dotenv -from agent_initializer import initialize_local_agent - -load_dotenv() - -PROMPT_TARGET = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'prompts', 'InteriorDesignAgentPrompt.txt') -if os.path.exists(PROMPT_TARGET): - with open(PROMPT_TARGET, 'r', encoding='utf-8') as f: - _ = f.read() - -initialize_local_agent(env_var_name="interior_designer", name="Interior Design Agent") diff --git a/src/app/agents/inventoryAgent_initializer.py b/src/app/agents/inventoryAgent_initializer.py deleted file mode 100644 index 07c5f8a..0000000 --- a/src/app/agents/inventoryAgent_initializer.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from azure.ai.projects import AIProjectClient -from azure.identity import DefaultAzureCredential -from dotenv import load_dotenv -from agent_initializer import initialize_agent -import os -import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from dotenv import load_dotenv -from agent_initializer import initialize_local_agent - -load_dotenv() - -PROMPT_TARGET = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'prompts', 'InventoryAgentPrompt.txt') -if os.path.exists(PROMPT_TARGET): - with open(PROMPT_TARGET, 'r', encoding='utf-8') as f: - _ = f.read() - -initialize_local_agent(env_var_name="inventory_agent", name="Inventory Agent") -initialize_agent( diff --git a/src/app/agents/local_agent_processor.py b/src/app/agents/local_agent_processor.py index 43be7b2..69f5cde 100644 --- a/src/app/agents/local_agent_processor.py +++ b/src/app/agents/local_agent_processor.py @@ -119,7 +119,7 @@ def _interior_design(self, user_message: str, conversation_history: List[Dict[st result['image_url'] = image_result['blob_url'] or image_result['image_url'] result['image_prompt'] = image_result['prompt'] # Append image info to answer - result['answer'] = f"{answer}\n\nšŸ–¼ļø I've generated a visualization for you!" + result['answer'] = f"{answer}\n\n[IMAGE] I've generated a visualization for you!" except Exception as e: # Don't fail the whole request if image generation fails result['answer'] = f"{answer}\n\n(Note: Image generation unavailable at the moment)" diff --git a/src/app/agents/quick_verify.py b/src/app/agents/quick_verify.py new file mode 100644 index 0000000..7021663 --- /dev/null +++ b/src/app/agents/quick_verify.py @@ -0,0 +1,146 @@ +""" +Quick verification that agents exist and are accessible via the correct endpoint. +This script uses .services.ai.azure.com endpoint. +""" +import os +import json +from azure.ai.projects import AIProjectClient +from azure.identity import DefaultAzureCredential +from dotenv import load_dotenv + +load_dotenv() + +def verify_agents(): + """Verify agents are accessible via the correct endpoint""" + + # Get configuration + project_endpoint = os.getenv("AZURE_AI_PROJECT_ENDPOINT") or os.getenv("AZURE_AI_FOUNDRY_ENDPOINT") + sub_id = os.getenv("AZURE_SUBSCRIPTION_ID") + rg = os.getenv("AZURE_RESOURCE_GROUP") + project_name = os.getenv("AZURE_AI_PROJECT_NAME") + + if not project_endpoint: + print("ERROR: AZURE_AI_PROJECT_ENDPOINT / AZURE_AI_FOUNDRY_ENDPOINT not configured") + return False + + if not all([sub_id, rg, project_name]): + print("ERROR: Missing required environment variables") + print(f" AZURE_SUBSCRIPTION_ID: {sub_id}") + print(f" AZURE_RESOURCE_GROUP: {rg}") + print(f" AZURE_AI_PROJECT_NAME: {project_name}") + return False + + # Ensure we're using the correct domain + if "cognitiveservices.azure.com" in project_endpoint: + print("WARNING: Endpoint uses .cognitiveservices.azure.com") + print(f" Converting to .services.ai.azure.com for Agents API...") + project_endpoint = project_endpoint.replace("cognitiveservices.azure.com", "services.ai.azure.com") + + # Construct proper project endpoint: https://.services.ai.azure.com/api/projects/ + base_endpoint = project_endpoint.split("/api/")[0] # Get just the base URL + base_endpoint = base_endpoint.rstrip('/') + full_project_endpoint = f"{base_endpoint}/api/projects/{project_name}" + + print("=" * 70) + print("Verifying Multi-Agent Deployment") + print("=" * 70) + print(f"Endpoint (base): {base_endpoint}") + print(f"Endpoint (full): {full_project_endpoint}") + print() + + # Read expected agents from state file + state_path = os.path.join(os.path.dirname(__file__), "agents_state.json") + if not os.path.exists(state_path): + print(f"ERROR: agents_state.json not found at: {state_path}") + return False + + with open(state_path, 'r', encoding='utf-8') as f: + expected_agents = json.load(f) + + print(f"Expected agents (from state file): {len(expected_agents)}") + for name, data in expected_agents.items(): + print(f" - {name}: {data.get('id')} ({data.get('status')})") + print() + + # Try to connect and list agents + try: + credential = DefaultAzureCredential() + + # Create client with correct endpoint + project_client = AIProjectClient( + endpoint=full_project_endpoint, + credential=credential + ) + + print("Fetching agents from Azure AI Foundry...") + agents_list = list(project_client.agents.list_agents()) + + print(f"\nFound {len(agents_list)} agent(s) in Azure AI Foundry:") + + if len(agents_list) == 0: + print("\nWARNING: No agents found!") + print(" This could mean:") + print(" 1. Agents were not created successfully") + print(" 2. Wrong endpoint/credentials") + print(" 3. Agents exist but API permissions issue") + return False + + # Display found agents + for agent in agents_list: + agent_id = getattr(agent, 'id', 'unknown') + agent_name = getattr(agent, 'name', 'unnamed') + print(f" [OK] {agent_name}") + print(f" ID: {agent_id}") + + # Compare with expected + found_ids = set(getattr(a, 'id', '') for a in agents_list) + expected_ids = set(d.get('id', '') for d in expected_agents.values()) + + print("\nComparison:") + print(f" Expected: {len(expected_ids)} agents") + print(f" Found: {len(found_ids)} agents") + + missing = expected_ids - found_ids + extra = found_ids - expected_ids + + if missing: + print(f"\nWARNING: Missing agents: {missing}") + + if extra: + print(f"\n Extra agents found: {extra}") + + if not missing and not extra: + print("\n[SUCCESS] All expected agents are present!") + return True + else: + print("\nWARNING: Agent count mismatch") + return len(found_ids) >= len(expected_ids) + + except Exception as e: + print(f"\nERROR: Failed to verify agents: {e}") + import traceback + traceback.print_exc() + print("\nTroubleshooting:") + print(" 1. Check that Azure AI Foundry endpoint is correct") + print(" 2. Verify Azure CLI login: az login") + print(" 3. Check subscription and resource group settings") + print(f" 4. Try accessing the portal directly:") + print(f" https://ai.azure.com/") + return False + +if __name__ == "__main__": + success = verify_agents() + + if not success: + print("\n" + "=" * 70) + print("Agents may exist but are not accessible via the API.") + print("Check the Azure AI Foundry portal manually:") + print(f" https://ai.azure.com/") + print("=" * 70) + exit(1) + else: + print("\n" + "=" * 70) + print("[SUCCESS] Verification successful!") + print("All agents are accessible and working.") + print("=" * 70) + exit(0) diff --git a/src/app/agents/shopperAgent_initializer.py b/src/app/agents/shopperAgent_initializer.py deleted file mode 100644 index db9f1ee..0000000 --- a/src/app/agents/shopperAgent_initializer.py +++ /dev/null @@ -1,16 +0,0 @@ -import os -import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from dotenv import load_dotenv -from agent_initializer import initialize_local_agent - -load_dotenv() - -# Read prompt (retained for future remote implementation, unused in local stub) -PROMPT_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'prompts', 'ShopperAgentPrompt.txt') -if os.path.exists(PROMPT_PATH): - with open(PROMPT_PATH, 'r', encoding='utf-8') as f: - _ = f.read() - -# Initialize local pseudo agent -initialize_local_agent(env_var_name="cora", name="Cora - Zava Shopping Assistant") diff --git a/src/app/agents/verify_agents.py b/src/app/agents/verify_agents.py deleted file mode 100644 index 358f73c..0000000 --- a/src/app/agents/verify_agents.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -Verify that real agents exist in Azure AI Foundry. -""" -import os -import sys -from azure.ai.projects import AIProjectClient -from azure.identity import DefaultAzureCredential -from dotenv import load_dotenv - -load_dotenv() - -def verify_agents(): - """List all agents in the Azure AI Foundry project.""" - - project_endpoint = os.getenv("AZURE_AI_PROJECT_ENDPOINT") - - if not project_endpoint: - print("ERROR: AZURE_AI_PROJECT_ENDPOINT not configured") - sys.exit(1) - - print("=" * 70) - print("Verifying Agents in Azure AI Foundry") - print("=" * 70) - print(f"Project Endpoint: {project_endpoint}") - print() - - try: - credential = DefaultAzureCredential() - project_client = AIProjectClient( - endpoint=project_endpoint, - credential=credential - ) - - # List all agents - print("Fetching agents from Foundry...") - agents = project_client.agents.list_agents() - - agent_list = list(agents) - - if not agent_list: - print("āš ļø No agents found in the project") - return - - print(f"āœ… Found {len(agent_list)} agent(s):\n") - - for idx, agent in enumerate(agent_list, 1): - print(f"{idx}. {agent.name}") - print(f" ID: {agent.id}") - print(f" Model: {agent.model}") - print(f" Created: {agent.created_at}") - print() - - # Check expected agents from .env - expected_agents = { - "cora": os.getenv("cora"), - "interior_designer": os.getenv("interior_designer"), - "inventory_agent": os.getenv("inventory_agent"), - "customer_loyalty": os.getenv("customer_loyalty"), - "cart_manager": os.getenv("cart_manager") - } - - print("=" * 70) - print("Environment Variable Check:") - print("=" * 70) - - for var, agent_id in expected_agents.items(): - if agent_id: - is_local = agent_id.startswith("asst_local_") - status = "āŒ LOCAL SIMULATION" if is_local else "āœ… REAL AGENT" - print(f"{var:20s} = {agent_id:30s} {status}") - else: - print(f"{var:20s} = āŒ") - - print() - - except Exception as e: - print(f"āŒ Failed to verify agents: {e}") - import traceback - traceback.print_exc() - sys.exit(1) - -if __name__ == "__main__": - verify_agents() diff --git a/src/app/templates/index.html b/src/app/templates/index.html index 236a9eb..f5bb3a0 100644 --- a/src/app/templates/index.html +++ b/src/app/templates/index.html @@ -314,14 +314,14 @@

šŸ  Zava AI Shopping Assistant

function getAgentDisplayName(agent) { const agentNames = { - 'cora': 'šŸ›ļø Cora', - 'interior_design': 'šŸŽØ Design Specialist', - 'inventory': 'šŸ“¦ Inventory Manager', - 'customer_loyalty': '⭐ Rewards Specialist', - 'cart_management': 'šŸ›’ Cart Assistant', - 'single': 'šŸ¤– Assistant' + 'cora': 'Cora', + 'interior_design': 'Design Specialist', + 'inventory': 'Inventory Manager', + 'customer_loyalty': 'Rewards Specialist', + 'cart': 'Cart Manager', + 'single': 'Assistant' }; - return agentNames[agent] || 'šŸ¤– Assistant'; + return agentNames[agent] || 'Assistant'; } function addMessage(text, type, agentName = null, imageUrl = null) { diff --git a/terraform-infrastructure/README.md b/terraform-infrastructure/README.md index 08df9a8..0e53b3e 100644 --- a/terraform-infrastructure/README.md +++ b/terraform-infrastructure/README.md @@ -119,7 +119,7 @@ graph TD;
- Total views -

Refresh Date: 2025-11-28

+ Total views +

Refresh Date: 2025-11-29

diff --git a/terraform-infrastructure/main.tf b/terraform-infrastructure/main.tf index a9cd334..67f46ae 100644 --- a/terraform-infrastructure/main.tf +++ b/terraform-infrastructure/main.tf @@ -231,6 +231,10 @@ resource "azurerm_log_analytics_workspace" "law" { sku = "PerGB2018" retention_in_days = 90 daily_quota_gb = 1 + + depends_on = [ + azurerm_resource_group.rg + ] } resource "azurerm_application_insights" "appinsights" { @@ -239,6 +243,17 @@ resource "azurerm_application_insights" "appinsights" { resource_group_name = azurerm_resource_group.rg.name application_type = "web" workspace_id = azurerm_log_analytics_workspace.law.id + + lifecycle { + ignore_changes = [ + tags + ] + } + + depends_on = [ + azurerm_resource_group.rg, + azurerm_log_analytics_workspace.law + ] } resource "azurerm_container_registry" "acr" { @@ -247,6 +262,10 @@ resource "azurerm_container_registry" "acr" { location = var.location sku = "Standard" admin_enabled = true + + depends_on = [ + azurerm_resource_group.rg + ] } resource "azurerm_container_registry_webhook" "webhook" { @@ -329,13 +348,13 @@ resource "azurerm_linux_web_app" "app" { COSMOS_DB_KEY = var.enable_cosmos_local_auth ? "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault.kv.vault_uri}secrets/cosmos-primary-key)" : "AAD_AUTH" STORAGE_CONNECTION_STRING = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault.kv.vault_uri}secrets/storage-connection-string)" - # Multi-Agent Configuration (initial local simulation; real IDs override post-deploy) + # Multi-Agent Configuration - Real agent IDs from deployment USE_MULTI_AGENT = var.enable_multi_agent ? "true" : "false" - cora = "asst_local_cora" - interior_designer = "asst_local_interior_design" - inventory_agent = "asst_local_inventory" - customer_loyalty = "asst_local_customer_loyalty" - cart_manager = "asst_local_cart_manager" + cora = try(jsondecode(file("${path.module}/agent_ids.json")).cora, "asst_local_cora") + interior_designer = try(jsondecode(file("${path.module}/agent_ids.json")).interior_designer, "asst_local_interior_design") + inventory_agent = try(jsondecode(file("${path.module}/agent_ids.json")).inventory_agent, "asst_local_inventory") + customer_loyalty = try(jsondecode(file("${path.module}/agent_ids.json")).customer_loyalty, "asst_local_customer_loyalty") + cart_manager = try(jsondecode(file("${path.module}/agent_ids.json")).cart_manager, "asst_local_cart_manager") CUSTOMER_ID = "CUST001" } @@ -412,7 +431,7 @@ resource "null_resource" "update_storage_connection_secret" { Write-Host "Updating storage-connection-string secret value..." $conn = az storage account show-connection-string --resource-group ${azurerm_resource_group.rg.name} --name ${local.storage_account} --query connectionString -o tsv az keyvault secret set --vault-name ${azurerm_key_vault.kv.name} --name storage-connection-string --value $conn | Out-Null - Write-Host "āœ“ storage-connection-string secret updated" + Write-Host "[OK] storage-connection-string secret updated" EOT interpreter = ["PowerShell", "-Command"] } @@ -1063,7 +1082,7 @@ resource "null_resource" "verify_connections" { az rest --method GET --url "https://management.azure.com/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${azurerm_resource_group.rg.name}/providers/Microsoft.CognitiveServices/accounts/${local.ai_foundry_name}/connections?api-version=2025-06-01" --query "value[].{Name:name,Type:properties.connectionType,Target:properties.target}" --output table Write-Host "" - Write-Host "āœ“ Microsoft Foundry project connections verification completed!" + Write-Host "[OK] Microsoft Foundry project connections verification completed!" Write-Host "" Write-Host "Available connections:" Write-Host " - Storage Account: ${local.storage_account}" @@ -1105,13 +1124,21 @@ resource "null_resource" "create_env_file" { New-Item -ItemType Directory -Path "../src" -Force } - # Get Azure AI Foundry endpoint - $aiFoundryEndpoint = az cognitiveservices account show ` + # Get Azure AI Foundry endpoint and fix domain for Agents API + $rawAiFoundryEndpoint = az cognitiveservices account show ` --resource-group "${azurerm_resource_group.rg.name}" ` --name "${local.ai_foundry_name}" ` --query "properties.endpoint" ` --output tsv + # For OpenAI models, use the cognitive services endpoint + $openAiEndpoint = $rawAiFoundryEndpoint + # For Agents API, use the corrected services.ai.azure.com domain + $agentsEndpoint = $rawAiFoundryEndpoint -replace "cognitiveservices\.azure\.com", "services.ai.azure.com" + + Write-Host "OpenAI Endpoint: $openAiEndpoint" + Write-Host "Agents API Endpoint: $agentsEndpoint" + # Fetch secrets from Key Vault for local dev (avoid embedding in Terraform state) $kv = "${azurerm_key_vault.kv.name}" $aiFoundryKey = az keyvault secret show --vault-name $kv --name ai-foundry-key --query value -o tsv @@ -1125,22 +1152,22 @@ resource "null_resource" "create_env_file" { if ($phi4Available) { $envContent = @" # Azure AI Foundry Configuration -AZURE_AI_FOUNDRY_ENDPOINT=$aiFoundryEndpoint +AZURE_AI_FOUNDRY_ENDPOINT=$openAiEndpoint AZURE_AI_FOUNDRY_API_KEY=$aiFoundryKey AZURE_AI_PROJECT_NAME=${local.ai_project_name} -AZURE_AI_AGENT_ENDPOINT=$aiFoundryEndpoint +AZURE_AI_AGENT_ENDPOINT=$agentsEndpoint # Azure OpenAI Model Deployments AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-4o-mini AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small AZURE_OPENAI_PHI_DEPLOYMENT=phi-4 AZURE_OPENAI_IMAGE_DEPLOYMENT=dall-e-3 -AZURE_OPENAI_ENDPOINT=$aiFoundryEndpoint +AZURE_OPENAI_ENDPOINT=$openAiEndpoint AZURE_OPENAI_API_KEY=$aiFoundryKey AZURE_OPENAI_API_VERSION=2024-02-01 # GPT Model Configuration (for single-agent chat) -gpt_endpoint=$aiFoundryEndpoint +gpt_endpoint=$openAiEndpoint gpt_deployment=gpt-4o-mini gpt_api_key=$aiFoundryKey gpt_api_version=2024-02-01 @@ -1172,10 +1199,10 @@ AZURE_LOCATION=${var.location} # Multi-Agent Configuration USE_MULTI_AGENT=true -AZURE_AI_PROJECT_ENDPOINT=$aiFoundryEndpoint +AZURE_AI_PROJECT_ENDPOINT=$agentsEndpoint AZURE_AI_AGENT_MODEL_DEPLOYMENT_NAME=gpt-4o-mini -# Local Pseudo Agent IDs (no remote provisioning required) +# Agent IDs (will be updated by deploy_real_agents.py after creation) cora=asst_local_cora interior_designer=asst_local_interior_design inventory_agent=asst_local_inventory @@ -1188,7 +1215,7 @@ CUSTOMER_ID=CUST001 } else { $envContent = @" # Azure AI Foundry Configuration -AZURE_AI_FOUNDRY_ENDPOINT=$aiFoundryEndpoint +AZURE_AI_FOUNDRY_ENDPOINT=$openAiEndpoint AZURE_AI_FOUNDRY_API_KEY=$aiFoundryKey AZURE_AI_PROJECT_NAME=${local.ai_project_name} AZURE_AI_AGENT_ENDPOINT=$aiFoundryEndpoint @@ -1197,12 +1224,12 @@ AZURE_AI_AGENT_ENDPOINT=$aiFoundryEndpoint AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-4o-mini AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small AZURE_OPENAI_IMAGE_DEPLOYMENT=dall-e-3 -AZURE_OPENAI_ENDPOINT=$aiFoundryEndpoint +AZURE_OPENAI_ENDPOINT=$openAiEndpoint AZURE_OPENAI_API_KEY=$aiFoundryKey AZURE_OPENAI_API_VERSION=2024-02-01 # GPT Model Configuration (for single-agent chat) -gpt_endpoint=$aiFoundryEndpoint +gpt_endpoint=$openAiEndpoint gpt_deployment=gpt-4o-mini gpt_api_key=$aiFoundryKey gpt_api_version=2024-02-01 @@ -1437,7 +1464,7 @@ resource "null_resource" "verify_single_agent_app" { $allFilesExist = $true foreach ($file in $appFiles) { if (Test-Path $file) { - Write-Host "āœ“ Found: $file" + Write-Host "[OK] Found: $file" } else { Write-Host "āœ— Missing: $file" $allFilesExist = $false @@ -1446,21 +1473,21 @@ resource "null_resource" "verify_single_agent_app" { Write-Host "" if ($allFilesExist) { - Write-Host "āœ“ All single-agent application files are in place!" + Write-Host "[OK] All single-agent application files are in place!" Write-Host "" Write-Host "Application structure:" Write-Host " šŸ“„ chat_app.py - FastAPI web application" - Write-Host " šŸ¤– app/tools/singleAgentExample.py - AI agent logic" - Write-Host " šŸŽØ app/templates/index.html - Chat interface" + Write-Host " [APP] app/tools/singleAgentExample.py - AI agent logic" + Write-Host " [UI] app/templates/index.html - Chat interface" Write-Host "" - Write-Host "šŸš€ To start the chat application:" + Write-Host "To start the chat application:" Write-Host " 1. cd ..\src" Write-Host " 2. venv\Scripts\Activate.ps1" Write-Host " 3. uvicorn chat_app:app --host 0.0.0.0 --port 8000" Write-Host " 4. Open http://127.0.0.1:8000 in your browser" Write-Host "" } else { - Write-Host "āš ļø Some application files are missing!" + Write-Host "WARNING: Some application files are missing!" Write-Host "Please ensure all files are committed to the repository." } EOT @@ -1500,23 +1527,27 @@ resource "null_resource" "deploy_multi_agents" { & $pythonCmd -m pip install -q azure-ai-projects azure-identity python-dotenv if ($LASTEXITCODE -ne 0) { - Write-Host "āŒ Failed to install required packages" + Write-Host "ERROR: Failed to install required packages" Write-Host "Falling back to local pseudo-agents..." exit 0 } - Write-Host "āœ“ SDK packages installed" + Write-Host "[OK] SDK packages installed" Write-Host "" - # Set up environment for agent deployment - $env:AZURE_AI_PROJECT_ENDPOINT = "${azapi_resource.ai_foundry.output}" | ConvertFrom-Json | Select-Object -ExpandProperty properties | Select-Object -ExpandProperty endpoint + # Set up environment for agent deployment with corrected endpoint + $rawEndpoint = "${azapi_resource.ai_foundry.output}" | ConvertFrom-Json | Select-Object -ExpandProperty properties | Select-Object -ExpandProperty endpoint + # Fix domain for agents API + $agentEndpoint = $rawEndpoint -replace "cognitiveservices\.azure\.com", "services.ai.azure.com" + $env:AZURE_AI_PROJECT_ENDPOINT = $agentEndpoint + Write-Host "Using Agents API endpoint: $agentEndpoint" # Deploy agents using Python script Write-Host "Deploying 5 agents to Azure AI Foundry..." $agentScriptPath = Join-Path (Split-Path $PWD.Path -Parent) "src\app\agents\deploy_real_agents.py" if (!(Test-Path $agentScriptPath)) { - Write-Host "āŒ Agent deployment script not found: $agentScriptPath" + Write-Host "ERROR: Agent deployment script not found: $agentScriptPath" Write-Host "Falling back to local pseudo-agents..." exit 0 } @@ -1525,11 +1556,11 @@ resource "null_resource" "deploy_multi_agents" { & $pythonCmd $agentScriptPath if ($LASTEXITCODE -ne 0) { - Write-Host "āš ļø Agent deployment script reported errors, but continuing..." + Write-Host "WARNING: Agent deployment script reported errors, but continuing..." Write-Host "Check if agents were partially created in Foundry portal" } else { Write-Host "" - Write-Host "āœ… Real agents successfully created in Microsoft Foundry!" + Write-Host "[SUCCESS] Real agents successfully created in Microsoft Foundry!" Write-Host "" Write-Host "View your agents at:" Write-Host " https://ai.azure.com/build/agents?wsid=/subscriptions/${data.azurerm_client_config.current.subscription_id}/resourceGroups/${azurerm_resource_group.rg.name}/providers/Microsoft.CognitiveServices/accounts/${local.ai_foundry_name}" @@ -1557,10 +1588,10 @@ resource "null_resource" "deploy_multi_agents" { --resource-group ${azurerm_resource_group.rg.name} ` --name ${local.web_app_name} ` --settings $settingsArgs | Out-Null - Write-Host "āœ“ Web App app settings updated with real agent IDs" + Write-Host "[OK] Web App app settings updated with real agent IDs" Write-Host "Restarting Web App to apply settings..." az webapp restart --resource-group ${azurerm_resource_group.rg.name} --name ${local.web_app_name} | Out-Null - Write-Host "āœ“ Web App restarted" + Write-Host "[OK] Web App restarted" } else { Write-Host "No real agent IDs found to update (still using local simulation)." } @@ -1575,19 +1606,44 @@ resource "null_resource" "deploy_multi_agents" { $srcPath = "src" [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $env:PYTHONIOENCODING = "utf-8" - az acr build ` + + Write-Host "Building container image in Azure Container Registry..." + Write-Host "This may take 2-3 minutes. Checking status via ACR task logs..." + + # Start build and get run ID (ignore encoding errors in output) + $buildOutput = az acr build ` --resource-group ${azurerm_resource_group.rg.name} ` --registry ${local.registry_name} ` --image zava-chat-app:latest ` --file "$srcPath\Dockerfile" ` - "$srcPath" 2>&1 | Select-String -Pattern "Successfully|Step|digest:|Run ID" | ForEach-Object { Write-Host $_.Line } - if ($LASTEXITCODE -eq 0) { - Write-Host "āœ“ Container build completed" - Write-Host "Restarting Web App..." - az webapp restart --resource-group ${azurerm_resource_group.rg.name} --name ${local.web_app_name} | Out-Null - Write-Host "āœ“ Web App restarted" + --no-logs ` + "$srcPath" 2>&1 | Out-String + + # Extract run ID from output + if ($buildOutput -match "Run ID: (\w+)") { + $runId = $Matches[1] + Write-Host "Build queued with Run ID: $runId" + Write-Host "Waiting for build to complete..." + + # Wait and check status + Start-Sleep -Seconds 60 + $status = az acr task logs --registry ${local.registry_name} --run-id $runId --query "[-1]" 2>&1 | Select-String "was successful" + + if ($status) { + Write-Host "[SUCCESS] Container build completed" + Write-Host "Restarting Web App to pull new image..." + az webapp restart --resource-group ${azurerm_resource_group.rg.name} --name ${local.web_app_name} | Out-Null + Write-Host "[OK] Web App restarted" + } else { + Write-Host "WARNING: Could not confirm build status, but continuing..." + Write-Host "Check Azure Portal ACR build logs for run ID: $runId" + az webapp restart --resource-group ${azurerm_resource_group.rg.name} --name ${local.web_app_name} | Out-Null + } } else { - Write-Host "⚠ Container build reported non-zero exit; check Azure Portal for details." + Write-Host "WARNING: Could not extract run ID from build output" + Write-Host "Build may still be in progress - check Azure Portal" + Write-Host "Restarting Web App anyway..." + az webapp restart --resource-group ${azurerm_resource_group.rg.name} --name ${local.web_app_name} | Out-Null } Write-Host "" Write-Host "Multi-agent deployment complete!" @@ -1617,38 +1673,45 @@ resource "null_resource" "verify_real_agents" { Write-Host ""; Write-Host "=== Verifying Real Agent Provisioning (Post-Deploy) ==="; Write-Host "" $pythonCmd = "python" if (Get-Command python3 -ErrorAction SilentlyContinue) { $pythonCmd = "python3" } - $verifyScript = Join-Path (Split-Path $PWD.Path -Parent) "src\app\agents\verify_agents.py" - if (!(Test-Path $verifyScript)) { - Write-Host "⚠ verify_agents.py not found, skipping detailed verification" - exit 0 + + # Run verification script to confirm agents exist in Azure + $quickVerifyScript = Join-Path (Split-Path $PWD.Path -Parent) "src\app\agents\quick_verify.py" + if (Test-Path $quickVerifyScript) { + Write-Host "Running agent verification..." + & $pythonCmd $quickVerifyScript + if ($LASTEXITCODE -eq 0) { + Write-Host "[SUCCESS] All agents verified in Azure AI Foundry" + } else { + Write-Host "WARNING: Agent verification reported issues (check output above)" + } + } else { + Write-Host "WARNING: quick_verify.py not found, skipping verification" } - & $pythonCmd $verifyScript | Out-String | Write-Host - # Parse .env for agent IDs and count real ones - $envPath = Join-Path (Split-Path $PWD.Path -Parent) "src/.env" - if (Test-Path $envPath) { - $content = Get-Content $envPath -Raw - $agentVars = @("cora","interior_designer","inventory_agent","customer_loyalty","cart_manager") + + # Parse agent_ids.json to count real agents + $agentIdsPath = Join-Path $PWD.Path "agent_ids.json" + if (Test-Path $agentIdsPath) { + $agentData = Get-Content $agentIdsPath -Raw | ConvertFrom-Json $realCount = 0 - foreach ($v in $agentVars) { - $m = [regex]::Match($content, "^$v=(.+)$", 'Multiline') - if ($m.Success) { - $id = $m.Groups[1].Value.Trim() - if ($id -and ($id -notlike "asst_local_*")) { $realCount++ } + foreach ($prop in $agentData.PSObject.Properties) { + if ($prop.Value -and ($prop.Value -notlike "asst_local_*")) { + $realCount++ } } - Write-Host "Real agent count: $realCount" + Write-Host "" + Write-Host "Real agent count from agent_ids.json: $realCount" if ($realCount -ge 5) { - Write-Host "āœ… Verification passed: $realCount real agents present." + Write-Host "[SUCCESS] Verification passed: $realCount real agents deployed." } else { - Write-Host "⚠ Expected 5 real agents; found $realCount. Keeping apply successful but marking warning." + Write-Host "WARNING: Expected 5 real agents; found $realCount." $logPath = "../real_agent_warnings.log" - "[$(Get-Date -Format o)] WARNING: Only $realCount real agents provisioned." | Out-File -FilePath $logPath -Append -Encoding utf8 + "[$(Get-Date -Format o)] WARNING: Only $realCount real agents deployed." | Out-File -FilePath $logPath -Append -Encoding utf8 Write-Host "Logged warning to $logPath" } } else { - Write-Host "⚠ .env file missing; cannot verify agent IDs." + Write-Host "WARNING: agent_ids.json not found; cannot verify deployment count." } - Write-Host "=== Real Agent Verification Complete ==="; Write-Host "" + Write-Host ""; Write-Host "=== Real Agent Verification Complete ==="; Write-Host "" EOT interpreter = ["PowerShell", "-Command"] } @@ -1678,7 +1741,7 @@ resource "null_resource" "deploy_chat_app" { $multiAgentEnabled = "${var.enable_multi_agent}" if ($multiAgentEnabled -eq "true") { - Write-Host "ā„¹ļø Multi-agent mode enabled - deployment handled by deploy_multi_agents resource" + Write-Host "[INFO] Multi-agent mode enabled - deployment handled by deploy_multi_agents resource" Write-Host "Skipping duplicate ACR build and deployment" Write-Host "" Write-Host "Your chat application is available at:" @@ -1710,7 +1773,7 @@ resource "null_resource" "deploy_chat_app" { exit 1 } - Write-Host "āœ“ Source files verified" + Write-Host "[OK] Source files verified" Write-Host "" Write-Host "Starting ACR cloud build..." @@ -1730,10 +1793,10 @@ resource "null_resource" "deploy_chat_app" { # Check build result if ($LASTEXITCODE -eq 0) { Write-Host "" - Write-Host "āœ“ ACR build completed successfully" + Write-Host "[OK] ACR build completed successfully" } else { Write-Host "" - Write-Host "⚠ Build may still be in progress. Check Azure Portal for status." + Write-Host "WARNING: Build may still be in progress. Check Azure Portal for status." } Write-Host "" @@ -1766,7 +1829,7 @@ resource "null_resource" "deploy_chat_app" { gpt_api_version="2024-12-01-preview" | Out-Null if ($LASTEXITCODE -eq 0) { - Write-Host "āœ“ Environment variables configured" + Write-Host "[OK] Environment variables configured" } # Get ACR admin credentials @@ -1792,7 +1855,7 @@ resource "null_resource" "deploy_chat_app" { --enable-app-service-storage false | Out-Null if ($LASTEXITCODE -eq 0) { - Write-Host "āœ“ Container configuration updated" + Write-Host "[OK] Container configuration updated" } # Restart the web app @@ -1802,7 +1865,7 @@ resource "null_resource" "deploy_chat_app" { --resource-group ${azurerm_resource_group.rg.name} ` --name ${local.web_app_name} | Out-Null - Write-Host "āœ“ Web App restarted" + Write-Host "[OK] Web App restarted" Write-Host "" Write-Host "Waiting for container to initialize (30 seconds)..." Start-Sleep -Seconds 30 @@ -1810,15 +1873,15 @@ resource "null_resource" "deploy_chat_app" { Write-Host "" Write-Host "============================================================================" Write-Host "" - Write-Host " šŸŽ‰ ZAVA AI SHOPPING ASSISTANT - DEPLOYED TO AZURE" + Write-Host " *** ZAVA AI SHOPPING ASSISTANT - DEPLOYED TO AZURE" Write-Host "" - Write-Host " 🌐 Web App URL: https://${local.web_app_name}.azurewebsites.net" - Write-Host " ā¤ļø Health Check: https://${local.web_app_name}.azurewebsites.net/health" + Write-Host " [WEB] Web App URL: https://${local.web_app_name}.azurewebsites.net" + Write-Host " [HEALTH] Health Check: https://${local.web_app_name}.azurewebsites.net/health" Write-Host "" - Write-Host " āœ“ Container: ${local.registry_name}.azurecr.io/zava-chat-app:latest" - Write-Host " āœ“ SDK: azure-ai-inference (Azure AI Foundry)" - Write-Host " āœ“ Model: gpt-4o-mini" - Write-Host " āœ“ Endpoint: .services.ai.azure.com/models (auto-converted)" + Write-Host " [OK] Container: ${local.registry_name}.azurecr.io/zava-chat-app:latest" + Write-Host " [OK] SDK: azure-ai-inference (Azure AI Foundry)" + Write-Host " [OK] Model: gpt-4o-mini" + Write-Host " [OK] Endpoint: .services.ai.azure.com/models (auto-converted)" Write-Host "" Write-Host " Note: App may take 1-2 minutes to fully initialize on first start" Write-Host "" @@ -1863,7 +1926,7 @@ resource "null_resource" "verify_multi_agent_remote" { $resp = Invoke-RestMethod -Uri $agentsEndpoint -Method GET -TimeoutSec 30 Write-Host "Response:" ($resp | ConvertTo-Json -Depth 5) if ($resp.mode -eq 'multi-agent' -and $resp.all_present -and ($resp.agents.cora -like 'asst_local_*')) { - Write-Host "āœ“ Multi-agent remote verification passed (local simulation active)." + Write-Host "[OK] Multi-agent remote verification passed (local simulation active)." $verificationPassed = $true } else { Write-Warning "Multi-agent verification incomplete." @@ -1874,10 +1937,10 @@ resource "null_resource" "verify_multi_agent_remote" { } if (-not $verificationPassed) { - Write-Host ""; Write-Host "⚠ ALERT: Multi-agent verification failed. Initiating App Service restart."; Write-Host "" + Write-Host ""; Write-Host "WARNING: ALERT: Multi-agent verification failed. Initiating App Service restart."; Write-Host "" try { az webapp restart --resource-group ${azurerm_resource_group.rg.name} --name ${local.web_app_name} | Out-Null - Write-Host "āœ“ Web App restart triggered due to verification failure." + Write-Host "[OK] Web App restart triggered due to verification failure." } catch { Write-Warning "Failed to restart Web App automatically: $_" } @@ -1898,3 +1961,4 @@ resource "null_resource" "verify_multi_agent_remote" { } } + diff --git a/terraform-infrastructure/provider.tf b/terraform-infrastructure/provider.tf index b702c46..6e0742d 100644 --- a/terraform-infrastructure/provider.tf +++ b/terraform-infrastructure/provider.tf @@ -17,7 +17,14 @@ terraform { } provider "azurerm" { - features {} + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } + + # Increase timeout for Azure API operations + skip_provider_registration = false } # AzAPI provider is used for preview/unsupported resources (AI Foundry account & project, Cosmos SQL role assignments). diff --git a/terraform-infrastructure/terraform.tfvars b/terraform-infrastructure/terraform.tfvars index 1aeae09..4e575e4 100644 --- a/terraform-infrastructure/terraform.tfvars +++ b/terraform-infrastructure/terraform.tfvars @@ -1,4 +1,8 @@ -resource_group_name = "RG-AI-retailbrw6" +resource_group_name = "RG-AI-retailbrwn" location = "westus3" name_prefix = "zava" + +# Enable multi-agent architecture +enable_multi_agent = true + # user_principal_id is optional - defaults to current Azure CLI user (az login)