Skip to main content

Challenge 21: Agent Fundamentals and Architecture

Estimated Time

45-60 min | Cost: $2-5 (estimated) | Domain: Implement Agentic Solutions (5-10%)

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