Skip to content

Commit b267696

Browse files
Merge pull request #89 from microsoft/integration
Integration
2 parents fc2913f + 0819d1a commit b267696

File tree

6 files changed

+229
-70
lines changed

6 files changed

+229
-70
lines changed

docs/02_setup/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ You can run the project in your local VS Code Dev Container using the [Dev Conta
9999
```
100100
101101
1. When prompted provided the following names:
102-
1. Environment name (used as the name of the new resource group to deploy into)
102+
1. Environment name (used as the name of the new or existing resource group to deploy into)
103103
1. Azure Subscription to use
104104
1. Azure location to use
105105

docs/03_observability/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ The bicep infrastructure deploys an Application Insights resource where you can
3030

3131
## Observe Application Behavior
3232

33+
### Local Configuration
34+
35+
You can see telemetry data if you are running the app locally, in a local dev container, or in Azure via `azd up`. It will not work if you're running in Github Codespaces.
36+
37+
If running locally or in a dev container, there are configuration steps to wire up the Aspire Dashboard:
38+
1. Update the `.env` file with this value: `TELEMETRY_SCENARIO=console,aspire_dashboard`
39+
1. Run this command to host the Aspire Dashboard in a local container:
40+
41+
```bash
42+
docker run --rm -it -d -p 18888:18888 -p 4317:18889 -e DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true --name aspire-dashboard mcr.microsoft.com/dotnet/aspire-dashboard:9.0
43+
```
44+
45+
If running locally (not in a dev container), update the `.env` file with this value: `ASPIRE_DASHBOARD_ENDPOINT=http://localhost:4317`
46+
47+
No additional steps are needed if you are running the app that was deployed by `azd up`.
48+
3349
### Conduct a conversation with the customer service agents
3450

3551
Conduct a conversation with the customer service agents following this sequence:

docs/04_explore/README.md

Lines changed: 130 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125

126126
5. Let's add data source for insurance policy / rental policies, etc. with AI Search
127127

128-
Create a JSON file for car rental policies. Name the file car_rental_policy.json and store it in the data folder along with the flight & hotel policies:
128+
a) Create a JSON file for car rental policies. Name the file car_rental_policy.json and store it in the data folder along with the flight & hotel policies:
129129

130130
<details>
131131
<summary> Click to expand/collapse</summary>
@@ -153,7 +153,7 @@
153153
</details>
154154

155155

156-
Update the plugin to setup Azure OpenAI client for embeddings, create get_embedding method to generate text embeddings. Define the SearchClient class with semantic search functionality and finally initialize the search client.
156+
b) Update the plugin (car_rental_plugin.py) to setup Azure OpenAI client for embeddings, create get_embedding method to generate text embeddings. Define the SearchClient class with semantic search functionality and finally initialize the search client.
157157

158158
<details>
159159
<summary> Click to expand/collapse</summary>
@@ -196,7 +196,7 @@
196196

197197
</details>
198198

199-
Update the Car_Rental_Tools class to include policy search:
199+
c) Update the Car_Rental_Tools class in the plugin (car_rental_plugin.py) to include policy search:
200200

201201
<details>
202202
<summary> Click to expand/collapse</summary>
@@ -222,7 +222,7 @@
222222

223223
</details>
224224

225-
Create an embedding generation script & store it in ./scripts/generate_car_rental_policy_embeddings.py :
225+
d) Create an embedding generation script called generate_car_rental_policy_embeddings.py & in app/backend/scripts folder:
226226

227227
<details>
228228
<summary> Click to expand/collapse</summary>
@@ -269,7 +269,7 @@
269269
</details>
270270

271271

272-
Run the embedding generation script in the terminal
272+
e) Run the embedding generation script in the terminal
273273

274274
<details>
275275
<summary> Click to expand/collapse</summary>
@@ -278,29 +278,140 @@
278278
cd c:\Code\multi-modal-customer-service-agent\voice_agent\app\backend
279279
python scripts/generate_car_rental_policy_embeddings.py
280280
```
281-
282281
</details>
283282

284-
The car rental policy search uses the same semantic search approach as the hotel policies. The policies are stored in JSON format with embeddings for efficient semantic search.
283+
f) After implementation, test with queries about:
284+
- Insurance coverage options and costs
285+
- Rental requirements and age restrictions
286+
- Deductibles and additional coverage options
287+
288+
### Expand to see the complete car_rental_plugins.py file
289+
<details>
290+
<summary> Click to expand/collapse</summary>
291+
292+
```python
293+
from typing import Annotated, Any
294+
from semantic_kernel.functions import kernel_function
295+
import os
296+
from openai import AzureOpenAI
297+
import json
298+
from scipy import spatial
299+
300+
# Constants for Azure OpenAI
301+
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
302+
AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
303+
AZURE_OPENAI_EMB_DEPLOYMENT = os.getenv("AZURE_OPENAI_EMB_DEPLOYMENT")
304+
AZURE_OPENAI_EMB_ENDPOINT = os.getenv("AZURE_OPENAI_EMB_ENDPOINT", AZURE_OPENAI_ENDPOINT)
305+
AZURE_OPENAI_EMB_API_KEY = os.getenv("AZURE_OPENAI_EMB_API_KEY", AZURE_OPENAI_API_KEY)
306+
AZURE_OPENAI_CHAT_DEPLOYMENT = os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT")
307+
308+
# Azure OpenAI client setup
309+
embedding_client = AzureOpenAI(
310+
api_key=AZURE_OPENAI_EMB_API_KEY,
311+
azure_endpoint=AZURE_OPENAI_EMB_ENDPOINT,
312+
api_version="2023-12-01-preview"
313+
)
314+
315+
def get_embedding(text: str, model: str = AZURE_OPENAI_EMB_DEPLOYMENT) -> list[float]:
316+
"""Generate text embeddings using Azure OpenAI."""
317+
text = text.replace("\n", " ")
318+
return embedding_client.embeddings.create(input=[text], model=model).data[0].embedding
285319

286-
The Car_Rental_Tools class now includes a method to search policies.
320+
class SearchClient:
321+
"""Client for performing semantic search on a knowledge base."""
322+
def __init__(self, emb_map_file_path: str):
323+
with open(emb_map_file_path) as file:
324+
self.chunks_emb = json.load(file)
325+
326+
def find_article(self, question: str, topk: int = 3) -> str:
327+
"""Find relevant articles based on cosine similarity."""
328+
input_vector = get_embedding(question)
329+
cosine_list = [
330+
(item['id'], item['policy_text'], 1 - spatial.distance.cosine(input_vector, item['policy_text_embedding']))
331+
for item in self.chunks_emb
332+
]
333+
cosine_list.sort(key=lambda x: x[2], reverse=True)
334+
cosine_list = cosine_list[:topk]
335+
336+
return "\n".join(f"{chunk_id}\n{content}" for chunk_id, content, _ in cosine_list)
287337

288-
The embedding generation script needs to be run once to prepare the policy data.
338+
# Initialize the search client
339+
search_client = SearchClient("./data/car_rental_policy.json")
289340

290-
You can expand the policy database by adding more entries to the JSON file.
341+
# Kernel functions
342+
class Car_Rental_Tools:
343+
"""Tools for car rental agent to perform various rental operations"""
344+
345+
agent_name = "car_rental_agent" # Name of the agent
291346

292-
After implementing these changes:
347+
@kernel_function(
348+
description="Search for available rental cars",
349+
name="search_cars"
350+
)
351+
async def search_cars(self, location: str, pickup_date: str, return_date: str) -> str:
352+
# Mock implementation - replace with actual car rental API
353+
return f"Found available cars in {location} from {pickup_date} to {return_date}: \n" \
354+
"1. Economy - Toyota Corolla ($45/day)\n" \
355+
"2. SUV - Honda CR-V ($65/day)\n" \
356+
"3. Luxury - BMW 5 Series ($95/day)"
293357

294-
- The car rental agent will be available alongside the hotel and flight agents
295-
- The agent can be triggered through intent detection
296-
- The agent will have access to car rental specific tools through its plugin
297-
- The agent will maintain its own context and persona while handling customer interactions
358+
@kernel_function(
359+
description="Get rental car details",
360+
name="get_car_details"
361+
)
362+
async def get_car_details(self, car_id: str) -> str:
363+
# Mock implementation
364+
car_details = {
365+
"1": "Toyota Corolla: 4 doors, 5 seats, automatic transmission, GPS, bluetooth",
366+
"2": "Honda CR-V: 5 doors, 5 seats, automatic transmission, GPS, bluetooth, roof rack",
367+
"3": "BMW 5 Series: 4 doors, 5 seats, automatic transmission, GPS, bluetooth, leather seats"
368+
}
369+
return car_details.get(car_id, "Car not found")
298370

299-
Test the new agent with various car rental scenarios.
300371

301-
Future steps to try on your own.
302-
- Expand the Car_Rental_Tools class with additional functions as needed.
303-
- Replace mock implementations with actual car rental API integrations
372+
@kernel_function(
373+
name="search_rental_policies",
374+
description="Search car rental and insurance policies for relevant information."
375+
)
376+
async def search_rental_policies(self,
377+
search_query: Annotated[str, "The search query about rental or insurance policies."]
378+
) -> str:
379+
return search_client.find_article(search_query)
380+
```
381+
382+
</details>
383+
384+
### Summarizing Exercise 1 - Adding a New Car Rental Agent with Semantic Search
385+
386+
This exercise demonstrates how to extend the voice agent system by adding a completely new domain-specific agent for car rentals, complete with semantic search capabilities for policy information.
387+
388+
#### Files Created:
389+
- `car_rental_agent_profile.yaml` - Defines the agent's persona and responsibilities
390+
- `car_rental_plugins.py` - Contains tools for car searching, details, and policy lookups
391+
- `car_rental_policy.json` - Stores policy data with embeddings for semantic search
392+
- `scripts/generate_car_rental_policy_embeddings.py` - Script to generate policy embeddings
393+
394+
#### Files Updated:
395+
- `rtmt.py` - Modified to register and initialize the new car rental agent
396+
- `utility.py` - Updated to include car rental in intent detection system messages
397+
398+
#### Key Components:
399+
1. **Agent Profile**: Defines the car rental agent's persona and scope
400+
2. **Tool Integration**: Custom tools for searching cars, viewing details, and accessing policies
401+
3. **SearchClient Class**: Implements vector similarity search using cosine distance
402+
4. **Intent Detection**: Updates to route car rental queries to the specialized agent
403+
404+
#### Integration Benefits:
405+
- Demonstrates the extensibility of the multi-agent architecture
406+
- Adds a completely new domain (car rentals) to the existing hotel and flight agents
407+
- Semantic search provides accurate policy information retrieval
408+
- Maintains consistent agent experience while expanding capabilities
409+
410+
#### Future Enhancements:
411+
- Expand the policy database with more detailed entries
412+
- Add domain-specific tools to the Car_Rental_Tools class
413+
- Implement actual API integrations to replace mock implementations
414+
- Create more sophisticated embedding strategies for better matching
304415

305416
### Exercise 2: debug/fix/commit flow
306417
- scenario which 'fails' from a functionality standpoint -> use observability (tracing in AI Foundry), figure out the problem, change system-prompt/etc., run test suite (local run of Evaluations), then PR

voice_agent/app/backend/.env.sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ TURN_DETECTION_PREFIX_PADDING_MS=300
1818
TURN_DETECTION_SILENCE_DURATION_MS=200
1919
AZURE_REDIS_ENDPOINT=#optional, if you want to use redis for caching which support distributed caching for high
2020
AZURE_REDIS_KEY=#optional, if you want to use redis for caching which support distributed caching for high
21+
ASPIRE_DASHBOARD_ENDPOINT=http://host.docker.internal:4317
22+
TELEMETRY_SCENARIO=console

voice_agent/app/infra/main.bicep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ param containerAppsSubnetPrefix string = '10.0.2.0/23'
120120

121121
// Organize resources in a resource group
122122
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
123-
name: 'rg-${environmentName}'
123+
name: environmentName
124124
location: location
125125
tags: tags
126126
}

voice_agent/app/start.sh

Lines changed: 79 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,91 @@
1-
#!/bin/sh
2-
# This script sets up and starts both the backend and the frontend for local development.
3-
# The backend is run with a Python virtual environment and the frontend uses Vite’s dev server.
1+
#!/usr/bin/env bash
2+
#
3+
# dev.sh – start backend (FastAPI) and frontend (Vite) in watch mode
4+
# while taking care of Python venv / npm install, plus
5+
# graceful shutdown on Ctrl-C.
6+
7+
set -euo pipefail
8+
9+
###############################################################################
10+
# Helper functions
11+
###############################################################################
12+
die () { echo "ERROR: $*" >&2; exit 1; }
13+
14+
cleanup () {
15+
echo # blank line
16+
echo "Stopping services…"
17+
[[ -n "${backend_pid:-}" ]] && kill "$backend_pid" 2>/dev/null || true
18+
[[ -n "${frontend_pid:-}" ]] && kill "$frontend_pid" 2>/dev/null || true
19+
exit 0
20+
}
21+
22+
trap cleanup INT TERM
423

5-
# --------------------------- Start the Backend Service ---------------------------
6-
echo ""
24+
###############################################################################
25+
# Backend
26+
###############################################################################
27+
echo
728
echo "Setting up the Python virtual environment for the backend..."
829

9-
cd backend || { echo "backend folder not found."; exit 1; }
30+
[[ -d backend ]] || die "backend folder not found."
31+
pushd backend > /dev/null
1032

11-
# Create virtual environment if it doesn't exist
12-
if [ ! -d ".venv" ]; then
13-
python3 -m venv .venv
33+
# ------------------------------------------------------------------
34+
# Create / upgrade the virtual-env
35+
# ------------------------------------------------------------------
36+
if [[ ! -d .venv ]]; then
37+
# --upgrade-deps guarantees fresh pip / setuptools / wheel
38+
python -m venv --upgrade-deps .venv
39+
else
40+
# env exists – still make sure the essentials are up to date
41+
.venv/bin/python -m pip install --upgrade pip setuptools wheel packaging
1442
fi
1543

16-
echo ""
17-
echo "Installing backend Python packages..."
18-
./.venv/bin/python -m pip install -r requirements.txt || { echo "Backend dependency installation failed."; exit 1; }
19-
20-
echo ""
21-
echo "Starting backend service (listening on port 8765)..."
22-
# Use --reload for auto-restarting during development
23-
./.venv/bin/python app.py --reload &
24-
BACKEND_PID=$!
25-
26-
cd ..
27-
28-
# --------------------------- Start the Frontend Service ---------------------------
29-
echo ""
30-
echo "Installing frontend npm packages..."
31-
32-
cd frontend || { echo "frontend folder not found."; exit 1; }
33-
npm install || { echo "Frontend npm installation failed."; exit 1; }
34-
35-
echo ""
36-
echo "Building frontend application..."
37-
npm run build || { echo "Frontend build failed."; exit 1; }
38-
# Note:
39-
# • The .env file for the frontend should be placed in the frontend directory (i.e., ./frontend/.env)
40-
# • This file can contain environment variables such as BACKEND_WS_URL that Vite will load.
41-
#
42-
# Example .env file:
43-
export VITE_BACKEND_WS_URL="ws://localhost:8765"
44+
echo
45+
echo "Installing backend Python packages…"
46+
47+
venv_py=".venv/bin/python"
48+
[[ -x "$venv_py" ]] || { popd >/dev/null; die "Could not find the virtual-env Python executable."; }
49+
50+
"$venv_py" -m pip install -r requirements.txt || { popd >/dev/null; die "Backend dependency installation failed."; }
4451

45-
echo ""
46-
echo "Starting frontend dev server..."
47-
node server.cjs &
48-
FRONTEND_PID=$!
49-
50-
echo "Frontend proxy started at: http://localhost:3000"
51-
52-
cd ..
52+
echo
53+
echo "Starting backend service (listening on port 8765)…"
54+
55+
# Launch FastAPI (or whatever app.py holds) in the background
56+
"$venv_py" app.py --reload &
57+
backend_pid=$!
58+
59+
popd > /dev/null
60+
61+
###############################################################################
62+
# Frontend
63+
###############################################################################
64+
echo
65+
echo "Installing frontend npm packages…"
66+
67+
[[ -d frontend ]] || { kill "$backend_pid"; die "frontend folder not found."; }
68+
pushd frontend > /dev/null
69+
70+
npm install || { popd >/dev/null; kill "$backend_pid"; die "Frontend npm installation failed."; }
71+
72+
echo
73+
echo "Starting frontend dev server…"
74+
75+
export VITE_BACKEND_WS_URL="ws://localhost:8765"
76+
npm run dev &
77+
frontend_pid=$!
5378

54-
# --------------------------- Clean-up on Exit ---------------------------
55-
trap "echo 'Stopping services...'; kill $BACKEND_PID; kill $FRONTEND_PID; exit 0" INT TERM
79+
popd > /dev/null
5680

57-
echo ""
81+
###############################################################################
82+
# Wait forever until user presses Ctrl-C
83+
###############################################################################
84+
echo
5885
echo "Both backend and frontend services are running."
5986
echo "Press Ctrl+C to stop."
6087

61-
wait
88+
# Keep script alive
89+
while true; do
90+
sleep 1
91+
done

0 commit comments

Comments
 (0)