From fe72933f4257c3e113bdb6a0924bdcd6a4d533e8 Mon Sep 17 00:00:00 2001 From: v-vfarias Date: Mon, 3 Nov 2025 14:48:26 +0000 Subject: [PATCH 01/37] Revise Azure deployment workflow with validations Updated workflow for full Azure AI Foundry setup and model deployment, including validation steps for Azure login, subscription, resource group, and AI Foundry resource. --- .github/workflows/setup-and-deploy.yml | 82 ++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 .github/workflows/setup-and-deploy.yml diff --git a/.github/workflows/setup-and-deploy.yml b/.github/workflows/setup-and-deploy.yml new file mode 100644 index 0000000..acfdd64 --- /dev/null +++ b/.github/workflows/setup-and-deploy.yml @@ -0,0 +1,82 @@ +name: Full Azure AI Foundry Setup and Model Deployment with Validation + +on: + workflow_dispatch: + +jobs: + setup-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install Azure CLI + run: | + curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash + + - name: Login to Azure + run: | + az login --service-principal \ + --username ${{ secrets.AZURE_CLIENT_ID }} \ + --password ${{ secrets.AZURE_CLIENT_SECRET }} \ + --tenant ${{ secrets.AZURE_TENANT_ID }} + + - name: Validate Azure Login + run: | + az account show || { echo "Azure login failed"; exit 1; } + + - name: Set subscription + run: | + az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Validate Subscription + run: | + az account show --query "id" -o tsv | grep ${{ secrets.AZURE_SUBSCRIPTION_ID }} || { echo "Subscription validation failed"; exit 1; } + + - name: Create Resource Group + run: | + az group create --name ${{ secrets.AZURE_RESOURCE_GROUP }} --location ${{ secrets.AZURE_REGION }} + + - name: Validate Resource Group + run: | + az group exists --name ${{ secrets.AZURE_RESOURCE_GROUP }} || { echo "Resource group creation failed"; exit 1; } + + - name: Create Azure AI Foundry Resource + run: | + az resource create \ + --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \ + --name ${{ secrets.AZURE_PROJECT_NAME }} \ + --resource-type "Microsoft.AI/Foundry" \ + --location ${{ secrets.AZURE_REGION }} \ + --properties '{}' + + - name: Validate AI Foundry Resource + run: | + az resource show \ + --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \ + --name ${{ secrets.AZURE_PROJECT_NAME }} \ + --resource-type "Microsoft.AI/Foundry" || { echo "AI Foundry resource creation failed"; exit 1; } + + - name: Deploy GPT-4o Model + run: | + az rest --method post \ + --url "https://management.azure.com/subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/resourceGroups/${{ secrets.AZURE_RESOURCE_GROUP }}/providers/Microsoft.AI/Foundry/${{ secrets.AZURE_PROJECT_NAME }}/deployments?api-version=2024-05-01-preview" \ + --body '{ + "name": "gpt-4o-deployment", + "properties": { + "model": "${{ secrets.AZURE_MODEL_NAME }}", + "scaleSettings": { "scaleType": "Standard" } + } + }' + + - name: Validate Deployment + run: | + STATUS=$(az rest --method get \ + --url "https://management.azure.com/subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/resourceGroups/${{ secrets.AZURE_RESOURCE_GROUP }}/providers/Microsoft.AI/Foundry/${{ secrets.AZURE_PROJECT_NAME }}/deployments?api-version=2024-05-01-preview" \ + --query "value[0].properties.provisioningState" -o tsv) + if [ "$STATUS" != "Succeeded" ]; then + echo "Deployment failed with status: $STATUS" + exit 1 + fi + echo "Deployment succeeded!" From 75288ba28500798513e27e69ac8c9a6248a54270 Mon Sep 17 00:00:00 2001 From: v-vfarias Date: Mon, 3 Nov 2025 14:52:37 +0000 Subject: [PATCH 02/37] Change resource type from AI Foundry to Cognitive Services --- .github/workflows/setup-and-deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/setup-and-deploy.yml b/.github/workflows/setup-and-deploy.yml index acfdd64..3de0d4f 100644 --- a/.github/workflows/setup-and-deploy.yml +++ b/.github/workflows/setup-and-deploy.yml @@ -47,7 +47,7 @@ jobs: az resource create \ --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \ --name ${{ secrets.AZURE_PROJECT_NAME }} \ - --resource-type "Microsoft.AI/Foundry" \ + --resource-type "Microsoft.CognitiveServices/accounts" \ --location ${{ secrets.AZURE_REGION }} \ --properties '{}' @@ -56,7 +56,7 @@ jobs: az resource show \ --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \ --name ${{ secrets.AZURE_PROJECT_NAME }} \ - --resource-type "Microsoft.AI/Foundry" || { echo "AI Foundry resource creation failed"; exit 1; } + --resource-type "Microsoft.CognitiveServices/accounts" || { echo "AI Foundry resource creation failed"; exit 1; } - name: Deploy GPT-4o Model run: | From 703480be30a066ebd3a94f38792f47021eb95d22 Mon Sep 17 00:00:00 2001 From: v-vfarias Date: Mon, 3 Nov 2025 15:19:12 +0000 Subject: [PATCH 03/37] Update Azure resource type to Microsoft.AI/Foundry --- .github/workflows/setup-and-deploy.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/setup-and-deploy.yml b/.github/workflows/setup-and-deploy.yml index 3de0d4f..8b998ad 100644 --- a/.github/workflows/setup-and-deploy.yml +++ b/.github/workflows/setup-and-deploy.yml @@ -47,16 +47,19 @@ jobs: az resource create \ --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \ --name ${{ secrets.AZURE_PROJECT_NAME }} \ - --resource-type "Microsoft.CognitiveServices/accounts" \ + --resource-type "Microsoft.AI/Foundry" \ --location ${{ secrets.AZURE_REGION }} \ - --properties '{}' + --properties '{}' \ + --api-version 2024-05-01-preview - name: Validate AI Foundry Resource run: | az resource show \ --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \ --name ${{ secrets.AZURE_PROJECT_NAME }} \ - --resource-type "Microsoft.CognitiveServices/accounts" || { echo "AI Foundry resource creation failed"; exit 1; } + --resource-type "Microsoft.AI/Foundry" \ + --api-version 2024-05-01-preview || { echo "AI Foundry resource creation failed"; exit 1; + - name: Deploy GPT-4o Model run: | From 46d92666fc807b44e5276a7f04992de7ba983bf1 Mon Sep 17 00:00:00 2001 From: v-vfarias Date: Mon, 3 Nov 2025 15:57:28 +0000 Subject: [PATCH 04/37] Change Azure AI Foundry resource creation method Updated the resource creation method for Azure AI Foundry to use az rest command with a POST request and a JSON body. --- .github/workflows/setup-and-deploy.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/setup-and-deploy.yml b/.github/workflows/setup-and-deploy.yml index 8b998ad..747ea1e 100644 --- a/.github/workflows/setup-and-deploy.yml +++ b/.github/workflows/setup-and-deploy.yml @@ -44,13 +44,14 @@ jobs: - name: Create Azure AI Foundry Resource run: | - az resource create \ - --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \ - --name ${{ secrets.AZURE_PROJECT_NAME }} \ - --resource-type "Microsoft.AI/Foundry" \ - --location ${{ secrets.AZURE_REGION }} \ - --properties '{}' \ - --api-version 2024-05-01-preview + az rest --method post \ + --url "https://management.azure.com/subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/resourceGroups/${{ secrets.AZURE_RESOURCE_GROUP }}/providers/Microsoft.ProjectAI/projects/${{ secrets.AZURE_PROJECT_NAME }}?api-version=2024-05-01-preview" \ + --body '{ + "location": "${{ secrets.AZURE_REGION }}", + "properties": { + "description": "AI Foundry project created via GitHub Actions" + } + }' - name: Validate AI Foundry Resource run: | From 3b7c02f95ec57fcf31e8c33dfdc347e9147fd2d3 Mon Sep 17 00:00:00 2001 From: v-vfarias Date: Mon, 3 Nov 2025 16:09:18 +0000 Subject: [PATCH 05/37] Refactor deployment workflow for GPT-4o model Updated the workflow to deploy GPT-4o to Azure AI Foundry using REST API calls instead of Azure CLI commands. Removed validation steps and added token retrieval and endpoint fetching. --- .github/workflows/setup-and-deploy.yml | 77 ++++++++++---------------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/.github/workflows/setup-and-deploy.yml b/.github/workflows/setup-and-deploy.yml index 747ea1e..09fc153 100644 --- a/.github/workflows/setup-and-deploy.yml +++ b/.github/workflows/setup-and-deploy.yml @@ -1,10 +1,10 @@ -name: Full Azure AI Foundry Setup and Model Deployment with Validation +name: Deploy GPT-4o to Azure AI Foundry via REST API on: workflow_dispatch: jobs: - setup-and-deploy: + deploy-ai-foundry: runs-on: ubuntu-latest steps: @@ -22,65 +22,48 @@ jobs: --password ${{ secrets.AZURE_CLIENT_SECRET }} \ --tenant ${{ secrets.AZURE_TENANT_ID }} - - name: Validate Azure Login + - name: Get Access Token for AI Foundry + id: get-token run: | - az account show || { echo "Azure login failed"; exit 1; } + TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv) + echo "token=$TOKEN" >> $GITHUB_OUTPUT - - name: Set subscription + - name: Create AI Foundry Project run: | - az account set --subscription ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Validate Subscription - run: | - az account show --query "id" -o tsv | grep ${{ secrets.AZURE_SUBSCRIPTION_ID }} || { echo "Subscription validation failed"; exit 1; } - - - name: Create Resource Group - run: | - az group create --name ${{ secrets.AZURE_RESOURCE_GROUP }} --location ${{ secrets.AZURE_REGION }} - - - name: Validate Resource Group - run: | - az group exists --name ${{ secrets.AZURE_RESOURCE_GROUP }} || { echo "Resource group creation failed"; exit 1; } - - - name: Create Azure AI Foundry Resource - run: | - az rest --method post \ - --url "https://management.azure.com/subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/resourceGroups/${{ secrets.AZURE_RESOURCE_GROUP }}/providers/Microsoft.ProjectAI/projects/${{ secrets.AZURE_PROJECT_NAME }}?api-version=2024-05-01-preview" \ - --body '{ - "location": "${{ secrets.AZURE_REGION }}", - "properties": { - "description": "AI Foundry project created via GitHub Actions" - } + curl -X POST "https://ai.azure.com/api/projects" \ + -H "Authorization: Bearer ${{ steps.get-token.outputs.token }}" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "'"${{ secrets.AZURE_PROJECT_NAME }}"'", + "location": "'"${{ secrets.AZURE_REGION }}"'", + "description": "AI Foundry project created via GitHub Actions" }' - - name: Validate AI Foundry Resource - run: | - az resource show \ - --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \ - --name ${{ secrets.AZURE_PROJECT_NAME }} \ - --resource-type "Microsoft.AI/Foundry" \ - --api-version 2024-05-01-preview || { echo "AI Foundry resource creation failed"; exit 1; - - - name: Deploy GPT-4o Model run: | - az rest --method post \ - --url "https://management.azure.com/subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/resourceGroups/${{ secrets.AZURE_RESOURCE_GROUP }}/providers/Microsoft.AI/Foundry/${{ secrets.AZURE_PROJECT_NAME }}/deployments?api-version=2024-05-01-preview" \ - --body '{ + curl -X POST "https://ai.azure.com/api/projects/${{ secrets.AZURE_PROJECT_NAME }}/deployments" \ + -H "Authorization: Bearer ${{ steps.get-token.outputs.token }}" \ + -H "Content-Type: application/json" \ + -d '{ "name": "gpt-4o-deployment", - "properties": { - "model": "${{ secrets.AZURE_MODEL_NAME }}", - "scaleSettings": { "scaleType": "Standard" } - } + "model": "'"${{ secrets.AZURE_MODEL_NAME }}"'", + "scaleSettings": { "scaleType": "Standard" } }' - name: Validate Deployment run: | - STATUS=$(az rest --method get \ - --url "https://management.azure.com/subscriptions/${{ secrets.AZURE_SUBSCRIPTION_ID }}/resourceGroups/${{ secrets.AZURE_RESOURCE_GROUP }}/providers/Microsoft.AI/Foundry/${{ secrets.AZURE_PROJECT_NAME }}/deployments?api-version=2024-05-01-preview" \ - --query "value[0].properties.provisioningState" -o tsv) + STATUS=$(curl -s -X GET "https://ai.azure.com/api/projects/${{ secrets.AZURE_PROJECT_NAME }}/deployments/gpt-4o-deployment" \ + -H "Authorization: Bearer ${{ steps.get-token.outputs.token }}" \ + | jq -r '.status') if [ "$STATUS" != "Succeeded" ]; then echo "Deployment failed with status: $STATUS" exit 1 fi echo "Deployment succeeded!" + + - name: Get Endpoint + run: | + ENDPOINT=$(curl -s -X GET "https://ai.azure.com/api/projects/${{ secrets.AZURE_PROJECT_NAME }}" \ + -H "Authorization: Bearer ${{ steps.get-token.outputs.token }}" \ + | jq -r '.endpoint') + echo "Azure AI Foundry Endpoint: $ENDPOINT" From 394d1e7d28fe671d609af32afba55a2404181b93 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 9 Feb 2026 18:16:33 +0000 Subject: [PATCH 06/37] Update to a supported model --- infra/main.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index 3675047..9c63ba9 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -48,7 +48,7 @@ param location string @metadata({azd: { type: 'location' usageName: [ - 'OpenAI.GlobalStandard.gpt-4o-mini,10' + 'OpenAI.GlobalStandard.gpt-4.1-mini,10' ]} }) param aiDeploymentsLocation string From b63469d7037bf839b978fedd29a963f4d23f837d Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Thu, 12 Feb 2026 13:00:11 +0000 Subject: [PATCH 07/37] Refactor .env loading logic and improve error handling --- .../trail_guide_agent/trail_guide_agent.py | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/agents/trail_guide_agent/trail_guide_agent.py b/src/agents/trail_guide_agent/trail_guide_agent.py index 5f80de5..a1e044a 100644 --- a/src/agents/trail_guide_agent/trail_guide_agent.py +++ b/src/agents/trail_guide_agent/trail_guide_agent.py @@ -5,25 +5,44 @@ from azure.ai.projects import AIProjectClient from azure.ai.projects.models import PromptAgentDefinition -# Load environment variables from repository root -repo_root = Path(__file__).parent.parent.parent -env_file = repo_root / '.env' -load_dotenv(env_file) +# Find and load .env from repo root (walk upwards until found) +current = Path(__file__).resolve() +env_file = None +for parent in [current.parent, *current.parents]: + candidate = parent / ".env" + if candidate.exists(): + env_file = candidate + break + +if env_file is None: + raise FileNotFoundError("Could not find a .env file by walking up from this script location.") + +loaded = load_dotenv(env_file) +if not loaded: + raise RuntimeError(f"Failed to load .env from: {env_file}") + +# Use the exact key name you set in .env (case-sensitive) +endpoint = os.getenv("AZURE_AI_PROJECT_ENDPOINT") +if not endpoint: + raise KeyError( + f"Missing AZURE_AI_PROJECT_ENDPOINT. .env loaded from: {env_file}. " + "Ensure the key name matches exactly (including casing)." + ) # Read instructions from prompt file -prompt_file = Path(__file__).parent / 'prompts' / 'v2_instructions.txt' -with open(prompt_file, 'r') as f: +prompt_file = Path(__file__).parent / "prompts" / "v2_instructions.txt" +with open(prompt_file, "r", encoding="utf-8") as f: instructions = f.read().strip() project_client = AIProjectClient( - endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], + endpoint=endpoint, credential=DefaultAzureCredential(), ) agent = project_client.agents.create_version( agent_name=os.environ["AGENT_NAME"], definition=PromptAgentDefinition( - model=os.getenv("MODEL_NAME", "gpt-4.1"), # Use Global Standard model + model=os.getenv("MODEL_NAME", "gpt-4.1"), instructions=instructions, ), ) From b61e3f6b02e1be8bac38d5f5920802d02423c7d7 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Thu, 12 Feb 2026 13:01:56 +0000 Subject: [PATCH 08/37] Refactor .env loading logic and improve error handling --- .../trail_guide_agent/trail_guide_agent.py | 35 +++++-------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/src/agents/trail_guide_agent/trail_guide_agent.py b/src/agents/trail_guide_agent/trail_guide_agent.py index a1e044a..5f80de5 100644 --- a/src/agents/trail_guide_agent/trail_guide_agent.py +++ b/src/agents/trail_guide_agent/trail_guide_agent.py @@ -5,44 +5,25 @@ from azure.ai.projects import AIProjectClient from azure.ai.projects.models import PromptAgentDefinition -# Find and load .env from repo root (walk upwards until found) -current = Path(__file__).resolve() -env_file = None -for parent in [current.parent, *current.parents]: - candidate = parent / ".env" - if candidate.exists(): - env_file = candidate - break - -if env_file is None: - raise FileNotFoundError("Could not find a .env file by walking up from this script location.") - -loaded = load_dotenv(env_file) -if not loaded: - raise RuntimeError(f"Failed to load .env from: {env_file}") - -# Use the exact key name you set in .env (case-sensitive) -endpoint = os.getenv("AZURE_AI_PROJECT_ENDPOINT") -if not endpoint: - raise KeyError( - f"Missing AZURE_AI_PROJECT_ENDPOINT. .env loaded from: {env_file}. " - "Ensure the key name matches exactly (including casing)." - ) +# Load environment variables from repository root +repo_root = Path(__file__).parent.parent.parent +env_file = repo_root / '.env' +load_dotenv(env_file) # Read instructions from prompt file -prompt_file = Path(__file__).parent / "prompts" / "v2_instructions.txt" -with open(prompt_file, "r", encoding="utf-8") as f: +prompt_file = Path(__file__).parent / 'prompts' / 'v2_instructions.txt' +with open(prompt_file, 'r') as f: instructions = f.read().strip() project_client = AIProjectClient( - endpoint=endpoint, + endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=DefaultAzureCredential(), ) agent = project_client.agents.create_version( agent_name=os.environ["AGENT_NAME"], definition=PromptAgentDefinition( - model=os.getenv("MODEL_NAME", "gpt-4.1"), + model=os.getenv("MODEL_NAME", "gpt-4.1"), # Use Global Standard model instructions=instructions, ), ) From 90ac6de7a6dcc75105169522032ff7a307068981 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Fri, 13 Feb 2026 10:26:05 +0000 Subject: [PATCH 09/37] Fix deployment location string for AI model in Bicep file --- infra/main.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index 9c63ba9..3675047 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -48,7 +48,7 @@ param location string @metadata({azd: { type: 'location' usageName: [ - 'OpenAI.GlobalStandard.gpt-4.1-mini,10' + 'OpenAI.GlobalStandard.gpt-4o-mini,10' ]} }) param aiDeploymentsLocation string From a67bc74d35f32121a09ebfd3ad17ea28a284f311 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 16 Feb 2026 10:44:54 +0000 Subject: [PATCH 10/37] Add interactive test script for Trail Guide Agent --- src/tests/interact_with_agent2.py | 87 +++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/tests/interact_with_agent2.py diff --git a/src/tests/interact_with_agent2.py b/src/tests/interact_with_agent2.py new file mode 100644 index 0000000..6f830d4 --- /dev/null +++ b/src/tests/interact_with_agent2.py @@ -0,0 +1,87 @@ +""" +Interactive test script for Trail Guide Agent. +Allows you to chat with the agent from the terminal. +""" +import os +import sys +from pathlib import Path + +from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.ai.agents import AgentsClient + +# Load environment variables from repository root +repo_root = Path(__file__).parent.parent.parent +env_file = repo_root / ".env" +load_dotenv(env_file) + + +def interact_with_agent(): + """Start an interactive chat session with the Trail Guide Agent.""" + + client = AgentsClient( + endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], + credential=DefaultAzureCredential(), + ) + + # AgentsClient typically uses agent_id (not name) + agent_id = os.getenv("AGENT_ID") + if not agent_id: + raise RuntimeError( + "Missing AGENT_ID in environment. Set AGENT_ID in your .env to the Agent's ID." + ) + + print(f"\n{'='*60}") + print("Trail Guide Agent - Interactive Chat") + print(f"Agent ID: {agent_id}") + print(f"{'='*60}") + print("\nType your questions or requests. Type 'exit' or 'quit' to end the session.\n") + + thread = client.create_thread() + print(f"Started conversation (Thread ID: {thread.id})\n") + + try: + while True: + user_input = input("You: ").strip() + + if not user_input: + continue + + if user_input.lower() in ["exit", "quit", "q"]: + print("\nEnding session. Goodbye!") + break + + client.create_message( + thread_id=thread.id, + role="user", + content=user_input, + ) + + # Run the agent + client.create_and_process_run( + thread_id=thread.id, + agent_id=agent_id, + ) + + # Show latest assistant message + messages = client.list_messages(thread_id=thread.id) + for message in messages: + if message.role == "assistant": + print(f"\nAgent: {message.content[0].text.value}\n") + break + + except KeyboardInterrupt: + print("\n\nSession interrupted. Goodbye!") + except Exception as e: + print(f"\nError: {e}") + sys.exit(1) + finally: + try: + client.delete_thread(thread.id) + print("Conversation thread cleaned up.") + except Exception: + pass + + +if __name__ == "__main__": + interact_with_agent() \ No newline at end of file From ab84c46f7299fe7b66dff805f047793377a48a35 Mon Sep 17 00:00:00 2001 From: Victor Farias Date: Mon, 16 Feb 2026 02:51:03 -0800 Subject: [PATCH 11/37] Updated path finder --- src/agents/trail_guide_agent/trail_guide_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/trail_guide_agent/trail_guide_agent.py b/src/agents/trail_guide_agent/trail_guide_agent.py index 5f80de5..04a951c 100644 --- a/src/agents/trail_guide_agent/trail_guide_agent.py +++ b/src/agents/trail_guide_agent/trail_guide_agent.py @@ -6,7 +6,7 @@ from azure.ai.projects.models import PromptAgentDefinition # Load environment variables from repository root -repo_root = Path(__file__).parent.parent.parent +repo_root = Path(__file__).parent.parent.parent.parent env_file = repo_root / '.env' load_dotenv(env_file) From 79fa0aae885508ec5e897cfd3e928637876d2384 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 16 Feb 2026 10:57:52 +0000 Subject: [PATCH 12/37] Enhance agent ID resolution logic and improve error handling in interactive test script --- src/tests/interact_with_agent2.py | 47 +++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/tests/interact_with_agent2.py b/src/tests/interact_with_agent2.py index 6f830d4..1357851 100644 --- a/src/tests/interact_with_agent2.py +++ b/src/tests/interact_with_agent2.py @@ -16,6 +16,40 @@ load_dotenv(env_file) +def _resolve_agent_id(client: AgentsClient, agent_name: str) -> str: + """ + Resolve agent_id from AGENT_ID or by looking up an agent by name. + """ + agent_id = os.getenv("AGENT_ID") + if agent_id: + return agent_id + + # Fallback: search by name + matches = [] + for a in client.list_agents(): + if getattr(a, "name", None) == agent_name: + matches.append(a) + + if not matches: + # Print a short, actionable hint without requiring copy/paste + print(f"\nError: AGENT_ID not set and no agent found named '{agent_name}'.") + print("Available agents (name -> id):") + for a in client.list_agents(): + print(f" - {getattr(a, 'name', '')} -> {getattr(a, 'id', '')}") + raise RuntimeError( + "Set AGENT_ID in .env, or set AGENT_NAME to an existing agent name." + ) + + if len(matches) > 1: + print(f"\nError: Multiple agents found named '{agent_name}'.") + print("Candidates (id):") + for a in matches: + print(f" - {a.id}") + raise RuntimeError("Set AGENT_ID in .env to disambiguate.") + + return matches[0].id + + def interact_with_agent(): """Start an interactive chat session with the Trail Guide Agent.""" @@ -24,16 +58,13 @@ def interact_with_agent(): credential=DefaultAzureCredential(), ) - # AgentsClient typically uses agent_id (not name) - agent_id = os.getenv("AGENT_ID") - if not agent_id: - raise RuntimeError( - "Missing AGENT_ID in environment. Set AGENT_ID in your .env to the Agent's ID." - ) + agent_name = os.getenv("AGENT_NAME", "trail-guide-v1") + agent_id = _resolve_agent_id(client, agent_name) print(f"\n{'='*60}") print("Trail Guide Agent - Interactive Chat") - print(f"Agent ID: {agent_id}") + print(f"Agent name: {agent_name}") + print(f"Agent id: {agent_id}") print(f"{'='*60}") print("\nType your questions or requests. Type 'exit' or 'quit' to end the session.\n") @@ -57,13 +88,11 @@ def interact_with_agent(): content=user_input, ) - # Run the agent client.create_and_process_run( thread_id=thread.id, agent_id=agent_id, ) - # Show latest assistant message messages = client.list_messages(thread_id=thread.id) for message in messages: if message.role == "assistant": From b9727ad77ea7d4061516b4f0872f0d7f4ec63cb6 Mon Sep 17 00:00:00 2001 From: Victor Farias Date: Mon, 16 Feb 2026 03:26:31 -0800 Subject: [PATCH 13/37] Update requirements.txt to pin package versions for stability --- requirements.txt | 56 ++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2743d0e..533fd2b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,50 +1,50 @@ # GenAI Operations Dependencies - Trail Guide Agent # Adventure Works Outdoor Gear - AI Trail Assistant -# Updated: 2026-01-16 +# Updated: 2026-02-16 -# Core Azure AI Projects SDK (Latest Versions) -azure-ai-projects>=1.0.0b1 -azure-identity>=1.15.0 -azure-core>=1.29.0 -azure-mgmt-resource>=23.0.0 +# Core Azure AI Projects SDK (Pinned for lab stability) +azure-ai-projects==1.0.0b7 +azure-identity==1.19.0 +azure-core==1.32.0 +azure-mgmt-resource==23.2.0 # Evaluation and ML libraries -pandas>=2.1.0 -numpy>=1.25.0 -scikit-learn>=1.3.0 +pandas==2.2.3 +numpy==1.26.4 +scikit-learn==1.5.2 # Development and testing -pytest>=7.4.0 -pytest-asyncio>=0.21.0 -pytest-cov>=4.1.0 -black>=23.0.0 -isort>=5.12.0 -flake8>=6.0.0 -mypy>=1.5.0 +pytest==8.3.4 +pytest-asyncio==0.24.0 +pytest-cov==6.0.0 +black==24.10.0 +isort==5.13.2 +flake8==7.1.1 +mypy==1.13.0 # Environment and Configuration -python-dotenv>=1.0.0 +python-dotenv==1.0.1 # API Integration -requests>=2.31.0 -httpx>=0.25.0 +requests==2.32.3 +httpx==0.28.1 # Logging and Monitoring -structlog>=23.1.0 +structlog==24.4.0 # Jupyter notebook support (optional) -jupyter>=1.0.0 -ipykernel>=6.25.0 +jupyter==1.1.1 +ipykernel==6.29.5 # Data processing and visualization -openpyxl>=3.1.0 -matplotlib>=3.7.0 -seaborn>=0.12.0 +openpyxl==3.1.5 +matplotlib==3.9.3 +seaborn==0.13.2 # Documentation and Reporting -markdown>=3.5.0 -Jinja2>=3.1.0 +markdown==3.7 +Jinja2==3.1.4 # GitHub Actions and automation -pyyaml>=6.0.0 +pyyaml==6.0.2 From 840aa11bedab4441db969d7daa784404c21a83a5 Mon Sep 17 00:00:00 2001 From: v-vfarias Date: Mon, 16 Feb 2026 11:50:24 +0000 Subject: [PATCH 14/37] Update requirements.txt for minimum version constraints Updated package versions to use minimum required versions for stability. --- requirements.txt | 57 ++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/requirements.txt b/requirements.txt index 533fd2b..6372e0e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,50 +1,49 @@ # GenAI Operations Dependencies - Trail Guide Agent # Adventure Works Outdoor Gear - AI Trail Assistant -# Updated: 2026-02-16 +# Updated: 2026-01-16 -# Core Azure AI Projects SDK (Pinned for lab stability) -azure-ai-projects==1.0.0b7 -azure-identity==1.19.0 -azure-core==1.32.0 -azure-mgmt-resource==23.2.0 +# Core Azure AI Projects SDK (Latest Versions) +azure-ai-projects>=1.0.0b1 +azure-identity>=1.15.0 +azure-core>=1.29.0 +azure-mgmt-resource>=23.0.0 # Evaluation and ML libraries -pandas==2.2.3 -numpy==1.26.4 -scikit-learn==1.5.2 +pandas>=2.1.0 +numpy>=1.25.0 +scikit-learn>=1.3.0 # Development and testing -pytest==8.3.4 -pytest-asyncio==0.24.0 -pytest-cov==6.0.0 -black==24.10.0 -isort==5.13.2 -flake8==7.1.1 -mypy==1.13.0 +pytest>=7.4.0 +pytest-asyncio>=0.21.0 +pytest-cov>=4.1.0 +black>=23.0.0 +isort>=5.12.0 +flake8>=6.0.0 +mypy>=1.5.0 # Environment and Configuration -python-dotenv==1.0.1 +python-dotenv>=1.0.0 # API Integration -requests==2.32.3 -httpx==0.28.1 +requests>=2.31.0 +httpx>=0.25.0 # Logging and Monitoring -structlog==24.4.0 +structlog>=23.1.0 # Jupyter notebook support (optional) -jupyter==1.1.1 -ipykernel==6.29.5 +jupyter>=1.0.0 +ipykernel>=6.25.0 # Data processing and visualization -openpyxl==3.1.5 -matplotlib==3.9.3 -seaborn==0.13.2 +openpyxl>=3.1.0 +matplotlib>=3.7.0 +seaborn>=0.12.0 # Documentation and Reporting -markdown==3.7 -Jinja2==3.1.4 +markdown>=3.5.0 +Jinja2>=3.1.0 # GitHub Actions and automation -pyyaml==6.0.2 - +pyyaml>=6.0.0 From a27dfa29f8f4e068500882c2fd3b9bc40ae44dfc Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 16 Feb 2026 11:57:06 +0000 Subject: [PATCH 15/37] Refactor interactive test script for Trail Guide Agent: update agent ID resolution, enhance message handling, and improve run execution logic --- requirements.txt | 3 +- .../trail_guide_agent/trail_guide_agent2.py | 70 ++++++++++ src/tests/interact_with_agent2.py | 125 ++++++++++++------ 3 files changed, 153 insertions(+), 45 deletions(-) create mode 100644 src/agents/trail_guide_agent/trail_guide_agent2.py diff --git a/requirements.txt b/requirements.txt index 2743d0e..c5bb397 100644 --- a/requirements.txt +++ b/requirements.txt @@ -46,5 +46,4 @@ markdown>=3.5.0 Jinja2>=3.1.0 # GitHub Actions and automation -pyyaml>=6.0.0 - +pyyaml>=6.0.0 \ No newline at end of file diff --git a/src/agents/trail_guide_agent/trail_guide_agent2.py b/src/agents/trail_guide_agent/trail_guide_agent2.py new file mode 100644 index 0000000..2e53b76 --- /dev/null +++ b/src/agents/trail_guide_agent/trail_guide_agent2.py @@ -0,0 +1,70 @@ +import os +from pathlib import Path + +from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient + +# Newer SDKs sometimes rename or move models; keep a safe import fallback. +try: + from azure.ai.projects.models import PromptAgentDefinition # type: ignore +except Exception: # pragma: no cover + PromptAgentDefinition = None # type: ignore + +# Load environment variables from repository root +repo_root = Path(__file__).parent.parent.parent.parent +env_file = repo_root / ".env" +load_dotenv(env_file) + +# Read instructions from prompt file +prompt_file = Path(__file__).parent / "prompts" / "v2_instructions.txt" +with open(prompt_file, "r", encoding="utf-8") as f: + instructions = f.read().strip() + +project_client = AIProjectClient( + endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], + credential=DefaultAzureCredential(), +) + +agent_name = os.getenv("AGENT_NAME", "trail-guide") +model_name = os.getenv("MODEL_NAME", "gpt-4.1") + +agents = project_client.agents + +# Prefer the newer create_agent API when present; fall back to older create_version. +if hasattr(agents, "create_agent"): + if PromptAgentDefinition is not None: + agent = agents.create_agent( + agent_name=agent_name, + definition=PromptAgentDefinition( + model=model_name, + instructions=instructions, + ), + ) + else: + # Fallback for SDKs that accept flat parameters (keeps script working across versions). + agent = agents.create_agent( + agent_name=agent_name, + model=model_name, + instructions=instructions, + ) +elif hasattr(agents, "create_version"): + # Older template-style API + agent = agents.create_version( + agent_name=agent_name, + definition=PromptAgentDefinition( # type: ignore[misc] + model=model_name, + instructions=instructions, + ), + ) +else: + raise AttributeError( + "No compatible agent creation method found on project_client.agents " + "(expected create_agent or create_version)." + ) + +print( + f"Agent created (id: {getattr(agent, 'id', '')}, " + f"name: {getattr(agent, 'name', agent_name)}, " + f"version: {getattr(agent, 'version', '')})" +) \ No newline at end of file diff --git a/src/tests/interact_with_agent2.py b/src/tests/interact_with_agent2.py index 1357851..e5dbf57 100644 --- a/src/tests/interact_with_agent2.py +++ b/src/tests/interact_with_agent2.py @@ -4,11 +4,12 @@ """ import os import sys +import time from pathlib import Path from dotenv import load_dotenv from azure.identity import DefaultAzureCredential -from azure.ai.agents import AgentsClient +from azure.ai.projects import AIProjectClient # Load environment variables from repository root repo_root = Path(__file__).parent.parent.parent @@ -16,59 +17,89 @@ load_dotenv(env_file) -def _resolve_agent_id(client: AgentsClient, agent_name: str) -> str: +def _extract_message_text(message) -> str: """ - Resolve agent_id from AGENT_ID or by looking up an agent by name. + SDK message shapes vary by version. Handle common cases: + - message.content is a string + - message.content is a list with items that have .text.value + - message.content is a list with items that have .text """ - agent_id = os.getenv("AGENT_ID") - if agent_id: - return agent_id - - # Fallback: search by name - matches = [] - for a in client.list_agents(): - if getattr(a, "name", None) == agent_name: - matches.append(a) - - if not matches: - # Print a short, actionable hint without requiring copy/paste - print(f"\nError: AGENT_ID not set and no agent found named '{agent_name}'.") - print("Available agents (name -> id):") - for a in client.list_agents(): - print(f" - {getattr(a, 'name', '')} -> {getattr(a, 'id', '')}") - raise RuntimeError( - "Set AGENT_ID in .env, or set AGENT_NAME to an existing agent name." + content = getattr(message, "content", "") + if isinstance(content, str): + return content + + if isinstance(content, list) and content: + first = content[0] + text = getattr(first, "text", None) + if isinstance(text, str): + return text + if text is not None and hasattr(text, "value"): + return text.value # type: ignore[attr-defined] + + # Last resort: stringify the first content item + return str(first) + + return str(content) + + +def _run_agent(agents_client, thread_id: str, agent_name: str): + """ + Prefer newer 'create_and_process_run' if available. + Otherwise: create_run + poll get_run until terminal state. + """ + if hasattr(agents_client, "create_and_process_run"): + return agents_client.create_and_process_run(thread_id=thread_id, agent_name=agent_name) + + if not hasattr(agents_client, "create_run"): + raise AttributeError( + "No compatible run method found on project_client.agents " + "(expected create_and_process_run or create_run)." ) - if len(matches) > 1: - print(f"\nError: Multiple agents found named '{agent_name}'.") - print("Candidates (id):") - for a in matches: - print(f" - {a.id}") - raise RuntimeError("Set AGENT_ID in .env to disambiguate.") + run = agents_client.create_run(thread_id=thread_id, agent_name=agent_name) - return matches[0].id + # Poll until run completes (status names vary slightly across versions) + terminal_statuses = {"completed", "failed", "cancelled", "canceled", "expired"} + in_progress_statuses = {"queued", "in_progress", "running"} + + get_run = getattr(agents_client, "get_run", None) + if get_run is None: + # If SDK doesn't support polling, return the run object we have. + return run + + while True: + status = str(getattr(run, "status", "")).lower() + if status in terminal_statuses: + return run + if status not in in_progress_statuses and status: + # Unknown status; stop polling to avoid infinite loop. + return run + time.sleep(0.8) + run = get_run(thread_id=thread_id, run_id=run.id) def interact_with_agent(): """Start an interactive chat session with the Trail Guide Agent.""" - client = AgentsClient( + # Initialize project client + project_client = AIProjectClient( endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=DefaultAzureCredential(), ) + # Get agent name from environment or use default agent_name = os.getenv("AGENT_NAME", "trail-guide-v1") - agent_id = _resolve_agent_id(client, agent_name) print(f"\n{'='*60}") print("Trail Guide Agent - Interactive Chat") - print(f"Agent name: {agent_name}") - print(f"Agent id: {agent_id}") + print(f"Agent: {agent_name}") print(f"{'='*60}") print("\nType your questions or requests. Type 'exit' or 'quit' to end the session.\n") - thread = client.create_thread() + agents = project_client.agents + + # Create a thread for the conversation + thread = agents.create_thread() print(f"Started conversation (Thread ID: {thread.id})\n") try: @@ -82,31 +113,39 @@ def interact_with_agent(): print("\nEnding session. Goodbye!") break - client.create_message( + # Send message to agent + agents.create_message( thread_id=thread.id, role="user", content=user_input, ) - client.create_and_process_run( - thread_id=thread.id, - agent_id=agent_id, - ) + # Run the agent (SDK-version tolerant) + _run_agent(agents, thread.id, agent_name) + + # Get latest assistant response + messages = agents.list_messages(thread_id=thread.id) - messages = client.list_messages(thread_id=thread.id) - for message in messages: - if message.role == "assistant": - print(f"\nAgent: {message.content[0].text.value}\n") + latest_assistant = None + for m in messages: + if getattr(m, "role", None) == "assistant": + latest_assistant = m break + if latest_assistant is None: + print("\nAgent: (No assistant message returned.)\n") + else: + print(f"\nAgent: {_extract_message_text(latest_assistant)}\n") + except KeyboardInterrupt: print("\n\nSession interrupted. Goodbye!") except Exception as e: print(f"\nError: {e}") sys.exit(1) finally: + # Clean up thread try: - client.delete_thread(thread.id) + agents.delete_thread(thread.id) print("Conversation thread cleaned up.") except Exception: pass From 45cd0d008d7e0ff5934ef0ab0f37edbc0cae7283 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 16 Feb 2026 13:53:49 +0000 Subject: [PATCH 16/37] Update requirements.txt for Azure AI Projects SDK version and refactor interact_with_agent script to use AzureDeveloperCliCredential --- requirements.txt | 2 +- src/tests/debug_sdk.py | 29 ++++++ src/tests/interact_with_agent.py | 7 +- src/tests/interact_with_agent2.py | 155 ------------------------------ 4 files changed, 35 insertions(+), 158 deletions(-) create mode 100644 src/tests/debug_sdk.py delete mode 100644 src/tests/interact_with_agent2.py diff --git a/requirements.txt b/requirements.txt index c5bb397..5097b89 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ # Updated: 2026-01-16 # Core Azure AI Projects SDK (Latest Versions) -azure-ai-projects>=1.0.0b1 +azure-ai-projects==1.0.0b5 azure-identity>=1.15.0 azure-core>=1.29.0 azure-mgmt-resource>=23.0.0 diff --git a/src/tests/debug_sdk.py b/src/tests/debug_sdk.py new file mode 100644 index 0000000..d62956c --- /dev/null +++ b/src/tests/debug_sdk.py @@ -0,0 +1,29 @@ +import azure.ai.projects +import os +from pathlib import Path +from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient + +# Load env +repo_root = Path(__file__).parent.parent.parent +env_file = repo_root / '.env' +load_dotenv(env_file) + +print(f"Azure AI Projects SDK Version: {azure.ai.projects.__version__}") + +try: + project_client = AIProjectClient( + endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], + credential=DefaultAzureCredential(), + ) + + print("\n--- Project Client Attributes ---") + print([d for d in dir(project_client) if not d.startswith('_')]) + + print("\n--- Agents Operations Attributes (project_client.agents) ---") + # This will list what commands ARE available (e.g., create_thread, list_agents, etc.) + print([d for d in dir(project_client.agents) if not d.startswith('_')]) + +except Exception as e: + print(f"Error initializing client: {e}") \ No newline at end of file diff --git a/src/tests/interact_with_agent.py b/src/tests/interact_with_agent.py index 8253fda..90de49b 100644 --- a/src/tests/interact_with_agent.py +++ b/src/tests/interact_with_agent.py @@ -6,7 +6,7 @@ import sys from pathlib import Path from dotenv import load_dotenv -from azure.identity import DefaultAzureCredential +from azure.identity import AzureDeveloperCliCredential from azure.ai.projects import AIProjectClient # Load environment variables from repository root @@ -20,7 +20,10 @@ def interact_with_agent(): # Initialize project client project_client = AIProjectClient( endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], - credential=DefaultAzureCredential(), + credential=AzureDeveloperCliCredential(), + subscription_id=os.environ.get("AZURE_SUBSCRIPTION_ID"), + resource_group_name=os.environ.get("AZURE_RESOURCE_GROUP"), + project_name=os.environ.get("AZURE_AI_PROJECT_NAME") ) # Get agent name from environment or use default diff --git a/src/tests/interact_with_agent2.py b/src/tests/interact_with_agent2.py deleted file mode 100644 index e5dbf57..0000000 --- a/src/tests/interact_with_agent2.py +++ /dev/null @@ -1,155 +0,0 @@ -""" -Interactive test script for Trail Guide Agent. -Allows you to chat with the agent from the terminal. -""" -import os -import sys -import time -from pathlib import Path - -from dotenv import load_dotenv -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient - -# Load environment variables from repository root -repo_root = Path(__file__).parent.parent.parent -env_file = repo_root / ".env" -load_dotenv(env_file) - - -def _extract_message_text(message) -> str: - """ - SDK message shapes vary by version. Handle common cases: - - message.content is a string - - message.content is a list with items that have .text.value - - message.content is a list with items that have .text - """ - content = getattr(message, "content", "") - if isinstance(content, str): - return content - - if isinstance(content, list) and content: - first = content[0] - text = getattr(first, "text", None) - if isinstance(text, str): - return text - if text is not None and hasattr(text, "value"): - return text.value # type: ignore[attr-defined] - - # Last resort: stringify the first content item - return str(first) - - return str(content) - - -def _run_agent(agents_client, thread_id: str, agent_name: str): - """ - Prefer newer 'create_and_process_run' if available. - Otherwise: create_run + poll get_run until terminal state. - """ - if hasattr(agents_client, "create_and_process_run"): - return agents_client.create_and_process_run(thread_id=thread_id, agent_name=agent_name) - - if not hasattr(agents_client, "create_run"): - raise AttributeError( - "No compatible run method found on project_client.agents " - "(expected create_and_process_run or create_run)." - ) - - run = agents_client.create_run(thread_id=thread_id, agent_name=agent_name) - - # Poll until run completes (status names vary slightly across versions) - terminal_statuses = {"completed", "failed", "cancelled", "canceled", "expired"} - in_progress_statuses = {"queued", "in_progress", "running"} - - get_run = getattr(agents_client, "get_run", None) - if get_run is None: - # If SDK doesn't support polling, return the run object we have. - return run - - while True: - status = str(getattr(run, "status", "")).lower() - if status in terminal_statuses: - return run - if status not in in_progress_statuses and status: - # Unknown status; stop polling to avoid infinite loop. - return run - time.sleep(0.8) - run = get_run(thread_id=thread_id, run_id=run.id) - - -def interact_with_agent(): - """Start an interactive chat session with the Trail Guide Agent.""" - - # Initialize project client - project_client = AIProjectClient( - endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], - credential=DefaultAzureCredential(), - ) - - # Get agent name from environment or use default - agent_name = os.getenv("AGENT_NAME", "trail-guide-v1") - - print(f"\n{'='*60}") - print("Trail Guide Agent - Interactive Chat") - print(f"Agent: {agent_name}") - print(f"{'='*60}") - print("\nType your questions or requests. Type 'exit' or 'quit' to end the session.\n") - - agents = project_client.agents - - # Create a thread for the conversation - thread = agents.create_thread() - print(f"Started conversation (Thread ID: {thread.id})\n") - - try: - while True: - user_input = input("You: ").strip() - - if not user_input: - continue - - if user_input.lower() in ["exit", "quit", "q"]: - print("\nEnding session. Goodbye!") - break - - # Send message to agent - agents.create_message( - thread_id=thread.id, - role="user", - content=user_input, - ) - - # Run the agent (SDK-version tolerant) - _run_agent(agents, thread.id, agent_name) - - # Get latest assistant response - messages = agents.list_messages(thread_id=thread.id) - - latest_assistant = None - for m in messages: - if getattr(m, "role", None) == "assistant": - latest_assistant = m - break - - if latest_assistant is None: - print("\nAgent: (No assistant message returned.)\n") - else: - print(f"\nAgent: {_extract_message_text(latest_assistant)}\n") - - except KeyboardInterrupt: - print("\n\nSession interrupted. Goodbye!") - except Exception as e: - print(f"\nError: {e}") - sys.exit(1) - finally: - # Clean up thread - try: - agents.delete_thread(thread.id) - print("Conversation thread cleaned up.") - except Exception: - pass - - -if __name__ == "__main__": - interact_with_agent() \ No newline at end of file From d6c3b5e5b59b0c3bf9bd1aa0fe44b6027dadf2d6 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 16 Feb 2026 15:33:00 +0000 Subject: [PATCH 17/37] Update requirements.txt for Azure AI Projects SDK version and refactor interact_with_agent script for improved client handling and error management --- requirements.txt | 2 +- .../trail_guide_agent/trail_guide_agent2.py | 70 ------- src/tests/debug_sdk.py | 29 --- src/tests/interact_with_agent.py | 185 +++++++++++------- 4 files changed, 120 insertions(+), 166 deletions(-) delete mode 100644 src/agents/trail_guide_agent/trail_guide_agent2.py delete mode 100644 src/tests/debug_sdk.py diff --git a/requirements.txt b/requirements.txt index 5097b89..8e645cc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ # Updated: 2026-01-16 # Core Azure AI Projects SDK (Latest Versions) -azure-ai-projects==1.0.0b5 +azure-ai-projects>=1.0.0b8 azure-identity>=1.15.0 azure-core>=1.29.0 azure-mgmt-resource>=23.0.0 diff --git a/src/agents/trail_guide_agent/trail_guide_agent2.py b/src/agents/trail_guide_agent/trail_guide_agent2.py deleted file mode 100644 index 2e53b76..0000000 --- a/src/agents/trail_guide_agent/trail_guide_agent2.py +++ /dev/null @@ -1,70 +0,0 @@ -import os -from pathlib import Path - -from dotenv import load_dotenv -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient - -# Newer SDKs sometimes rename or move models; keep a safe import fallback. -try: - from azure.ai.projects.models import PromptAgentDefinition # type: ignore -except Exception: # pragma: no cover - PromptAgentDefinition = None # type: ignore - -# Load environment variables from repository root -repo_root = Path(__file__).parent.parent.parent.parent -env_file = repo_root / ".env" -load_dotenv(env_file) - -# Read instructions from prompt file -prompt_file = Path(__file__).parent / "prompts" / "v2_instructions.txt" -with open(prompt_file, "r", encoding="utf-8") as f: - instructions = f.read().strip() - -project_client = AIProjectClient( - endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], - credential=DefaultAzureCredential(), -) - -agent_name = os.getenv("AGENT_NAME", "trail-guide") -model_name = os.getenv("MODEL_NAME", "gpt-4.1") - -agents = project_client.agents - -# Prefer the newer create_agent API when present; fall back to older create_version. -if hasattr(agents, "create_agent"): - if PromptAgentDefinition is not None: - agent = agents.create_agent( - agent_name=agent_name, - definition=PromptAgentDefinition( - model=model_name, - instructions=instructions, - ), - ) - else: - # Fallback for SDKs that accept flat parameters (keeps script working across versions). - agent = agents.create_agent( - agent_name=agent_name, - model=model_name, - instructions=instructions, - ) -elif hasattr(agents, "create_version"): - # Older template-style API - agent = agents.create_version( - agent_name=agent_name, - definition=PromptAgentDefinition( # type: ignore[misc] - model=model_name, - instructions=instructions, - ), - ) -else: - raise AttributeError( - "No compatible agent creation method found on project_client.agents " - "(expected create_agent or create_version)." - ) - -print( - f"Agent created (id: {getattr(agent, 'id', '')}, " - f"name: {getattr(agent, 'name', agent_name)}, " - f"version: {getattr(agent, 'version', '')})" -) \ No newline at end of file diff --git a/src/tests/debug_sdk.py b/src/tests/debug_sdk.py deleted file mode 100644 index d62956c..0000000 --- a/src/tests/debug_sdk.py +++ /dev/null @@ -1,29 +0,0 @@ -import azure.ai.projects -import os -from pathlib import Path -from dotenv import load_dotenv -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient - -# Load env -repo_root = Path(__file__).parent.parent.parent -env_file = repo_root / '.env' -load_dotenv(env_file) - -print(f"Azure AI Projects SDK Version: {azure.ai.projects.__version__}") - -try: - project_client = AIProjectClient( - endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], - credential=DefaultAzureCredential(), - ) - - print("\n--- Project Client Attributes ---") - print([d for d in dir(project_client) if not d.startswith('_')]) - - print("\n--- Agents Operations Attributes (project_client.agents) ---") - # This will list what commands ARE available (e.g., create_thread, list_agents, etc.) - print([d for d in dir(project_client.agents) if not d.startswith('_')]) - -except Exception as e: - print(f"Error initializing client: {e}") \ No newline at end of file diff --git a/src/tests/interact_with_agent.py b/src/tests/interact_with_agent.py index 90de49b..1c9687d 100644 --- a/src/tests/interact_with_agent.py +++ b/src/tests/interact_with_agent.py @@ -1,90 +1,143 @@ -""" -Interactive test script for Trail Guide Agent. -Allows you to chat with the agent from the terminal. -""" import os import sys from pathlib import Path from dotenv import load_dotenv -from azure.identity import AzureDeveloperCliCredential +from azure.identity import AzureDeveloperCliCredential, DefaultAzureCredential from azure.ai.projects import AIProjectClient -# Load environment variables from repository root +# Load environment variables repo_root = Path(__file__).parent.parent.parent env_file = repo_root / '.env' load_dotenv(env_file) -def interact_with_agent(): - """Start an interactive chat session with the Trail Guide Agent.""" - - # Initialize project client - project_client = AIProjectClient( - endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], - credential=AzureDeveloperCliCredential(), - subscription_id=os.environ.get("AZURE_SUBSCRIPTION_ID"), - resource_group_name=os.environ.get("AZURE_RESOURCE_GROUP"), - project_name=os.environ.get("AZURE_AI_PROJECT_NAME") - ) - - # Get agent name from environment or use default - agent_name = os.getenv("AGENT_NAME", "trail-guide-v1") +def get_authenticated_client(): + """Robust client creation for both local and VM environments.""" + endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT") + # Try 'azd' credential first (best for lab VMs) + try: + return AIProjectClient( + endpoint=endpoint, + credential=AzureDeveloperCliCredential(), + ) + except Exception: + # Fallback to default (local debugging, etc.) + return AIProjectClient( + endpoint=endpoint, + credential=DefaultAzureCredential(), + ) + +def find_openai_connection(project_client): + """ + Auto-discovers the Azure OpenAI connection. + This ensures the script works regardless of what 'azd' names the connection. + """ + try: + connections = project_client.connections.list() + for conn in connections: + # Check for standard OpenAI connection types + if "AzureOpenAI" in str(conn) or (hasattr(conn, 'type') and conn.type == "AzureOpenAI"): + return conn.name + except Exception as e: + print(f"Warning: Could not list connections: {e}") + return None + +def interact_with_agent(): print(f"\n{'='*60}") print(f"Trail Guide Agent - Interactive Chat") - print(f"Agent: {agent_name}") print(f"{'='*60}") - print("\nType your questions or requests. Type 'exit' or 'quit' to end the session.\n") - # Create a thread for the conversation - thread = project_client.agents.create_thread() - print(f"Started conversation (Thread ID: {thread.id})\n") + # 1. Initialize Client + try: + project_client = get_authenticated_client() + except Exception as e: + print(f"Error connecting to project: {e}") + print("Tip: Run 'azd auth login' if running locally.") + return + + # 2. Find Agent ID + agent_name = os.getenv("AGENT_NAME", "trail-guide") + agent_id = None + print(f"Connecting to Agent: {agent_name}...") try: - while True: - # Get user input - user_input = input("You: ").strip() - - if not user_input: - continue - - if user_input.lower() in ['exit', 'quit', 'q']: - print("\nEnding session. Goodbye!") + agents = project_client.agents.list() + for agent in agents: + if agent.name == agent_name: + agent_id = agent.id break + + if not agent_id: + print(f"Error: Agent '{agent_name}' not found.") + print("Did you run 'python src/trail_guide_agent.py' to create it?") + return + + print(f"Success! Found Agent ID: {agent_id}") + + except Exception as e: + print(f"Error listing agents: {e}") + return + + # 3. Find Connection (The 'Magic' Step) + connection_name = find_openai_connection(project_client) + + if not connection_name: + # Fallback: Try the one we manually created, just in case + connection_name = "aoai-connection" + print(f"Warning: Auto-discovery failed. Trying fallback name: '{connection_name}'") + else: + print(f"Auto-detected Inference Connection: '{connection_name}'") + + # 4. Chat Loop + print("\nInitializing Chat Runtime...") + + try: + # Use the discovered connection name + with project_client.get_openai_client(connection_name=connection_name) as openai_client: - # Send message to agent - project_client.agents.create_message( - thread_id=thread.id, - role="user", - content=user_input - ) - - # Run the agent - run = project_client.agents.create_and_process_run( - thread_id=thread.id, - agent_name=agent_name - ) - - # Get the assistant's response - messages = project_client.agents.list_messages(thread_id=thread.id) + thread = openai_client.beta.threads.create() + print(f"Session Started (Thread ID: {thread.id})\n") + print("Type your questions. Type 'exit' to quit.\n") - # Find the latest assistant message - for message in messages: - if message.role == "assistant": - print(f"\nAgent: {message.content[0].text.value}\n") + while True: + user_input = input("You: ").strip() + if not user_input: continue + if user_input.lower() in ['exit', 'quit', 'q']: break - - except KeyboardInterrupt: - print("\n\nSession interrupted. Goodbye!") + + # Send + openai_client.beta.threads.messages.create( + thread_id=thread.id, + role="user", + content=user_input + ) + + # Run + run = openai_client.beta.threads.runs.create_and_poll( + thread_id=thread.id, + assistant_id=agent_id + ) + + # Receive + if run.status == 'completed': + messages = openai_client.beta.threads.messages.list( + thread_id=thread.id + ) + for msg in messages: + if msg.role == "assistant": + for content in msg.content: + if content.type == 'text': + print(f"\nAgent: {content.text.value}\n") + break + else: + print(f"Run status: {run.status}") + except Exception as e: - print(f"\nError: {e}") - sys.exit(1) - finally: - # Clean up thread - try: - project_client.agents.delete_thread(thread.id) - print(f"Conversation thread cleaned up.") - except: - pass + print(f"\nRuntime Error: {e}") + if "404" in str(e): + print("\nTroubleshooting 404:") + print("1. The project does not have a connection to Azure OpenAI.") + print("2. Verify 'azd up' completed successfully.") if __name__ == "__main__": - interact_with_agent() + interact_with_agent() \ No newline at end of file From 5561ebe233375c92640099c2afef790d272614e8 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 16 Feb 2026 16:07:08 +0000 Subject: [PATCH 18/37] Refactor main.bicep: remove AI project module and associated outputs for cleaner deployment structure --- infra/main.bicep | 59 +----------------------------------------------- 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 3675047..3d9ff30 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -108,61 +108,4 @@ var dependentResources = (enableHostedAgents) && !hasAcr ? union(aiProjectDepend resource: 'registry' connectionName: 'acr-connection' } -]) : aiProjectDependentResources - -// AI Project module -module aiProject 'core/ai/ai-project.bicep' = { - scope: rg - name: 'ai-project' - params: { - tags: tags - location: aiDeploymentsLocation - aiFoundryProjectName: aiFoundryProjectName - principalId: principalId - principalType: principalType - existingAiAccountName: aiFoundryResourceName - deployments: aiProjectDeployments - connections: aiProjectConnections - additionalDependentResources: dependentResources - enableMonitoring: enableMonitoring - enableHostedAgents: enableHostedAgents - } -} - -// Resources -output AZURE_RESOURCE_GROUP string = resourceGroupName -output AZURE_AI_ACCOUNT_ID string = aiProject.outputs.accountId -output AZURE_AI_PROJECT_ID string = aiProject.outputs.projectId -output AZURE_AI_FOUNDRY_PROJECT_ID string = aiProject.outputs.projectId -output AZURE_AI_ACCOUNT_NAME string = aiProject.outputs.aiServicesAccountName -output AZURE_AI_PROJECT_NAME string = aiProject.outputs.projectName - -// Endpoints -output AZURE_AI_PROJECT_ENDPOINT string = aiProject.outputs.AZURE_AI_PROJECT_ENDPOINT -output AZURE_OPENAI_ENDPOINT string = aiProject.outputs.AZURE_OPENAI_ENDPOINT -output APPLICATIONINSIGHTS_CONNECTION_STRING string = aiProject.outputs.APPLICATIONINSIGHTS_CONNECTION_STRING - -// Dependent Resources and Connections - -// ACR -output AZURE_AI_PROJECT_ACR_CONNECTION_NAME string = aiProject.outputs.dependentResources.registry.connectionName -output AZURE_CONTAINER_REGISTRY_ENDPOINT string = aiProject.outputs.dependentResources.registry.loginServer - -// Bing Search -output BING_GROUNDING_CONNECTION_NAME string = aiProject.outputs.dependentResources.bing_grounding.connectionName -output BING_GROUNDING_RESOURCE_NAME string = aiProject.outputs.dependentResources.bing_grounding.name -output BING_GROUNDING_CONNECTION_ID string = aiProject.outputs.dependentResources.bing_grounding.connectionId - -// Bing Custom Search -output BING_CUSTOM_GROUNDING_CONNECTION_NAME string = aiProject.outputs.dependentResources.bing_custom_grounding.connectionName -output BING_CUSTOM_GROUNDING_NAME string = aiProject.outputs.dependentResources.bing_custom_grounding.name -output BING_CUSTOM_GROUNDING_CONNECTION_ID string = aiProject.outputs.dependentResources.bing_custom_grounding.connectionId - -// Azure AI Search -output AZURE_AI_SEARCH_CONNECTION_NAME string = aiProject.outputs.dependentResources.search.connectionName -output AZURE_AI_SEARCH_SERVICE_NAME string = aiProject.outputs.dependentResources.search.serviceName - -// Azure Storage -output AZURE_STORAGE_CONNECTION_NAME string = aiProject.outputs.dependentResources.storage.connectionName -output AZURE_STORAGE_ACCOUNT_NAME string = aiProject.outputs.dependentResources.storage.accountName - +]) : aiProjectDependentResources \ No newline at end of file From 94d302239962fb867f78b3a941c0ab57d5c88c73 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 16 Feb 2026 16:08:47 +0000 Subject: [PATCH 19/37] Add AI Project module and OpenAI connection resource for enhanced deployment capabilities --- infra/main.bicep | 78 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index 3d9ff30..e3a3b91 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -108,4 +108,80 @@ var dependentResources = (enableHostedAgents) && !hasAcr ? union(aiProjectDepend resource: 'registry' connectionName: 'acr-connection' } -]) : aiProjectDependentResources \ No newline at end of file +]) : aiProjectDependentResources + +// AI Project module +module aiProject 'core/ai/ai-project.bicep' = { + scope: rg + name: 'ai-project' + params: { + tags: tags + location: aiDeploymentsLocation + aiFoundryProjectName: aiFoundryProjectName + principalId: principalId + principalType: principalType + existingAiAccountName: aiFoundryResourceName + deployments: aiProjectDeployments + connections: aiProjectConnections + additionalDependentResources: dependentResources + enableMonitoring: enableMonitoring + enableHostedAgents: enableHostedAgents + } +} + +// ============================================================================================== +// CRITICAL FIX: Explicitly create the OpenAI Connection +// This ensures that 'azd up' automatically wires the Project to the Inference Endpoint. +// ============================================================================================== +resource aoaiConnection 'Microsoft.MachineLearningServices/workspaces/connections@2024-04-01-preview' = { + name: '${aiProject.outputs.projectName}/aoai-connection' + scope: rg + properties: { + category: 'AzureOpenAI' + target: aiProject.outputs.accountId + authType: 'ApiKey' + isSharedToAll: true + metadata: { + ApiType: 'Azure' + ResourceId: aiProject.outputs.accountId + } + } +} +// ============================================================================================== + +// Resources +output AZURE_RESOURCE_GROUP string = resourceGroupName +output AZURE_AI_ACCOUNT_ID string = aiProject.outputs.accountId +output AZURE_AI_PROJECT_ID string = aiProject.outputs.projectId +output AZURE_AI_FOUNDRY_PROJECT_ID string = aiProject.outputs.projectId +output AZURE_AI_ACCOUNT_NAME string = aiProject.outputs.aiServicesAccountName +output AZURE_AI_PROJECT_NAME string = aiProject.outputs.projectName + +// Endpoints +output AZURE_AI_PROJECT_ENDPOINT string = aiProject.outputs.AZURE_AI_PROJECT_ENDPOINT +output AZURE_OPENAI_ENDPOINT string = aiProject.outputs.AZURE_OPENAI_ENDPOINT +output APPLICATIONINSIGHTS_CONNECTION_STRING string = aiProject.outputs.APPLICATIONINSIGHTS_CONNECTION_STRING + +// Dependent Resources and Connections + +// ACR +output AZURE_AI_PROJECT_ACR_CONNECTION_NAME string = aiProject.outputs.dependentResources.registry.connectionName +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = aiProject.outputs.dependentResources.registry.loginServer + +// Bing Search +output BING_GROUNDING_CONNECTION_NAME string = aiProject.outputs.dependentResources.bing_grounding.connectionName +output BING_GROUNDING_RESOURCE_NAME string = aiProject.outputs.dependentResources.bing_grounding.name +output BING_GROUNDING_CONNECTION_ID string = aiProject.outputs.dependentResources.bing_grounding.connectionId + +// Bing Custom Search +output BING_CUSTOM_GROUNDING_CONNECTION_NAME string = aiProject.outputs.dependentResources.bing_custom_grounding.connectionName +output BING_CUSTOM_GROUNDING_NAME string = aiProject.outputs.dependentResources.bing_custom_grounding.name +output BING_CUSTOM_GROUNDING_CONNECTION_ID string = aiProject.outputs.dependentResources.bing_custom_grounding.connectionId + +// Azure AI Search +output AZURE_AI_SEARCH_CONNECTION_NAME string = aiProject.outputs.dependentResources.search.connectionName +output AZURE_AI_SEARCH_SERVICE_NAME string = aiProject.outputs.dependentResources.search.serviceName + +// Azure Storage +output AZURE_STORAGE_CONNECTION_NAME string = aiProject.outputs.dependentResources.storage.connectionName +output AZURE_STORAGE_ACCOUNT_NAME string = aiProject.outputs.dependentResources.storage.accountName \ No newline at end of file From 3f87f2632ffc53667f00c094079b08437d3f141e Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 16 Feb 2026 16:12:32 +0000 Subject: [PATCH 20/37] Add OpenAI connection resource to ensure automatic wiring to Inference Endpoint --- infra/main.bicep | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index e3a3b91..602ec01 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -129,10 +129,8 @@ module aiProject 'core/ai/ai-project.bicep' = { } } -// ============================================================================================== -// CRITICAL FIX: Explicitly create the OpenAI Connection -// This ensures that 'azd up' automatically wires the Project to the Inference Endpoint. -// ============================================================================================== +// --- ADDED SECTION START --- +// This resource manually forces the connection to be created using the outputs from the module above. resource aoaiConnection 'Microsoft.MachineLearningServices/workspaces/connections@2024-04-01-preview' = { name: '${aiProject.outputs.projectName}/aoai-connection' scope: rg @@ -147,7 +145,7 @@ resource aoaiConnection 'Microsoft.MachineLearningServices/workspaces/connection } } } -// ============================================================================================== +// --- ADDED SECTION END --- // Resources output AZURE_RESOURCE_GROUP string = resourceGroupName From da2faa3dc6ecdff662dfb8b4dc60dec372282971 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 16 Feb 2026 16:17:14 +0000 Subject: [PATCH 21/37] Refactor interact_with_agent: simplify OpenAI client connection by removing explicit connection name --- src/tests/interact_with_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/interact_with_agent.py b/src/tests/interact_with_agent.py index 1c9687d..70beb4c 100644 --- a/src/tests/interact_with_agent.py +++ b/src/tests/interact_with_agent.py @@ -93,7 +93,7 @@ def interact_with_agent(): try: # Use the discovered connection name - with project_client.get_openai_client(connection_name=connection_name) as openai_client: + with project_client.get_openai_client() as openai_client: thread = openai_client.beta.threads.create() print(f"Session Started (Thread ID: {thread.id})\n") From 58821f28947b4c3984fb92ff6ad0b4ada11bc89a Mon Sep 17 00:00:00 2001 From: v-vfarias Date: Mon, 16 Feb 2026 16:26:54 +0000 Subject: [PATCH 22/37] Refactor Bicep parameters and outputs for AI project --- infra/main.bicep | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 602ec01..bab73f3 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -129,24 +129,6 @@ module aiProject 'core/ai/ai-project.bicep' = { } } -// --- ADDED SECTION START --- -// This resource manually forces the connection to be created using the outputs from the module above. -resource aoaiConnection 'Microsoft.MachineLearningServices/workspaces/connections@2024-04-01-preview' = { - name: '${aiProject.outputs.projectName}/aoai-connection' - scope: rg - properties: { - category: 'AzureOpenAI' - target: aiProject.outputs.accountId - authType: 'ApiKey' - isSharedToAll: true - metadata: { - ApiType: 'Azure' - ResourceId: aiProject.outputs.accountId - } - } -} -// --- ADDED SECTION END --- - // Resources output AZURE_RESOURCE_GROUP string = resourceGroupName output AZURE_AI_ACCOUNT_ID string = aiProject.outputs.accountId @@ -182,4 +164,4 @@ output AZURE_AI_SEARCH_SERVICE_NAME string = aiProject.outputs.dependentResource // Azure Storage output AZURE_STORAGE_CONNECTION_NAME string = aiProject.outputs.dependentResources.storage.connectionName -output AZURE_STORAGE_ACCOUNT_NAME string = aiProject.outputs.dependentResources.storage.accountName \ No newline at end of file +output AZURE_STORAGE_ACCOUNT_NAME string = aiProject.outputs.dependentResources.storage.accountName From 42ce1f72874a3a075e26e751e8762854c543f037 Mon Sep 17 00:00:00 2001 From: v-vfarias Date: Mon, 16 Feb 2026 16:27:55 +0000 Subject: [PATCH 23/37] Enhance agent interaction with connection checks Added functions to auto-detect the AI project name and ensure OpenAI connection exists. Updated the interaction flow with error handling and subprocess calls. --- src/tests/interact_with_agent.py | 171 +++++++++++++++++-------------- 1 file changed, 95 insertions(+), 76 deletions(-) diff --git a/src/tests/interact_with_agent.py b/src/tests/interact_with_agent.py index 70beb4c..7251b00 100644 --- a/src/tests/interact_with_agent.py +++ b/src/tests/interact_with_agent.py @@ -1,5 +1,7 @@ import os import sys +import subprocess +import json from pathlib import Path from dotenv import load_dotenv from azure.identity import AzureDeveloperCliCredential, DefaultAzureCredential @@ -10,119 +12,140 @@ env_file = repo_root / '.env' load_dotenv(env_file) -def get_authenticated_client(): - """Robust client creation for both local and VM environments.""" - endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT") +def get_real_project_name(rg_name): + """Auto-detects the actual AI Project name from Azure.""" + try: + cmd = [ + "az", "resource", "list", "-g", rg_name, + "--resource-type", "Microsoft.MachineLearningServices/workspaces", + "--query", "[0].name", "-o", "tsv" + ] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0 and result.stdout.strip(): + return result.stdout.strip() + except Exception: + pass + return os.environ.get("AZURE_AI_PROJECT_NAME") + +def ensure_connection_exists(rg_name, project_name): + """Checks for an OpenAI connection and creates it if missing.""" + print("Verifying OpenAI connection...") - # Try 'azd' credential first (best for lab VMs) + # 1. Check existing connections try: - return AIProjectClient( - endpoint=endpoint, - credential=AzureDeveloperCliCredential(), - ) + cmd = [ + "az", "rest", "--method", "get", + "--url", f"https://management.azure.com/subscriptions/{os.environ.get('AZURE_SUBSCRIPTION_ID')}/resourceGroups/{rg_name}/providers/Microsoft.MachineLearningServices/workspaces/{project_name}/connections?api-version=2024-04-01-preview" + ] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0: + connections = json.loads(result.stdout) + for conn in connections.get('value', []): + if conn['properties']['category'] == 'AzureOpenAI': + print(f"Found existing connection: {conn['name']}") + return conn['name'] except Exception: - # Fallback to default (local debugging, etc.) - return AIProjectClient( - endpoint=endpoint, - credential=DefaultAzureCredential(), - ) + pass -def find_openai_connection(project_client): - """ - Auto-discovers the Azure OpenAI connection. - This ensures the script works regardless of what 'azd' names the connection. - """ + # 2. If missing, find OpenAI Resource ID and Create + print("Connection missing. Attempting auto-fix...") try: - connections = project_client.connections.list() - for conn in connections: - # Check for standard OpenAI connection types - if "AzureOpenAI" in str(conn) or (hasattr(conn, 'type') and conn.type == "AzureOpenAI"): - return conn.name + # Find OpenAI ID + cmd = ["az", "resource", "list", "-g", rg_name, "--resource-type", "Microsoft.CognitiveServices/accounts", "--query", "[0].id", "-o", "tsv"] + openai_id = subprocess.run(cmd, capture_output=True, text=True).stdout.strip() + + if not openai_id: + print("Error: No OpenAI resource found.") + return None + + # Create Connection + body = { + "properties": { + "authType": "ApiKey", + "category": "AI", + "target": openai_id, + "isSharedToAll": True, + "metadata": {"ApiType": "Azure", "ResourceId": openai_id} + } + } + + url = f"https://management.azure.com/subscriptions/{os.environ.get('AZURE_SUBSCRIPTION_ID')}/resourceGroups/{rg_name}/providers/Microsoft.MachineLearningServices/workspaces/{project_name}/connections/aoai-connection?api-version=2024-04-01-preview" + + subprocess.run( + ["az", "rest", "--method", "put", "--url", url, "--body", json.dumps(body)], + capture_output=True, text=True + ) + print("Success: Created 'aoai-connection'") + return "aoai-connection" except Exception as e: - print(f"Warning: Could not list connections: {e}") - return None + print(f"Auto-fix failed: {e}") + return None def interact_with_agent(): print(f"\n{'='*60}") print(f"Trail Guide Agent - Interactive Chat") print(f"{'='*60}") + + # 1. Resolve Resources + rg_name = os.environ.get("AZURE_RESOURCE_GROUP") + project_name = get_real_project_name(rg_name) - # 1. Initialize Client + print(f"Project: {project_name}") + print(f"Resource Group: {rg_name}") + + # 2. Ensure Connection Exists (Self-Healing) + connection_name = ensure_connection_exists(rg_name, project_name) + + # 3. Initialize Client try: - project_client = get_authenticated_client() + project_client = AIProjectClient( + endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], + credential=AzureDeveloperCliCredential(), + ) except Exception as e: - print(f"Error connecting to project: {e}") - print("Tip: Run 'azd auth login' if running locally.") + print(f"Error connecting: {e}") return - # 2. Find Agent ID + # 4. Find Agent agent_name = os.getenv("AGENT_NAME", "trail-guide") agent_id = None - - print(f"Connecting to Agent: {agent_name}...") try: agents = project_client.agents.list() for agent in agents: if agent.name == agent_name: agent_id = agent.id break - if not agent_id: - print(f"Error: Agent '{agent_name}' not found.") - print("Did you run 'python src/trail_guide_agent.py' to create it?") + print(f"Agent '{agent_name}' not found.") return - - print(f"Success! Found Agent ID: {agent_id}") - - except Exception as e: - print(f"Error listing agents: {e}") + print(f"Agent ID: {agent_id}") + except Exception: + print("Error listing agents.") return - # 3. Find Connection (The 'Magic' Step) - connection_name = find_openai_connection(project_client) - - if not connection_name: - # Fallback: Try the one we manually created, just in case - connection_name = "aoai-connection" - print(f"Warning: Auto-discovery failed. Trying fallback name: '{connection_name}'") - else: - print(f"Auto-detected Inference Connection: '{connection_name}'") - - # 4. Chat Loop + # 5. Chat Loop print("\nInitializing Chat Runtime...") - try: - # Use the discovered connection name + # Use simple get_openai_client (v2 standard) + # The auto-fix above ensures the default connection now exists! with project_client.get_openai_client() as openai_client: - thread = openai_client.beta.threads.create() - print(f"Session Started (Thread ID: {thread.id})\n") - print("Type your questions. Type 'exit' to quit.\n") + print(f"Session Started (Thread: {thread.id})\n") while True: user_input = input("You: ").strip() + if user_input.lower() in ['exit', 'quit', 'q']: break if not user_input: continue - if user_input.lower() in ['exit', 'quit', 'q']: - break - # Send openai_client.beta.threads.messages.create( - thread_id=thread.id, - role="user", - content=user_input + thread_id=thread.id, role="user", content=user_input ) - - # Run run = openai_client.beta.threads.runs.create_and_poll( - thread_id=thread.id, - assistant_id=agent_id + thread_id=thread.id, assistant_id=agent_id ) - # Receive if run.status == 'completed': - messages = openai_client.beta.threads.messages.list( - thread_id=thread.id - ) + messages = openai_client.beta.threads.messages.list(thread_id=thread.id) for msg in messages: if msg.role == "assistant": for content in msg.content: @@ -130,14 +153,10 @@ def interact_with_agent(): print(f"\nAgent: {content.text.value}\n") break else: - print(f"Run status: {run.status}") + print(f"Run failed: {run.status}") except Exception as e: print(f"\nRuntime Error: {e}") - if "404" in str(e): - print("\nTroubleshooting 404:") - print("1. The project does not have a connection to Azure OpenAI.") - print("2. Verify 'azd up' completed successfully.") if __name__ == "__main__": - interact_with_agent() \ No newline at end of file + interact_with_agent() From 1fb8af2520f89c1770f74cff8c1007ce4c7e88e4 Mon Sep 17 00:00:00 2001 From: v-vfarias Date: Mon, 16 Feb 2026 16:53:09 +0000 Subject: [PATCH 24/37] Refactor interact_with_agent to simple_chat --- src/tests/interact_with_agent.py | 178 ++++++------------------------- 1 file changed, 31 insertions(+), 147 deletions(-) diff --git a/src/tests/interact_with_agent.py b/src/tests/interact_with_agent.py index 7251b00..5545363 100644 --- a/src/tests/interact_with_agent.py +++ b/src/tests/interact_with_agent.py @@ -1,162 +1,46 @@ import os -import sys -import subprocess -import json from pathlib import Path from dotenv import load_dotenv -from azure.identity import AzureDeveloperCliCredential, DefaultAzureCredential +from azure.identity import AzureDeveloperCliCredential from azure.ai.projects import AIProjectClient -# Load environment variables -repo_root = Path(__file__).parent.parent.parent -env_file = repo_root / '.env' -load_dotenv(env_file) +# 1. Setup +load_dotenv(Path(__file__).parent.parent.parent / '.env') -def get_real_project_name(rg_name): - """Auto-detects the actual AI Project name from Azure.""" - try: - cmd = [ - "az", "resource", "list", "-g", rg_name, - "--resource-type", "Microsoft.MachineLearningServices/workspaces", - "--query", "[0].name", "-o", "tsv" - ] - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode == 0 and result.stdout.strip(): - return result.stdout.strip() - except Exception: - pass - return os.environ.get("AZURE_AI_PROJECT_NAME") +def simple_chat(): + # 2. Connect + project_client = AIProjectClient( + endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], + credential=AzureDeveloperCliCredential() + ) -def ensure_connection_exists(rg_name, project_name): - """Checks for an OpenAI connection and creates it if missing.""" - print("Verifying OpenAI connection...") - - # 1. Check existing connections - try: - cmd = [ - "az", "rest", "--method", "get", - "--url", f"https://management.azure.com/subscriptions/{os.environ.get('AZURE_SUBSCRIPTION_ID')}/resourceGroups/{rg_name}/providers/Microsoft.MachineLearningServices/workspaces/{project_name}/connections?api-version=2024-04-01-preview" - ] - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode == 0: - connections = json.loads(result.stdout) - for conn in connections.get('value', []): - if conn['properties']['category'] == 'AzureOpenAI': - print(f"Found existing connection: {conn['name']}") - return conn['name'] - except Exception: - pass - - # 2. If missing, find OpenAI Resource ID and Create - print("Connection missing. Attempting auto-fix...") - try: - # Find OpenAI ID - cmd = ["az", "resource", "list", "-g", rg_name, "--resource-type", "Microsoft.CognitiveServices/accounts", "--query", "[0].id", "-o", "tsv"] - openai_id = subprocess.run(cmd, capture_output=True, text=True).stdout.strip() - - if not openai_id: - print("Error: No OpenAI resource found.") - return None - - # Create Connection - body = { - "properties": { - "authType": "ApiKey", - "category": "AI", - "target": openai_id, - "isSharedToAll": True, - "metadata": {"ApiType": "Azure", "ResourceId": openai_id} - } - } - - url = f"https://management.azure.com/subscriptions/{os.environ.get('AZURE_SUBSCRIPTION_ID')}/resourceGroups/{rg_name}/providers/Microsoft.MachineLearningServices/workspaces/{project_name}/connections/aoai-connection?api-version=2024-04-01-preview" - - subprocess.run( - ["az", "rest", "--method", "put", "--url", url, "--body", json.dumps(body)], - capture_output=True, text=True - ) - print("Success: Created 'aoai-connection'") - return "aoai-connection" - except Exception as e: - print(f"Auto-fix failed: {e}") - return None - -def interact_with_agent(): - print(f"\n{'='*60}") - print(f"Trail Guide Agent - Interactive Chat") - print(f"{'='*60}") - - # 1. Resolve Resources - rg_name = os.environ.get("AZURE_RESOURCE_GROUP") - project_name = get_real_project_name(rg_name) + # 3. Find Agent + agent_name = os.getenv("AGENT_NAME", "trail-guide") + agent = next((a for a in project_client.agents.list() if a.name == agent_name), None) - print(f"Project: {project_name}") - print(f"Resource Group: {rg_name}") - - # 2. Ensure Connection Exists (Self-Healing) - connection_name = ensure_connection_exists(rg_name, project_name) + if not agent: + print(f"Agent {agent_name} not found."); return - # 3. Initialize Client - try: - project_client = AIProjectClient( - endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], - credential=AzureDeveloperCliCredential(), - ) - except Exception as e: - print(f"Error connecting: {e}") - return - - # 4. Find Agent - agent_name = os.getenv("AGENT_NAME", "trail-guide") - agent_id = None - try: - agents = project_client.agents.list() - for agent in agents: - if agent.name == agent_name: - agent_id = agent.id - break - if not agent_id: - print(f"Agent '{agent_name}' not found.") - return - print(f"Agent ID: {agent_id}") - except Exception: - print("Error listing agents.") - return + # 4. Chat using the NATIVE SDK methods + # This avoids the get_openai_client() 404 issues + thread = project_client.agents.create_thread() + print(f"Chat started! (Agent: {agent.name})\n") - # 5. Chat Loop - print("\nInitializing Chat Runtime...") - try: - # Use simple get_openai_client (v2 standard) - # The auto-fix above ensures the default connection now exists! - with project_client.get_openai_client() as openai_client: - thread = openai_client.beta.threads.create() - print(f"Session Started (Thread: {thread.id})\n") - - while True: - user_input = input("You: ").strip() - if user_input.lower() in ['exit', 'quit', 'q']: break - if not user_input: continue + while True: + user_msg = input("You: ").strip() + if user_msg.lower() in ['exit', 'quit']: break - openai_client.beta.threads.messages.create( - thread_id=thread.id, role="user", content=user_input - ) - run = openai_client.beta.threads.runs.create_and_poll( - thread_id=thread.id, assistant_id=agent_id - ) + # Send message + project_client.agents.create_message(thread_id=thread.id, role="user", content=user_msg) - if run.status == 'completed': - messages = openai_client.beta.threads.messages.list(thread_id=thread.id) - for msg in messages: - if msg.role == "assistant": - for content in msg.content: - if content.type == 'text': - print(f"\nAgent: {content.text.value}\n") - break - else: - print(f"Run failed: {run.status}") + # Run and Wait (The SDK handles the polling for you here) + run = project_client.agents.create_and_process_run(thread_id=thread.id, assistant_id=agent.id) - except Exception as e: - print(f"\nRuntime Error: {e}") + # Print Response + if run.status == "completed": + messages = project_client.agents.list_messages(thread_id=thread.id) + # The first message in the list is the most recent + print(f"\nAgent: {messages.data[0].content[0].text.value}\n") if __name__ == "__main__": - interact_with_agent() + simple_chat() From 7eac261ca134cf6e794768f669583007f583023a Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 16 Feb 2026 17:09:26 +0000 Subject: [PATCH 25/37] Refactor interact_with_agent function to improve chat session handling and streamline message processing --- src/tests/interact_with_agent.py | 105 +++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/src/tests/interact_with_agent.py b/src/tests/interact_with_agent.py index 5545363..e85606b 100644 --- a/src/tests/interact_with_agent.py +++ b/src/tests/interact_with_agent.py @@ -1,46 +1,87 @@ +""" +Interactive test script for Trail Guide Agent. +Allows you to chat with the agent from the terminal. +""" import os +import sys from pathlib import Path from dotenv import load_dotenv -from azure.identity import AzureDeveloperCliCredential +from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient -# 1. Setup -load_dotenv(Path(__file__).parent.parent.parent / '.env') +# Load environment variables from repository root +repo_root = Path(__file__).parent.parent.parent +env_file = repo_root / '.env' +load_dotenv(env_file) -def simple_chat(): - # 2. Connect +def interact_with_agent(): + """Start an interactive chat session with the Trail Guide Agent.""" + + # Initialize project client project_client = AIProjectClient( endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], - credential=AzureDeveloperCliCredential() + credential=DefaultAzureCredential(), ) - - # 3. Find Agent - agent_name = os.getenv("AGENT_NAME", "trail-guide") - agent = next((a for a in project_client.agents.list() if a.name == agent_name), None) - if not agent: - print(f"Agent {agent_name} not found."); return - - # 4. Chat using the NATIVE SDK methods - # This avoids the get_openai_client() 404 issues + # Get agent name from environment or use default + agent_name = os.getenv("AGENT_NAME", "trail-guide-v1") + + print(f"\n{'='*60}") + print(f"Trail Guide Agent - Interactive Chat") + print(f"Agent: {agent_name}") + print(f"{'='*60}") + print("\nType your questions or requests. Type 'exit' or 'quit' to end the session.\n") + + # Create a thread for the conversation thread = project_client.agents.create_thread() - print(f"Chat started! (Agent: {agent.name})\n") - - while True: - user_msg = input("You: ").strip() - if user_msg.lower() in ['exit', 'quit']: break - - # Send message - project_client.agents.create_message(thread_id=thread.id, role="user", content=user_msg) - - # Run and Wait (The SDK handles the polling for you here) - run = project_client.agents.create_and_process_run(thread_id=thread.id, assistant_id=agent.id) - - # Print Response - if run.status == "completed": + print(f"Started conversation (Thread ID: {thread.id})\n") + + try: + while True: + # Get user input + user_input = input("You: ").strip() + + if not user_input: + continue + + if user_input.lower() in ['exit', 'quit', 'q']: + print("\nEnding session. Goodbye!") + break + + # Send message to agent + project_client.agents.create_message( + thread_id=thread.id, + role="user", + content=user_input + ) + + # Run the agent + run = project_client.agents.create_and_process_run( + thread_id=thread.id, + agent_name=agent_name + ) + + # Get the assistant's response messages = project_client.agents.list_messages(thread_id=thread.id) - # The first message in the list is the most recent - print(f"\nAgent: {messages.data[0].content[0].text.value}\n") + + # Find the latest assistant message + for message in messages: + if message.role == "assistant": + print(f"\nAgent: {message.content[0].text.value}\n") + break + + except KeyboardInterrupt: + print("\n\nSession interrupted. Goodbye!") + except Exception as e: + print(f"\nError: {e}") + sys.exit(1) + finally: + # Clean up thread + try: + project_client.agents.delete_thread(thread.id) + print(f"Conversation thread cleaned up.") + except: + pass if __name__ == "__main__": - simple_chat() + interact_with_agent() \ No newline at end of file From 4967a632843b3634922d77a6d154050a691e406d Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Mon, 16 Feb 2026 18:15:04 +0000 Subject: [PATCH 26/37] Refactor interact_with_agent to use AgentsClient for improved agent interaction and message handling --- requirements.txt | 1 + src/tests/interact_with_agent.py | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8e645cc..3dc4abd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ # Core Azure AI Projects SDK (Latest Versions) azure-ai-projects>=1.0.0b8 +azure-ai-agents>=1.1.0b3 azure-identity>=1.15.0 azure-core>=1.29.0 azure-mgmt-resource>=23.0.0 diff --git a/src/tests/interact_with_agent.py b/src/tests/interact_with_agent.py index e85606b..fd3790d 100644 --- a/src/tests/interact_with_agent.py +++ b/src/tests/interact_with_agent.py @@ -7,7 +7,7 @@ from pathlib import Path from dotenv import load_dotenv from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from azure.ai.agents import AgentsClient # Load environment variables from repository root repo_root = Path(__file__).parent.parent.parent @@ -18,11 +18,18 @@ def interact_with_agent(): """Start an interactive chat session with the Trail Guide Agent.""" # Initialize project client - project_client = AIProjectClient( + project_client = AgentsClient( endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=DefaultAzureCredential(), ) + agent_Id = None + + for agent in project_client.list_agents(): + if getattr(agent, "name", None) == "trail-guide": + agent_Id = agent.id + break + # Get agent name from environment or use default agent_name = os.getenv("AGENT_NAME", "trail-guide-v1") @@ -33,7 +40,7 @@ def interact_with_agent(): print("\nType your questions or requests. Type 'exit' or 'quit' to end the session.\n") # Create a thread for the conversation - thread = project_client.agents.create_thread() + thread = project_client.threads.create() print(f"Started conversation (Thread ID: {thread.id})\n") try: @@ -49,20 +56,20 @@ def interact_with_agent(): break # Send message to agent - project_client.agents.create_message( + project_client.messages.create( thread_id=thread.id, role="user", content=user_input ) # Run the agent - run = project_client.agents.create_and_process_run( + run = project_client.runs.create_and_process( thread_id=thread.id, - agent_name=agent_name + agent_id=agent_Id ) # Get the assistant's response - messages = project_client.agents.list_messages(thread_id=thread.id) + messages = project_client.messages.list(thread_id=thread.id) # Find the latest assistant message for message in messages: @@ -78,7 +85,7 @@ def interact_with_agent(): finally: # Clean up thread try: - project_client.agents.delete_thread(thread.id) + project_client.threads.delete(thread.id) print(f"Conversation thread cleaned up.") except: pass From ca29baa17c2d8dc11991b704aa5021968be54c90 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Wed, 18 Feb 2026 10:21:31 +0000 Subject: [PATCH 27/37] Refactor interact_with_agent to use AIProjectClient for improved agent interaction and message handling --- requirements.txt | 3 +-- src/tests/interact_with_agent.py | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3dc4abd..c5bb397 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,7 @@ # Updated: 2026-01-16 # Core Azure AI Projects SDK (Latest Versions) -azure-ai-projects>=1.0.0b8 -azure-ai-agents>=1.1.0b3 +azure-ai-projects>=1.0.0b1 azure-identity>=1.15.0 azure-core>=1.29.0 azure-mgmt-resource>=23.0.0 diff --git a/src/tests/interact_with_agent.py b/src/tests/interact_with_agent.py index fd3790d..e85606b 100644 --- a/src/tests/interact_with_agent.py +++ b/src/tests/interact_with_agent.py @@ -7,7 +7,7 @@ from pathlib import Path from dotenv import load_dotenv from azure.identity import DefaultAzureCredential -from azure.ai.agents import AgentsClient +from azure.ai.projects import AIProjectClient # Load environment variables from repository root repo_root = Path(__file__).parent.parent.parent @@ -18,18 +18,11 @@ def interact_with_agent(): """Start an interactive chat session with the Trail Guide Agent.""" # Initialize project client - project_client = AgentsClient( + project_client = AIProjectClient( endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=DefaultAzureCredential(), ) - agent_Id = None - - for agent in project_client.list_agents(): - if getattr(agent, "name", None) == "trail-guide": - agent_Id = agent.id - break - # Get agent name from environment or use default agent_name = os.getenv("AGENT_NAME", "trail-guide-v1") @@ -40,7 +33,7 @@ def interact_with_agent(): print("\nType your questions or requests. Type 'exit' or 'quit' to end the session.\n") # Create a thread for the conversation - thread = project_client.threads.create() + thread = project_client.agents.create_thread() print(f"Started conversation (Thread ID: {thread.id})\n") try: @@ -56,20 +49,20 @@ def interact_with_agent(): break # Send message to agent - project_client.messages.create( + project_client.agents.create_message( thread_id=thread.id, role="user", content=user_input ) # Run the agent - run = project_client.runs.create_and_process( + run = project_client.agents.create_and_process_run( thread_id=thread.id, - agent_id=agent_Id + agent_name=agent_name ) # Get the assistant's response - messages = project_client.messages.list(thread_id=thread.id) + messages = project_client.agents.list_messages(thread_id=thread.id) # Find the latest assistant message for message in messages: @@ -85,7 +78,7 @@ def interact_with_agent(): finally: # Clean up thread try: - project_client.threads.delete(thread.id) + project_client.agents.delete_thread(thread.id) print(f"Conversation thread cleaned up.") except: pass From ace283e93540817a2194771f974c2dfd8cd785b5 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Wed, 18 Feb 2026 11:15:44 +0000 Subject: [PATCH 28/37] Refactor interact_with_agent to utilize OpenAI client for conversation management and response handling --- src/tests/interact_with_agent.py | 34 +++++++++++--------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/tests/interact_with_agent.py b/src/tests/interact_with_agent.py index e85606b..bca0b8b 100644 --- a/src/tests/interact_with_agent.py +++ b/src/tests/interact_with_agent.py @@ -25,6 +25,8 @@ def interact_with_agent(): # Get agent name from environment or use default agent_name = os.getenv("AGENT_NAME", "trail-guide-v1") + + openai_client = project_client.get_openai_client() print(f"\n{'='*60}") print(f"Trail Guide Agent - Interactive Chat") @@ -33,8 +35,8 @@ def interact_with_agent(): print("\nType your questions or requests. Type 'exit' or 'quit' to end the session.\n") # Create a thread for the conversation - thread = project_client.agents.create_thread() - print(f"Started conversation (Thread ID: {thread.id})\n") + conversation = openai_client.conversations.create() + print(f"Started conversation (Conversation ID: {conversation.id})\n") try: while True: @@ -49,26 +51,12 @@ def interact_with_agent(): break # Send message to agent - project_client.agents.create_message( - thread_id=thread.id, - role="user", - content=user_input - ) - - # Run the agent - run = project_client.agents.create_and_process_run( - thread_id=thread.id, - agent_name=agent_name - ) - - # Get the assistant's response - messages = project_client.agents.list_messages(thread_id=thread.id) - - # Find the latest assistant message - for message in messages: - if message.role == "assistant": - print(f"\nAgent: {message.content[0].text.value}\n") - break + response = openai_client.responses.create( + conversation_id=conversation.id, + extra_body={"agent": {"name": agent_name, "type": "agent_reference"}}, + input=user_input, + ) + print(f"Response output: {response.output_text}") except KeyboardInterrupt: print("\n\nSession interrupted. Goodbye!") @@ -78,7 +66,7 @@ def interact_with_agent(): finally: # Clean up thread try: - project_client.agents.delete_thread(thread.id) + openai_client.conversations.delete(conversation.id) print(f"Conversation thread cleaned up.") except: pass From b01702400cc4c909c94dff39dc1708072891c5cc Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Wed, 18 Feb 2026 11:40:58 +0000 Subject: [PATCH 29/37] Refactor interact_with_agent to enhance conversation handling and response generation --- src/tests/interact_with_agent.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/tests/interact_with_agent.py b/src/tests/interact_with_agent.py index bca0b8b..3a2000f 100644 --- a/src/tests/interact_with_agent.py +++ b/src/tests/interact_with_agent.py @@ -50,13 +50,25 @@ def interact_with_agent(): print("\nEnding session. Goodbye!") break - # Send message to agent - response = openai_client.responses.create( + # 1) Add the user message to the conversation as an item + openai_client.conversations.items.create( conversation_id=conversation.id, + items=[{ + "type": "message", + "role": "user", + "content": user_input + }] + ) + + # 2) Ask the agent to respond + response = openai_client.responses.create( + # NOTE: the sample uses `conversation=...` (not conversation_id) + conversation=conversation.id, extra_body={"agent": {"name": agent_name, "type": "agent_reference"}}, - input=user_input, - ) - print(f"Response output: {response.output_text}") + input="", # sample keeps this empty because the message is already in the conversation items + ) + + print(f"Agent: {response.output_text}") except KeyboardInterrupt: print("\n\nSession interrupted. Goodbye!") From 75f230e237d72903cec90c579af55c4863d5eb4d Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Wed, 18 Feb 2026 13:09:42 +0000 Subject: [PATCH 30/37] Update aiProjectDeploymentsJson to include gpt4o-mini-deploy model configuration --- infra/main.bicep | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/infra/main.bicep b/infra/main.bicep index bab73f3..c62ebc1 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -66,7 +66,20 @@ param aiFoundryResourceName string = '' param aiFoundryProjectName string = 'ai-project-${environmentName}' @description('List of model deployments') -param aiProjectDeploymentsJson string = '[]' +param aiProjectDeploymentsJson string = ''' +[ + { + "name": "gpt4o-mini-deploy", + "model": { + "provider": "OpenAI", + "name": "gpt-4o-mini" + }, + "sku": { + "name": "GlobalStandard" + } + } +] +''' @description('List of connections') param aiProjectConnectionsJson string = '[]' From 4fff3aa396dc8b7eda803290ec92ee1c0bb0cb6d Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Wed, 18 Feb 2026 13:24:45 +0000 Subject: [PATCH 31/37] Update AI deployments to use gpt-4.1 model configuration --- infra/main.bicep | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index c62ebc1..eb3f95a 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -48,7 +48,7 @@ param location string @metadata({azd: { type: 'location' usageName: [ - 'OpenAI.GlobalStandard.gpt-4o-mini,10' + 'OpenAI.GlobalStandard.gpt-4.1,10' ]} }) param aiDeploymentsLocation string @@ -69,10 +69,10 @@ param aiFoundryProjectName string = 'ai-project-${environmentName}' param aiProjectDeploymentsJson string = ''' [ { - "name": "gpt4o-mini-deploy", + "name": "gpt41-deploy", "model": { "provider": "OpenAI", - "name": "gpt-4o-mini" + "name": "gpt-4.1" }, "sku": { "name": "GlobalStandard" From f2f214e83675ebfba9b17f6b036977fab12bfa59 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Wed, 18 Feb 2026 14:09:15 +0000 Subject: [PATCH 32/37] Enhance AI deployment configuration with model format and SKU capacity --- infra/core/ai/ai-project.bicep | 30 +++++++++++++++--------------- infra/main.bicep | 5 +++-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/infra/core/ai/ai-project.bicep b/infra/core/ai/ai-project.bicep index d0e8753..1a0527a 100644 --- a/infra/core/ai/ai-project.bicep +++ b/infra/core/ai/ai-project.bicep @@ -98,12 +98,12 @@ resource aiAccount 'Microsoft.CognitiveServices/accounts@2025-06-01' = { @batchSize(1) resource seqDeployments 'deployments' = [ - for dep in (deployments??[]): { + for dep in (deployments ?? []): { name: dep.name + sku: dep.sku properties: { model: dep.model } - sku: dep.sku } ] @@ -314,28 +314,28 @@ output dependentResources object = { } } +// Add simple confirmation outputs (so main.bicep can surface them) +output openAiDeploymentNames array = [for dep in (deployments ?? []): dep.name] +output openAiDeploymentIds array = [for dep in (deployments ?? []): resourceId('Microsoft.CognitiveServices/accounts/deployments', aiAccount.name, dep.name)] + +// Replace the deploymentsType definition with a concrete schema type deploymentsType = { - @description('Specify the name of cognitive service account deployment.') + @description('Deployment name') name: string - @description('Required. Properties of Cognitive Services account deployment model.') + @description('Model definition for the deployment') model: { - @description('Required. The name of Cognitive Services account deployment model.') - name: string - - @description('Required. The format of Cognitive Services account deployment model.') + @description('Model format, e.g. OpenAI') format: string - - @description('Required. The version of Cognitive Services account deployment model.') - version: string + @description('Model name, e.g. gpt-4.1') + name: string + @description('Optional model version') + version: string? } - @description('The resource model definition representing SKU.') + @description('SKU for the deployment') sku: { - @description('Required. The name of the resource model definition representing SKU.') name: string - - @description('The capacity of the resource model definition representing SKU.') capacity: int } }[]? diff --git a/infra/main.bicep b/infra/main.bicep index eb3f95a..e7d4491 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -71,11 +71,12 @@ param aiProjectDeploymentsJson string = ''' { "name": "gpt41-deploy", "model": { - "provider": "OpenAI", + "format": "OpenAI", "name": "gpt-4.1" }, "sku": { - "name": "GlobalStandard" + "name": "GlobalStandard", + "capacity": 10 } } ] From eb01fe31a79a2c8d8aea66831f8589c4703fc6c8 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Wed, 18 Feb 2026 14:16:23 +0000 Subject: [PATCH 33/37] Update AI deployment configuration to use gpt4o-mini model and adjust deployment name --- infra/core/ai/ai-project.bicep | 1 - infra/main.bicep | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/infra/core/ai/ai-project.bicep b/infra/core/ai/ai-project.bicep index 1a0527a..f41f4ee 100644 --- a/infra/core/ai/ai-project.bicep +++ b/infra/core/ai/ai-project.bicep @@ -316,7 +316,6 @@ output dependentResources object = { // Add simple confirmation outputs (so main.bicep can surface them) output openAiDeploymentNames array = [for dep in (deployments ?? []): dep.name] -output openAiDeploymentIds array = [for dep in (deployments ?? []): resourceId('Microsoft.CognitiveServices/accounts/deployments', aiAccount.name, dep.name)] // Replace the deploymentsType definition with a concrete schema type deploymentsType = { diff --git a/infra/main.bicep b/infra/main.bicep index e7d4491..1b82489 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -69,10 +69,10 @@ param aiFoundryProjectName string = 'ai-project-${environmentName}' param aiProjectDeploymentsJson string = ''' [ { - "name": "gpt41-deploy", + "name": "gpt4o-mini-deploy", "model": { "format": "OpenAI", - "name": "gpt-4.1" + "name": "gpt-4o-mini" }, "sku": { "name": "GlobalStandard", From ebb6d7b71fff12cb2b5583b8bb8c41d3ae1172ee Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Fri, 20 Feb 2026 11:24:58 +0000 Subject: [PATCH 34/37] Update AI deployment configuration to use gpt-4.1 model and adjust deployment name --- infra/main.bicep | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/infra/main.bicep b/infra/main.bicep index 1b82489..362a4d1 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -48,7 +48,7 @@ param location string @metadata({azd: { type: 'location' usageName: [ - 'OpenAI.GlobalStandard.gpt-4.1,10' + 'OpenAI.GlobalStandard.gpt-4o-mini,10' ]} }) param aiDeploymentsLocation string @@ -69,10 +69,10 @@ param aiFoundryProjectName string = 'ai-project-${environmentName}' param aiProjectDeploymentsJson string = ''' [ { - "name": "gpt4o-mini-deploy", + "name": "gpt-4_1-mini-deploy", "model": { "format": "OpenAI", - "name": "gpt-4o-mini" + "name": "gpt-4.1-mini" }, "sku": { "name": "GlobalStandard", @@ -179,3 +179,6 @@ output AZURE_AI_SEARCH_SERVICE_NAME string = aiProject.outputs.dependentResource // Azure Storage output AZURE_STORAGE_CONNECTION_NAME string = aiProject.outputs.dependentResources.storage.connectionName output AZURE_STORAGE_ACCOUNT_NAME string = aiProject.outputs.dependentResources.storage.accountName + +// OpenAI Deployments +output AZURE_OPENAI_DEPLOYMENT_NAMES array = aiProject.outputs.openAiDeploymentNames From c33fdf008a326f4bb9558c317adbb792d5c1030b Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Fri, 20 Feb 2026 12:05:44 +0000 Subject: [PATCH 35/37] Remove aiProjectDeploymentsJson parameter from main.parameters.json --- infra/main.parameters.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/infra/main.parameters.json b/infra/main.parameters.json index 323829e..de56a9d 100644 --- a/infra/main.parameters.json +++ b/infra/main.parameters.json @@ -26,9 +26,6 @@ "principalType": { "value": "${AZURE_PRINCIPAL_TYPE}" }, - "aiProjectDeploymentsJson": { - "value": "${AI_PROJECT_DEPLOYMENTS=[]}" - }, "aiProjectConnectionsJson": { "value": "${AI_PROJECT_CONNECTIONS=[]}" }, From 441b1daa3351da436d757147f6ae2c0ed85a61f7 Mon Sep 17 00:00:00 2001 From: Victor Barreto Date: Fri, 20 Feb 2026 15:40:46 +0000 Subject: [PATCH 36/37] Update AI deployment configurations for gpt-4.1 model and adjust deployment names --- docs/01-infrastructure-setup.md | 25 ++++++++++++++++++++++--- infra/main.bicep | 14 +++++++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/01-infrastructure-setup.md b/docs/01-infrastructure-setup.md index 018e9ba..254dc89 100644 --- a/docs/01-infrastructure-setup.md +++ b/docs/01-infrastructure-setup.md @@ -61,6 +61,13 @@ You'll use the Azure Developer CLI to deploy all required Azure resources using Sign in with your Azure credentials when prompted. This authentication is needed for the Python SDK and other Azure operations in subsequent labs. + > ⚠️ **Important ** + > In some environments, the VS Code integrated terminal may crash or close during the interactive login flow. + > If this happens, authenticate using explicit credentials instead: + > ```powershell + > az login --username --password + > ``` + 1. Provision resources: ```powershell @@ -96,6 +103,16 @@ You'll use the Azure Developer CLI to deploy all required Azure resources using ```powershell azd env get-values > .env ``` + + > ⚠️ **Important – File Encoding** + > + > After generating the `.env` file, make sure it is saved using **UTF-8** encoding. + > + > In editors like **VS Code**, check the encoding indicator in the bottom-right corner. + > If it shows **UTF-16 LE** (or any encoding other than UTF-8), click it, choose **Save with Encoding**, and select **UTF-8**. + > + > Using the wrong encoding may cause environment variables to be read incorrectly + This creates a `.env` file in your project root with all the provisioned resource information: - Resource names and IDs @@ -182,15 +199,17 @@ Interact with your deployed agent from the terminal to verify it's working corre 1. When prompted, ask the agent a question about hiking, for example: ``` - You: I want to go hiking this weekend near Seattle. Any suggestions? + I want to go hiking this weekend near Seattle. Any suggestions? ``` 1. The agent will respond with trail recommendations. Continue the conversation or type `exit` to quit. ``` Agent: I'd recommend checking out Rattlesnake Ledge Trail... - - You: exit + ``` + + ``` + exit ``` ## Verify your deployment diff --git a/infra/main.bicep b/infra/main.bicep index 362a4d1..f5fcf9d 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -69,7 +69,7 @@ param aiFoundryProjectName string = 'ai-project-${environmentName}' param aiProjectDeploymentsJson string = ''' [ { - "name": "gpt-4_1-mini-deploy", + "name": "gpt-4.1-mini", "model": { "format": "OpenAI", "name": "gpt-4.1-mini" @@ -78,7 +78,19 @@ param aiProjectDeploymentsJson string = ''' "name": "GlobalStandard", "capacity": 10 } + }, + { + "name": "gpt-4.1", + "model": { + "format": "OpenAI", + "name": "gpt-4.1" + }, + "sku": { + "name": "GlobalStandard", + "capacity": 10 + } } + ] ''' From ac90074217606d98b9c722376c9cd9d981461baf Mon Sep 17 00:00:00 2001 From: v-vfarias Date: Fri, 20 Feb 2026 15:51:24 +0000 Subject: [PATCH 37/37] Delete .github/workflows/setup-and-deploy.yml --- .github/workflows/setup-and-deploy.yml | 69 -------------------------- 1 file changed, 69 deletions(-) delete mode 100644 .github/workflows/setup-and-deploy.yml diff --git a/.github/workflows/setup-and-deploy.yml b/.github/workflows/setup-and-deploy.yml deleted file mode 100644 index 09fc153..0000000 --- a/.github/workflows/setup-and-deploy.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Deploy GPT-4o to Azure AI Foundry via REST API - -on: - workflow_dispatch: - -jobs: - deploy-ai-foundry: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Install Azure CLI - run: | - curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash - - - name: Login to Azure - run: | - az login --service-principal \ - --username ${{ secrets.AZURE_CLIENT_ID }} \ - --password ${{ secrets.AZURE_CLIENT_SECRET }} \ - --tenant ${{ secrets.AZURE_TENANT_ID }} - - - name: Get Access Token for AI Foundry - id: get-token - run: | - TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv) - echo "token=$TOKEN" >> $GITHUB_OUTPUT - - - name: Create AI Foundry Project - run: | - curl -X POST "https://ai.azure.com/api/projects" \ - -H "Authorization: Bearer ${{ steps.get-token.outputs.token }}" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "'"${{ secrets.AZURE_PROJECT_NAME }}"'", - "location": "'"${{ secrets.AZURE_REGION }}"'", - "description": "AI Foundry project created via GitHub Actions" - }' - - - name: Deploy GPT-4o Model - run: | - curl -X POST "https://ai.azure.com/api/projects/${{ secrets.AZURE_PROJECT_NAME }}/deployments" \ - -H "Authorization: Bearer ${{ steps.get-token.outputs.token }}" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "gpt-4o-deployment", - "model": "'"${{ secrets.AZURE_MODEL_NAME }}"'", - "scaleSettings": { "scaleType": "Standard" } - }' - - - name: Validate Deployment - run: | - STATUS=$(curl -s -X GET "https://ai.azure.com/api/projects/${{ secrets.AZURE_PROJECT_NAME }}/deployments/gpt-4o-deployment" \ - -H "Authorization: Bearer ${{ steps.get-token.outputs.token }}" \ - | jq -r '.status') - if [ "$STATUS" != "Succeeded" ]; then - echo "Deployment failed with status: $STATUS" - exit 1 - fi - echo "Deployment succeeded!" - - - name: Get Endpoint - run: | - ENDPOINT=$(curl -s -X GET "https://ai.azure.com/api/projects/${{ secrets.AZURE_PROJECT_NAME }}" \ - -H "Authorization: Bearer ${{ steps.get-token.outputs.token }}" \ - | jq -r '.endpoint') - echo "Azure AI Foundry Endpoint: $ENDPOINT"