Jacob Petterle commited on
Commit
9de15d6
·
1 Parent(s): d750d08

working demo with llm

Browse files
.gitignore CHANGED
@@ -83,4 +83,3 @@ dmypy.json
83
  .pyre/
84
  .pytype/
85
  cython_debug/
86
- temp.pdf
 
83
  .pyre/
84
  .pytype/
85
  cython_debug/
 
.projen/deps.json CHANGED
@@ -14,7 +14,7 @@
14
  },
15
  {
16
  "name": "projen",
17
- "version": "0.71.71",
18
  "type": "devenv"
19
  },
20
  {
 
14
  },
15
  {
16
  "name": "projen",
17
+ "version": "0.71.72",
18
  "type": "devenv"
19
  },
20
  {
requirements-dev.txt CHANGED
@@ -2,7 +2,7 @@
2
  black
3
  flake8
4
  mypy
5
- projen==0.71.71
6
  pylint
7
  pytest
8
  streamlit
 
2
  black
3
  flake8
4
  mypy
5
+ projen==0.71.72
6
  pylint
7
  pytest
8
  streamlit
requirements.txt CHANGED
@@ -9,4 +9,3 @@ pydantic[dotenv]
9
  streamlit_chat
10
  tiktoken
11
  youtube-transcript-api
12
- loguru
 
9
  streamlit_chat
10
  tiktoken
11
  youtube-transcript-api
 
taai_demo/plugin/{chat_response.py → llm.py} RENAMED
@@ -4,16 +4,42 @@ from pydantic import BaseModel, PrivateAttr, validator
4
  import openai
5
  import tiktoken
6
  from loguru import logger
7
- from src.runtime_settings import Settings
8
 
9
 
10
- SEARCH_SYSTEM_PROMPT = "You a search engine. You will be given information and a question and you will need to answer the question using the information. If the information provided is not enough or if I don't provide any information, you must respond: 'No Results Found'. Remember if the information I provide is not relevant or if I don't provide any information to you, you must respond: 'No Results Found' and nothing else. You must only only respond with 'No Results Found'."
11
- SEARCH_CONTEXT_PROMPT = """I found some information to help you. Please read it and use it to help you respond. If the information provided is not enough, you must respond: 'No Results Found'
12
-
13
- Information: {context}
14
- Query: {question}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- Remember, if you can't respond using the provided information, you must respond: 'No Results Found' and nothing else.
 
 
 
 
 
 
17
  """
18
 
19
  TA_SYSTEM_PROMPT = (
@@ -23,14 +49,8 @@ TA_SYSTEM_PROMPT = (
23
  "To do this, you need to ask questions to understand the user as best as possible. "
24
  "This will allow you to better understand who the person is and what they need help with. "
25
  "Finally, you MUST use markdown format when you respond to the user."
 
26
  )
27
- TA_CONTEXT_PROMPT = """I found some information to help you. Please read it and use it to help you respond.
28
-
29
- Information: {context}
30
- Query: {question}
31
-
32
- Remember, if you can't respond using the provided information, you must respond: 'No Results Found' and nothing else.
33
- """
34
 
35
  MODEL_CONTEXT_WINDOW = 3800
36
  MAX_TOKENS_FOR_RESPONSE = 800
@@ -102,12 +122,15 @@ class GPTTurboChatSession(BaseModel):
102
  return result
103
  else:
104
  raise StopIteration
105
-
 
 
 
106
  def add_message(self, messages: Union[Sequence[GPTTurboChat], GPTTurboChat]) -> None:
107
  """Add a message to the chat session and return a new chat session model"""
108
  if isinstance(messages, GPTTurboChat):
109
  messages = (messages,)
110
- new_messages = self.messages + tuple(messages)
111
  self.messages = new_messages
112
 
113
  def messages_exist(self) -> bool:
@@ -123,6 +146,13 @@ class GPTTurboChatSession(BaseModel):
123
  """Get the content of the last message."""
124
  return self.messages[-1].content
125
 
 
 
 
 
 
 
 
126
  def count_tokens(string: str) -> int:
127
  """
128
  Get the token count of a string.
@@ -208,6 +238,7 @@ def truncate_chat_session(
208
  class LLMType(str, Enum):
209
  SEARCH_ENGINE = "search_engine"
210
  TA = "teaching_assistant"
 
211
 
212
  class LLMWrapper:
213
  def __init__(self, api_key: str, llm_type: LLMType):
@@ -218,20 +249,23 @@ class LLMWrapper:
218
  elif llm_type == LLMType.TA:
219
  self._system_prompt = TA_SYSTEM_PROMPT
220
  self._context_prompt = TA_CONTEXT_PROMPT
 
 
221
 
222
  def get_llm_response(self, chat_session: Union[GPTTurboChatSession, GPTTurboChat], context: Optional[Union[str, list[str]]]=None) -> Union[GPTTurboChatSession, GPTTurboChat]:
223
  # construct a series of messages using the conversation history
224
  prompt_is_instance_of_chat = isinstance(chat_session, GPTTurboChat)
225
  if prompt_is_instance_of_chat:
226
  chat_session = GPTTurboChatSession(messages=(chat_session,))
227
-
 
228
  system_token_count = count_tokens(self._system_prompt)
229
  token_context_window = MODEL_CONTEXT_WINDOW
230
  chat_session = truncate_chat_session(chat_session, system_token_count, MAX_TOKENS_FOR_RESPONSE, token_context_window)
231
  if context and chat_session.messages_exist():
232
  context = context if isinstance(context, str) else "\n".join(context)
233
- final_prompt = self._context_prompt.format(context=context, question=chat_session.last_message_content())
234
- chat_session.replace_last_message(GPTTurboChat(content=final_prompt, role=Role.USER))
235
  prompt_messages = [
236
  {"role": Role.SYSTEM.value, "content": self._system_prompt}
237
  ]
@@ -240,6 +274,7 @@ class LLMWrapper:
240
  user_tokens_for_request += chat.token_count
241
  prompt_messages.append(chat.dict(exclude={"token_count"}))
242
 
 
243
  response = openai.ChatCompletion.create(
244
  model="gpt-3.5-turbo",
245
  api_key=self._api_key,
@@ -247,6 +282,8 @@ class LLMWrapper:
247
  )
248
  message = response.choices[0].message.content
249
  completion_tokens = response.usage.completion_tokens
 
 
250
  chat_session.add_message(GPTTurboChat(
251
  role=Role.ASSISTANT,
252
  content=message,
 
4
  import openai
5
  import tiktoken
6
  from loguru import logger
7
+ from taai_demo.settings import Settings
8
 
9
 
10
+ SEARCH_SYSTEM_PROMPT = """
11
+ You a search engine. You will be provided a search query and you should then find relevant information to answer the query for the user.
12
+ The information you find may come from a pdf or from a youtube video. If from a Youtube video, you will know this as the
13
+ a metadata field will be returned with the youtube video id, start_time and end_time. If any of the information you use in your response to the user query
14
+ comes from the youtube video, you must return a bulleted list of youtube video urls (format: https://www.youtube.com/watch?v=video_id&t=95sstart_time -> where start_time is formatted like 300s).
15
+ so that the user can find the information. This will ensure the user isn't plagerizing and is using the information to learn.
16
+ Finally, you MUST use markdown format when you respond to the user to help them format their response. Any links
17
+ should be clickable using markdown format. Remember, if you can't find something, please respond with {{No information found}}.
18
+ """
19
+ SEARCH_CONTEXT_PROMPT = """Thought: I should search for information related to the users query.
20
+ Action: Search database for information relevant to the user query.
21
+ Information returned from the database: {context}
22
+ Here's my final response to the user query: {query}
23
+ FORMAT for response:
24
+ {{Response}}
25
+ {{citations from the metadata field returned from database}} -> if the citation is a youtube video (denoted by the metadata field), then format the citation like this: https://www.youtube.com/watch?v=video_id&t=95sstart_time -> where start_time is formatted like 300s
26
+ """
27
+ TA_CONTEXT_PROMPT = """Thought: I should search for information related to the users query.
28
+ Action: Search database for information relevant to the user query.
29
+ Information returned from the database: {context}
30
+ Here's my final response to the user query: {query}
31
+ FORMAT for response:
32
+ {{Response}}
33
+ {{citations from the metadata field returned from database}} -> if the citation is a youtube video (denoted by the metadata field), then format the citation like this: https://www.youtube.com/watch?v=video_id&t=95sstart_time -> where start_time is formatted like 300s
34
+ """
35
 
36
+ QUERY_AUGMENTER_SYSTEM_PROMPT = """
37
+ You are specialized at editing a sentence that you are provided. The user will provide you a sentence
38
+ or a phrase and your job is to return 3 sentences or phrases that are similar to the provided sentence or phrase.
39
+ If the sentence appears unclear or confusing, do your best to make it clear and understandable, but don't not dialogue
40
+ with the user, you must only respond with the 3 similar sentences or phrases and the original sentence or phrase.
41
+ Please do not respond with anything other than the original sentence or phrase and the 3 similar sentences or phrases.
42
+ Remember, in order to be successful, do not respond with anything other than the original sentence or phrase and the 3 similar sentences or phrases.
43
  """
44
 
45
  TA_SYSTEM_PROMPT = (
 
49
  "To do this, you need to ask questions to understand the user as best as possible. "
50
  "This will allow you to better understand who the person is and what they need help with. "
51
  "Finally, you MUST use markdown format when you respond to the user."
52
+ "Please ensure all links are clickable using markdown format."
53
  )
 
 
 
 
 
 
 
54
 
55
  MODEL_CONTEXT_WINDOW = 3800
56
  MAX_TOKENS_FOR_RESPONSE = 800
 
122
  return result
123
  else:
124
  raise StopIteration
125
+
126
+ def __reversed__(self):
127
+ return reversed(self.messages)
128
+
129
  def add_message(self, messages: Union[Sequence[GPTTurboChat], GPTTurboChat]) -> None:
130
  """Add a message to the chat session and return a new chat session model"""
131
  if isinstance(messages, GPTTurboChat):
132
  messages = (messages,)
133
+ new_messages = self.messages + messages
134
  self.messages = new_messages
135
 
136
  def messages_exist(self) -> bool:
 
146
  """Get the content of the last message."""
147
  return self.messages[-1].content
148
 
149
+ def remove_last_ai_message(self) -> None:
150
+ """Remove the last AI message."""
151
+ for i, message in enumerate(reversed(self.messages)):
152
+ if message.role != Role.USER:
153
+ self.messages = self.messages[: -(i + 1)]
154
+ break
155
+
156
  def count_tokens(string: str) -> int:
157
  """
158
  Get the token count of a string.
 
238
  class LLMType(str, Enum):
239
  SEARCH_ENGINE = "search_engine"
240
  TA = "teaching_assistant"
241
+ QUERY_AUGMENTATION = "query_augmentation"
242
 
243
  class LLMWrapper:
244
  def __init__(self, api_key: str, llm_type: LLMType):
 
249
  elif llm_type == LLMType.TA:
250
  self._system_prompt = TA_SYSTEM_PROMPT
251
  self._context_prompt = TA_CONTEXT_PROMPT
252
+ elif llm_type == LLMType.QUERY_AUGMENTATION:
253
+ self._system_prompt = QUERY_AUGMENTER_SYSTEM_PROMPT
254
 
255
  def get_llm_response(self, chat_session: Union[GPTTurboChatSession, GPTTurboChat], context: Optional[Union[str, list[str]]]=None) -> Union[GPTTurboChatSession, GPTTurboChat]:
256
  # construct a series of messages using the conversation history
257
  prompt_is_instance_of_chat = isinstance(chat_session, GPTTurboChat)
258
  if prompt_is_instance_of_chat:
259
  chat_session = GPTTurboChatSession(messages=(chat_session,))
260
+ logger.info(isinstance(chat_session, GPTTurboChatSession))
261
+ logger.info(f"Chat session: {chat_session}")
262
  system_token_count = count_tokens(self._system_prompt)
263
  token_context_window = MODEL_CONTEXT_WINDOW
264
  chat_session = truncate_chat_session(chat_session, system_token_count, MAX_TOKENS_FOR_RESPONSE, token_context_window)
265
  if context and chat_session.messages_exist():
266
  context = context if isinstance(context, str) else "\n".join(context)
267
+ final_prompt = self._context_prompt.format(context=context, query=chat_session.last_message_content())
268
+ chat_session.add_message(GPTTurboChat(content=final_prompt, role=Role.ASSISTANT))
269
  prompt_messages = [
270
  {"role": Role.SYSTEM.value, "content": self._system_prompt}
271
  ]
 
274
  user_tokens_for_request += chat.token_count
275
  prompt_messages.append(chat.dict(exclude={"token_count"}))
276
 
277
+ logger.info(f"Prompt messages: {prompt_messages}")
278
  response = openai.ChatCompletion.create(
279
  model="gpt-3.5-turbo",
280
  api_key=self._api_key,
 
282
  )
283
  message = response.choices[0].message.content
284
  completion_tokens = response.usage.completion_tokens
285
+ if context and chat_session.messages_exist():
286
+ chat_session.remove_last_ai_message()
287
  chat_session.add_message(GPTTurboChat(
288
  role=Role.ASSISTANT,
289
  content=message,
taai_demo/plugin/semantic_search.py CHANGED
@@ -46,10 +46,10 @@ class SemanticSearchWrapper:
46
  # return get_semantic_snippets(obj.file_path)
47
  if isinstance(obj, TextbookPDF):
48
  self.object_type = "textbook"
49
- return get_fix_length_snippets(obj.file_path, 1000)
50
  if isinstance(obj, YoutubeVideo):
51
  self.object_type = "youtube"
52
- return get_yt_transcript_chunks(obj.url)
53
  print("I SHOULDN'T BE HERE")
54
 
55
  def insert_pdf(self, buffer) -> None:
@@ -79,13 +79,19 @@ class SemanticSearchWrapper:
79
  vector = Vector(vector=vector, metadata={}, text=query)
80
  ids = self._pinecone_index.search(
81
  query=vector,
82
- top_k=5,
83
  )
84
  text = []
 
 
 
 
 
85
  for vector_id in ids:
86
  for vector_text_mapping in self.vector_text_mappings or []: # i know this is not efficient but it's a demo and won't be slow with the amount of data we have
87
  if vector_id == vector_text_mapping.vector_id:
88
- text.append(vector_text_mapping.text)
 
89
  return text
90
 
91
  @classmethod
 
46
  # return get_semantic_snippets(obj.file_path)
47
  if isinstance(obj, TextbookPDF):
48
  self.object_type = "textbook"
49
+ return get_fix_length_snippets(obj.file_path, 500)
50
  if isinstance(obj, YoutubeVideo):
51
  self.object_type = "youtube"
52
+ return get_yt_transcript_chunks(obj.url, 400)
53
  print("I SHOULDN'T BE HERE")
54
 
55
  def insert_pdf(self, buffer) -> None:
 
79
  vector = Vector(vector=vector, metadata={}, text=query)
80
  ids = self._pinecone_index.search(
81
  query=vector,
82
+ top_k=25,
83
  )
84
  text = []
85
+ # keep the first 10 and last 5
86
+ logger.info(f"Found {len(ids)} relevant snippets")
87
+ ids = ids[:10] + ids[-3:]
88
+ ids = list(set(ids))
89
+ logger.info(f"Found {len(ids)} unique relevant snippets")
90
  for vector_id in ids:
91
  for vector_text_mapping in self.vector_text_mappings or []: # i know this is not efficient but it's a demo and won't be slow with the amount of data we have
92
  if vector_id == vector_text_mapping.vector_id:
93
+ text.append(f"Info: {vector_text_mapping.text}\nmetadata: {vector_text_mapping.metadata}\n")
94
+ logger.info(f"Relevant context: {''.join(text) if text else 'No relevant context found'}")
95
  return text
96
 
97
  @classmethod
taai_demo/plugin/vector_db.py CHANGED
@@ -12,6 +12,7 @@ class Vector(NamedTuple):
12
  class VectorIdTextMapping(BaseModel):
13
  vector_id: str
14
  text: str
 
15
 
16
  class QueryMatch(TypedDict):
17
  id: str
@@ -37,7 +38,7 @@ class PineconeIndex:
37
  vector.vector,
38
  vector.metadata,
39
  )
40
- vector_mappings.append(VectorIdTextMapping(vector_id=str(i), text=vector.text))
41
  pinecone_vectors.append(pinecone_vector)
42
  try:
43
  self.index.upsert(vectors=pinecone_vectors, batch_size=20)
 
12
  class VectorIdTextMapping(BaseModel):
13
  vector_id: str
14
  text: str
15
+ metadata: Dict[str, str]
16
 
17
  class QueryMatch(TypedDict):
18
  id: str
 
38
  vector.vector,
39
  vector.metadata,
40
  )
41
+ vector_mappings.append(VectorIdTextMapping(vector_id=str(i), text=vector.text, metadata=vector.metadata))
42
  pinecone_vectors.append(pinecone_vector)
43
  try:
44
  self.index.upsert(vectors=pinecone_vectors, batch_size=20)
taai_demo/plugin/yt_transcript_chunker.py CHANGED
@@ -19,7 +19,12 @@ def get_yt_transcript_chunks(url, max_length=500) -> Sequence[SemanticSnippet]:
19
  if len(chunk) > max_length or i == len(transcript) - 1:
20
  current_chunk.pop()
21
  chunk = " ".join(map(lambda x: x["text"], current_chunk))
22
- chunks.append(SemanticSnippet(text=chunk, metadata={"start": current_chunk[0]["start"], "end": current_chunk[-1]["end"]}))
 
 
 
 
 
23
  current_chunk = [current_chunk[-1], snippet]
24
 
25
  return chunks
 
19
  if len(chunk) > max_length or i == len(transcript) - 1:
20
  current_chunk.pop()
21
  chunk = " ".join(map(lambda x: x["text"], current_chunk))
22
+ metadata = {
23
+ "start": current_chunk[0]["start"],
24
+ "end": current_chunk[-1]["end"],
25
+ "video_id": query["v"][0],
26
+ }
27
+ chunks.append(SemanticSnippet(text=chunk, metadata=metadata))
28
  current_chunk = [current_chunk[-1], snippet]
29
 
30
  return chunks
taai_demo/ui/chat_page.py CHANGED
@@ -1,20 +1,26 @@
 
1
  import streamlit as st
2
  from streamlit_chat import message as st_message
3
- from taai_demo.plugin.chat_wrapper import ChatWrapper
 
 
4
 
5
 
6
  def chat_page():
 
 
 
 
 
7
  message = st.text_input("Chat with ai")
8
- if message:
9
- st.session_state.chat_history.append({"message": message, "role": "user"})
10
-
11
- settings = st.session_state.settings
12
- semantic_search_wrapper = st.session_state.semantic_search_wrapper
13
- chat_wrapper = ChatWrapper.from_settings(settings)
14
 
15
  context = semantic_search_wrapper.search(message)
16
- response = chat_wrapper.get_openai_response(st.session_state.chat_history, context)
17
-
18
- st.session_state.chat_history.append({"message": response, "role": "assistant"})
19
- for i, entry in enumerate(st.session_state.chat_history):
20
- st_message(entry["message"], is_user=entry["role"]=="user", key=f"{i}-message", seed=st.session_state.message_seed)
 
1
+ from loguru import logger
2
  import streamlit as st
3
  from streamlit_chat import message as st_message
4
+ from taai_demo.plugin.llm import LLMWrapper,LLMType, GPTTurboChatSession, GPTTurboChat, Role
5
+ from taai_demo.plugin.semantic_search import SemanticSearchWrapper
6
+ from taai_demo.settings import Settings
7
 
8
 
9
  def chat_page():
10
+ if 'message' not in st.session_state:
11
+ st.session_state.message = ""
12
+ entry: GPTTurboChat
13
+ for i, entry in enumerate(st.session_state.chat_history):
14
+ st_message(entry.content, is_user=entry.role == Role.USER.value, key=f"{i}-message", seed=st.session_state.message_seed)
15
  message = st.text_input("Chat with ai")
16
+ if message != st.session_state.message:
17
+ chat_history: GPTTurboChatSession = st.session_state.chat_history
18
+ chat_history.add_message(GPTTurboChat(content=message, role="user"))
19
+ settings: Settings = st.session_state.settings
20
+ semantic_search_wrapper: SemanticSearchWrapper = st.session_state.semantic_search_wrapper
21
+ chat_wrapper = LLMWrapper.from_settings(settings, LLMType.TA)
22
 
23
  context = semantic_search_wrapper.search(message)
24
+ st.session_state.chat_history = chat_wrapper.get_llm_response(chat_history, context)
25
+ st.session_state.message = message
26
+ st.experimental_rerun()
 
 
taai_demo/ui/search_page.py CHANGED
@@ -1,11 +1,20 @@
1
  import streamlit as st
2
  from taai_demo.plugin.semantic_search import SemanticSearchWrapper
 
 
3
 
4
 
5
  def search_page():
6
  query = st.text_input("Search class materials", key="search_input")
7
  if query:
8
- semantic_search_wrapper = st.session_state.semantic_search_wrapper
9
- result = semantic_search_wrapper.search(query)
10
- for text in result:
11
- st.markdown(text)
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
  from taai_demo.plugin.semantic_search import SemanticSearchWrapper
3
+ from taai_demo.plugin.llm import GPTTurboChat, LLMWrapper, LLMType, Role
4
+ from taai_demo.settings import Settings
5
 
6
 
7
  def search_page():
8
  query = st.text_input("Search class materials", key="search_input")
9
  if query:
10
+ # this doesn't work super well
11
+ # llm_query_augmenter = LLMWrapper.from_settings(Settings(), LLMType.QUERY_AUGMENTATION)
12
+ # query = llm_query_augmenter.get_llm_response(GPTTurboChat(content=query, role=Role.USER))
13
+ # assert isinstance(query, GPTTurboChat)
14
+ content = query
15
+ semantic_search_wrapper: SemanticSearchWrapper = st.session_state.semantic_search_wrapper
16
+ result = semantic_search_wrapper.search(content)
17
+ llm = LLMWrapper.from_settings(Settings(), LLMType.SEARCH_ENGINE)
18
+ llm_result = llm.get_llm_response(GPTTurboChat(content=content, role=Role.USER), result)
19
+ assert isinstance(llm_result, GPTTurboChat)
20
+ st.markdown(llm_result.content)
taai_demo/ui/upload_page.py CHANGED
@@ -1,11 +1,12 @@
1
  import streamlit as st
2
  from taai_demo.ui.state_management import transition_state
3
  from taai_demo.plugin.semantic_search import SemanticSearchWrapper
 
4
  from taai_demo.settings import Settings
5
 
6
 
7
  def upload_page():
8
- st.session_state.chat_history = []
9
  uploaded_file = st.file_uploader("Upload PDF")
10
  settings: Settings = st.session_state.settings
11
  semantic_search_wrapper = SemanticSearchWrapper.from_settings(settings)
 
1
  import streamlit as st
2
  from taai_demo.ui.state_management import transition_state
3
  from taai_demo.plugin.semantic_search import SemanticSearchWrapper
4
+ from taai_demo.plugin.llm import GPTTurboChatSession
5
  from taai_demo.settings import Settings
6
 
7
 
8
  def upload_page():
9
+ st.session_state.chat_history = GPTTurboChatSession()
10
  uploaded_file = st.file_uploader("Upload PDF")
11
  settings: Settings = st.session_state.settings
12
  semantic_search_wrapper = SemanticSearchWrapper.from_settings(settings)
temp.pdf ADDED
Binary file (137 kB). View file