From 2d7378acb551d2f03ed1ccaab95c44923046915a Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Mon, 10 Jun 2024 20:40:32 +0000 Subject: [PATCH 1/2] Various UI and backend improvements --- README.md | 19 ++- src/fastapi_app/api_routes.py | 2 +- src/fastapi_app/rag_advanced.py | 74 ++++++----- src/fastapi_app/rag_simple.py | 56 ++++---- src/frontend/package-lock.json | 124 +++++++++++++++--- src/frontend/package.json | 2 +- src/frontend/src/api/api.ts | 13 -- src/frontend/src/api/index.ts | 1 - src/frontend/src/api/models.ts | 38 +----- .../AnalysisPanel/AnalysisPanel.tsx | 24 +--- .../src/components/Answer/Answer.module.css | 5 + src/frontend/src/components/Answer/Answer.tsx | 35 +++-- src/frontend/src/pages/chat/Chat.tsx | 63 ++------- 13 files changed, 239 insertions(+), 217 deletions(-) delete mode 100644 src/frontend/src/api/api.ts diff --git a/README.md b/README.md index c46ba92b..2bd4c84a 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ A related option is VS Code Dev Containers, which will open the project in your 1. Start Docker Desktop (install it if not already installed) 2. Open the project: - [![Open in Dev Containers](https://img.shields.io/static/v1?style=for-the-badge&label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](placeholder) + [![Open in Dev Containers](https://img.shields.io/static/v1?style=for-the-badge&label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/azure-samples/rag-postgres-openai-python) 3. In the VS Code window that opens, once the project files show up (this may take several minutes), open a terminal window. 4. Continue with the [deployment steps](#deployment) @@ -135,7 +135,17 @@ Since the local app uses OpenAI models, you should first deploy it for the optim If you opened the project in Codespaces or a Dev Container, these commands will already have been run for you. -2. Run the FastAPI backend: +2. Build the frontend: + + ```bash + cd src/frontend + npm install + npm run build + ``` + + There must be an initial build of static assets before running the backend, since the backend serves static files from the `src/static` directory. + +3. Run the FastAPI backend (with hot reloading): ```shell python3 -m uvicorn fastapi_app:create_app --factory --reload @@ -143,7 +153,7 @@ Since the local app uses OpenAI models, you should first deploy it for the optim Or you can run "Backend" in the VS Code Run & Debug menu. -3. Run the frontend: +4. Run the frontend (with hot reloading): ```bash cd src/frontend @@ -152,7 +162,7 @@ Since the local app uses OpenAI models, you should first deploy it for the optim Or you can run "Frontend" or "Frontend & Backend" in the VS Code Run & Debug menu. -4. Open the browser at `http://localhost:5173/` and you will see the frontend. +5. Open the browser at `http://localhost:5173/` and you will see the frontend. ## Costs @@ -161,6 +171,7 @@ You may try the [Azure pricing calculator](https://azure.microsoft.com/pricing/c * Azure Container Apps: Pay-as-you-go tier. Costs based on vCPU and memory used. [Pricing](https://azure.microsoft.com/pricing/details/container-apps/) * Azure OpenAI: Standard tier, GPT and Ada models. Pricing per 1K tokens used, and at least 1K tokens are used per question. [Pricing](https://azure.microsoft.com/pricing/details/cognitive-services/openai-service/) +* Azure PostgreSQL Flexible Server: Burstable Tier with 1 CPU core, 32GB storage. Pricing is hourly. [Pricing](https://azure.microsoft.com/pricing/details/postgresql/flexible-server/) * Azure Monitor: Pay-as-you-go tier. Costs based on data ingested. [Pricing](https://azure.microsoft.com/pricing/details/monitor/) ## Security Guidelines diff --git a/src/fastapi_app/api_routes.py b/src/fastapi_app/api_routes.py index 8c03dda7..fde96db2 100644 --- a/src/fastapi_app/api_routes.py +++ b/src/fastapi_app/api_routes.py @@ -52,7 +52,7 @@ async def search_handler(query: str, top: int = 5, enable_vector_search: bool = return [item.to_dict() for item in results] -@router.post("/chat") +@router.post("/chat/") async def chat_handler(chat_request: ChatRequest): messages = [message.model_dump() for message in chat_request.messages] overrides = chat_request.context.get("overrides", {}) diff --git a/src/fastapi_app/rag_advanced.py b/src/fastapi_app/rag_advanced.py index 426ae996..00e96bfd 100644 --- a/src/fastapi_app/rag_advanced.py +++ b/src/fastapi_app/rag_advanced.py @@ -99,42 +99,44 @@ async def run( n=1, stream=False, ) - chat_resp = chat_completion_response.model_dump() - chat_resp["choices"][0]["context"] = { - "data_points": {"text": sources_content}, - "thoughts": [ - ThoughtStep( - title="Prompt to generate search arguments", - description=[str(message) for message in query_messages], - props=( - {"model": self.chat_model, "deployment": self.chat_deployment} - if self.chat_deployment - else {"model": self.chat_model} + first_choice = chat_completion_response.model_dump()["choices"][0] + return { + "message": first_choice["message"], + "context": { + "data_points": {item.id: item.to_dict() for item in results}, + "thoughts": [ + ThoughtStep( + title="Prompt to generate search arguments", + description=[str(message) for message in query_messages], + props=( + {"model": self.chat_model, "deployment": self.chat_deployment} + if self.chat_deployment + else {"model": self.chat_model} + ), ), - ), - ThoughtStep( - title="Search using generated search arguments", - description=query_text, - props={ - "top": top, - "vector_search": vector_search, - "text_search": text_search, - "filters": filters, - }, - ), - ThoughtStep( - title="Search results", - description=[result.to_dict() for result in results], - ), - ThoughtStep( - title="Prompt to generate answer", - description=[str(message) for message in messages], - props=( - {"model": self.chat_model, "deployment": self.chat_deployment} - if self.chat_deployment - else {"model": self.chat_model} + ThoughtStep( + title="Search using generated search arguments", + description=query_text, + props={ + "top": top, + "vector_search": vector_search, + "text_search": text_search, + "filters": filters, + }, ), - ), - ], + ThoughtStep( + title="Search results", + description=[result.to_dict() for result in results], + ), + ThoughtStep( + title="Prompt to generate answer", + description=[str(message) for message in messages], + props=( + {"model": self.chat_model, "deployment": self.chat_deployment} + if self.chat_deployment + else {"model": self.chat_model} + ), + ), + ], + }, } - return chat_resp diff --git a/src/fastapi_app/rag_simple.py b/src/fastapi_app/rag_simple.py index 4cf328a7..bf1613e2 100644 --- a/src/fastapi_app/rag_simple.py +++ b/src/fastapi_app/rag_simple.py @@ -66,32 +66,34 @@ async def run( n=1, stream=False, ) - chat_resp = chat_completion_response.model_dump() - chat_resp["choices"][0]["context"] = { - "data_points": {"text": sources_content}, - "thoughts": [ - ThoughtStep( - title="Search query for database", - description=original_user_query if text_search else None, - props={ - "top": top, - "vector_search": vector_search, - "text_search": text_search, - }, - ), - ThoughtStep( - title="Search results", - description=[result.to_dict() for result in results], - ), - ThoughtStep( - title="Prompt to generate answer", - description=[str(message) for message in messages], - props=( - {"model": self.chat_model, "deployment": self.chat_deployment} - if self.chat_deployment - else {"model": self.chat_model} + first_choice = chat_completion_response.model_dump()["choices"][0] + return { + "message": first_choice["message"], + "context": { + "data_points": {item.id: item.to_dict() for item in results}, + "thoughts": [ + ThoughtStep( + title="Search query for database", + description=original_user_query if text_search else None, + props={ + "top": top, + "vector_search": vector_search, + "text_search": text_search, + }, ), - ), - ], + ThoughtStep( + title="Search results", + description=[result.to_dict() for result in results], + ), + ThoughtStep( + title="Prompt to generate answer", + description=[str(message) for message in messages], + props=( + {"model": self.chat_model, "deployment": self.chat_deployment} + if self.chat_deployment + else {"model": self.chat_model} + ), + ), + ], + }, } - return chat_resp diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 7a250730..64a6a845 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -13,10 +13,10 @@ "@fluentui/react": "^8.112.5", "@fluentui/react-components": "^9.37.3", "@fluentui/react-icons": "^2.0.221", + "@microsoft/ai-chat-protocol": "1.0.0-beta.20240604.1", "@react-spring/web": "^9.7.3", "dompurify": "^3.0.6", "marked": "^9.1.6", - "ndjson-readablestream": "^1.0.7", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.18.0", @@ -2423,6 +2423,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@microsoft/ai-chat-protocol": { + "version": "1.0.0-beta.20240604.1", + "resolved": "https://registry.npmjs.org/@microsoft/ai-chat-protocol/-/ai-chat-protocol-1.0.0-beta.20240604.1.tgz", + "integrity": "sha512-g1sy0z5KHp1q1UruJhca/RIfayGvw+YeKxTkQHvUvmB0N/7NeGjlz8nSxSSPtjNvKoeF7bE06mxr8H7qhL3fQQ==", + "dependencies": { + "@typespec/ts-http-runtime": "^1.0.0-alpha.20240228.1" + } + }, "node_modules/@microsoft/load-themed-styles": { "version": "1.10.295", "license": "MIT" @@ -2637,6 +2645,19 @@ "version": "2.0.7", "license": "MIT" }, + "node_modules/@typespec/ts-http-runtime": { + "version": "1.0.0-alpha.20240610.1", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-1.0.0-alpha.20240610.1.tgz", + "integrity": "sha512-f1pHRnMpCZG1u7EucgZ00E9MpqI/HpZZ7FOu8oub/QH/9ki+5BtRbQfM17EDTi5w5JDWlp9Os+7fQVWLidozKQ==", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.1.1", "dev": true, @@ -2655,6 +2676,17 @@ "vite": "^4.2.0" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "dev": true, @@ -2785,7 +2817,6 @@ }, "node_modules/debug": { "version": "4.3.4", - "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -2976,6 +3007,30 @@ "node": "*" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/is-alphabetical": { "version": "1.0.4", "license": "MIT", @@ -3095,7 +3150,6 @@ }, "node_modules/ms": { "version": "2.1.2", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -3115,10 +3169,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/ndjson-readablestream": { - "version": "1.0.7", - "license": "MIT" - }, "node_modules/node-releases": { "version": "2.0.13", "dev": true, @@ -3468,8 +3518,9 @@ } }, "node_modules/tslib": { - "version": "2.5.0", - "license": "0BSD" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/typescript": { "version": "5.2.2", @@ -5180,6 +5231,14 @@ } } }, + "@microsoft/ai-chat-protocol": { + "version": "1.0.0-beta.20240604.1", + "resolved": "https://registry.npmjs.org/@microsoft/ai-chat-protocol/-/ai-chat-protocol-1.0.0-beta.20240604.1.tgz", + "integrity": "sha512-g1sy0z5KHp1q1UruJhca/RIfayGvw+YeKxTkQHvUvmB0N/7NeGjlz8nSxSSPtjNvKoeF7bE06mxr8H7qhL3fQQ==", + "requires": { + "@typespec/ts-http-runtime": "^1.0.0-alpha.20240228.1" + } + }, "@microsoft/load-themed-styles": { "version": "1.10.295" }, @@ -5323,6 +5382,16 @@ "@types/unist": { "version": "2.0.7" }, + "@typespec/ts-http-runtime": { + "version": "1.0.0-alpha.20240610.1", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-1.0.0-alpha.20240610.1.tgz", + "integrity": "sha512-f1pHRnMpCZG1u7EucgZ00E9MpqI/HpZZ7FOu8oub/QH/9ki+5BtRbQfM17EDTi5w5JDWlp9Os+7fQVWLidozKQ==", + "requires": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + } + }, "@vitejs/plugin-react": { "version": "4.1.1", "dev": true, @@ -5334,6 +5403,14 @@ "react-refresh": "^0.14.0" } }, + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "requires": { + "debug": "^4.3.4" + } + }, "ansi-styles": { "version": "3.2.1", "dev": true, @@ -5396,7 +5473,6 @@ }, "debug": { "version": "4.3.4", - "dev": true, "requires": { "ms": "2.1.2" } @@ -5510,6 +5586,24 @@ "highlight.js": { "version": "10.7.3" }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, "is-alphabetical": { "version": "1.0.4" }, @@ -5572,16 +5666,12 @@ "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==" }, "ms": { - "version": "2.1.2", - "dev": true + "version": "2.1.2" }, "nanoid": { "version": "3.3.6", "dev": true }, - "ndjson-readablestream": { - "version": "1.0.7" - }, "node-releases": { "version": "2.0.13", "dev": true @@ -5784,7 +5874,9 @@ "dev": true }, "tslib": { - "version": "2.5.0" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "typescript": { "version": "5.2.2" diff --git a/src/frontend/package.json b/src/frontend/package.json index d6ae12e9..2e286273 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -23,7 +23,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.18.0", - "ndjson-readablestream": "^1.0.7", + "@microsoft/ai-chat-protocol": "1.0.0-beta.20240604.1", "react-syntax-highlighter": "^15.5.0", "scheduler": "^0.20.2" }, diff --git a/src/frontend/src/api/api.ts b/src/frontend/src/api/api.ts deleted file mode 100644 index f99be713..00000000 --- a/src/frontend/src/api/api.ts +++ /dev/null @@ -1,13 +0,0 @@ -const BACKEND_URI = ""; - -import { ChatAppRequest } from "./models"; - -export async function chatApi(request: ChatAppRequest): Promise { - return await fetch(`${BACKEND_URI}/chat`, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(request) - }); -} \ No newline at end of file diff --git a/src/frontend/src/api/index.ts b/src/frontend/src/api/index.ts index 0475d357..609c2bb2 100644 --- a/src/frontend/src/api/index.ts +++ b/src/frontend/src/api/index.ts @@ -1,2 +1 @@ -export * from "./api"; export * from "./models"; diff --git a/src/frontend/src/api/models.ts b/src/frontend/src/api/models.ts index 9c9b196e..deee7b68 100644 --- a/src/frontend/src/api/models.ts +++ b/src/frontend/src/api/models.ts @@ -1,3 +1,5 @@ +import { AIChatCompletion } from "@microsoft/ai-chat-protocol"; + export const enum RetrievalMode { Hybrid = "hybrid", Vectors = "vectors", @@ -12,44 +14,18 @@ export type ChatAppRequestOverrides = { prompt_template?: string; }; -export type ResponseMessage = { - content: string; - role: string; -}; - export type Thoughts = { title: string; description: any; // It can be any output from the api props?: { [key: string]: string }; }; -export type ResponseContext = { - data_points: string[]; +export type RAGContext = { + data_points: { [key: string]: any }; followup_questions: string[] | null; thoughts: Thoughts[]; }; -export type ResponseChoice = { - index: number; - message: ResponseMessage; - context: ResponseContext; - session_state: any; -}; - -export type ChatAppResponseOrError = { - choices?: ResponseChoice[]; - error?: string; -}; - -export type ChatAppResponse = { - choices: ResponseChoice[]; -}; - -export type ChatAppRequestContext = { - overrides?: ChatAppRequestOverrides; -}; - -export type ChatAppRequest = { - messages: ResponseMessage[]; - context?: ChatAppRequestContext; -}; \ No newline at end of file +export interface RAGChatCompletion extends AIChatCompletion { + context: RAGContext; +} diff --git a/src/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx b/src/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx index d1bde2b8..015a83aa 100644 --- a/src/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx +++ b/src/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx @@ -3,7 +3,7 @@ import { Stack, Pivot, PivotItem } from "@fluentui/react"; import styles from "./AnalysisPanel.module.css"; import { SupportingContent } from "../SupportingContent"; -import { ChatAppResponse } from "../../api"; +import { RAGChatCompletion } from "../../api"; import { AnalysisPanelTabs } from "./AnalysisPanelTabs"; import { ThoughtProcess } from "./ThoughtProcess"; import { MarkdownViewer } from "../MarkdownViewer"; @@ -15,14 +15,14 @@ interface Props { onActiveTabChanged: (tab: AnalysisPanelTabs) => void; activeCitation: string | undefined; citationHeight: string; - answer: ChatAppResponse; + answer: RAGChatCompletion; } const pivotItemDisabledStyle = { disabled: true, style: { color: "grey" } }; export const AnalysisPanel = ({ answer, activeTab, activeCitation, citationHeight, className, onActiveTabChanged }: Props) => { - const isDisabledThoughtProcessTab: boolean = !answer.choices[0].context.thoughts; - const isDisabledSupportingContentTab: boolean = !answer.choices[0].context.data_points; + const isDisabledThoughtProcessTab: boolean = !answer.context.thoughts; + const isDisabledSupportingContentTab: boolean = !answer.context.data_points; const isDisabledCitationTab: boolean = !activeCitation; const [citation, setCitation] = useState(""); @@ -75,21 +75,7 @@ export const AnalysisPanel = ({ answer, activeTab, activeCitation, citationHeigh headerText="Thought process" headerButtonProps={isDisabledThoughtProcessTab ? pivotItemDisabledStyle : undefined} > - - - - - - - {renderFileViewer()} + ); diff --git a/src/frontend/src/components/Answer/Answer.module.css b/src/frontend/src/components/Answer/Answer.module.css index 782f05d7..0b816216 100644 --- a/src/frontend/src/components/Answer/Answer.module.css +++ b/src/frontend/src/components/Answer/Answer.module.css @@ -35,6 +35,11 @@ outline: 2px solid rgba(115, 118, 225, 1); } +.referenceMetadata { + margin-top: -16px; + font-style: italic; +} + .citationLearnMore { margin-right: 5px; font-weight: 600; diff --git a/src/frontend/src/components/Answer/Answer.tsx b/src/frontend/src/components/Answer/Answer.tsx index 4aaf8223..a542064c 100644 --- a/src/frontend/src/components/Answer/Answer.tsx +++ b/src/frontend/src/components/Answer/Answer.tsx @@ -4,12 +4,12 @@ import DOMPurify from "dompurify"; import styles from "./Answer.module.css"; -import { ChatAppResponse } from "../../api"; +import { RAGChatCompletion } from "../../api/models"; import { parseAnswerToHtml } from "./AnswerParser"; import { AnswerIcon } from "./AnswerIcon"; interface Props { - answer: ChatAppResponse; + answer: RAGChatCompletion; isSelected?: boolean; isStreaming: boolean; onCitationClicked: (filePath: string) => void; @@ -29,8 +29,8 @@ export const Answer = ({ onFollowupQuestionClicked, showFollowupQuestions }: Props) => { - const followupQuestions = answer.choices[0].context.followup_questions; - const messageContent = answer.choices[0].message.content; + const followupQuestions = answer.context.followup_questions; + const messageContent = answer.message.content; const parsedAnswer = useMemo(() => parseAnswerToHtml(messageContent, isStreaming, onCitationClicked), [answer]); const sanitizedAnswerHtml = DOMPurify.sanitize(parsedAnswer.answerHtml); @@ -47,15 +47,7 @@ export const Answer = ({ title="Show thought process" ariaLabel="Show thought process" onClick={() => onThoughtProcessClicked()} - disabled={!answer.choices[0].context.thoughts?.length} - /> - onSupportingContentClicked()} - disabled={!answer.choices[0].context.data_points} + disabled={!answer.context.thoughts?.length} /> @@ -68,14 +60,21 @@ export const Answer = ({ {!!parsedAnswer.citations.length && ( - Citations: - {parsedAnswer.citations.map((x, i) => { + References: +
    + {parsedAnswer.citations.map((rowId, ind) => { + const citation = answer.context.data_points[rowId]; + if (!citation) return null; return ( - - {`${++i}. ${x}`} - +
  1. +

    {citation.name}

    +

    Brand: {citation.brand}

    +

    Price: {citation.price}

    +

    {citation.description}

    +
  2. ); })} +
)} diff --git a/src/frontend/src/pages/chat/Chat.tsx b/src/frontend/src/pages/chat/Chat.tsx index 73dcf142..6918cf76 100644 --- a/src/frontend/src/pages/chat/Chat.tsx +++ b/src/frontend/src/pages/chat/Chat.tsx @@ -1,17 +1,11 @@ import { useRef, useState, useEffect } from "react"; import { Panel, DefaultButton, TextField, SpinButton, Slider, Checkbox } from "@fluentui/react"; import { SparkleFilled } from "@fluentui/react-icons"; +import { AIChatMessage, AIChatProtocolClient } from "@microsoft/ai-chat-protocol"; import styles from "./Chat.module.css"; -import { - chatApi, - RetrievalMode, - ChatAppResponse, - ChatAppResponseOrError, - ChatAppRequest, - ResponseMessage -} from "../../api"; +import {RetrievalMode, RAGChatCompletion} from "../../api"; import { Answer, AnswerError, AnswerLoading } from "../../components/Answer"; import { QuestionInput } from "../../components/QuestionInput"; import { ExampleList } from "../../components/Example"; @@ -33,15 +27,13 @@ const Chat = () => { const chatMessageStreamEnd = useRef(null); const [isLoading, setIsLoading] = useState(false); - const [isStreaming, setIsStreaming] = useState(false); const [error, setError] = useState(); const [activeCitation, setActiveCitation] = useState(); const [activeAnalysisPanelTab, setActiveAnalysisPanelTab] = useState(undefined); const [selectedAnswer, setSelectedAnswer] = useState(0); - const [answers, setAnswers] = useState<[user: string, response: ChatAppResponse][]>([]); - const [streamedAnswers, setStreamedAnswers] = useState<[user: string, response: ChatAppResponse][]>([]); + const [answers, setAnswers] = useState<[user: string, response: RAGChatCompletion][]>([]); const makeApiRequest = async (question: string) => { lastQuestionRef.current = question; @@ -52,13 +44,12 @@ const Chat = () => { setActiveAnalysisPanelTab(undefined); try { - const messages: ResponseMessage[] = answers.flatMap(a => [ - { content: a[0], role: "user" }, - { content: a[1].choices[0].message.content, role: "assistant" } + const messages: AIChatMessage[] = answers.flatMap(answer => [ + { content: answer[0], role: "user" }, + { content: answer[1].message.content, role: "assistant" } ]); - - const request: ChatAppRequest = { - messages: [...messages, { content: question, role: "user" }], + const allMessages: AIChatMessage[] = [...messages, { content: question, role: "user" }]; + const options = { context: { overrides: { use_advanced_flow: useAdvancedFlow, @@ -67,17 +58,11 @@ const Chat = () => { prompt_template: promptTemplate.length === 0 ? undefined : promptTemplate, temperature: temperature } - }, + } }; - const response = await chatApi(request); - if (!response.body) { - throw Error("No response body"); - } - const parsedResponse: ChatAppResponseOrError = await response.json(); - if (response.status > 299 || !response.ok) { - throw Error(parsedResponse.error || "Unknown error"); - } - setAnswers([...answers, [question, parsedResponse as ChatAppResponse]]); + const chatClient: AIChatProtocolClient = new AIChatProtocolClient("/chat"); + const result = await chatClient.getCompletion(allMessages, options) as RAGChatCompletion; + setAnswers([...answers, [question, result]]); } catch (e) { setError(e); } finally { @@ -91,13 +76,10 @@ const Chat = () => { setActiveCitation(undefined); setActiveAnalysisPanelTab(undefined); setAnswers([]); - setStreamedAnswers([]); setIsLoading(false); - setIsStreaming(false); }; useEffect(() => chatMessageStreamEnd.current?.scrollIntoView({ behavior: "smooth" }), [isLoading]); - useEffect(() => chatMessageStreamEnd.current?.scrollIntoView({ behavior: "auto" }), [streamedAnswers]); const onPromptTemplateChange = (_ev?: React.FormEvent, newValue?: string) => { setPromptTemplate(newValue || ""); @@ -161,26 +143,7 @@ const Chat = () => { ) : (
- {isStreaming && - streamedAnswers.map((streamedAnswer, index) => ( -
- -
- onShowCitation(c, index)} - onThoughtProcessClicked={() => onToggleTab(AnalysisPanelTabs.ThoughtProcessTab, index)} - onSupportingContentClicked={() => onToggleTab(AnalysisPanelTabs.SupportingContentTab, index)} - onFollowupQuestionClicked={q => makeApiRequest(q)} - /> -
-
- ))} - {!isStreaming && - answers.map((answer, index) => ( + {answers.map((answer, index) => (
From e6a0fda0588a7a8116eb4f77d9e8ed155ef57437 Mon Sep 17 00:00:00 2001 From: Pamela Fox Date: Tue, 11 Jun 2024 00:09:01 +0000 Subject: [PATCH 2/2] Update to latest SDK --- src/fastapi_app/api_routes.py | 2 +- src/frontend/package-lock.json | 14 +++++++------- src/frontend/package.json | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/fastapi_app/api_routes.py b/src/fastapi_app/api_routes.py index fde96db2..8c03dda7 100644 --- a/src/fastapi_app/api_routes.py +++ b/src/fastapi_app/api_routes.py @@ -52,7 +52,7 @@ async def search_handler(query: str, top: int = 5, enable_vector_search: bool = return [item.to_dict() for item in results] -@router.post("/chat/") +@router.post("/chat") async def chat_handler(chat_request: ChatRequest): messages = [message.model_dump() for message in chat_request.messages] overrides = chat_request.context.get("overrides", {}) diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 64a6a845..91ccbd66 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -13,7 +13,7 @@ "@fluentui/react": "^8.112.5", "@fluentui/react-components": "^9.37.3", "@fluentui/react-icons": "^2.0.221", - "@microsoft/ai-chat-protocol": "1.0.0-beta.20240604.1", + "@microsoft/ai-chat-protocol": "1.0.0-beta.20240610.1", "@react-spring/web": "^9.7.3", "dompurify": "^3.0.6", "marked": "^9.1.6", @@ -2424,9 +2424,9 @@ "license": "MIT" }, "node_modules/@microsoft/ai-chat-protocol": { - "version": "1.0.0-beta.20240604.1", - "resolved": "https://registry.npmjs.org/@microsoft/ai-chat-protocol/-/ai-chat-protocol-1.0.0-beta.20240604.1.tgz", - "integrity": "sha512-g1sy0z5KHp1q1UruJhca/RIfayGvw+YeKxTkQHvUvmB0N/7NeGjlz8nSxSSPtjNvKoeF7bE06mxr8H7qhL3fQQ==", + "version": "1.0.0-beta.20240610.1", + "resolved": "https://registry.npmjs.org/@microsoft/ai-chat-protocol/-/ai-chat-protocol-1.0.0-beta.20240610.1.tgz", + "integrity": "sha512-VGRt4DTCnoCKLqXs1H+3F9yeD8kTATktWxL4j2OUeOoqEiqWUiNm66qQMBzQJRv9Oi+vV9weQyZ6O6mHrf91HQ==", "dependencies": { "@typespec/ts-http-runtime": "^1.0.0-alpha.20240228.1" } @@ -5232,9 +5232,9 @@ } }, "@microsoft/ai-chat-protocol": { - "version": "1.0.0-beta.20240604.1", - "resolved": "https://registry.npmjs.org/@microsoft/ai-chat-protocol/-/ai-chat-protocol-1.0.0-beta.20240604.1.tgz", - "integrity": "sha512-g1sy0z5KHp1q1UruJhca/RIfayGvw+YeKxTkQHvUvmB0N/7NeGjlz8nSxSSPtjNvKoeF7bE06mxr8H7qhL3fQQ==", + "version": "1.0.0-beta.20240610.1", + "resolved": "https://registry.npmjs.org/@microsoft/ai-chat-protocol/-/ai-chat-protocol-1.0.0-beta.20240610.1.tgz", + "integrity": "sha512-VGRt4DTCnoCKLqXs1H+3F9yeD8kTATktWxL4j2OUeOoqEiqWUiNm66qQMBzQJRv9Oi+vV9weQyZ6O6mHrf91HQ==", "requires": { "@typespec/ts-http-runtime": "^1.0.0-alpha.20240228.1" } diff --git a/src/frontend/package.json b/src/frontend/package.json index 2e286273..86378191 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -23,7 +23,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.18.0", - "@microsoft/ai-chat-protocol": "1.0.0-beta.20240604.1", + "@microsoft/ai-chat-protocol": "1.0.0-beta.20240610.1", "react-syntax-highlighter": "^15.5.0", "scheduler": "^0.20.2" },