Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Streamlit PR #3

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,14 @@ uploads/_1_1_1
uploads/_1_1
uploads/_1
uploads/_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1_1
TreeHugger_Exam_ans.csv
Test_Results/results_test_12-29-23_log.txt
Test_Results/results_test_12-29-23.csv
Test_Results/results_test_1-6-24_log.txt
Test_Results/results_test_1-6-24.csv
Test_Results/results_test_1-3-24_log.txt
Test_Results/results_test_1-3-24.csv
Test_Results/results_test_1-2-24_log.txt

venv/
__pycache__/
__pycache__/*.pyc
40 changes: 40 additions & 0 deletions src/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# __import__('pysqlite3')
# import sys
# sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
# import sqlite3

import streamlit as st
from components.frontend.chat import Chat_UI
from components.frontend.sidebar import Sidebar
from components.backend.pipeline.pipeline import Pipeline
import os
import uuid


st.set_page_config(layout='wide')


@st.cache_resource
def initalize():
pipeline = Pipeline()
return pipeline, Sidebar(pipeline), Chat_UI(pipeline)

class UI:
def __init__(self):
self._pipeline, self.sidebar, self.chat = initalize()
st.session_state['documents'] = [0]
st.session_state['user_id'] = str(uuid.uuid4())
st.session_state['api_key'] = "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"

if 'messages' not in st.session_state:
st.session_state['messages'] = []

def render(self):
self.sidebar()
self.chat()

def main():
UI().render()

if __name__ == "__main__":
main()
Binary file added src/assets/eugenie.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions src/components/backend/pipeline/llm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from components.backend.tools.python_interpreter import PythonInterpreter
from components.backend.tools.arxiv_search import ArxivSearch
from components.backend.tools.calculator import Calculator
from components.backend.tools.web_search import WebSearch
from langchain_openai import ChatOpenAI

import os, re, json

class LLM:
def __init__(self, temperature=0.0001):
self.llm = ChatOpenAI(model_name='gpt-4', temperature=temperature)


38 changes: 38 additions & 0 deletions src/components/backend/pipeline/pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from components.backend.pipeline.vectorstore import VectorStore
from components.backend.pipeline.llm import LLM

import os, io

from components.backend.tools.python_interpreter import PythonInterpreter
from components.backend.tools.arxiv_search import ArxivSearch
from components.backend.tools.calculator import Calculator
from components.backend.tools.web_search import WebSearch

from langchain.agents import initialize_agent

os.environ["OPENAI_API_KEY"] = "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"
os.environ['PINECONE_API_KEY'] = "204755b4-f7d8-4aa4-b16b-764e66796cc3"
os.environ["GOOGLE_API_KEY"] = "AIzaSyDKxAadUfBZ9oAMDlRjRe0jlp3N0oZKqvg"
os.environ["GOOGLE_CSE_ID"] = "57d010b1a25ce48c0"

class Pipeline:
def __init__(self, max_iterations=5):
self.llm = LLM()
self.vectorstore = VectorStore()
self.tools = [
PythonInterpreter(llm=self.llm.llm).initialize(),
ArxivSearch().initialize(),
Calculator(llm=self.llm.llm).initialize(),
WebSearch(llm=self.llm.llm, vectorstore_public=self.vectorstore.vectorstore).initialize(),
]

self.agent = initialize_agent(self.tools,
self.llm.llm,
agent="chat-conversational-react-description",
verbose=True,
handle_parsing_errors=True,
max_iterations=max_iterations
)

def run(self, query, chat_history):
return self.agent.invoke({'input': query.strip(), 'chat_history': chat_history})
15 changes: 15 additions & 0 deletions src/components/backend/pipeline/vectorstore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import chromadb
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings

class VectorStore:
def __init__(self):
self.chroma_client = chromadb.Client()
_ = self.chroma_client.create_collection(name="user")
self.embeddings_model = OpenAIEmbeddings()

self.vectorstore = Chroma(
client=self.chroma_client,
collection_name="user",
embedding_function=self.embeddings_model,
)
15 changes: 15 additions & 0 deletions src/components/backend/tools/arxiv_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from langchain.utilities import ArxivAPIWrapper
from langchain.tools import Tool
from pydantic import BaseModel, Field


class ArxivSearch:
def __init__(self):
self.arxiv = ArxivAPIWrapper()

def initialize(self):
return Tool.from_function(
func=self.arxiv.run,
name="arxiv",
description="useful for when you need to answer research based questions or find scientific documents or papers",
)
14 changes: 14 additions & 0 deletions src/components/backend/tools/calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from langchain.chains import LLMMathChain
from langchain.tools import Tool
from pydantic import BaseModel, Field

class Calculator:
def __init__(self, llm):
self.llm = llm

def initialize(self):
return Tool.from_function(
func=LLMMathChain.from_llm(llm=self.llm, verbose=True).run,
name="Calculator",
description="useful for when you need to answer questions about math",
)
40 changes: 40 additions & 0 deletions src/components/backend/tools/python_interpreter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from langchain_core.output_parsers import StrOutputParser
from langchain_experimental.utilities import PythonREPL
from langchain.tools import Tool
from langchain_core.prompts import ChatPromptTemplate

class PythonInterpreter:
def __init__(self, llm):
self.llm = llm

def _sanitize_output(self, text: str):
_, after = text.split("```python")
return after.split("```")[0]

def python_interpreter(self, query):
template = """Write some python code to solve the user's problem.

Return only python code in Markdown format, e.g.:

```python
....
```"""
prompt = ChatPromptTemplate.from_messages([("system", template), ("human", "{input}")])
chain = prompt | self.llm | StrOutputParser() | self._sanitize_output | PythonREPL().run
output = chain.invoke({"input": query})
print("Python interpreter")
print(output)
return output

def initialize(self):
return Tool.from_function(
func=self.python_interpreter,
name="python_interpreter",
description="""The Python Code Generator Tool is a sophisticated utility designed to craft Python code solutions for a wide array of questions. When provided with a question, this tool leverages advanced algorithms to generate concise and efficient Python code snippets as answers.

Usage Instructions:

Pose a question requiring a Python code solution.
If existing tools are deemed insufficient for the task, instruct the Assistant to utilize the Python Code Generator Tool.
Expect a response in the form of a Markdown-formatted Python code block, enclosed within triple backticks.""",
)
22 changes: 22 additions & 0 deletions src/components/backend/tools/web_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from langchain.tools import Tool
from langchain.chains import RetrievalQAWithSourcesChain
from langchain.retrievers.web_research import WebResearchRetriever
from langchain.utilities import GoogleSearchAPIWrapper

class WebSearch:
def __init__(self, llm, vectorstore_public):
self.llm = llm
self.search = GoogleSearchAPIWrapper()
self.web_retriever = WebResearchRetriever.from_llm(
vectorstore=vectorstore_public,
llm=self.llm,
search=self.search,
num_search_results=3
)

def initialize(self):
return Tool.from_function(
func=RetrievalQAWithSourcesChain.from_chain_type(llm=self.llm, retriever=self.web_retriever),
name="web_QA",
description="web_QA is a web searching tool for the LLM agent, triggered when the similarity score from in-context QA is too low. It dynamically integrates the LLM and a web retriever to broaden knowledge through targeted web searches, enhancing the agent's responsiveness and adaptability to diverse user queries",
)
160 changes: 160 additions & 0 deletions src/components/frontend/chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import streamlit as st
import os, re, json
import base64
import extra_streamlit_components as stx
from annotated_text import annotated_text
import datetime
from langchain_core.messages import AIMessage, HumanMessage

@st.cache_resource(experimental_allow_widgets=True)
def get_manager():
return stx.CookieManager()

class CookieManager:
def __init__(self, cookie_name = 'messages'):
self.manager = get_manager()
self.cookie_name = cookie_name

def __call__(self):
_ = self.manager.get_all()

def get(self):
return self.manager.get(cookie=self.cookie_name)

def set(self, value):
self.manager.set(self.cookie_name, value)

def delete(self):
self.manager.delete(cookie=self.cookie_name)

class Chat_UI:
def __init__(self, pipeline):
self.pipeline = pipeline
self.cookie_manager = CookieManager()

def render(self):
self.chat()

def initiate_memory(self):
history = self.get_messages()

print('History: ', history)
if not history:
st.session_state['messages'] = [{"role": "assistant", "content": "Hello! The name's euGenio. I'm here to help you with your pipeline. Ask me a question!"}]
else:
st.session_state['messages'] = history

def append(self, message:dict):
st.session_state['messages'].append(message)

def __call__(self):
self.cookie_manager()
# Instantiates the chat history
self.initiate_memory()
self.load_memory()

# Load's the text tab
self.load_chatbox()

def load_chatbox(self):
user_input = st.text_input("*Got a question?*", help='Try to specify keywords and intent in your question!', key="text", on_change=self.handle_query)

if st.button('Delete History', use_container_width=True, type='primary'):
self.delete_messages()

def load_memory(self):
messages = st.session_state['messages']
if messages:
for message in messages :
role = message["role"]
content = message["content"]

with st.chat_message(role):
if type(content) == dict and role == 'assistant':
with st.expander("Thought Process!", expanded=True):
st.json(content)

else:

st.markdown(content)

def format_history(self):
messages = st.session_state['messages']

if messages:
formatted = []
for message in messages[1:]:
if message['role'] == 'user':
formatted.append(HumanMessage(content=str(message['content'])))
else:
formatted.append(AIMessage(content=str(message['content'])))
return formatted
else:
return []

def handle_query(self):
text = st.session_state["text"]
st.session_state["text"] = ""

user_message = {"role": "user", "content": text}
self.append(user_message)

with st.chat_message("user"):
st.markdown(text)

with st.chat_message("assistant"):
idx, tool = 0, None

with st.spinner('Thinking...'):
results = self.pipeline.run(query=text, chat_history=self.format_history())

st.markdown(results['output'])
idx += 1

assistant_message = {"role": "assistant", "content": {key: value for key, value in results.items() if key != 'chat_history'}}

self.append(assistant_message)
self.store_messages(user_message, assistant_message)

def store_messages(self, user_message, assistant_message):
past = self.cookie_manager.get()

if past:
if user_message not in past and assistant_message not in past:
past.append(user_message)
past.append(assistant_message)
self.cookie_manager.set(past)
else:
self.cookie_manager.set(st.session_state.messages)

def get_messages(self):
return self.cookie_manager.get()

def delete_messages(self):
self.cookie_manager.delete()
self.initiate_memory()

def _annotated_parser(self, text):
pattern = r'\[(.*?)\]'

annotated_parts = []
last_end = 0

for match in re.finditer(pattern, text):
start, end = match.span()

annotated_parts.append(text[last_end:start])

bracketed_text = match.group(1)
annotated_parts.append((bracketed_text, 'important'))

last_end = end

annotated_parts.append(text[last_end:])

return tuple(annotated_parts)


class CookieTester:
def __init__(self):
self.cookie = None
Loading