Skip to content

Commit

Permalink
Merge pull request #11 from Azure-Samples/chatprot
Browse files Browse the repository at this point in the history
Port to Chat Protocol
  • Loading branch information
pamelafox authored May 22, 2024
2 parents a681930 + 4b45e34 commit 3b5fa8c
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 36 deletions.
11 changes: 8 additions & 3 deletions src/quartapp/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ async def configure_openai():
client_args["azure_ad_token_provider"] = azure.identity.aio.get_bearer_token_provider(
default_credential, "https://cognitiveservices.azure.com/.default"
)
if not os.getenv("AZURE_OPENAI_ENDPOINT"):
raise ValueError("AZURE_OPENAI_ENDPOINT is required for Azure OpenAI")
if not os.getenv("AZURE_OPENAI_CHATGPT_DEPLOYMENT"):
raise ValueError("AZURE_OPENAI_CHATGPT_DEPLOYMENT is required for Azure OpenAI")
bp.openai_client = openai.AsyncAzureOpenAI(
api_version=os.getenv("AZURE_OPENAI_API_VERSION") or "2024-02-15-preview",
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
Expand Down Expand Up @@ -92,7 +96,6 @@ def extract_username(headers, default_username="You"):

token = json.loads(base64.b64decode(headers.get("X-MS-CLIENT-PRINCIPAL")))
claims = {claim["typ"]: claim["val"] for claim in token["claims"]}
current_app.logger.info(f"Claims: {claims}")
return claims.get("name", default_username)


Expand All @@ -102,7 +105,7 @@ async def index():
return await render_template("index.html", username=username)


@bp.post("/chat")
@bp.post("/chat/stream")
async def chat_handler():
request_messages = (await request.get_json())["messages"]

Expand All @@ -121,7 +124,9 @@ async def response_stream():
)
try:
async for event in await chat_coroutine:
yield json.dumps(event.model_dump(), ensure_ascii=False) + "\n"
event_dict = event.model_dump()
if event_dict["choices"]:
yield json.dumps(event_dict["choices"][0], ensure_ascii=False) + "\n"
except Exception as e:
current_app.logger.error(e)
yield json.dumps({"error": str(e)}, ensure_ascii=False) + "\n"
Expand Down
44 changes: 31 additions & 13 deletions src/quartapp/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@
<body>
<main class="h-100 mh-100 d-flex flex-column overflow-hidden justify-content-start">

<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">Azure OpenAI Chat App + Microsoft Entra</a>
{% if username %}
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ml-auto" style="margin-left: auto;">
<li class="nav-item">
<a class="nav-link" href="#" style="color:white;">{{ username }}</a>
</li>
<li class="nav-item">
<a class="nav-link btn btn-primary" style="color:white;" href=".auth/logout">Logout</a>
</li>
</ul>
</div>
{% endif %}
</div>
</nav>

<div id="messages" class="px-4 pb-4 pt-2 flex-grow-1 overflow-y-auto overflow-x-hidden align-items-stretch">

<template id="message-template-user">
Expand Down Expand Up @@ -63,7 +81,7 @@
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/showdown.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ndjson-readablestream@1.0.1/dist/ndjson-readablestream.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@microsoft/ai-chat-protocol@1.0.0-alpha.20240520.1/dist/iife/index.js"></script>
<script>
const form = document.getElementById("chat-form");
const messageInput = document.getElementById("message");
Expand All @@ -73,6 +91,8 @@
const converter = new showdown.Converter();
const messages = [];

const client = new ChatProtocol.AIChatProtocolClient("/chat");

form.addEventListener("submit", async function(e) {
e.preventDefault();
const message = messageInput.value;
Expand All @@ -89,27 +109,25 @@
"role": "user",
"content": message
});
const response = await fetch("/chat", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({messages: messages})
});

const result = await client.getStreamedCompletion(messages);

let answer = "";
for await (const event of readNDJSONStream(response.body)) {
if (event["choices"] && event["choices"].length > 0 && event["choices"][0]["delta"]["content"]) {
for await (const response of result) {
if (!response.delta) {
continue;
}
if (response.delta.content) {
// Clear out the DIV if its the first answer chunk we've received
if (answer == "") {
messageDiv.innerHTML = "";
}
answer += event["choices"][0]["delta"]["content"];
answer += response.delta.content;
messageDiv.innerHTML = converter.makeHtml(answer);
messageDiv.scrollIntoView();
} else if (event["error"]) {
messageDiv.innerHTML = "Error: " + event["error"];
}
if (response.error) {
messageDiv.innerHTML = "Error: " + response.error;
}
}
messages.push({
Expand Down
17 changes: 8 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,8 @@
{"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}
{"delta": {"content": null, "function_call": null, "role": "assistant", "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {}}
{"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"}}}
{"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"}}}
{"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"}}}
{"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"}}}
{"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"}}}
{"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"}}}
{"delta": {"content": null, "function_call": null, "role": null, "tool_calls": null}, "finish_reason": "stop", "index": 0, "logprobs": null, "content_filter_results": {}}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
{"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": " Germany", "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": " Berlin.", "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}
{"delta": {"content": null, "function_call": null, "role": "assistant", "tool_calls": null}, "finish_reason": null, "index": 0, "logprobs": null, "content_filter_results": {}}
{"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"}}}
{"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"}}}
{"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"}}}
{"delta": {"content": " Germany", "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"}}}
{"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"}}}
{"delta": {"content": " Berlin.", "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"}}}
{"delta": {"content": null, "function_call": null, "role": null, "tool_calls": null}, "finish_reason": "stop", "index": 0, "logprobs": null, "content_filter_results": {}}
4 changes: 2 additions & 2 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async def test_index(client):
@pytest.mark.asyncio
async def test_chat_stream_text(client, snapshot):
response = await client.post(
"/chat",
"/chat/stream",
json={
"messages": [
{"role": "user", "content": "What is the capital of France?"},
Expand All @@ -33,7 +33,7 @@ async def test_chat_stream_text(client, snapshot):
@pytest.mark.asyncio
async def test_chat_stream_text_history(client, snapshot):
response = await client.post(
"/chat",
"/chat/stream",
json={
"messages": [
{"role": "user", "content": "What is the capital of France?"},
Expand Down

0 comments on commit 3b5fa8c

Please sign in to comment.