Spaces:
Sleeping
Sleeping
File size: 7,865 Bytes
76da980 beaea8b 76da980 beaea8b 76da980 beaea8b 76da980 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | import gradio as gr
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
from langchain.prompts import PromptTemplate
from langchain.output_parsers import PydanticOutputParser, OutputFixingParser
from pydantic import BaseModel, Field
from enum import Enum
from langchain_openai import ChatOpenAI
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
import json
import os
from dotenv import load_dotenv
load_dotenv()
class IsAnswerable(Enum):
YES = "YES - the given 'question' can be confidently answered using the given 'context'"
NO = "NO - the given 'question' cannot be answered with the given 'context'"
class AnswerStatus(BaseModel):
status: IsAnswerable = Field(description="")
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. ")
class FAQBot():
def __init__(self):
self.model = ChatOpenAI(
model_name='gpt-3.5-turbo',
openai_api_key=os.getenv("OPENAI_API_KEY"),
openai_organization=os.getenv("OPENAI_ORGANIZATION"),
)
embedding_function = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
self.db = Chroma(persist_directory="./chroma/db", embedding_function=embedding_function, collection_name="course")
self.db_faq = Chroma(persist_directory="./chroma/db_faq", embedding_function=embedding_function, collection_name="faq")
self.qna_dict = json.load(open('./chroma/qna_dict'))
self.course_db = json.load(open('./chroma/course_db'))
self.parser = PydanticOutputParser(pydantic_object=AnswerStatus)
self.fix_parser = OutputFixingParser.from_llm(parser=self.parser, llm=self.model, max_retries=3)
self.prompt = PromptTemplate(
template = '''
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
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'.
{format_instructions}\n
***
'query' : {question}
***
$$$
'context' : {context}
$$$
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'.
Answer only in HTML format.
''',
input_variables=["question", "context", "course"],
partial_variables={
"format_instructions": self.parser.get_format_instructions(),
},
)
self.search_conf_thresh = 1
self.excuse_me_msg = '''<p>I dont think I know the answer for this, let me check with the professor.</p>'''
def ask_question(self, question, verbose=False):
retrieved_answers = ''
## search in faq
if verbose:
print('Search in FAQ')
results_faq = self.db_faq.similarity_search_with_score(question, k=3)
### save only the high confidence search results
if verbose:
print('\tanswers retrieved')
is_faq_title_printed = False
for i, val in enumerate(results_faq):
if verbose:
print('\t\ttext: {}\n\t\tChapter: {}\n\t\tconf:{}\n'.format(val[0].page_content, val[0].metadata, val[1]))
if val[1] < self.search_conf_thresh:
if not is_faq_title_printed:
retrieved_answers += '''Question and Answers from the past that are similar to the student's question\n-----------------\n'''
is_faq_title_printed = True
# collect the corresponding answers of the qna pair for gpt
retrieved_answers += ' Question:{}\n Answer:{}\n'.format(val[0].page_content, self.qna_dict[val[0].page_content])
## search in coursework
if verbose:
print('Search in coursework')
results = self.db.similarity_search_with_score(question, k=5)
### save only the high confidence search results
if verbose:
print('\tanswers retrieved')
is_snippet_title_printed = False
max_chapters = 3
neighboring_sections = 2 # + or -
chapter_cnt = 0
seen_chapters = []
for i, val in enumerate(results):
if verbose:
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]))
print(self.course_db[val[0].metadata['source']].keys())
if val[1] < self.search_conf_thresh:
if not is_snippet_title_printed:
retrieved_answers += '''\n$$$$$$$$$$\nSnippets of text from the course that are relevant to the student's question\n-----------------\n'''
is_snippet_title_printed = True
if val[0].metadata['source'] not in seen_chapters and chapter_cnt<max_chapters:
html_str = self.course_db[val[0].metadata['source']][str((val[0].metadata['split']))]
extended_context = ''
for ind in range(val[0].metadata['split']-neighboring_sections, val[0].metadata['split']+neighboring_sections):
if str(ind) in self.course_db[val[0].metadata['source']]:
extended_context += '\n{}'.format(self.course_db[val[0].metadata['source']][str(ind)])
retrieved_answers += '\n Relevant text snippet {}: {}\n\n '.format(chapter_cnt, extended_context)
if verbose:
print('\n\t\tlength:({}, {})'.format(len(html_str), len(extended_context)))
seen_chapters.append(val[0].metadata['source'])
chapter_cnt += 1
if len(retrieved_answers)>2000:
if verbose:
print('retrieved_answers length greater than 2000 : {}'.format(len(retrieved_answers)))
break
#### if there is atleast one search result ask GPT to answer
if len(retrieved_answers):
# ask GPT to answer
prompt_string = self.prompt.format_prompt(question=question, context=retrieved_answers, course = 'Distributed Algorithms').to_string()
if verbose:
print(prompt_string)
response = self.model([
HumanMessage(
prompt_string
)
])
if verbose:
print('\t\t\tRaw GPT response: {}\n'.format(response))
faq_response = None
try:
faq_response = self.parser.parse(response.content)
except Exception as e:
faq_response = self.fix_parser.parse(response.content)
if verbose:
print('\t\t\tfinal response: {}\n'.format(faq_response))
if faq_response != None and faq_response.status == IsAnswerable.YES:
return faq_response.answer
else:
return self.excuse_me_msg
else:
return self.excuse_me_msg
fb = FAQBot()
demo = gr.ChatInterface(fb.ask_question)
if __name__ == "__main__":
demo.launch() |