Skip to main content

Challenge 21: Agent Fundamentals and Architecture

Estimated Time

45-60 min | Cost: $2-5 (estimated) | Domain: Generative AI & Agentic Solutions (35-40%)

Exam skills covered​

  • Understand role and use cases of an AI agent
  • Configure necessary resources for agent solutions
  • Implement function calling with Azure OpenAI

Overview​

AI agents extend beyond simple chatbots by combining LLM reasoning with the ability to take actions. While a chatbot answers questions from its training data, an agent can call external tools, execute code, query databases, and orchestrate multi-step workflows autonomously.

Key concepts:

  • Tool/Function calling: The model decides which functions to invoke based on user intent
  • Planning: Breaking complex tasks into sequential steps
  • Memory: Maintaining conversation context and state across interactions
  • Grounding: Connecting the model to real-time data sources

This challenge implements function calling with Azure OpenAI — the foundation of all agent architectures.

Architecture​

Challenge 21 - AI Agent Architecture

Prerequisites​

  • Azure subscription with Azure OpenAI access
  • Azure OpenAI resource with GPT-4o deployed
  • Python 3.9+ or .NET 8
  • Packages: openai>=1.0.0 (Python) or Azure.AI.OpenAI (C#)

Implementation​

Task 1: Deploy Azure OpenAI Resource and Model​

# Create resource group
az group create --name rg-ai102-agents --location eastus2

# Create Azure OpenAI resource
az cognitiveservices account create \
--name aoai-ai102-agents \
--resource-group rg-ai102-agents \
--kind OpenAI \
--sku S0 \
--location eastus2

# Deploy GPT-4o model
az cognitiveservices account deployment create \
--name aoai-ai102-agents \
--resource-group rg-ai102-agents \
--deployment-name gpt-4o \
--model-name gpt-4o \
--model-version "2024-08-06" \
--model-format OpenAI \
--sku-capacity 10 \
--sku-name Standard

# Get endpoint and key
az cognitiveservices account show \
--name aoai-ai102-agents \
--resource-group rg-ai102-agents \
--query properties.endpoint -o tsv

az cognitiveservices account keys list \
--name aoai-ai102-agents \
--resource-group rg-ai102-agents \
--query key1 -o tsv

Task 2: Implement Function Calling​

import os
import json
from openai import AzureOpenAI

client = AzureOpenAI(
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
api_key=os.environ["AZURE_OPENAI_KEY"],
api_version="2024-10-21"
)

# Define tools the agent can use
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, e.g. 'Seattle, WA'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
},
{
"type": "function",
"function": {
"name": "search_products",
"description": "Search for products in the catalog by name or category",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query for products"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "home", "sports"],
"description": "Product category filter"
},
"max_price": {
"type": "number",
"description": "Maximum price filter"
}
},
"required": ["query"]
}
}
}
]

# Simulated tool implementations
def get_weather(location: str, unit: str = "celsius") -> dict:
"""Simulated weather API call"""
return {
"location": location,
"temperature": 22 if unit == "celsius" else 72,
"unit": unit,
"condition": "Partly cloudy",
"humidity": 65
}

def search_products(query: str, category: str = None, max_price: float = None) -> dict:
"""Simulated product search"""
results = [
{"name": f"{query} Pro", "price": 299.99, "category": category or "electronics"},
{"name": f"{query} Basic", "price": 149.99, "category": category or "electronics"}
]
if max_price:
results = [r for r in results if r["price"] <= max_price]
return {"results": results, "total": len(results)}

# Map function names to implementations
available_functions = {
"get_weather": get_weather,
"search_products": search_products
}

def run_agent(user_message: str):
"""Run the agent loop with function calling"""
messages = [
{"role": "system", "content": "You are a helpful assistant with access to weather and product search tools."},
{"role": "user", "content": user_message}
]

# First call - model decides whether to use tools
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto"
)

response_message = response.choices[0].message

# Check if the model wants to call functions
if response_message.tool_calls:
messages.append(response_message)

# Execute each tool call
for tool_call in response_message.tool_calls:
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)

print(f" [Agent] Calling: {function_name}({function_args})")

# Execute the function
function_response = available_functions[function_name](**function_args)

# Add the function result to messages
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": json.dumps(function_response)
})

# Second call - model synthesizes final response
final_response = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
return final_response.choices[0].message.content
else:
return response_message.content

# Test the agent
print("=" * 60)
print("Test 1: Weather query (should trigger get_weather)")
print("=" * 60)
result = run_agent("What's the weather like in Seattle?")
print(f"Agent: {result}\n")

print("=" * 60)
print("Test 2: Product search (should trigger search_products)")
print("=" * 60)
result = run_agent("Find me headphones under $200")
print(f"Agent: {result}\n")

print("=" * 60)
print("Test 3: No tool needed (general knowledge)")
print("=" * 60)
result = run_agent("What is the capital of France?")
print(f"Agent: {result}\n")

Task 3: Implement Parallel Function Calling​

# The model can call multiple tools in parallel when appropriate
# Test with a query that requires both tools

result = run_agent(
"I'm planning a trip to Miami. What's the weather there? "
"Also, find me some sunglasses under $100."
)
print(f"Agent (parallel calls): {result}")

# Verify parallel execution by checking tool_calls count
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What's the weather in NYC and LA simultaneously?"}
]

response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto"
)

if response.choices[0].message.tool_calls:
num_calls = len(response.choices[0].message.tool_calls)
print(f"\nParallel tool calls made: {num_calls}")
for tc in response.choices[0].message.tool_calls:
print(f" - {tc.function.name}({tc.function.arguments})")

Expected Output​

============================================================
Test 1: Weather query (should trigger get_weather)
============================================================
[Agent] Calling: get_weather({"location": "Seattle, WA", "unit": "celsius"})
Agent: The current weather in Seattle is 22°C and partly cloudy with 65% humidity.

============================================================
Test 2: Product search (should trigger search_products)
============================================================
[Agent] Calling: search_products({"query": "headphones", "max_price": 200})
Agent: I found 2 headphones under $200:
- Headphones Basic: $149.99

============================================================
Test 3: No tool needed (general knowledge)
============================================================
Agent: The capital of France is Paris.

Parallel tool calls made: 2
- get_weather({"location": "New York, NY"})
- get_weather({"location": "Los Angeles, CA"})

Break & fix​

ScenarioSymptomRoot CauseFix
Missing tool_call_id in response400 Bad RequestTool response message must include the matching tool_call_idEnsure each tool response references the correct tool_call.id
Function returns non-stringTypeErrorTool message content must be a JSON stringAlways json.dumps() the function return value
Infinite loopAgent keeps calling toolsNo exit condition or tool returns errorAdd max iteration count; validate tool responses
tool_choice: "required"Model always calls a tool even when unnecessaryForcing tool usageUse "auto" to let model decide; use "required" only for guaranteed tool use
Schema mismatchModel generates wrong argument typesFunction parameters schema is too vagueAdd descriptions, enums, and examples to parameter schema

Knowledge Check​

1. What is the primary difference between an AI agent and a chatbot?

2. In Azure OpenAI function calling, what happens when tool_choice is set to 'auto'?

3. When the model returns tool_calls in the response, what must you include in the follow-up tool message?

4. How does Azure OpenAI handle multiple tool calls in a single response?

5. What is the correct format for defining function parameters in the tools array?

Cleanup​

az group delete --name rg-ai102-agents --yes --no-wait

Learn More​