Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: implement chat endpoint switch #905

Merged
merged 41 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6174b34
Attribute to choose between custom and byod conversation types
gaurarpit May 14, 2024
1ebdea0
Merge branch 'main' into arpit/feature/implement-chat-endpoint-switch
gaurarpit May 14, 2024
34ecb33
Merge branch 'main' into arpit/feature/implement-chat-endpoint-switch
gaurarpit May 15, 2024
f12ef32
add the /conversation binding to use the ChatConversationType as an i…
gaurarpit May 15, 2024
4774eeb
Merge branch 'main' into arpit/feature/implement-chat-endpoint-switch
gaurarpit May 15, 2024
65a7688
Merge branch 'main' into arpit/feature/implement-chat-endpoint-switch
gaurarpit May 15, 2024
7c20822
Merge branch 'main' into arpit/feature/implement-chat-endpoint-switch
gaurarpit May 15, 2024
695f3ed
Merge remote-tracking branch 'origin/arpit/feature/implement-chat-end…
gaurarpit May 15, 2024
d95fd89
Merge remote-tracking branch 'origin/arpit/feature/implement-chat-end…
gaurarpit May 15, 2024
73e7fbd
Merge remote-tracking branch 'origin/arpit/feature/implement-chat-end…
gaurarpit May 15, 2024
e4ac540
fix typo
gaurarpit May 16, 2024
3f9fbd8
Merge branch 'main' into arpit/feature/implement-chat-endpoint-switch
gaurarpit May 16, 2024
2a983dc
add the chat conversation type to main.json
gaurarpit May 16, 2024
7630b59
add bicep params and make changes to call the /conversation from the …
gaurarpit May 16, 2024
5f3209c
Merge branch 'main' into arpit/feature/implement-chat-endpoint-switch
gaurarpit May 16, 2024
629339c
undo premature bindings removal
gaurarpit May 16, 2024
5b4221e
update var name to a better suggestion from CHAT_CONVERSATION_TYPE to…
gaurarpit May 16, 2024
2eecd7b
Merge branch 'main' into arpit/feature/implement-chat-endpoint-switch
gaurarpit May 16, 2024
315a63d
Attribute to choose between custom and byod conversation types
gaurarpit May 14, 2024
03a8070
add the /conversation binding to use the ChatConversationType as an i…
gaurarpit May 15, 2024
e838b66
fix typo
gaurarpit May 16, 2024
130bfc4
add the chat conversation type to main.json
gaurarpit May 16, 2024
813c312
add bicep params and make changes to call the /conversation from the …
gaurarpit May 16, 2024
f0bd85e
undo premature bindings removal
gaurarpit May 16, 2024
2573a04
update var name to a better suggestion from CHAT_CONVERSATION_TYPE to…
gaurarpit May 16, 2024
ce99d31
Merge remote-tracking branch 'origin/arpit/feature/implement-chat-end…
gaurarpit May 16, 2024
d37946b
merged and ran pre-commit again.
gaurarpit May 16, 2024
d64a19f
Merge branch 'main' into arpit/feature/implement-chat-endpoint-switch
gaurarpit May 17, 2024
f8cc67a
Merge branch 'main' into arpit/feature/implement-chat-endpoint-switch
gaurarpit May 21, 2024
2a25b69
chore: force poetry to use python 3.11
liammoat May 21, 2024
072a2f8
fix: use await when invoking conversation_custom
liammoat May 21, 2024
37b4f2d
test: added test for conversation flow
liammoat May 21, 2024
6e1a080
refactor: removing conversation flow specific endpoints
liammoat May 21, 2024
dea40fb
test: update test_app to use env_helper_mock, supporting new endpoint
liammoat May 21, 2024
d12b2dc
test: test for failure condition on conversation flow
liammoat May 21, 2024
7f0fd5f
Merge remote-tracking branch 'origin/main' into arpit/feature/impleme…
liammoat May 21, 2024
ea33999
Merge remote-tracking branch 'origin/main' into arpit/feature/impleme…
liammoat May 21, 2024
42257f0
Update merged test with new endpoints.
gaurarpit May 21, 2024
e9c860b
refactor: switch to Enum of conversation flow
liammoat May 22, 2024
496c553
refactor: pylint for couple of lines
liammoat May 22, 2024
7ffe361
docs: documentation for conversation flow options
liammoat May 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .devcontainer/postCreate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ pip install poetry

# https://pypi.org/project/poetry-plugin-export/
pip install poetry-plugin-export

poetry env use python3.11

poetry config warnings.export false

poetry install --with dev
Expand Down
2 changes: 2 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ AZURE_SPEECH_SERVICE_REGION=
AZURE_AUTH_TYPE=keys
USE_KEY_VAULT=true
AZURE_KEY_VAULT_ENDPOINT=
# Chat conversation type to decide between custom or byod (bring your own data) conversation type
CONVERSATION_FLOW=
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ By default, this repo comes with one specific set of RAG configurations includin

The accelerator presented here provides several options, for example:
* The ability to ground a model using both data and public web pages
* A backend that mimics the [On Your Data](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/use-your-data) flow, with the ability to switch to a custom backend
* A backend with support for 'custom' and 'On Your Data' [conversation flows](./docs/conversation_flow_options.md)
* Advanced prompt engineering capabilities
* An admin site for ingesting/inspecting/configuring your dataset on the fly
* Push or Pull model for data ingestion: See [integrated vectorization](./docs/integrated_vectorization.md) documentation for more details
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from enum import Enum


class ConversationFlow(Enum):
CUSTOM = "custom"
BYOD = "byod"
5 changes: 5 additions & 0 deletions code/backend/batch/utilities/helpers/env_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from dotenv import load_dotenv
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from azure.keyvault.secrets import SecretClient
from backend.batch.utilities.helpers.config.conversation_flow import ConversationFlow
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the weird project structure, using an absolute import here breaks the Admin app and Azure Function when running in Azure

I think that using a relative import should fix it


logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -204,6 +205,10 @@ def __load_config(self, **kwargs) -> None:
self.ORCHESTRATION_STRATEGY = os.getenv(
"ORCHESTRATION_STRATEGY", "openai_function"
)
# Conversation Type - which chooses between custom or byod
self.CONVERSATION_FLOW = os.getenv(
"CONVERSATION_FLOW", ConversationFlow.CUSTOM.value
)
# Speech Service
self.AZURE_SPEECH_SERVICE_NAME = os.getenv("AZURE_SPEECH_SERVICE_NAME", "")
self.AZURE_SPEECH_SERVICE_REGION = os.getenv("AZURE_SPEECH_SERVICE_REGION")
Expand Down
30 changes: 21 additions & 9 deletions code/create_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from backend.batch.utilities.helpers.env_helper import EnvHelper
from backend.batch.utilities.helpers.orchestrator_helper import Orchestrator
from backend.batch.utilities.helpers.config.config_helper import ConfigHelper
from backend.batch.utilities.helpers.config.conversation_flow import ConversationFlow
from azure.mgmt.cognitiveservices import CognitiveServicesManagementClient
from azure.identity import DefaultAzureCredential

Expand Down Expand Up @@ -333,7 +334,6 @@ def static_file(path):
def health():
return "OK"

@app.route("/api/conversation/azure_byod", methods=["POST"])
def conversation_azure_byod():
try:
if env_helper.should_use_data():
Expand All @@ -342,19 +342,16 @@ def conversation_azure_byod():
return conversation_without_data(request, env_helper)
except Exception as e:
error_message = str(e)
logger.exception(
"Exception in /api/conversation/azure_byod | %s", error_message
)
logger.exception("Exception in /api/conversation | %s", error_message)
return (
jsonify(
{
"error": "Exception in /api/conversation/azure_byod. See log for more details."
"error": "Exception in /api/conversation. See log for more details."
}
),
500,
)

@app.route("/api/conversation/custom", methods=["POST"])
async def conversation_custom():
message_orchestrator = get_message_orchestrator()

Expand Down Expand Up @@ -387,13 +384,28 @@ async def conversation_custom():

except Exception as e:
error_message = str(e)
logger.exception(
"Exception in /api/conversation/custom | %s", error_message
logger.exception("Exception in /api/conversation | %s", error_message)
return (
jsonify(
{
"error": "Exception in /api/conversation. See log for more details."
}
),
500,
)

@app.route("/api/conversation", methods=["POST"])
async def conversation():
conversation_flow = env_helper.CONVERSATION_FLOW
if conversation_flow == ConversationFlow.CUSTOM.value:
return await conversation_custom()
elif conversation_flow == ConversationFlow.BYOD.value:
return conversation_azure_byod()
else:
return (
jsonify(
{
"error": "Exception in /api/conversation/custom. See log for more details."
"error": "Invalid conversation flow configured. Value can only be 'custom' or 'byod'."
}
),
500,
Expand Down
19 changes: 2 additions & 17 deletions code/frontend/src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,8 @@
import { ConversationRequest } from "./models";

export async function conversationApi(options: ConversationRequest, abortSignal: AbortSignal): Promise<Response> {
const response = await fetch("/api/conversation/azure_byod", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
messages: options.messages
}),
signal: abortSignal
});

return response;
}


export async function customConversationApi(options: ConversationRequest, abortSignal: AbortSignal): Promise<Response> {
const response = await fetch("/api/conversation/custom", {
export async function callConversationApi(options: ConversationRequest, abortSignal: AbortSignal): Promise<Response> {
const response = await fetch("/api/conversation", {
method: "POST",
headers: {
"Content-Type": "application/json"
Expand Down
4 changes: 2 additions & 2 deletions code/frontend/src/pages/chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { multiLingualSpeechRecognizer } from "../../util/SpeechToText";
import {
ChatMessage,
ConversationRequest,
customConversationApi,
callConversationApi,
Citation,
ToolMessageContent,
ChatResponse,
Expand Down Expand Up @@ -75,7 +75,7 @@ const Chat = () => {

let result = {} as ChatResponse;
try {
const response = await customConversationApi(
const response = await callConversationApi(
request,
abortController.signal
);
Expand Down
2 changes: 2 additions & 0 deletions code/tests/functional/app_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import base64
import logging
import os
from backend.batch.utilities.helpers.config.conversation_flow import ConversationFlow

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -69,6 +70,7 @@ class AppConfig:
"LOAD_CONFIG_FROM_BLOB_STORAGE": "True",
"LOGLEVEL": "DEBUG",
"ORCHESTRATION_STRATEGY": "openai_function",
"CONVERSATION_FLOW": ConversationFlow.CUSTOM.value,
"AZURE_SPEECH_RECOGNIZER_LANGUAGES": "en-US,es-ES",
"TIKTOKEN_CACHE_DIR": f"{os.path.dirname(os.path.realpath(__file__))}/resources",
"USE_ADVANCED_IMAGE_PROCESSING": "False",
Expand Down
2 changes: 2 additions & 0 deletions code/tests/functional/tests/backend_api/default/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import pytest
from backend.batch.utilities.helpers.config.conversation_flow import ConversationFlow
from tests.functional.app_config import AppConfig
from tests.functional.tests.backend_api.common import get_free_port, start_app
from backend.batch.utilities.helpers.config.config_helper import ConfigHelper
Expand Down Expand Up @@ -34,6 +35,7 @@ def app_config(make_httpserver, ca):
"USE_ADVANCED_IMAGE_PROCESSING": "True",
"SSL_CERT_FILE": ca_temp_path,
"CURL_CA_BUNDLE": ca_temp_path,
"CONVERSATION_FLOW": ConversationFlow.CUSTOM.value,
}
)
logger.info(f"Created app config: {app_config.get_all()}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

pytestmark = pytest.mark.functional

path = "/api/conversation/custom"
path = "/api/conversation"
body = {
"conversation_id": "123",
"messages": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

pytestmark = pytest.mark.functional

path = "/api/conversation/custom"
path = "/api/conversation"
body = {
"conversation_id": "123",
"messages": [
Expand Down Expand Up @@ -646,7 +646,7 @@ def test_post_returns_error_when_downstream_fails(

# when
response = requests.post(
f"{app_url}/api/conversation/custom",
f"{app_url}/api/conversation",
json={
"conversation_id": "123",
"messages": [
Expand All @@ -661,5 +661,5 @@ def test_post_returns_error_when_downstream_fails(
assert response.status_code == 500
assert response.headers["Content-Type"] == "application/json"
assert json.loads(response.text) == {
"error": "Exception in /api/conversation/custom. See log for more details."
"error": "Exception in /api/conversation. See log for more details."
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

pytestmark = pytest.mark.functional

path = "/api/conversation/custom"
path = "/api/conversation"
body = {
"conversation_id": "123",
"messages": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

pytestmark = pytest.mark.functional

path = "/api/conversation/custom"
path = "/api/conversation"
body = {
"conversation_id": "123",
"messages": [
Expand Down Expand Up @@ -257,7 +257,7 @@ def test_post_returns_error_when_downstream_fails(

# when
response = requests.post(
f"{app_url}/api/conversation/custom",
f"{app_url}/api/conversation",
json={
"conversation_id": "123",
"messages": [
Expand All @@ -272,5 +272,5 @@ def test_post_returns_error_when_downstream_fails(
assert response.status_code == 500
assert response.headers["Content-Type"] == "application/json"
assert json.loads(response.text) == {
"error": "Exception in /api/conversation/custom. See log for more details."
"error": "Exception in /api/conversation. See log for more details."
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

pytestmark = pytest.mark.functional

path = "/api/conversation/custom"
path = "/api/conversation"
body = {
"conversation_id": "123",
"messages": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

pytestmark = pytest.mark.functional

path = "/api/conversation/custom"
path = "/api/conversation"
body = {
"conversation_id": "123",
"messages": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

pytestmark = pytest.mark.functional

path = "/api/conversation/custom"
path = "/api/conversation"
body = {
"conversation_id": "123",
"messages": [
Expand Down Expand Up @@ -264,7 +264,7 @@ def test_post_returns_error_when_downstream_fails(

# when
response = requests.post(
f"{app_url}/api/conversation/custom",
f"{app_url}/api/conversation",
json={
"conversation_id": "123",
"messages": [
Expand All @@ -279,5 +279,5 @@ def test_post_returns_error_when_downstream_fails(
assert response.status_code == 500
assert response.headers["Content-Type"] == "application/json"
assert json.loads(response.text) == {
"error": "Exception in /api/conversation/custom. See log for more details."
"error": "Exception in /api/conversation. See log for more details."
}
Empty file.
60 changes: 60 additions & 0 deletions code/tests/functional/tests/backend_api/with_byod/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import logging
import pytest
from backend.batch.utilities.helpers.config.conversation_flow import ConversationFlow
from tests.functional.app_config import AppConfig
from tests.functional.tests.backend_api.common import get_free_port, start_app
from backend.batch.utilities.helpers.config.config_helper import ConfigHelper
from backend.batch.utilities.helpers.env_helper import EnvHelper

logger = logging.getLogger(__name__)


@pytest.fixture(scope="package")
def app_port() -> int:
logger.info("Getting free port")
return get_free_port()


@pytest.fixture(scope="package")
def app_url(app_port: int) -> str:
return f"http://localhost:{app_port}"


@pytest.fixture(scope="package")
def app_config(make_httpserver, ca):
logger.info("Creating APP CONFIG")
with ca.cert_pem.tempfile() as ca_temp_path:
app_config = AppConfig(
{
"AZURE_OPENAI_ENDPOINT": f"https://localhost:{make_httpserver.port}/",
"AZURE_SEARCH_SERVICE": f"https://localhost:{make_httpserver.port}/",
"AZURE_CONTENT_SAFETY_ENDPOINT": f"https://localhost:{make_httpserver.port}/",
"AZURE_SPEECH_REGION_ENDPOINT": f"https://localhost:{make_httpserver.port}/",
"AZURE_STORAGE_ACCOUNT_ENDPOINT": f"https://localhost:{make_httpserver.port}/",
"SSL_CERT_FILE": ca_temp_path,
"CURL_CA_BUNDLE": ca_temp_path,
"CONVERSATION_FLOW": ConversationFlow.BYOD.value,
}
)
logger.info(f"Created app config: {app_config.get_all()}")
yield app_config


@pytest.fixture(scope="package", autouse=True)
def manage_app(app_port: int, app_config: AppConfig):
app_config.apply_to_environment()
EnvHelper.clear_instance()
ConfigHelper.clear_config()
start_app(app_port)
yield
app_config.remove_from_environment()
EnvHelper.clear_instance()
ConfigHelper.clear_config()


@pytest.fixture(autouse=True)
def reset_default_config():
"""
Reset the default config after each test
"""
ConfigHelper.clear_config()
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

pytestmark = pytest.mark.functional

path = "/api/conversation/azure_byod"
path = "/api/conversation"
body = {
"conversation_id": "123",
"messages": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import pytest
from backend.batch.utilities.helpers.config.conversation_flow import ConversationFlow
from tests.functional.app_config import AppConfig
from tests.functional.tests.backend_api.common import get_free_port, start_app
from backend.batch.utilities.helpers.config.config_helper import ConfigHelper
Expand Down Expand Up @@ -33,6 +34,7 @@ def app_config(make_httpserver, ca):
"AZURE_STORAGE_ACCOUNT_ENDPOINT": f"https://localhost:{make_httpserver.port}/",
"SSL_CERT_FILE": ca_temp_path,
"CURL_CA_BUNDLE": ca_temp_path,
"CONVERSATION_FLOW": ConversationFlow.BYOD.value,
}
)
logger.info(f"Created app config: {app_config.get_all()}")
Expand Down
Loading
Loading