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" }, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy