Skip to content

Commit

Permalink
Merge pull request #117 from Azure-Samples/chatprotocol
Browse files Browse the repository at this point in the history
Port to Chat Protocol SDK
  • Loading branch information
pamelafox authored May 22, 2024
2 parents 621ef4e + a1f2d92 commit 8f8e1fe
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 38 deletions.
4 changes: 1 addition & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
{
"name": "Azure Developer CLI",
"name": "openai-chat-app-quickstart",
"image": "mcr.microsoft.com/devcontainers/python:3.11-bullseye",
"forwardPorts": [50505],
"features": {
"ghcr.io/devcontainers/features/powershell:1.2.0": {},
"ghcr.io/devcontainers/features/docker-in-docker:latest": {
"dockerDashComposeVersion": "v2"
},
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/azure/azure-dev/azd:latest": {}
},
"customizations": {
Expand Down
10 changes: 8 additions & 2 deletions src/quartapp/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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 All @@ -63,7 +67,7 @@ async def index():
return await render_template("index.html")


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

Expand All @@ -82,7 +86,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
27 changes: 14 additions & 13 deletions src/quartapp/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/showdown.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ndjson-readablestream.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@microsoft/[email protected]/dist/iife/index.js"></script>

<script>
const form = document.getElementById("chat-form");
const messageInput = document.getElementById("message");
Expand All @@ -75,6 +76,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 @@ -91,27 +94,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 @@ -14,7 +14,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 @@ -29,7 +29,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 8f8e1fe

Please sign in to comment.