Skip to content

Commit 781bf21

Browse files
praveen-palanisamyblutrilpamelafox
authored
Add Markdown Render Support to GPT completions (#56)
* Add Markdown support. Use rehype-raw to retain raw innerHTML * Rm extra whitespace breaks * Replace python-jose with pyjwt. * Replace non-existent get_unverified_claims function * Change Exception to handle JWT-specific errors * Convert the public key to PEM format * Add pem format tests * Another test, plus autherror fixes * Remove directives to not return markdown. * Add table support * Update snapshots with new system prompt --------- Co-authored-by: pmorgen <[email protected]> Co-authored-by: Pamela Fox <[email protected]> Co-authored-by: Pamela Fox <[email protected]>
1 parent d313c6a commit 781bf21

File tree

66 files changed

+2291
-2514
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2291
-2514
lines changed

app/backend/approaches/chatreadretrieveread.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def __init__(
5757
def system_message_chat_conversation(self):
5858
return """Assistant helps the company employees with their healthcare plan questions, and questions about the employee handbook. Be brief in your answers.
5959
Answer ONLY with the facts listed in the list of sources below. If there isn't enough information below, say you don't know. Do not generate answers that don't use the sources below. If asking a clarifying question to the user would help, ask the question.
60-
For tabular information return it as an html table. Do not return markdown format. If the question is not in English, answer in the language used in the question.
60+
If the question is not in English, answer in the language used in the question.
6161
Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. Use square brackets to reference the source, for example [info1.txt]. Don't combine sources, list each source separately, for example [info1.txt][info2.pdf].
6262
{follow_up_questions_prompt}
6363
{injected_prompt}

app/backend/approaches/chatreadretrievereadvision.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ def system_message_chat_conversation(self):
7575
Answer the following question using only the data provided in the sources below.
7676
If asking a clarifying question to the user would help, ask the question.
7777
Be brief in your answers.
78-
For tabular information return it as an html table. Do not return markdown format.
7978
The text and image source can be the same file name, don't use the image title when citing the image source, only use the file name as mentioned
8079
If you cannot answer using the sources below, say you don't know. Return just the answer without any input texts.
8180
{follow_up_questions_prompt}

app/backend/approaches/retrievethenread.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ class RetrieveThenReadApproach(Approach):
2121
"You are an intelligent assistant helping Contoso Inc employees with their healthcare plan questions and employee handbook questions. "
2222
+ "Use 'you' to refer to the individual asking the questions even if they ask with 'I'. "
2323
+ "Answer the following question using only the data provided in the sources below. "
24-
+ "For tabular information return it as an html table. Do not return markdown format. "
2524
+ "Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. "
2625
+ "If you cannot answer using the sources below, say you don't know. Use below example to answer"
2726
)

app/backend/approaches/retrievethenreadvision.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ class RetrieveThenReadVisionApproach(Approach):
2828
+ "Each text source starts in a new line and has the file name followed by colon and the actual information "
2929
+ "Always include the source name from the image or text for each fact you use in the response in the format: [filename] "
3030
+ "Answer the following question using only the data provided in the sources below. "
31-
+ "For tabular information return it as an html table. Do not return markdown format. "
3231
+ "The text and image source can be the same file name, don't use the image title when citing the image source, only use the file name as mentioned "
3332
+ "If you cannot answer using the sources below, say you don't know. Return just the answer without any input texts "
3433
)

app/frontend/package-lock.json

Lines changed: 2200 additions & 2434 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/frontend/package.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,32 @@
1212
"preview": "vite preview"
1313
},
1414
"dependencies": {
15-
"@azure/msal-react": "^2.0.21",
1615
"@azure/msal-browser": "^3.17.0",
16+
"@azure/msal-react": "^2.0.21",
1717
"@fluentui/react": "^8.112.5",
1818
"@fluentui/react-components": "^9.54.13",
1919
"@fluentui/react-icons": "^2.0.249",
2020
"@react-spring/web": "^9.7.3",
21-
"marked": "^13.0.2",
2221
"dompurify": "^3.0.6",
22+
"ndjson-readablestream": "^1.2.0",
2323
"react": "^18.3.1",
2424
"react-dom": "^18.3.1",
25+
"react-markdown": "^9.0.1",
2526
"react-router-dom": "^6.23.1",
26-
"ndjson-readablestream": "^1.2.0",
2727
"react-syntax-highlighter": "^15.5.0",
28+
"rehype-raw": "^7.0.0",
29+
"remark-gfm": "^4.0.0",
2830
"scheduler": "^0.20.2"
2931
},
3032
"devDependencies": {
33+
"@types/dom-speech-recognition": "^0.0.4",
3134
"@types/dompurify": "^3.0.4",
3235
"@types/react": "^18.3.3",
3336
"@types/react-dom": "^18.3.0",
37+
"@types/react-syntax-highlighter": "^15.5.13",
3438
"@vitejs/plugin-react": "^4.3.1",
3539
"prettier": "^3.0.3",
3640
"typescript": "^5.5.3",
37-
"@types/react-syntax-highlighter": "^15.5.13",
38-
"@types/dom-speech-recognition": "^0.0.4",
3941
"vite": "^4.5.3"
4042
}
4143
}

app/frontend/src/components/Answer/Answer.module.css

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@
1818
line-height: 1.375em;
1919
padding-top: 1em;
2020
padding-bottom: 1em;
21-
white-space: pre-line;
21+
}
22+
23+
.answerText h1,
24+
h2,
25+
h2 {
26+
font-size: 1rem;
27+
font-weight: bold;
2228
}
2329

2430
.answerText table {

app/frontend/src/components/Answer/Answer.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { useMemo } from "react";
22
import { Stack, IconButton } from "@fluentui/react";
33
import DOMPurify from "dompurify";
4+
import ReactMarkdown from "react-markdown";
5+
import remarkGfm from "remark-gfm";
6+
import rehypeRaw from "rehype-raw";
47

58
import styles from "./Answer.module.css";
69
import { ChatAppResponse, getCitationFilePath } from "../../api";
@@ -71,7 +74,9 @@ export const Answer = ({
7174
</Stack.Item>
7275

7376
<Stack.Item grow>
74-
<div className={styles.answerText} dangerouslySetInnerHTML={{ __html: sanitizedAnswerHtml }}></div>
77+
<div className={styles.answerText}>
78+
<ReactMarkdown children={sanitizedAnswerHtml} rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]} />
79+
</div>
7580
</Stack.Item>
7681

7782
{!!parsedAnswer.citations.length && (

app/frontend/src/components/MarkdownViewer/MarkdownViewer.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { Spinner, SpinnerSize, MessageBar, MessageBarType, Link, IconButton } from "@fluentui/react";
12
import React, { useState, useEffect } from "react";
2-
import { marked } from "marked";
3+
import ReactMarkdown from "react-markdown";
4+
import remarkGfm from "remark-gfm";
5+
36
import styles from "./MarkdownViewer.module.css";
4-
import { Spinner, SpinnerSize, MessageBar, MessageBarType, Link, IconButton } from "@fluentui/react";
57

68
interface MarkdownViewerProps {
79
src: string;
@@ -13,12 +15,12 @@ export const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ src }) => {
1315
const [error, setError] = useState<Error | null>(null);
1416

1517
/**
16-
* Anchor links are not handled well by 'marked' and result in HTTP 404 errors as the URL they point to does not exist.
17-
* This function removes them from the resulted HTML.
18+
* Anchor links result in HTTP 404 errors as the URL they point to does not exist.
19+
* This function removes them from the markdown.
1820
*/
19-
const removeAnchorLinks = (html: string) => {
20-
const ancorLinksRegex = /<a\s+(?:[^>]*?\s+)?href=['"](#[^"']*?)['"][^>]*?>/g;
21-
return html.replace(ancorLinksRegex, "");
21+
const removeAnchorLinks = (markdown: string) => {
22+
const ancorLinksRegex = /\[.*?\]\(#.*?\)/g;
23+
return markdown.replace(ancorLinksRegex, "");
2224
};
2325

2426
useEffect(() => {
@@ -30,10 +32,9 @@ export const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ src }) => {
3032
throw new Error("Failed loading markdown file.");
3133
}
3234

33-
const markdownText = await response.text();
34-
const parsedHtml = await marked.parse(markdownText);
35-
const cleanedHtml = removeAnchorLinks(parsedHtml);
36-
setContent(cleanedHtml);
35+
let markdownText = await response.text();
36+
markdownText = removeAnchorLinks(markdownText);
37+
setContent(markdownText);
3738
} catch (error: any) {
3839
setError(error);
3940
} finally {
@@ -70,7 +71,7 @@ export const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ src }) => {
7071
href={src}
7172
download
7273
/>
73-
<div className={`${styles.markdown} ${styles.markdownViewer}`} dangerouslySetInnerHTML={{ __html: content }} />
74+
<ReactMarkdown children={content} remarkPlugins={[remarkGfm]} className={`${styles.markdown} ${styles.markdownViewer}`} />
7475
</div>
7576
)}
7677
</div>

tests/snapshots/test_app/test_ask_rtr_hybrid/client0/result.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
},
4747
{
4848
"description": [
49-
"{'role': 'system', 'content': \"You are an intelligent assistant helping Contoso Inc employees with their healthcare plan questions and employee handbook questions. Use 'you' to refer to the individual asking the questions even if they ask with 'I'. Answer the following question using only the data provided in the sources below. For tabular information return it as an html table. Do not return markdown format. Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. If you cannot answer using the sources below, say you don't know. Use below example to answer\"}",
49+
"{'role': 'system', 'content': \"You are an intelligent assistant helping Contoso Inc employees with their healthcare plan questions and employee handbook questions. Use 'you' to refer to the individual asking the questions even if they ask with 'I'. Answer the following question using only the data provided in the sources below. Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. If you cannot answer using the sources below, say you don't know. Use below example to answer\"}",
5050
"{'role': 'user', 'content': \"\\n'What is the deductible for the employee plan for a visit to Overlake in Bellevue?'\\n\\nSources:\\ninfo1.txt: deductibles depend on whether you are in-network or out-of-network. In-network deductibles are $500 for employee and $1000 for family. Out-of-network deductibles are $1000 for employee and $2000 for family.\\ninfo2.pdf: Overlake is in-network for the employee plan.\\ninfo3.pdf: Overlake is the name of the area that includes a park and ride near Bellevue.\\ninfo4.pdf: In-network institutions include Overlake, Swedish and others in the region\\n\"}",
5151
"{'role': 'assistant', 'content': 'In-network deductibles are $500 for employee and $1000 for family [info1.txt] and Overlake is in-network for the employee plan [info2.pdf][info4.pdf].'}",
5252
"{'role': 'user', 'content': 'What is the capital of France?\\nSources:\\n Benefit_Options-2.pdf: There is a whistleblower policy.'}"

0 commit comments

Comments
 (0)