Skip to content

Commit d5ca534

Browse files
committed
multi-agent deployment by default
1 parent 521a5e3 commit d5ca534

15 files changed

Lines changed: 491 additions & 349 deletions

src/.dockerignore

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
*.egg-info/
8+
dist/
9+
build/
10+
venv/
11+
env/
12+
ENV/
13+
14+
# IDE
15+
.vscode/
16+
.idea/
17+
*.swp
18+
*.swo
19+
*~
20+
21+
# OS
22+
.DS_Store
23+
Thumbs.db
24+
25+
# Git
26+
.git/
27+
.gitignore
28+
29+
# Environment
30+
.env.local
31+
.env.*.local
32+
33+
# Testing
34+
.pytest_cache/
35+
.coverage
36+
htmlcov/
37+
38+
# Logs
39+
*.log
40+
41+
# Data (if you don't need this in container)
42+
data/
43+
44+
# Terraform
45+
terraform-infrastructure/

src/app/agents/agents_state.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
{
22
"cora": {
3-
"id": "asst_xPTPWSqJrKKTxhweUZMwXxNb",
3+
"id": "asst_PlBscdXbszB9KSwdKsY4O2ft",
44
"hash": "ec1323afb9692d92de14373b05eb60026bb3738f5fb3303976f74d5c40536092",
5-
"status": "created"
5+
"status": "existing"
66
},
77
"interior_designer": {
8-
"id": "asst_6YVlwwf9MRv91UCEZhKXYggr",
8+
"id": "asst_RbfreBvHmlR1JVPvUjfMRGvX",
99
"hash": "0fbee2d10b87eee5a9d0bd87a9d7af60f327c9093edd47d8e6683505db5aabba",
10-
"status": "created"
10+
"status": "existing"
1111
},
1212
"inventory_agent": {
13-
"id": "asst_cFo2Nh5ncBmw9ZimlOkzBITv",
13+
"id": "asst_nZQaxH8eRuZywig5umn4OHfA",
1414
"hash": "87deafceb6532b78ef075dd0e084909c4177286a0808cb9755c9a289f0076ba3",
15-
"status": "created"
15+
"status": "existing"
1616
},
1717
"customer_loyalty": {
18-
"id": "asst_4yiWqVI0AyJ46yKFwpnNjRrS",
18+
"id": "asst_DMnuT808C6ydiTwmgWLnwXz8",
1919
"hash": "1e0ffd8c5b4dac8cd247b90966b513f89b5c1c366edd476ca164d27b86dc276c",
20-
"status": "created"
20+
"status": "existing"
2121
},
2222
"cart_manager": {
23-
"id": "asst_QChARJsqcQrWKLRhOUt7ojOG",
23+
"id": "asst_2PzHUwpLAFoT2KIlGd45xrg3",
2424
"hash": "bd3985311b2b5e0d4d88ae4583c5c3b36113d6ddbbee67e6d36924f34292ccab",
25-
"status": "created"
25+
"status": "existing"
2626
}
2727
}

src/app/agents/cartManagerAgent_initializer.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/app/agents/customerLoyaltyAgent_initializer.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/app/agents/deploy_real_agents.py

Lines changed: 125 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -112,51 +112,66 @@ def deploy_agents():
112112
statuses = {}
113113

114114
try:
115-
# Define a Scoped Credential to force the correct scope for AI Foundry
116-
class ScopedCredential:
117-
def __init__(self, credential, scope):
118-
self.credential = credential
119-
self.scope = scope
120-
121-
def get_token(self, *scopes, **kwargs):
122-
# Ignore requested scopes and use the forced one
123-
return self.credential.get_token(self.scope, **kwargs)
124-
125-
print("Using DefaultAzureCredential with forced scope: https://ai.azure.com/.default")
126-
base_credential = DefaultAzureCredential()
127-
credential = ScopedCredential(base_credential, "https://ai.azure.com/.default")
115+
print("Initializing Azure AI Project Client...")
128116

129-
# Use the constructor directly with all required arguments
117+
# Use DefaultAzureCredential for authentication
118+
credential = DefaultAzureCredential()
119+
120+
# Get required environment variables
130121
sub_id = os.getenv("AZURE_SUBSCRIPTION_ID")
131122
rg = os.getenv("AZURE_RESOURCE_GROUP")
132123
project_name = os.getenv("AZURE_AI_PROJECT_NAME")
133124

125+
if not all([sub_id, rg, project_name]):
126+
raise ValueError("Missing required environment variables: AZURE_SUBSCRIPTION_ID, AZURE_RESOURCE_GROUP, AZURE_AI_PROJECT_NAME")
127+
134128
# Fix endpoint domain if needed (Cognitive Services -> AI Services)
135129
if project_endpoint and "cognitiveservices.azure.com" in project_endpoint:
136-
print(f"Adjusting endpoint domain from cognitiveservices.azure.com to services.ai.azure.com")
130+
print(f"Converting endpoint domain: cognitiveservices.azure.com -> services.ai.azure.com")
137131
project_endpoint = project_endpoint.replace("cognitiveservices.azure.com", "services.ai.azure.com")
132+
os.environ["AZURE_AI_PROJECT_ENDPOINT"] = project_endpoint
133+
134+
# Construct proper project endpoint: https://<hub>.services.ai.azure.com/api/projects/<project>
135+
# Remove any existing path segments first
136+
base_endpoint = project_endpoint.split("/api/")[0] # Get just the base URL
137+
base_endpoint = base_endpoint.rstrip('/')
138+
139+
# Now add the proper API path with project name
140+
full_project_endpoint = f"{base_endpoint}/api/projects/{project_name}"
141+
142+
print(f"Project Endpoint (base): {base_endpoint}")
143+
print(f"Project Endpoint (full): {full_project_endpoint}")
144+
print(f"Subscription: {sub_id}")
145+
print(f"Resource Group: {rg}")
146+
print(f"Project Name: {project_name}")
147+
148+
# Initialize AIProjectClient with endpoint and credential
149+
# The SDK requires the full project endpoint
150+
project_client = AIProjectClient(
151+
endpoint=full_project_endpoint,
152+
credential=credential
153+
)
154+
155+
print("Successfully initialized AIProjectClient")
156+
print("Fetching existing agents...")
157+
158+
existing_agents = {}
159+
try:
160+
agent_list = list(project_client.agents.list_agents())
161+
existing_agents = {a.name: a for a in agent_list}
162+
print(f"Found {len(existing_agents)} existing agent(s)")
163+
except Exception as list_err:
164+
print(f"Could not list existing agents (may be first run): {list_err}")
165+
existing_agents = {}
138166

139-
if sub_id and rg and project_name:
140-
# Append project path if not present
141-
if "/api/projects/" not in project_endpoint:
142-
project_endpoint = f"{project_endpoint.rstrip('/')}/api/projects/{project_name}"
143-
144-
print(f"Initializing AIProjectClient with endpoint={project_endpoint}")
145-
# AIProjectClient(endpoint, credential, **kwargs)
146-
project_client = AIProjectClient(
147-
endpoint=project_endpoint,
148-
credential=credential,
149-
subscription_id=sub_id,
150-
resource_group_name=rg,
151-
project_name=project_name
152-
)
153-
else:
154-
raise ValueError("Missing required environment variables for AIProjectClient")
155-
156-
existing_agents = {a.name: a for a in project_client.agents.list_agents()}
157167
except Exception as e:
158-
print(f"⚠ Unable to query existing agents: {e}")
168+
print(f"ERROR initializing AIProjectClient: {e}")
169+
import traceback
170+
traceback.print_exc()
171+
print("\nFalling back to local pseudo-agents...")
159172
existing_agents = {}
173+
# Don't exit - continue with fallback IDs
174+
project_client = None
160175

161176
for cfg in agents_config:
162177
name = cfg["name"]
@@ -165,52 +180,75 @@ def get_token(self, *scopes, **kwargs):
165180
instr_hash = _hash_instructions(instr)
166181
prior_hash = prior_state.get(env_var, {}).get("hash")
167182

168-
# Idempotent logic
183+
# Skip if no project client available
184+
if project_client is None:
185+
print(f"[{env_var}] No project client - using fallback ID")
186+
fallback_id = f"asst_local_{env_var}"
187+
deployed_agents[env_var] = fallback_id
188+
statuses[env_var] = "fallback-no-client"
189+
continue
190+
191+
# Idempotent logic - check if agent already exists
169192
if name in existing_agents:
170193
agent_obj = existing_agents[name]
171194
agent_id = getattr(agent_obj, "id", None) or getattr(agent_obj, "agentId", f"unknown-{env_var}")
195+
172196
# Attempt update if instructions changed
173197
if prior_hash and prior_hash != instr_hash:
174-
print(f"🔄 Updating agent (instructions changed): {name}")
198+
print(f"[{env_var}] Updating agent (instructions changed): {name}")
175199
try:
176200
# Try native update if available
177201
try:
178202
project_client.agents.update_agent(agent_id=agent_id, instructions=instr)
179203
statuses[env_var] = "updated"
204+
print(f"[{env_var}] Successfully updated: {agent_id}")
180205
except Exception:
181206
# Fallback recreate strategy
207+
print(f"[{env_var}] Update not supported, recreating...")
182208
try:
183209
project_client.agents.delete_agent(agent_id)
184210
except Exception:
185211
pass
186-
new_agent = project_client.agents.create_agent(model=cfg["model"], name=name, instructions=instr)
212+
new_agent = project_client.agents.create_agent(
213+
model=cfg["model"],
214+
name=name,
215+
instructions=instr
216+
)
187217
agent_id = new_agent.id
188218
statuses[env_var] = "recreated"
189-
print(f"✅ Agent updated: {agent_id}")
219+
print(f"[{env_var}] Successfully recreated: {agent_id}")
190220
except Exception as ue:
191-
print(f" Failed to update {name}: {ue}")
221+
print(f"[{env_var}] Failed to update {name}: {ue}")
192222
statuses[env_var] = "existing-no-update"
193223
deployed_agents[env_var] = agent_id
194224
else:
195-
print(f" Reusing existing agent: {name} ({agent_id})")
225+
print(f"[{env_var}] Reusing existing agent: {name} ({agent_id})")
196226
deployed_agents[env_var] = agent_id
197227
statuses[env_var] = "existing"
198228
continue
199229

200230
# Create new agent
201-
print(f"📦 Creating agent: {name}")
231+
print(f"[{env_var}] Creating new agent: {name}")
202232
try:
203-
agent = project_client.agents.create_agent(model=cfg["model"], name=name, instructions=instr)
233+
agent = project_client.agents.create_agent(
234+
model=cfg["model"],
235+
name=name,
236+
instructions=instr
237+
)
204238
agent_id = agent.id
205239
deployed_agents[env_var] = agent_id
206240
statuses[env_var] = "created"
207-
print(f"✅ Created: {name} -> {agent_id}")
241+
print(f"[{env_var}] SUCCESS - Created agent: {agent_id}")
208242
except Exception as ce:
209-
print(f"❌ Failed to create {name}: {ce}")
243+
print(f"[{env_var}] FAILED to create {name}: {ce}")
244+
import traceback
245+
traceback.print_exc()
246+
247+
# Use fallback local ID
210248
fallback_id = f"asst_local_{env_var}"
211249
deployed_agents[env_var] = fallback_id
212-
statuses[env_var] = "fallback-local"
213-
print(f" Using fallback local simulation: {fallback_id}")
250+
statuses[env_var] = "fallback-creation-failed"
251+
print(f"[{env_var}] Using fallback local simulation: {fallback_id}")
214252

215253
# Persist state (hash + id)
216254
new_state = {}
@@ -224,35 +262,59 @@ def get_token(self, *scopes, **kwargs):
224262
try:
225263
with open(state_path, "w", encoding="utf-8") as sf:
226264
json.dump(new_state, sf, indent=2)
227-
print(f"📝 State file updated: {state_path}")
265+
print(f"[STATE] State file updated: {state_path}")
228266
except Exception as se:
229-
print(f" Failed to write state file: {se}")
267+
print(f"WARNING: Failed to write state file: {se}")
230268

231269
# Update .env with real agent IDs (early propagation)
232270
env_path = os.path.join(os.path.dirname(__file__), '..', '..', '.env')
233271
if os.path.exists(env_path):
234272
try:
235273
with open(env_path, 'r', encoding='utf-8') as f:
236-
lines = f.readlines()
274+
content = f.read()
275+
276+
# Replace each agent ID
277+
for var, aid in deployed_agents.items():
278+
# Use regex to replace the value after the = sign
279+
import re
280+
pattern = rf'^{re.escape(var)}=.*$'
281+
replacement = f'{var}={aid}'
282+
content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
283+
284+
# Also fix the project endpoint domain in .env
285+
if "cognitiveservices.azure.com" in content:
286+
print("Fixing endpoint domains in .env...")
287+
content = content.replace(
288+
"AZURE_AI_PROJECT_ENDPOINT=https://aif-",
289+
"# AZURE_AI_PROJECT_ENDPOINT=https://aif-" # Comment out old
290+
)
291+
# Add corrected endpoint after the Azure AI Foundry section
292+
if "# Azure AI Foundry Configuration" in content:
293+
content = content.replace(
294+
"# Azure AI Foundry Configuration\n",
295+
"# Azure AI Foundry Configuration\n# Note: Agents API uses .services.ai.azure.com domain\n"
296+
)
297+
237298
with open(env_path, 'w', encoding='utf-8') as f:
238-
for line in lines:
239-
wrote = False
240-
for var, aid in deployed_agents.items():
241-
if line.startswith(f"{var}="):
242-
f.write(f"{var}={aid}\n")
243-
wrote = True
244-
break
245-
if not wrote:
246-
f.write(line)
247-
print(f"✅ Updated .env with agent IDs: {env_path}")
299+
f.write(content)
300+
301+
print(f"[{env_var}] Updated .env with agent IDs: {env_path}")
302+
print("Agent IDs written:")
303+
for var, aid in deployed_agents.items():
304+
print(f" {var}: {aid}")
248305
except Exception as ee:
249-
print(f"⚠ Failed to update .env: {ee}")
306+
print(f"WARNING: Failed to update .env: {ee}")
307+
import traceback
308+
traceback.print_exc()
250309
else:
251-
print(" .env file not found for agent ID propagation")
310+
print("INFO: .env file not found for agent ID propagation")
252311

253-
print("\nSummary:")
312+
print("\n" + "=" * 70)
313+
print("DEPLOYMENT SUMMARY")
314+
print("=" * 70)
254315
for k, v in deployed_agents.items():
255-
print(f" {k}: {v} ({statuses.get(k)})")
316+
status = statuses.get(k, "unknown")
317+
print(f" {k}: {v} [{status}]")
256318

257319
# Emit structured JSON sentinel block for Terraform parsing
258320
payload = {"agents": deployed_agents, "statuses": statuses}

0 commit comments

Comments
 (0)