Skip to content

Commit 99bd3b8

Browse files
committed
fixes
1 parent a9799b6 commit 99bd3b8

File tree

2 files changed

+185
-122
lines changed

2 files changed

+185
-122
lines changed

client/src/routes/inbox.tsx

+110-94
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export default function InboxPage() {
7676
const [content, setContent] = useState("");
7777
const [showAllMail, setShowAllMail] = useState(true);
7878
const [showUnreadMail, setShowUnreadMail] = useState(false);
79+
const [sourceList, setSourceList] = useState<JSX.Element[]>([]);
7980

8081
const activeThread = threads.filter((thread) => {
8182
return thread.id === active;
@@ -179,41 +180,45 @@ export default function InboxPage() {
179180
);
180181
};
181182

182-
const getResponse = useCallback(() => {
183-
// Checks if response is already stored
184-
const currEmailID =
185-
activeThread.emailList[activeThread.emailList.length - 1].id;
186-
if (storedResponses[currEmailID]) {
187-
const oldResponse = storedResponses[currEmailID];
188-
setResponse(oldResponse);
189-
setContent(oldResponse.content.replaceAll("\n", "<br/>"));
190-
return;
191-
}
183+
const getResponse = useCallback(
184+
(skip: boolean = false) => {
185+
// Checks if response is already stored
186+
const currEmailID =
187+
activeThread.emailList[activeThread.emailList.length - 1].id;
188+
if (storedResponses[currEmailID] && !skip) {
189+
const oldResponse = storedResponses[currEmailID];
190+
setResponse(oldResponse);
191+
setContent(oldResponse.content.replaceAll("\n", "<br/>"));
192+
return;
193+
}
192194

193-
// Otherwise fetches response from server
194-
const formData = new FormData();
195-
formData.append("id", currEmailID.toString());
195+
// Otherwise fetches response from server
196+
const formData = new FormData();
197+
formData.append("id", currEmailID.toString());
196198

197-
fetch(`/api/emails/get_response`, {
198-
method: "POST",
199-
body: formData,
200-
})
201-
.then((res) => {
202-
if (res.ok) return res.json();
203-
notifications.show({
204-
title: "Error!",
205-
color: "red",
206-
message: "Something went wrong!",
207-
});
199+
fetch(`/api/emails/get_response`, {
200+
method: "POST",
201+
body: formData,
208202
})
209-
.then((data) => {
210-
setStoredResponses((oldResponses) => {
211-
return { ...oldResponses, [currEmailID]: data };
203+
.then((res) => {
204+
if (res.ok) return res.json();
205+
notifications.show({
206+
title: "Error!",
207+
color: "red",
208+
message: "Something went wrong!",
209+
});
210+
return;
211+
})
212+
.then((data) => {
213+
setStoredResponses((oldResponses) => {
214+
return { ...oldResponses, [currEmailID]: data };
215+
});
216+
setResponse(data);
217+
setContent(data.content.replaceAll("\n", "<br/>"));
212218
});
213-
setResponse(data);
214-
setContent(data.content.replaceAll("\n", "<br/>"));
215-
});
216-
}, [activeThread, storedResponses]);
219+
},
220+
[activeThread, storedResponses],
221+
);
217222

218223
useEffect(() => {
219224
if (activeThread && !activeThread.resolved) getResponse();
@@ -300,7 +305,7 @@ export default function InboxPage() {
300305
});
301306
})
302307
.then(() => {
303-
getResponse();
308+
getResponse(true);
304309
notifications.update({
305310
id: "loading",
306311
title: "Success!",
@@ -574,67 +579,76 @@ export default function InboxPage() {
574579
);
575580
});
576581

577-
const sourceList = response?.questions.map((question, index) => {
578-
return (
579-
<div key={index}>
580-
<Text className={classes.sourceQuestion}>
581-
{"Question: " + question}
582-
</Text>
583-
<Accordion>
584-
{response.documents[index].map((document, documentIndex) => {
585-
return (
586-
<Accordion.Item
587-
style={{
588-
borderLeft: `6px solid ${computeColor(
589-
// Math.round((document.confidence / 0.8) * 100) / 100
590-
Math.round(document.confidence * 100) / 100,
591-
)}`,
592-
}}
593-
key={documentIndex}
594-
value={
595-
document.label.length === 0
596-
? "Unlabeled Document " + documentIndex
597-
: document.label + " " + documentIndex
598-
}
599-
>
600-
<Accordion.Control>
601-
{document.label.length === 0
602-
? "Unlabeled Document"
603-
: document.label}
604-
<Text className={classes.sourceConfidence}>
605-
{"Relevance: " +
606-
// Math.round((document.confidence / 0.8) * 100) / 100
607-
Math.round(document.confidence * 100) / 100}
608-
</Text>
609-
{document.to_delete && (
610-
<Text className={classes.deletedWarning}>
611-
{
612-
"This source has been deleted! Regenerating response recommended."
582+
useEffect(() => {
583+
console.log("setting source list");
584+
if (response) {
585+
setSourceList(
586+
response.questions.map((question, index) => {
587+
return (
588+
<div key={index}>
589+
<Text className={classes.sourceQuestion}>
590+
{"Question: " + question}
591+
</Text>
592+
<Accordion>
593+
{response.documents[index].map((document, documentIndex) => {
594+
return (
595+
<Accordion.Item
596+
style={{
597+
borderLeft: `6px solid ${computeColor(
598+
// Math.round((document.confidence / 0.8) * 100) / 100
599+
Math.round(document.confidence * 100) / 100,
600+
)}`,
601+
}}
602+
key={documentIndex}
603+
value={
604+
document.label.length === 0
605+
? "Unlabeled Document " + documentIndex
606+
: document.label + " " + documentIndex
613607
}
614-
</Text>
615-
)}
616-
</Accordion.Control>
617-
<Accordion.Panel>
618-
<div>
619-
{document.question.length > 0 && (
620-
<Text className={classes.sourceText}>
621-
{document.question}
622-
</Text>
623-
)}
624-
<Text className={classes.sourceText}>
625-
{document.content}
626-
</Text>
627-
<Text>Source: ({document.source})</Text>
628-
{/* Change source to links in the future */}
629-
</div>
630-
</Accordion.Panel>
631-
</Accordion.Item>
632-
);
633-
})}
634-
</Accordion>
635-
</div>
636-
);
637-
});
608+
>
609+
<Accordion.Control>
610+
{document.label.length === 0
611+
? "Unlabeled Document"
612+
: document.label}
613+
<Text className={classes.sourceConfidence}>
614+
{"Relevance: " +
615+
// Math.round((document.confidence / 0.8) * 100) / 100
616+
Math.round(document.confidence * 100) / 100}
617+
</Text>
618+
{document.to_delete && (
619+
<Text className={classes.deletedWarning}>
620+
{
621+
"This source has been deleted! Regenerating response recommended."
622+
}
623+
</Text>
624+
)}
625+
</Accordion.Control>
626+
<Accordion.Panel>
627+
<div>
628+
{document.question.length > 0 && (
629+
<Text className={classes.sourceText}>
630+
{document.question}
631+
</Text>
632+
)}
633+
<Text className={classes.sourceText}>
634+
{document.content}
635+
</Text>
636+
<Text>Source: ({document.source})</Text>
637+
Change source to links in the future
638+
</div>
639+
</Accordion.Panel>
640+
</Accordion.Item>
641+
);
642+
})}
643+
</Accordion>
644+
</div>
645+
);
646+
}),
647+
);
648+
} else {
649+
setSourceList([]);
650+
}
651+
}, [response]);
638652

639653
return (
640654
<Grid
@@ -846,8 +860,10 @@ export default function InboxPage() {
846860
</Grid.Col>
847861
{sourceActive && (
848862
<Grid.Col span={42} className={classes.threads}>
849-
<Text className={classes.inboxText}>Sources</Text>
850-
<ScrollArea h="95vh">{sourceList}</ScrollArea>
863+
<Stack>
864+
<Text className={classes.inboxText}>Sources</Text>
865+
<ScrollArea h="95vh">{sourceList}</ScrollArea>
866+
</Stack>
851867
</Grid.Col>
852868
)}
853869
</Grid>

server/nlp/responses.py

+75-28
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,14 @@ def openai_response(thread: list[OpenAIMessage], sender: str) -> str:
3939
first name and end the email with the footer 'Best regards, The \
4040
HackMIT Team'. Do not include the subject line in your response. \
4141
The participant's email address is {sender}.\
42-
You receive documents to help you answer the email. \
42+
The user will provide documents to help you answer the email. \
4343
Please do not include information that is not explicitly stated in the \
4444
documents. It is very important to keep responses brief and only answer \
4545
the questions asked. However, please write the emails in a friendly \
46-
tone.",
46+
tone. Finally, please make sure you address the actual content in the \
47+
email. The documents are not guaranteed to be relevant to the email. \
48+
Sometimes the emails don't actually have a question, in which case you \
49+
should just write a general response to the email. ",
4750
}
4851
]
4952
messages += thread
@@ -58,6 +61,7 @@ def openai_response(thread: list[OpenAIMessage], sender: str) -> str:
5861
}
5962
]
6063

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

@@ -76,32 +80,74 @@ def openai_parse(email: str) -> list[str]:
7680
Returns:
7781
list of questions parsed from the email
7882
"""
83+
messages = [
84+
{
85+
"role": "system",
86+
"content": "You are an organizer for HackMIT. Please parse incoming "
87+
"emails from participants into a python list of separate questions. "
88+
"Return a list of questions in the format of a python list. For "
89+
"example, given the following email: 'What is the best way to get "
90+
"started? What is HackMIT?', the list of questions would be ['What is "
91+
"the best way to get started?', 'What is HackMIT?'], and you should then "
92+
"format your response with only the python list: ['What is the best way "
93+
"to get started?', 'What is HackMIT?']. If there are no questions in the "
94+
"email, just return the whole email as a single element list. Do not "
95+
"return an empty list. ",
96+
},
97+
{"role": "user", "content": email},
98+
]
7999
response = openai.chat.completions.create(
80100
model=MODEL,
81-
messages=[
82-
{
83-
"role": "system",
84-
"content": "You are an organizer for HackMIT. Please parse incoming \
85-
emails from participants into separate questions. Return a list of \
86-
questions in the format of a python list.",
87-
},
88-
{"role": "user", "content": email},
89-
],
101+
messages=cast(list[ChatCompletionMessageParam], messages),
102+
)
103+
attempts = 0
104+
while attempts < 3:
105+
try:
106+
questions = ast.literal_eval(cast(str, response.choices[0].message.content))
107+
assert isinstance(questions, list)
108+
assert len(questions) > 0
109+
return questions
110+
except Exception as e:
111+
attempts += 1
112+
custom_log(
113+
"open ai failed to parse email after",
114+
attempts,
115+
"attempts. attempted response:",
116+
response.choices[0].message.content,
117+
"resulting in error:",
118+
e,
119+
"trying again...",
120+
)
121+
messages.append(
122+
{
123+
"role": "assistant",
124+
"content": response.choices[0].message.content
125+
if response.choices[0].message.content is not None
126+
else "",
127+
}
128+
)
129+
messages.append(
130+
{
131+
"role": "user",
132+
"content": "your answer is not formatted as a python list, or "
133+
"is empty. please format your answer with only the python list. "
134+
"do not include any markdown formatting or any explanation. just "
135+
"a parseable python list that I can directly parse with the "
136+
"python ast library. for reference once more, here is the email: "
137+
f"{email}",
138+
}
139+
)
140+
custom_log("query:", messages)
141+
response = openai.chat.completions.create(
142+
model=MODEL,
143+
messages=cast(list[ChatCompletionMessageParam], messages),
144+
)
145+
146+
custom_log(
147+
"open ai failed to parse email after three attempts. "
148+
"returning entire email as a single question instead.",
90149
)
91-
try:
92-
questions = ast.literal_eval(cast(str, response.choices[0].message.content))
93-
assert isinstance(questions, list)
94-
assert len(questions) > 0
95-
return questions
96-
except Exception as e:
97-
custom_log(
98-
"open ai parsed email as '",
99-
response.choices[0].message.content,
100-
"', resulting in error '",
101-
e,
102-
"'. returning entire email as a single question instead.",
103-
)
104-
return [email]
150+
return [email]
105151

106152

107153
def confidence_metric(confidences: list[float]) -> float:
@@ -136,7 +182,7 @@ def generate_context(
136182
contexts = []
137183
docs = {}
138184

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

150-
contexts.append({"role": "system", "content": message})
196+
contexts.append({"role": "user", "content": message})
197+
contexts.append({"role": "assistant", "content": "understood."})
151198
return contexts, docs, confidence_metric(confidences)
152199

153200

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

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

0 commit comments

Comments
 (0)