Skip to content

Commit 689cc8b

Browse files
committed
backend fix
1 parent 1f96a3c commit 689cc8b

File tree

9 files changed

+422
-67
lines changed

9 files changed

+422
-67
lines changed

src/a2a/status_automation.ps1

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,18 @@
1-
param(
2-
[string]$WebAppName = $env:WEB_APP_NAME,
3-
[string]$StatusUrl = $env:A2A_AUTOMATION_STATUS_URL
4-
)
5-
6-
# Check A2A Automation Framework Status
1+
# Check A2A Automation Framework Status
72
Write-Host "Checking A2A Automation Framework status..."
8-
$processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
9-
Where-Object { $_.CommandLine -like "*automated_main*" }
10-
11-
if ($processes) {
3+
= Get-Process -Name "python" -ErrorAction SilentlyContinue | Where-Object { .CommandLine -like "*automated_main*" }
4+
if () {
125
Write-Host "A2A Automation Framework is RUNNING"
13-
Write-Host "Processes: $($processes.Count)"
14-
$processes | Select-Object ProcessId,Name,CreationDate | Format-Table -AutoSize
6+
Write-Host "Processes: 0"
7+
| Format-Table Id,ProcessName,StartTime
158
} else {
169
Write-Host "A2A Automation Framework is STOPPED"
1710
}
1811

19-
# Build status URL dynamically
20-
if (-not $StatusUrl -and $WebAppName) {
21-
$StatusUrl = "https://$WebAppName.azurewebsites.net/a2a/automation/status"
22-
}
23-
24-
if (-not $StatusUrl) {
25-
Write-Host "Automation endpoint not accessible (missing WebAppName or StatusUrl)"
26-
return
27-
}
28-
2912
# Check automation endpoint
3013
try {
31-
$status = Invoke-RestMethod -Uri $StatusUrl -TimeoutSec 5
32-
Write-Host "Automation Status: $($status | ConvertTo-Json -Compress)"
14+
= Invoke-RestMethod -Uri "https://zava-8e5461ee-app.azurewebsites.net/a2a/automation/status" -TimeoutSec 5
15+
Write-Host "Automation Status: "
3316
} catch {
3417
Write-Host "Automation endpoint not accessible"
3518
}

src/app/agents/local_agent_processor.py

Lines changed: 131 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import json
22
import os
3+
import logging
4+
import traceback
5+
import uuid
36
from typing import List, Dict, Any, Generator
47
from azure.ai.inference import ChatCompletionsClient
58
from azure.core.credentials import AzureKeyCredential
@@ -34,6 +37,12 @@ class LocalAgentProcessor:
3437
def __init__(self, agent_id: str, domain: str):
3538
self.agent_id = agent_id
3639
self.domain = domain
40+
self.logger = logging.getLogger("local_agent_processor")
41+
self._debug = os.getenv("A2A_DEBUG", "").lower() in {"1", "true", "yes"}
42+
self._last_error_id: str | None = None
43+
self._last_error_detail: str | None = None
44+
if self._debug:
45+
self.logger.setLevel(logging.DEBUG)
3746

3847
# Initialize GPT client (shared across all agents)
3948
endpoint = (
@@ -58,6 +67,13 @@ def __init__(self, agent_id: str, domain: str):
5867
self.use_gpt = False
5968
self.client = None
6069
self.model = deployment
70+
self._inference_endpoint: str | None = None
71+
self._using_key_auth: bool = False
72+
73+
# Default to managed identity / Entra ID auth in Azure.
74+
# Only use key-based auth when explicitly enabled.
75+
self._prefer_aad: bool = os.getenv("A2A_PREFER_AAD", "true").lower() in {"1", "true", "yes"}
76+
self._allow_key_auth: bool = os.getenv("A2A_USE_KEY_AUTH", "").lower() in {"1", "true", "yes"}
6177

6278
if endpoint and deployment:
6379
# Convert endpoint to Foundry format if needed
@@ -67,21 +83,67 @@ def __init__(self, agent_id: str, domain: str):
6783
if not foundry_endpoint.endswith('/models'):
6884
foundry_endpoint = f"{foundry_endpoint.rstrip('/')}/models"
6985

86+
self._inference_endpoint = foundry_endpoint
87+
7088
try:
71-
# Prefer key auth if present; otherwise use token-based auth (Managed Identity in cloud).
72-
if api_key:
89+
# Prefer token-based auth (Managed Identity in cloud).
90+
# Keys are often disabled (disableLocalAuth) and should be opt-in.
91+
if api_key and self._allow_key_auth and not self._prefer_aad:
7392
credential = AzureKeyCredential(api_key)
93+
self._using_key_auth = True
7494
else:
7595
credential = get_inference_credential(
7696
api_key=None,
7797
default_credential=get_default_credential(),
7898
endpoint=foundry_endpoint,
7999
)
100+
self._using_key_auth = False
80101
self.client = ChatCompletionsClient(endpoint=foundry_endpoint, credential=credential)
81102
self.use_gpt = True
82103
except Exception:
104+
self.logger.exception("Failed to initialize ChatCompletionsClient (endpoint=%s, deployment=%s)", foundry_endpoint, deployment)
83105
self.use_gpt = False
84106

107+
def _format_exception_detail(self, error_id: str, exc: Exception) -> str:
108+
"""Format a detailed, UI-safe error message for troubleshooting."""
109+
parts: list[str] = []
110+
parts.append(f"error_id={error_id}")
111+
parts.append(f"agent_domain={self.domain}")
112+
parts.append(f"model={self.model}")
113+
parts.append(f"endpoint={self._inference_endpoint}")
114+
parts.append(f"auth_mode={'key' if self._using_key_auth else 'aad'}")
115+
116+
# Helpful identity context when running on Azure
117+
azure_client_id = os.getenv("AZURE_CLIENT_ID")
118+
if azure_client_id:
119+
parts.append(f"AZURE_CLIENT_ID={azure_client_id}")
120+
121+
parts.append(f"exception_type={type(exc).__name__}")
122+
parts.append(f"exception={str(exc)}")
123+
124+
# Try to extract HTTP response details if present
125+
response = getattr(exc, "response", None)
126+
if response is not None:
127+
status_code = getattr(response, "status_code", None)
128+
if status_code is not None:
129+
parts.append(f"http_status={status_code}")
130+
131+
headers = getattr(response, "headers", None) or {}
132+
for header_name in ("x-ms-request-id", "x-ms-client-request-id", "x-ms-correlation-request-id"):
133+
header_value = headers.get(header_name)
134+
if header_value:
135+
parts.append(f"{header_name}={header_value}")
136+
137+
status_code = getattr(exc, "status_code", None)
138+
if status_code is not None and "http_status=" not in "\n".join(parts):
139+
parts.append(f"http_status={status_code}")
140+
141+
# Include traceback only in debug mode
142+
if self._debug:
143+
parts.append("traceback=\n" + traceback.format_exc())
144+
145+
return "\n".join(parts)
146+
85147
def _call_gpt(self, user_message: str, conversation_history: List[Dict[str, str]] | None = None, additional_context: Dict[str, Any] | None = None) -> str:
86148
"""Call GPT with domain-specific system prompt."""
87149
if not self.use_gpt:
@@ -115,10 +177,55 @@ def _call_gpt(self, user_message: str, conversation_history: List[Dict[str, str]
115177
temperature=0.7,
116178
max_tokens=500
117179
)
118-
180+
self._last_error_id = None
181+
self._last_error_detail = None
119182
return response.choices[0].message.content
120183
except Exception as e:
121-
return f"I'm having trouble connecting right now. Error: {str(e)[:100]}"
184+
# If we attempted key auth and the resource has local auth disabled,
185+
# transparently retry once with Entra ID (managed identity) auth.
186+
if self._using_key_auth and self._inference_endpoint:
187+
try:
188+
error_code = getattr(e, "error", None)
189+
if hasattr(e, "response") and getattr(getattr(e, "response", None), "status_code", None) == 403:
190+
# Heuristic: the common case we see is AuthenticationTypeDisabled.
191+
msg = str(e) or ""
192+
if "AuthenticationTypeDisabled" in msg or "Key based authentication is disabled" in msg:
193+
self.logger.warning(
194+
"Key auth disabled for inference endpoint; retrying with AAD (domain=%s, endpoint=%s)",
195+
self.domain,
196+
self._inference_endpoint,
197+
)
198+
aad_cred = get_inference_credential(
199+
api_key=None,
200+
default_credential=get_default_credential(),
201+
endpoint=self._inference_endpoint,
202+
)
203+
self.client = ChatCompletionsClient(endpoint=self._inference_endpoint, credential=aad_cred)
204+
self._using_key_auth = False
205+
retry = self.client.complete(
206+
messages=messages,
207+
model=self.model,
208+
temperature=0.7,
209+
max_tokens=500,
210+
)
211+
self._last_error_id = None
212+
self._last_error_detail = None
213+
return retry.choices[0].message.content
214+
except Exception:
215+
# Fall through to normal error handling
216+
pass
217+
218+
error_id = uuid.uuid4().hex
219+
self._last_error_id = error_id
220+
self._last_error_detail = self._format_exception_detail(error_id, e)
221+
self.logger.exception(
222+
"GPT call failed (error_id=%s, domain=%s, endpoint=%s, model=%s)",
223+
error_id,
224+
self.domain,
225+
self._inference_endpoint,
226+
self.model,
227+
)
228+
return f"I'm having trouble connecting right now. (error_id={error_id})"
122229

123230
def _interior_design(self, user_message: str, conversation_history: List[Dict[str, str]] | None = None, additional_context: Dict[str, Any] | None = None) -> Dict[str, Any]:
124231
# Check if this is an image generation request
@@ -154,12 +261,20 @@ def _interior_design(self, user_message: str, conversation_history: List[Dict[st
154261

155262
def _inventory(self, user_message: str, conversation_history: List[Dict[str, str]] | None = None, additional_context: Dict[str, Any] | None = None) -> Dict[str, Any]:
156263
answer = self._call_gpt(user_message, conversation_history, additional_context)
157-
return {"answer": answer}
264+
result: Dict[str, Any] = {"answer": answer}
265+
if self._last_error_detail:
266+
result["error"] = self._last_error_detail
267+
result["error_id"] = self._last_error_id
268+
return result
158269

159270
def _customer_loyalty(self, customer_id: str | None, conversation_history: List[Dict[str, str]] | None = None, additional_context: Dict[str, Any] | None = None) -> Dict[str, Any]:
160271
user_message = f"Check loyalty benefits for customer {customer_id or 'current customer'}"
161272
answer = self._call_gpt(user_message, conversation_history, additional_context)
162-
return {"answer": answer, "discount_percentage": "10"}
273+
result: Dict[str, Any] = {"answer": answer, "discount_percentage": "10"}
274+
if self._last_error_detail:
275+
result["error"] = self._last_error_detail
276+
result["error_id"] = self._last_error_id
277+
return result
163278

164279
def _cart_management(self, user_message: str, conversation_history: List[Dict[str, str]] | None = None, additional_context: Dict[str, Any] | None = None) -> Dict[str, Any]:
165280
cart = additional_context.get("cart", []) if additional_context else []
@@ -176,11 +291,19 @@ def _cart_management(self, user_message: str, conversation_history: List[Dict[st
176291

177292
# Get GPT response about the cart action
178293
answer = self._call_gpt(user_message, conversation_history, {"cart": cart})
179-
return {"answer": answer, "cart": cart}
294+
result: Dict[str, Any] = {"answer": answer, "cart": cart}
295+
if self._last_error_detail:
296+
result["error"] = self._last_error_detail
297+
result["error_id"] = self._last_error_id
298+
return result
180299

181300
def _cora(self, user_message: str, conversation_history: List[Dict[str, str]] | None = None, additional_context: Dict[str, Any] | None = None) -> Dict[str, Any]:
182301
answer = self._call_gpt(user_message, conversation_history, additional_context)
183-
return {"answer": answer}
302+
result: Dict[str, Any] = {"answer": answer}
303+
if self._last_error_detail:
304+
result["error"] = self._last_error_detail
305+
result["error_id"] = self._last_error_id
306+
return result
184307

185308
def run_conversation_with_text_stream(
186309
self,

src/app/tools/singleAgentExample.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import time
3+
import logging
34
from azure.ai.inference import ChatCompletionsClient
45
from azure.core.credentials import AzureKeyCredential
56
from azure.identity import DefaultAzureCredential
@@ -29,6 +30,9 @@
2930

3031
# Global client instance
3132
client = None
33+
logger = logging.getLogger("single_agent_example")
34+
if os.getenv("A2A_DEBUG", "").lower() in {"1", "true", "yes"}:
35+
logging.basicConfig(level=logging.DEBUG)
3236

3337
def get_client():
3438
"""Lazily initialize and return the MSFT Foundry client"""
@@ -115,15 +119,19 @@ def generate_response(text_input):
115119
]
116120

117121
# Call MSFT Foundry chat API
118-
response = client.complete(
122+
try:
123+
response = client.complete(
119124
model=deployment,
120125
messages=messages,
121126
max_tokens=10000,
122127
temperature=1.0,
123128
top_p=1.0,
124129
frequency_penalty=0,
125130
presence_penalty=0
126-
)
131+
)
132+
except Exception:
133+
logger.exception("singleAgentExample GPT call failed (endpoint=%s, deployment=%s)", getattr(client, "_endpoint", None), deployment)
134+
raise
127135

128136
end_sum = time.time()
129137
print(f"generate_response Execution Time: {end_sum - start_time} seconds")

0 commit comments

Comments
 (0)