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!
-## 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
-

-
Refresh Date: 2025-11-28
+

+
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 \
-

-
Refresh Date: 2025-11-28
+

+
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;
-

-
Refresh Date: 2025-11-28
+

+
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)