Sharath commited on
Commit
beaea8b
·
1 Parent(s): 2f001bf

added full coursework + equation support

Browse files
Files changed (3) hide show
  1. .gitattributes +1 -0
  2. app.py +173 -57
  3. requirements.txt +5 -1
.gitattributes CHANGED
@@ -36,3 +36,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
36
  chroma/** filter=lfs diff=lfs merge=lfs -text
37
  chroma/**/* filter=lfs diff=lfs merge=lfs -text
38
  chroma/**/** filter=lfs diff=lfs merge=lfs -text
 
 
36
  chroma/** filter=lfs diff=lfs merge=lfs -text
37
  chroma/**/* filter=lfs diff=lfs merge=lfs -text
38
  chroma/**/** filter=lfs diff=lfs merge=lfs -text
39
+ chroma/**/**/** filter=lfs diff=lfs merge=lfs -text
app.py CHANGED
@@ -1,63 +1,179 @@
1
  import gradio as gr
2
- from huggingface_hub import InferenceClient
3
-
4
- """
5
- For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
6
- """
7
- client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
8
-
9
-
10
- def respond(
11
- message,
12
- history: list[tuple[str, str]],
13
- system_message,
14
- max_tokens,
15
- temperature,
16
- top_p,
17
- ):
18
- messages = [{"role": "system", "content": system_message}]
19
-
20
- for val in history:
21
- if val[0]:
22
- messages.append({"role": "user", "content": val[0]})
23
- if val[1]:
24
- messages.append({"role": "assistant", "content": val[1]})
25
-
26
- messages.append({"role": "user", "content": message})
27
-
28
- response = ""
29
-
30
- for message in client.chat_completion(
31
- messages,
32
- max_tokens=max_tokens,
33
- stream=True,
34
- temperature=temperature,
35
- top_p=top_p,
36
- ):
37
- token = message.choices[0].delta.content
38
-
39
- response += token
40
- yield response
41
-
42
- """
43
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
44
- """
45
- demo = gr.ChatInterface(
46
- respond,
47
- additional_inputs=[
48
- gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
49
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
50
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
51
- gr.Slider(
52
- minimum=0.1,
53
- maximum=1.0,
54
- value=0.95,
55
- step=0.05,
56
- label="Top-p (nucleus sampling)",
57
- ),
58
- ],
59
  )
 
 
 
 
 
 
 
 
 
 
 
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  if __name__ == "__main__":
63
  demo.launch()
 
1
  import gradio as gr
2
+ from langchain.schema import (
3
+ AIMessage,
4
+ HumanMessage,
5
+ SystemMessage
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  )
7
+ from langchain.prompts import PromptTemplate
8
+ from langchain.output_parsers import PydanticOutputParser, OutputFixingParser
9
+ from pydantic import BaseModel, Field
10
+ from enum import Enum
11
+ from langchain_openai import ChatOpenAI
12
+ from langchain.embeddings.huggingface import HuggingFaceEmbeddings
13
+ from langchain.vectorstores import Chroma
14
+ import json
15
+ import os
16
+ from dotenv import load_dotenv
17
+ load_dotenv()
18
 
19
+ class IsAnswerable(Enum):
20
+ YES = "YES - the given 'question' can be confidently answered using the given 'context'"
21
+ NO = "NO - the given 'question' cannot be answered with the given 'context'"
22
+
23
+ class AnswerStatus(BaseModel):
24
+ status: IsAnswerable = Field(description="")
25
+ answer: str = Field(description="answer the student's 'question' based solely on the given 'context'. Answer only in HTML format, and use math style for equations. ")
26
+
27
+ class FAQBot():
28
+ def __init__(self):
29
+
30
+ self.model = ChatOpenAI(
31
+ model_name='gpt-3.5-turbo',
32
+ openai_api_key=os.getenv("OPENAI_API_KEY"),
33
+ openai_organization=os.getenv("OPENAI_ORGANIZATION"),
34
+ )
35
+
36
+ embedding_function = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
37
+ self.db = Chroma(persist_directory="./chroma/db", embedding_function=embedding_function, collection_name="course")
38
+ self.db_faq = Chroma(persist_directory="./chroma/db_faq", embedding_function=embedding_function, collection_name="faq")
39
+ self.qna_dict = json.load(open('./chroma/qna_dict'))
40
+ self.course_db = json.load(open('./chroma/course_db'))
41
+
42
+ self.parser = PydanticOutputParser(pydantic_object=AnswerStatus)
43
+ self.fix_parser = OutputFixingParser.from_llm(parser=self.parser, llm=self.model, max_retries=3)
44
+ self.prompt = PromptTemplate(
45
+ template = '''
46
+ You're a helpful teaching assistant for a technical course on {course}. You will only answer student's 'question' based on the given 'context' of the course.\n
47
+ The 'context' is a combination of two things - 1) Previous question and answers on the {course} that are similar to the student's question, and 2) some snippets of text from the course contents that are relevant to the student's 'question'.
48
+
49
+ {format_instructions}\n
50
+
51
+ ***
52
+ 'query' : {question}
53
+ ***
54
+
55
+ $$$
56
+ 'context' : {context}
57
+ $$$
58
+ I am reminding you again, you are a teaching assistant, do not add any facts into the answer that is not given in the 'context'.
59
+ Answer only in HTML format.
60
+ ''',
61
+ input_variables=["question", "context", "course"],
62
+ partial_variables={
63
+ "format_instructions": self.parser.get_format_instructions(),
64
+ },
65
+ )
66
+ self.search_conf_thresh = 1
67
+ self.excuse_me_msg = '''<p>I dont think I know the answer for this, let me check with the professor.</p>'''
68
+
69
+ def ask_question(self, question, verbose=False):
70
+
71
+ retrieved_answers = ''
72
+ ## search in faq
73
+ if verbose:
74
+ print('Search in FAQ')
75
+ results_faq = self.db_faq.similarity_search_with_score(question, k=3)
76
+
77
+ ### save only the high confidence search results
78
+ if verbose:
79
+ print('\tanswers retrieved')
80
+
81
+ is_faq_title_printed = False
82
+ for i, val in enumerate(results_faq):
83
+ if verbose:
84
+ print('\t\ttext: {}\n\t\tChapter: {}\n\t\tconf:{}\n'.format(val[0].page_content, val[0].metadata, val[1]))
85
+
86
+ if val[1] < self.search_conf_thresh:
87
+ if not is_faq_title_printed:
88
+ retrieved_answers += '''Question and Answers from the past that are similar to the student's question\n-----------------\n'''
89
+ is_faq_title_printed = True
90
+ # collect the corresponding answers of the qna pair for gpt
91
+ retrieved_answers += ' Question:{}\n Answer:{}\n'.format(val[0].page_content, self.qna_dict[val[0].page_content])
92
+
93
+
94
+
95
+ ## search in coursework
96
+ if verbose:
97
+ print('Search in coursework')
98
+ results = self.db.similarity_search_with_score(question, k=5)
99
+
100
+ ### save only the high confidence search results
101
+ if verbose:
102
+ print('\tanswers retrieved')
103
+
104
+ is_snippet_title_printed = False
105
+ max_chapters = 3
106
+ neighboring_sections = 2 # + or -
107
+ chapter_cnt = 0
108
+ seen_chapters = []
109
+ for i, val in enumerate(results):
110
+ if verbose:
111
+ print('\t\ttext: {}\n\t\tChapter: {}\n\t\tSection: {}\n\t\tconf:{}\n'.format(val[0].page_content, val[0].metadata['source'], val[0].metadata['split'], val[1]))
112
+ print(self.course_db[val[0].metadata['source']].keys())
113
+ if val[1] < self.search_conf_thresh:
114
+ if not is_snippet_title_printed:
115
+ retrieved_answers += '''\n$$$$$$$$$$\nSnippets of text from the course that are relevant to the student's question\n-----------------\n'''
116
+ is_snippet_title_printed = True
117
+
118
+ if val[0].metadata['source'] not in seen_chapters and chapter_cnt<max_chapters:
119
+
120
+ html_str = self.course_db[val[0].metadata['source']][str((val[0].metadata['split']))]
121
+ extended_context = ''
122
+ for ind in range(val[0].metadata['split']-neighboring_sections, val[0].metadata['split']+neighboring_sections):
123
+ if str(ind) in self.course_db[val[0].metadata['source']]:
124
+ extended_context += '\n{}'.format(self.course_db[val[0].metadata['source']][str(ind)])
125
+
126
+ retrieved_answers += '\n Relevant text snippet {}: {}\n\n '.format(chapter_cnt, extended_context)
127
+ if verbose:
128
+ print('\n\t\tlength:({}, {})'.format(len(html_str), len(extended_context)))
129
+
130
+ seen_chapters.append(val[0].metadata['source'])
131
+ chapter_cnt += 1
132
+ if len(retrieved_answers)>2000:
133
+ if verbose:
134
+ print('retrieved_answers length greater than 2000 : {}'.format(len(retrieved_answers)))
135
+ break
136
+
137
+
138
+
139
+
140
+
141
+ #### if there is atleast one search result ask GPT to answer
142
+ if len(retrieved_answers):
143
+ # ask GPT to answer
144
+ prompt_string = self.prompt.format_prompt(question=question, context=retrieved_answers, course = 'Distributed Algorithms').to_string()
145
+
146
+ if verbose:
147
+ print(prompt_string)
148
+ response = self.model([
149
+ HumanMessage(
150
+ prompt_string
151
+ )
152
+ ])
153
+
154
+ if verbose:
155
+ print('\t\t\tRaw GPT response: {}\n'.format(response))
156
+
157
+ faq_response = None
158
+ try:
159
+ faq_response = self.parser.parse(response.content)
160
+ except Exception as e:
161
+ faq_response = self.fix_parser.parse(response.content)
162
+
163
+ if verbose:
164
+ print('\t\t\tfinal response: {}\n'.format(faq_response))
165
+
166
+ if faq_response != None and faq_response.status == IsAnswerable.YES:
167
+ return faq_response.answer
168
+ else:
169
+ return self.excuse_me_msg
170
+ else:
171
+ return self.excuse_me_msg
172
+
173
+
174
+ fb = FAQBot()
175
+
176
+ demo = gr.ChatInterface(fb.ask_question)
177
 
178
  if __name__ == "__main__":
179
  demo.launch()
requirements.txt CHANGED
@@ -1 +1,5 @@
1
- huggingface_hub==0.22.2
 
 
 
 
 
1
+ huggingface_hub==0.22.2
2
+ langchain
3
+ langchain-openai
4
+ setence-transformers
5
+ chromadb