Skip to content

Commit

Permalink
Added Azure AI Search
Browse files Browse the repository at this point in the history
Updated python to 3.12
Changed notebooks
Added deployment scripts for AI
Implemented Managed Identity Auth
  • Loading branch information
denniszielke committed May 31, 2024
1 parent 815de5b commit 17c1ace
Show file tree
Hide file tree
Showing 24 changed files with 418 additions and 228 deletions.
80 changes: 29 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ bash ./azd-hooks/deploy.sh phase1 $AZURE_ENV_NAME
### Test the deployed resource

```
PHASE1_URL="https://phase1.calmbush-f12187c5.swedencentral.azurecontainerapps.io"
PHASE1_URL="https://phase1..swedencentral.azurecontainerapps.io"
curl -X 'POST' \
"$PHASE1_URL/ask" \
Expand Down Expand Up @@ -73,8 +73,11 @@ uvicorn main:app --reload

Test the api with:
```
URL='http://localhost:8000'
URL='https://phase1..uksouth.azurecontainerapps.io'
curl -X 'POST' \
'http://localhost:8000/ask' \
"$URL/ask" \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
Expand All @@ -85,70 +88,53 @@ curl -X 'POST' \
```

## Deploy resources for Phase X
## Deploy resources for Phase 1

Run the following script

```
azd env get-values | grep AZURE_ENV_NAME
source <(azd env get-values | grep AZURE_ENV_NAME)
bash ./azd-hooks/deploy.sh phase1 $AZURE_ENV_NAME
```

All the other phases work the same.

## Connect to Qdrant
## Connect to Azure AI Search

The deployment will automatically inject the following environment variables into each running container:

```
QDRANT_PORT=6333
QDRANT_HOST=qdrant
QDRANT_ENDPOINT=qdrant:6333
QDRANT_PASSWORD=
AZURE_AI_SEARCH_NAME=
AZURE_AI_SEARCH_ENDPOINT=
AZURE_AI_SEARCH_KEY=
```

Here is some sample code that you can use to interact with the deployed Qdrant instance.
Here is some sample code that you can use to interact with the deployed Azure AI Search instance.

```
import os
import openai
from langchain.document_loaders import DirectoryLoader, UnstructuredMarkdownLoader
from langchain_openai import AzureOpenAIEmbeddings
from langchain_openai import AzureOpenAIEmbeddings
# Create an Embeddings Instance of Azure OpenAI
embeddings = AzureOpenAIEmbeddings(
azure_deployment = os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"),
openai_api_version = os.getenv("AZURE_OPENAI_VERSION"),
model= os.getenv("AZURE_OPENAI_EMBEDDING_MODEL")
)
# load your data
data_dir = "data/movies"
documents = DirectoryLoader(path=data_dir, glob="*.md", show_progress=True, loader_cls=UnstructuredMarkdownLoader).load()
from azure.core.credentials import AzureKeyCredential
credential = AzureKeyCredential(os.environ["AZURE_AI_SEARCH_KEY"]) if len(os.environ["AZURE_AI_SEARCH_KEY"]) > 0 else DefaultAzureCredential()
#create chunks
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
document_chunks = text_splitter.split_documents(documents)
from azure.search.documents import SearchClient
from langchain.vectorstores import Qdrant
index_name = "movies-semantic-index"
url = os.getenv('REDIS_PORT')
qdrant = Qdrant.from_documents(
data,
embeddings,
url=url,
prefer_grpc=False,
collection_name="movies",
search_client = SearchClient(
os.environ["AZURE_AI_SEARCH_ENDPOINT"],
azure_ai_search_index_name,
AzureKeyCredential(azure_ai_search_api_key)
)
vectorstore = qdrant
query = "What are the best movies about superheroes?"
query = "Can you suggest similar movies to The Matrix?"
query_results = qdrant.similarity_search(query)
for doc in query_results:
print(doc.metadata['source'])
results = list(search_client.search(
search_text=query,
query_type="simple",
include_total_count=True,
top=5
))
```

## Connect to Redis
Expand All @@ -167,7 +153,6 @@ Here is some sample code that you can use to interact with the deployed redis in
```
import redis
import os
import openai
# Redis connection details
redis_host = os.getenv('REDIS_HOST')
Expand All @@ -180,11 +165,4 @@ conn = redis.Redis(host=redis_host, port=redis_port, password=redis_password, en
if conn.ping():
print("Connected to Redis")
query = "Who is iron man?"
# Vectorize the query using OpenAI's text-embedding-ada-002 model
print("Vectorizing query...")
embedding = openai.Embedding.create(input=query, model=os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"))
query_vector = embedding["data"][0]["embedding"]
```
8 changes: 6 additions & 2 deletions azd-hooks/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ AZURE_CONTAINER_REGISTRY_NAME=$(az resource list -g $RESOURCE_GROUP --resource-t
OPENAI_NAME=$(az resource list -g $RESOURCE_GROUP --resource-type "Microsoft.CognitiveServices/accounts" --query "[0].name" -o tsv)
ENVIRONMENT_NAME=$(az resource list -g $RESOURCE_GROUP --resource-type "Microsoft.App/managedEnvironments" --query "[0].name" -o tsv)
IDENTITY_NAME=$(az resource list -g $RESOURCE_GROUP --resource-type "Microsoft.ManagedIdentity/userAssignedIdentities" --query "[0].name" -o tsv)
SEARCH_NAME=$(az resource list -g $RESOURCE_GROUP --resource-type "Microsoft.Search/searchServices" --query "[0].name" -o tsv)
SERVICE_NAME=$PHASE
AZURE_SUBSCRIPTION_ID=$(az account show --query id -o tsv)

Expand All @@ -47,6 +48,7 @@ echo "openai name: $OPENAI_NAME"
echo "environment name: $ENVIRONMENT_NAME"
echo "identity name: $IDENTITY_NAME"
echo "service name: $SERVICE_NAME"
echo "search name: $SEARCH_NAME"

CONTAINER_APP_EXISTS=$(az resource list -g $RESOURCE_GROUP --resource-type "Microsoft.App/containerApps" --query "[?contains(name, '$SERVICE_NAME')].id" -o tsv)
EXISTS="false"
Expand All @@ -61,7 +63,9 @@ fi
az acr build --subscription ${AZURE_SUBSCRIPTION_ID} --registry ${AZURE_CONTAINER_REGISTRY_NAME} --image $SERVICE_NAME:latest ./src-agents/$SERVICE_NAME
IMAGE_NAME="${AZURE_CONTAINER_REGISTRY_NAME}.azurecr.io/$SERVICE_NAME:latest"

az deployment group create -g $RESOURCE_GROUP -f ./infra/app/phaseX.bicep \
URI=$(az deployment group create -g $RESOURCE_GROUP -f ./infra/app/phaseX.bicep \
-p name=$SERVICE_NAME -p location=$LOCATION -p containerAppsEnvironmentName=$ENVIRONMENT_NAME \
-p containerRegistryName=$AZURE_CONTAINER_REGISTRY_NAME -p applicationInsightsName=$APPINSIGHTS_NAME \
-p openaiName=$OPENAI_NAME -p identityName=$IDENTITY_NAME -p imageName=$IMAGE_NAME -p exists=$EXISTS
-p openaiName=$OPENAI_NAME -p searchName=$SEARCH_NAME -p identityName=$IDENTITY_NAME -p imageName=$IMAGE_NAME -p exists=$EXISTS --query properties.outputs.uri.value)

echo "deployment uri: $URI"
35 changes: 35 additions & 0 deletions infra/ai/search.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
param name string
param location string = resourceGroup().location
param tags object = {}

resource search 'Microsoft.Search/searchServices@2023-11-01' = {
name: name
location: location
sku: {
name: 'standard'
}
tags: tags
properties: {
authOptions: {
aadOrApiKey: {
aadAuthFailureMode: 'http401WithBearerChallenge'
}
}
disableLocalAuth: false
encryptionWithCmk: {
enforcement: 'Unspecified'
}
hostingMode: 'Default'
networkRuleSet: {
ipRules: []
bypass: 'None'
}
partitionCount: 1
publicNetworkAccess: 'Enabled'
replicaCount: 1
}
}

output searchName string = search.name
output searchEndpoint string = 'https://${search.name}.search.windows.net'
output searchAdminKey string = listAdminKeys(search.id, '2023-11-01').primaryKey
15 changes: 11 additions & 4 deletions infra/app/phase1.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ param containerRegistryName string
param serviceName string = 'phase1'
param imageName string
param openaiApiVersion string
param searchName string
param searchEndpoint string

resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: identityName
Expand All @@ -40,6 +42,7 @@ module app '../core/host/container-app-upsert.bicep' = {
openaiName: openaiName
containerAppsEnvironmentName: containerAppsEnvironmentName
containerRegistryName: containerRegistryName
searchName: searchName
env: [
{
name: 'AZURE_CLIENT_ID'
Expand All @@ -49,10 +52,14 @@ module app '../core/host/container-app-upsert.bicep' = {
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: applicationInsights.properties.ConnectionString
}
// {
// name: 'AZURE_OPENAI_API_KEY'
// value: openaiApiKey
// }
{
name: 'AZURE_AI_SEARCH_NAME'
value: searchName
}
{
name: 'AZURE_AI_SEARCH_ENDPOINT'
value: searchEndpoint
}
{
name: 'AZURE_OPENAI_ENDPOINT'
value: openaiEndpoint
Expand Down
15 changes: 11 additions & 4 deletions infra/app/phaseX.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ param applicationInsightsName string
param identityName string
param openaiName string
param imageName string
param searchName string

var tags = { 'azd-env-name': containerAppsEnvironmentName }
var completionDeploymentModelName = 'gpt-35-turbo'
Expand Down Expand Up @@ -35,6 +36,7 @@ module app '../core/host/container-app-upsert.bicep' = {
openaiName: openaiName
containerAppsEnvironmentName: containerAppsEnvironmentName
containerRegistryName: containerRegistryName
searchName: searchName
env: [
{
name: 'AZURE_CLIENT_ID'
Expand All @@ -44,10 +46,14 @@ module app '../core/host/container-app-upsert.bicep' = {
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: applicationInsights.properties.ConnectionString
}
// {
// name: 'AZURE_OPENAI_API_KEY'
// value: openaiApiKey
// }
{
name: 'AZURE_AI_SEARCH_NAME'
value: searchName
}
{
name: 'AZURE_AI_SEARCH_ENDPOINT'
value: 'https://${searchName}.search.windows.net'
}
{
name: 'AZURE_OPENAI_ENDPOINT'
value: openaiEndpoint
Expand Down Expand Up @@ -77,3 +83,4 @@ output SERVICE_API_IDENTITY_PRINCIPAL_ID string = apiIdentity.properties.princip
output SERVICE_API_NAME string = app.outputs.name
output SERVICE_API_URI string = app.outputs.uri
output SERVICE_API_IMAGE_NAME string = app.outputs.imageName
output uri string = app.outputs.uri
2 changes: 2 additions & 0 deletions infra/core/host/container-app-upsert.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ param external bool = true
param targetPort int = 8080
param exists bool
param openaiName string
param searchName string

@description('User assigned identity name')
param identityName string = ''
Expand Down Expand Up @@ -44,6 +45,7 @@ module app 'container-app.bicep' = {
imageName: imageName
targetPort: targetPort
openaiName: openaiName
searchName: searchName
}
}

Expand Down
16 changes: 10 additions & 6 deletions infra/core/host/container-app.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ param external bool = true
param imageName string
param targetPort int = 80
param openaiName string
param searchName string

@description('User assigned identity name')
param identityName string = ''
Expand Down Expand Up @@ -41,6 +42,14 @@ module containerRegistryAccess '../security/registry-access.bicep' = {
}
}

module searchAccess '../security/search-access.bicep' = {
name: '${deployment().name}-search-access'
params: {
searchName: searchName
principalId: userIdentity.properties.principalId
}
}

resource app 'Microsoft.App/containerApps@2023-04-01-preview' = {
name: name
location: location
Expand Down Expand Up @@ -73,9 +82,6 @@ resource app 'Microsoft.App/containerApps@2023-04-01-preview' = {
}
template: {
serviceBinds : [
{
serviceId: qdrant.id
}
{
serviceId: redis.id
}
Expand Down Expand Up @@ -107,9 +113,7 @@ resource redis 'Microsoft.App/containerApps@2023-04-01-preview' existing = {
name: 'redis'
}

resource qdrant 'Microsoft.App/containerApps@2023-04-01-preview' existing = {
name: 'qdrant'
}


output defaultDomain string = containerAppsEnvironment.properties.defaultDomain
output imageName string = imageName
Expand Down
16 changes: 0 additions & 16 deletions infra/core/host/container-apps-environment.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,6 @@ resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2022-03-01'
}
}

resource qdrant 'Microsoft.App/containerApps@2023-04-01-preview' = {
name: 'qdrant'
location: location
tags: tags
properties: {
environmentId: containerAppsEnvironment.id
configuration: {
service: {
type: 'qdrant'
}
}
}
}

resource redis 'Microsoft.App/containerApps@2023-04-01-preview' = {
name: 'redis'
location: location
Expand All @@ -53,5 +39,3 @@ resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10

output defaultDomain string = containerAppsEnvironment.properties.defaultDomain
output name string = containerAppsEnvironment.name
output redisEndpoint string = redis.properties.configuration.ingress.fqdn
output qdrantEndpoint string = qdrant.properties.configuration.ingress.fqdn
2 changes: 0 additions & 2 deletions infra/core/host/container-apps.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,3 @@ output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain
output environmentName string = containerAppsEnvironment.outputs.name
output registryLoginServer string = containerRegistry.outputs.loginServer
output registryName string = containerRegistry.outputs.name
output redisEndpoint string = containerAppsEnvironment.outputs.redisEndpoint
output qdrantEndpoint string = containerAppsEnvironment.outputs.qdrantEndpoint
20 changes: 20 additions & 0 deletions infra/core/security/search-access.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
param searchName string
param principalId string

// https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
// Search Index Data Reader
var openAiUserRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '1407120a-92aa-4202-b7e9-c0e197c71c8f')

resource searchAccess 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
scope: search // Use when specifying a scope that is different than the deployment scope
name: guid(subscription().id, resourceGroup().id, principalId, openAiUserRole)
properties: {
roleDefinitionId: openAiUserRole
principalType: 'ServicePrincipal'
principalId: principalId
}
}

resource search 'Microsoft.Search/searchServices@2023-11-01' existing = {
name: searchName
}
Loading

0 comments on commit 17c1ace

Please sign in to comment.