Skip to content

Commit

Permalink
fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
azliu0 committed Jun 26, 2024
1 parent a9799b6 commit 99bd3b8
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 122 deletions.
204 changes: 110 additions & 94 deletions client/src/routes/inbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export default function InboxPage() {
const [content, setContent] = useState("");
const [showAllMail, setShowAllMail] = useState(true);
const [showUnreadMail, setShowUnreadMail] = useState(false);
const [sourceList, setSourceList] = useState<JSX.Element[]>([]);

const activeThread = threads.filter((thread) => {
return thread.id === active;
Expand Down Expand Up @@ -179,41 +180,45 @@ export default function InboxPage() {
);
};

const getResponse = useCallback(() => {
// Checks if response is already stored
const currEmailID =
activeThread.emailList[activeThread.emailList.length - 1].id;
if (storedResponses[currEmailID]) {
const oldResponse = storedResponses[currEmailID];
setResponse(oldResponse);
setContent(oldResponse.content.replaceAll("\n", "<br/>"));
return;
}
const getResponse = useCallback(
(skip: boolean = false) => {
// Checks if response is already stored
const currEmailID =
activeThread.emailList[activeThread.emailList.length - 1].id;
if (storedResponses[currEmailID] && !skip) {
const oldResponse = storedResponses[currEmailID];
setResponse(oldResponse);
setContent(oldResponse.content.replaceAll("\n", "<br/>"));
return;
}

// Otherwise fetches response from server
const formData = new FormData();
formData.append("id", currEmailID.toString());
// Otherwise fetches response from server
const formData = new FormData();
formData.append("id", currEmailID.toString());

fetch(`/api/emails/get_response`, {
method: "POST",
body: formData,
})
.then((res) => {
if (res.ok) return res.json();
notifications.show({
title: "Error!",
color: "red",
message: "Something went wrong!",
});
fetch(`/api/emails/get_response`, {
method: "POST",
body: formData,
})
.then((data) => {
setStoredResponses((oldResponses) => {
return { ...oldResponses, [currEmailID]: data };
.then((res) => {
if (res.ok) return res.json();
notifications.show({
title: "Error!",
color: "red",
message: "Something went wrong!",
});
return;
})
.then((data) => {
setStoredResponses((oldResponses) => {
return { ...oldResponses, [currEmailID]: data };
});
setResponse(data);
setContent(data.content.replaceAll("\n", "<br/>"));
});
setResponse(data);
setContent(data.content.replaceAll("\n", "<br/>"));
});
}, [activeThread, storedResponses]);
},
[activeThread, storedResponses],
);

useEffect(() => {
if (activeThread && !activeThread.resolved) getResponse();
Expand Down Expand Up @@ -300,7 +305,7 @@ export default function InboxPage() {
});
})
.then(() => {
getResponse();
getResponse(true);
notifications.update({
id: "loading",
title: "Success!",
Expand Down Expand Up @@ -574,67 +579,76 @@ export default function InboxPage() {
);
});

const sourceList = response?.questions.map((question, index) => {
return (
<div key={index}>
<Text className={classes.sourceQuestion}>
{"Question: " + question}
</Text>
<Accordion>
{response.documents[index].map((document, documentIndex) => {
return (
<Accordion.Item
style={{
borderLeft: `6px solid ${computeColor(
// Math.round((document.confidence / 0.8) * 100) / 100
Math.round(document.confidence * 100) / 100,
)}`,
}}
key={documentIndex}
value={
document.label.length === 0
? "Unlabeled Document " + documentIndex
: document.label + " " + documentIndex
}
>
<Accordion.Control>
{document.label.length === 0
? "Unlabeled Document"
: document.label}
<Text className={classes.sourceConfidence}>
{"Relevance: " +
// Math.round((document.confidence / 0.8) * 100) / 100
Math.round(document.confidence * 100) / 100}
</Text>
{document.to_delete && (
<Text className={classes.deletedWarning}>
{
"This source has been deleted! Regenerating response recommended."
useEffect(() => {
console.log("setting source list");
if (response) {
setSourceList(
response.questions.map((question, index) => {
return (
<div key={index}>
<Text className={classes.sourceQuestion}>
{"Question: " + question}
</Text>
<Accordion>
{response.documents[index].map((document, documentIndex) => {
return (
<Accordion.Item
style={{
borderLeft: `6px solid ${computeColor(
// Math.round((document.confidence / 0.8) * 100) / 100
Math.round(document.confidence * 100) / 100,
)}`,
}}
key={documentIndex}
value={
document.label.length === 0
? "Unlabeled Document " + documentIndex
: document.label + " " + documentIndex
}
</Text>
)}
</Accordion.Control>
<Accordion.Panel>
<div>
{document.question.length > 0 && (
<Text className={classes.sourceText}>
{document.question}
</Text>
)}
<Text className={classes.sourceText}>
{document.content}
</Text>
<Text>Source: ({document.source})</Text>
{/* Change source to links in the future */}
</div>
</Accordion.Panel>
</Accordion.Item>
);
})}
</Accordion>
</div>
);
});
>
<Accordion.Control>
{document.label.length === 0
? "Unlabeled Document"
: document.label}
<Text className={classes.sourceConfidence}>
{"Relevance: " +
// Math.round((document.confidence / 0.8) * 100) / 100
Math.round(document.confidence * 100) / 100}
</Text>
{document.to_delete && (
<Text className={classes.deletedWarning}>
{
"This source has been deleted! Regenerating response recommended."
}
</Text>
)}
</Accordion.Control>
<Accordion.Panel>
<div>
{document.question.length > 0 && (
<Text className={classes.sourceText}>
{document.question}
</Text>
)}
<Text className={classes.sourceText}>
{document.content}
</Text>
<Text>Source: ({document.source})</Text>
Change source to links in the future
</div>
</Accordion.Panel>
</Accordion.Item>
);
})}
</Accordion>
</div>
);
}),
);
} else {
setSourceList([]);
}
}, [response]);

return (
<Grid
Expand Down Expand Up @@ -846,8 +860,10 @@ export default function InboxPage() {
</Grid.Col>
{sourceActive && (
<Grid.Col span={42} className={classes.threads}>
<Text className={classes.inboxText}>Sources</Text>
<ScrollArea h="95vh">{sourceList}</ScrollArea>
<Stack>
<Text className={classes.inboxText}>Sources</Text>
<ScrollArea h="95vh">{sourceList}</ScrollArea>
</Stack>
</Grid.Col>
)}
</Grid>
Expand Down
103 changes: 75 additions & 28 deletions server/nlp/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,14 @@ def openai_response(thread: list[OpenAIMessage], sender: str) -> str:
first name and end the email with the footer 'Best regards, The \
HackMIT Team'. Do not include the subject line in your response. \
The participant's email address is {sender}.\
You receive documents to help you answer the email. \
The user will provide documents to help you answer the email. \
Please do not include information that is not explicitly stated in the \
documents. It is very important to keep responses brief and only answer \
the questions asked. However, please write the emails in a friendly \
tone.",
tone. Finally, please make sure you address the actual content in the \
email. The documents are not guaranteed to be relevant to the email. \
Sometimes the emails don't actually have a question, in which case you \
should just write a general response to the email. ",
}
]
messages += thread
Expand All @@ -58,6 +61,7 @@ def openai_response(thread: list[OpenAIMessage], sender: str) -> str:
}
]

custom_log("query:", messages)
messages = cast(list[ChatCompletionMessageParam], messages)
response = openai.chat.completions.create(model=MODEL, messages=messages)

Expand All @@ -76,32 +80,74 @@ def openai_parse(email: str) -> list[str]:
Returns:
list of questions parsed from the email
"""
messages = [
{
"role": "system",
"content": "You are an organizer for HackMIT. Please parse incoming "
"emails from participants into a python list of separate questions. "
"Return a list of questions in the format of a python list. For "
"example, given the following email: 'What is the best way to get "
"started? What is HackMIT?', the list of questions would be ['What is "
"the best way to get started?', 'What is HackMIT?'], and you should then "
"format your response with only the python list: ['What is the best way "
"to get started?', 'What is HackMIT?']. If there are no questions in the "
"email, just return the whole email as a single element list. Do not "
"return an empty list. ",
},
{"role": "user", "content": email},
]
response = openai.chat.completions.create(
model=MODEL,
messages=[
{
"role": "system",
"content": "You are an organizer for HackMIT. Please parse incoming \
emails from participants into separate questions. Return a list of \
questions in the format of a python list.",
},
{"role": "user", "content": email},
],
messages=cast(list[ChatCompletionMessageParam], messages),
)
attempts = 0
while attempts < 3:
try:
questions = ast.literal_eval(cast(str, response.choices[0].message.content))
assert isinstance(questions, list)
assert len(questions) > 0
return questions
except Exception as e:
attempts += 1
custom_log(
"open ai failed to parse email after",
attempts,
"attempts. attempted response:",
response.choices[0].message.content,
"resulting in error:",
e,
"trying again...",
)
messages.append(
{
"role": "assistant",
"content": response.choices[0].message.content
if response.choices[0].message.content is not None
else "",
}
)
messages.append(
{
"role": "user",
"content": "your answer is not formatted as a python list, or "
"is empty. please format your answer with only the python list. "
"do not include any markdown formatting or any explanation. just "
"a parseable python list that I can directly parse with the "
"python ast library. for reference once more, here is the email: "
f"{email}",
}
)
custom_log("query:", messages)
response = openai.chat.completions.create(
model=MODEL,
messages=cast(list[ChatCompletionMessageParam], messages),
)

custom_log(
"open ai failed to parse email after three attempts. "
"returning entire email as a single question instead.",
)
try:
questions = ast.literal_eval(cast(str, response.choices[0].message.content))
assert isinstance(questions, list)
assert len(questions) > 0
return questions
except Exception as e:
custom_log(
"open ai parsed email as '",
response.choices[0].message.content,
"', resulting in error '",
e,
"'. returning entire email as a single question instead.",
)
return [email]
return [email]


def confidence_metric(confidences: list[float]) -> float:
Expand Down Expand Up @@ -136,7 +182,7 @@ def generate_context(
contexts = []
docs = {}

results = query_all(3, questions)
results = query_all(5, questions)
message = "Here is some context to help you answer this email: \n"
for result in results:
confidence = 0
Expand All @@ -147,7 +193,8 @@ def generate_context(
docs[result["query"]].append(doc)
confidences.append(confidence)

contexts.append({"role": "system", "content": message})
contexts.append({"role": "user", "content": message})
contexts.append({"role": "assistant", "content": "understood."})
return contexts, docs, confidence_metric(confidences)


Expand All @@ -174,6 +221,6 @@ def generate_response(
contexts, docs, confidence = generate_context(email)

# generate new response
thread.append({"role": "user", "content": email})
thread.append({"role": "user", "content": "EMAIL FROM USER: \n\n" + email})
thread += contexts
return openai_response(thread, sender), docs, confidence

0 comments on commit 99bd3b8

Please sign in to comment.