Skip to content

Commit

Permalink
Merge pull request #114 from Azure-Samples/keyupdate
Browse files Browse the repository at this point in the history
Readme improvements
  • Loading branch information
pamelafox authored May 19, 2024
2 parents 5cd6cfb + d0e7fba commit 3baa19b
Show file tree
Hide file tree
Showing 15 changed files with 354 additions and 222 deletions.
193 changes: 110 additions & 83 deletions README.md

Large diffs are not rendered by default.

196 changes: 112 additions & 84 deletions docs/README.md

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions docs/deploy_existing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

# Deploying with existing Azure resources

If you already have existing Azure resources, or if you want to specify the exact name of new Azure Resource, you can do so by setting `azd` environment values.
You should set these values before running `azd up`. Once you've set them, return to the [deployment steps](../README.md#deployment).

* [Resource group](#resource-group)
* [Azure OpenAI resource](#azure-openai-resource)

## Resource group

1. Run `azd env set AZURE_RESOURCE_GROUP {Name of existing resource group}`
1. Run `azd env set AZURE_LOCATION {Location of existing resource group}`

## Azure OpenAI resource

If you already have an OpenAI resource and would like to re-use it, run `azd env set` to specify the values for the existing OpenAI resource.

```shell
azd env set AZURE_OPENAI_RESOURCE {name of OpenAI resource}
azd env set AZURE_OPENAI_RESOURCE_GROUP {name of resource group that it's inside}
azd env set AZURE_OPENAI_RESOURCE_GROUP_LOCATION {location for that group}
azd env set AZURE_OPENAI_SKU_NAME {name of the SKU, defaults to "S0"}
```
19 changes: 19 additions & 0 deletions docs/local_docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Local development server with Docker

In addition to the `Dockerfile` that's used in production, this repo includes a `docker-compose.yaml` for
local development which creates a volume for the app code. That allows you to make changes to the code
and see them instantly.

1. Install [Docker Desktop](https://www.docker.com/products/docker-desktop/). If you opened this inside Github Codespaces or a Dev Container in VS Code, installation is not needed. ⚠️ If you're on an Apple M1/M2, you won't be able to run `docker` commands inside a Dev Container; either use Codespaces or do not open the Dev Container.

2. Make sure that the `.env` file exists. The `azd up` deployment step should have created it.

3. Store a key for the OpenAI resource in the `.env` file. You can get the key from the Azure Portal, or from the output of `./infra/getkey.sh`. The key should be stored in the `.env` file as `AZURE_OPENAI_KEY`. This is necessary because Docker containers don't have access to your user Azure credentials.

4. Start the services with this command:

```shell
docker-compose up --build
```

5. Click 'http://0.0.0.0:50505' in the terminal, which should open a new tab in the browser. You may need to navigate to 'http://localhost:50505' if that URL doesn't work.
19 changes: 19 additions & 0 deletions docs/local_ollama.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Using a local LLM server

You may want to save costs by developing against a local LLM server, such as
[llamafile](https://github.com/Mozilla-Ocho/llamafile/). Note that a local LLM
will generally be slower and not as sophisticated.

Once you've got your local LLM running and serving an OpenAI-compatible endpoint, define `LOCAL_OPENAI_ENDPOINT` in your `.env` file.

For example, to point at a local llamafile server running on its default port:

```shell
LOCAL_OPENAI_ENDPOINT="http://localhost:8080/v1"
```

If you're running inside a dev container, use this local URL instead:

```shell
LOCAL_OPENAI_ENDPOINT="http://host.docker.internal:8080/v1"
```
Binary file added docs/screenshot_chatapp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion infra/aca.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ module app 'core/host/container-app-upsert.bicep' = {
value: 'true'
}
{
name: 'AZURE_OPENAI_CLIENT_ID'
// DefaultAzureCredential will look for an environment variable with this name:
name: 'AZURE_CLIENT_ID'
value: acaIdentity.properties.clientId
}
]
Expand Down
23 changes: 20 additions & 3 deletions infra/core/ai/cognitiveservices.bicep
Original file line number Diff line number Diff line change
@@ -1,23 +1,41 @@
metadata description = 'Creates an Azure Cognitive Services instance.'
param name string
param location string = resourceGroup().location
param tags object = {}

@description('The custom subdomain name used to access the API. Defaults to the value of the name parameter.')
param customSubDomainName string = name
param disableLocalAuth bool = false
param deployments array = []
param kind string = 'OpenAI'

@allowed([ 'Enabled', 'Disabled' ])
param publicNetworkAccess string = 'Enabled'
param sku object = {
name: 'S0'
}
@allowed([ 'None', 'AzureServices' ])
param bypass string = 'None'

var networkAcls = {
defaultAction: 'Allow'
}

var networkAclsWithBypass = {
defaultAction: 'Allow'
bypass: bypass
}

resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = {
resource account 'Microsoft.CognitiveServices/accounts@2023-10-01-preview' = {
name: name
location: location
tags: tags
kind: kind
properties: {
customSubDomainName: customSubDomainName
publicNetworkAccess: publicNetworkAccess
// Document Intelligence (FormRecognizer) does not support bypass in network acls
networkAcls: kind == 'FormRecognizer' ? networkAcls : networkAclsWithBypass
disableLocalAuth: disableLocalAuth
}
sku: sku
}
Expand All @@ -40,4 +58,3 @@ output endpoint string = account.properties.endpoint
output id string = account.id
output name string = account.name
output skuName string = account.sku.name
output key string = account.listKeys().key1
4 changes: 3 additions & 1 deletion infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ param openAiResourceGroupLocation string = ''
param openAiSkuName string = ''
param openAiDeploymentCapacity int = 30
param openAiApiVersion string = ''
param disableKeyBasedAuth bool = true

var resourceToken = toLower(uniqueString(subscription().id, name, location))
var tags = { 'azd-env-name': name }
Expand All @@ -47,6 +48,7 @@ module openAi 'core/ai/cognitiveservices.bicep' = {
name: !empty(openAiResourceName) ? openAiResourceName : '${resourceToken}-cog'
location: !empty(openAiResourceGroupLocation) ? openAiResourceGroupLocation : location
tags: tags
disableLocalAuth: disableKeyBasedAuth
sku: {
name: !empty(openAiSkuName) ? openAiSkuName : 'S0'
}
Expand All @@ -60,7 +62,7 @@ module openAi 'core/ai/cognitiveservices.bicep' = {
}
sku: {
name: 'Standard'
capacity: 30
capacity: openAiDeploymentCapacity
}
}
]
Expand Down
3 changes: 3 additions & 0 deletions infra/main.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
},
"acaExists": {
"value": "${SERVICE_ACA_RESOURCE_EXISTS=false}"
},
"disableKeyBasedAuth": {
"value": "${DISABLE_KEY_BASED_AUTH=true}"
}
}
}
4 changes: 2 additions & 2 deletions src/quartapp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

def create_app():
if os.getenv("RUNNING_IN_PRODUCTION"):
logging.basicConfig(level=logging.INFO)
logging.basicConfig(level=logging.WARNING)
else:
logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.INFO)

app = Quart(__name__)

Expand Down
24 changes: 7 additions & 17 deletions src/quartapp/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,13 @@ async def configure_openai():
current_app.logger.info("Using Azure OpenAI with key")
client_args["api_key"] = os.getenv("AZURE_OPENAI_KEY")
else:
if client_id := os.getenv("AZURE_OPENAI_CLIENT_ID"):
# Authenticate using a user-assigned managed identity on Azure
# See aca.bicep for value of AZURE_OPENAI_CLIENT_ID
current_app.logger.info(
"Using Azure OpenAI with managed identity for client ID %s",
client_id,
)
default_credential = azure.identity.aio.ManagedIdentityCredential(client_id=client_id)
else:
# Authenticate using the default Azure credential chain
# See https://docs.microsoft.com/azure/developer/python/azure-sdk-authenticate#defaultazurecredential
# This will *not* work inside a Docker container.
current_app.logger.info("Using Azure OpenAI with default credential")
default_credential = azure.identity.aio.DefaultAzureCredential(
exclude_shared_token_cache_credential=True
)
# Authenticate using the default Azure credential chain
# See https://docs.microsoft.com/azure/developer/python/azure-sdk-authenticate#defaultazurecredential
# This will *not* work inside a local Docker container.
# If using managed user-assigned identity, make sure that AZURE_CLIENT_ID is set
# to the client ID of the user-assigned identity.
current_app.logger.info("Using Azure OpenAI with default credential")
default_credential = azure.identity.aio.DefaultAzureCredential(exclude_shared_token_cache_credential=True)
client_args["azure_ad_token_provider"] = azure.identity.aio.get_bearer_token_provider(
default_credential, "https://cognitiveservices.azure.com/.default"
)
Expand Down Expand Up @@ -91,7 +82,6 @@ async def response_stream():
)
try:
async for event in await chat_coroutine:
current_app.logger.info(event)
yield json.dumps(event.model_dump(), ensure_ascii=False) + "\n"
except Exception as e:
current_app.logger.error(e)
Expand Down
28 changes: 15 additions & 13 deletions src/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#
aiofiles==23.2.1
# via quart
aiohttp==3.9.3
aiohttp==3.9.5
# via quartapp (pyproject.toml)
aiosignal==1.3.1
# via aiohttp
Expand All @@ -21,9 +21,9 @@ attrs==23.2.0
# via aiohttp
azure-core==1.30.1
# via azure-identity
azure-identity==1.15.0
azure-identity==1.16.0
# via quartapp (pyproject.toml)
blinker==1.7.0
blinker==1.8.2
# via
# flask
# quart
Expand All @@ -41,7 +41,7 @@ click==8.1.7
# flask
# quart
# uvicorn
cryptography==42.0.5
cryptography==42.0.7
# via
# azure-identity
# msal
Expand All @@ -54,7 +54,7 @@ frozenlist==1.4.1
# via
# aiohttp
# aiosignal
gunicorn==21.2.0
gunicorn==22.0.0
# via quartapp (pyproject.toml)
h11==0.14.0
# via
Expand Down Expand Up @@ -82,11 +82,11 @@ idna==3.7
# httpx
# requests
# yarl
itsdangerous==2.1.2
itsdangerous==2.2.0
# via
# flask
# quart
jinja2==3.1.3
jinja2==3.1.4
# via
# flask
# quart
Expand All @@ -105,7 +105,7 @@ multidict==6.0.5
# via
# aiohttp
# yarl
openai==1.16.2
openai==1.30.1
# via quartapp (pyproject.toml)
packaging==24.0
# via
Expand All @@ -117,12 +117,14 @@ priority==2.0.0
# via hypercorn
pycparser==2.22
# via cffi
pydantic==2.6.4
pydantic==2.7.1
# via openai
pydantic-core==2.16.3
pydantic-core==2.18.2
# via pydantic
pyjwt[crypto]==2.8.0
# via msal
# via
# msal
# pyjwt
python-dotenv==1.0.1
# via
# quartapp (pyproject.toml)
Expand All @@ -144,7 +146,7 @@ sniffio==1.3.1
# anyio
# httpx
# openai
tqdm==4.66.2
tqdm==4.66.4
# via openai
typing-extensions==4.11.0
# via
Expand All @@ -162,7 +164,7 @@ watchfiles==0.21.0
# via uvicorn
websockets==12.0
# via uvicorn
werkzeug==3.0.2
werkzeug==3.0.3
# via
# flask
# quart
Expand Down
18 changes: 9 additions & 9 deletions tests/snapshots/test_app/test_chat_stream_text/result.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{"id": "", "choices": [], "created": 0, "model": "", "object": "chat.completion.chunk", "system_fingerprint": null, "prompt_filter_results": [{"prompt_index": 0, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}]}
{"id": "test-123", "choices": [{"delta": {"content": null, "function_call": null, "role": "assistant", "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null}
{"id": "test-123", "choices": [{"delta": {"content": "The", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null}
{"id": "test-123", "choices": [{"delta": {"content": " capital", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null}
{"id": "test-123", "choices": [{"delta": {"content": " of", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null}
{"id": "test-123", "choices": [{"delta": {"content": " France", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null}
{"id": "test-123", "choices": [{"delta": {"content": " is", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null}
{"id": "test-123", "choices": [{"delta": {"content": " Paris.", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null}
{"id": "test-123", "choices": [{"delta": {"content": null, "function_call": null, "role": null, "tool_calls": null}, "finish_reason": "stop", "index": 0, "logprobs": null, "content_filter_results": {}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null}
{"id": "", "choices": [], "created": 0, "model": "", "object": "chat.completion.chunk", "system_fingerprint": null, "usage": null, "prompt_filter_results": [{"prompt_index": 0, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}]}
{"id": "test-123", "choices": [{"delta": {"content": null, "function_call": null, "role": "assistant", "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null, "usage": null}
{"id": "test-123", "choices": [{"delta": {"content": "The", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null, "usage": null}
{"id": "test-123", "choices": [{"delta": {"content": " capital", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null, "usage": null}
{"id": "test-123", "choices": [{"delta": {"content": " of", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null, "usage": null}
{"id": "test-123", "choices": [{"delta": {"content": " France", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null, "usage": null}
{"id": "test-123", "choices": [{"delta": {"content": " is", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null, "usage": null}
{"id": "test-123", "choices": [{"delta": {"content": " Paris.", "function_call": null, "role": null, "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {"hate": {"filtered": false, "severity": "safe"}, "self_harm": {"filtered": false, "severity": "safe"}, "sexual": {"filtered": false, "severity": "safe"}, "violence": {"filtered": false, "severity": "safe"}}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null, "usage": null}
{"id": "test-123", "choices": [{"delta": {"content": null, "function_call": null, "role": null, "tool_calls": null}, "finish_reason": "stop", "index": 0, "logprobs": null, "content_filter_results": {}}], "created": 1703462735, "model": "gpt-35-turbo", "object": "chat.completion.chunk", "system_fingerprint": null, "usage": null}
Loading

0 comments on commit 3baa19b

Please sign in to comment.