from scholaris.core import *
User Interfaces
Assistant
class and its methods are user-friendly and can be seamlessly integrated with Python’s built-in functions. However, if you prefer a web-browser-based application with a graphical user interface to interact with LLMs and utilize the tools available through Scholaris, you can use the Streamlit app as described below.
Working in a Jupyter notebook environment
= Assistant() assistant
Loaded Semantic Scholar API key from the environment variables.
Loaded email address from the environment variables.
A local directory /Users/user2/GitHub/scholaris/data already exists for storing data files. No of files: 1
"Tell me about your tools") assistant.chat(
I can assist with a variety of tasks, including:
* Extracting text from PDF files
* Retrieving titles and first authors of research articles
* Summarizing local documents (PDF, markdown, or text)
* Describing Python code in local files
* Converting IDs (PMIDs, PMCIDs, DOIs) using the id_converter_tool
* Querying OpenAlex API for article metadata based on title, PMID, PMCID, or DOI
* Querying Semantic Scholar Academic Graph API for article metadata based on title, PMID, or DOI
Please let me know how I can assist you with any of these capabilities!
'I can assist with a variety of tasks, including:\n\n* Extracting text from PDF files\n* Retrieving titles and first authors of research articles\n* Summarizing local documents (PDF, markdown, or text)\n* Describing Python code in local files\n* Converting IDs (PMIDs, PMCIDs, DOIs) using the id_converter_tool\n* Querying OpenAlex API for article metadata based on title, PMID, PMCID, or DOI\n* Querying Semantic Scholar Academic Graph API for article metadata based on title, PMID, or DOI\n\nPlease let me know how I can assist you with any of these capabilities!'
To list the files in the local data directory that has been created at initialization, simply use the Python built-in os
module. Pass the assistant’s data_dir
attribute as an argument to the os.listdir()
function, like so:
import os
os.listdir(assistant.dir_path)
You can alos use Python’s built-in functions to copy files from a designated source (e.g., Downloads) to the local data directory that was created at initialization. For example, to copy a file named example.md
from the Downloads directory to the local data directory, use the following code:
import shutil
'~/Downloads/example.md'), assistant.dir_path) shutil.copy(os.path.expanduser(
Likewise, to remove a file from the local data directory, use the following code:
'example.md')) os.remove(os.path.join(assistant.dir_path,
Use the assistant with Streamlit
To use the assistant with Streamlit, you need to install the Streamlit package.
Remember to set up a virtual environment for your project before installing Streamlit. This will help you avoid conflicts with other packages that you may have installed in your base environment.
You can install Streamlit using pip:
pip install streamlit
Then, download the ui.py
file from the scholaris
directory in the Scholaris repository. Run the following command in the terminal to download the file to your current working directory:
wget https://raw.githubusercontent.com/nicomarr/scholaris/main/ui.py
If wget is not installed on your local computer, you may also use curl, like so:
curl -O https://raw.githubusercontent.com/nicomarr/scholaris/main/ui.py
To run the streamlit app, activate the virtual environment. Then, go to the directory where the ui.py
file is located and run the following command in the terminal:
streamlit run ui.py
This will start the local server and the app will be accessible in your local network via a web browser. Alternatively, you can run the command in the terminal with an additional flag so that the app will only be accessible from your local computer:
streamlit run ui.py --browser.serverAddress localhost
If you don’t want to download the ui.py
file from Github, you can also copy the code block from below and paste it into a new Python file in your working directory. Then run it using the command streamlit run <filename>.py
.
Code
from scholaris.core import *
import streamlit as st
import ollama
from datetime import datetime
import json
import os
from pathlib import Path
def download_conversation_history():
= datetime.now().strftime("%Y-%m-%d_%Hh%Mm%Ss")
now = st.session_state.assistant.messages
conversation_history = f"conversation_history_{now}.json"
filename return json.dumps(conversation_history, indent=2), filename
def show_local_data(dir_path: Path) -> None:
try:
for file in dir_path.iterdir():
f"{file.name}")
st.write(except Exception as e:
f"Error: {e}")
st.write(
def handle_file_uploads(uploaded_files):
= st.session_state.assistant.dir_path
dir_path if not dir_path.exists():
return
for uploaded_file in uploaded_files:
= st.session_state.assistant.dir_path / uploaded_file.name
file_path if file_path.exists():
continue
try:
with open(file_path, "wb") as f:
f.write(uploaded_file.getbuffer())if "uploaded_files" not in st.session_state:
"uploaded_files"] = []
st.session_state["uploaded_files"].append(file_path)
st.session_state[except Exception as e:
= st.empty()
error_placeholder f"Error saving {uploaded_file.name}: {str(e)}")
error_placeholder.error(1) # Display error for 1 second
time.sleep(
error_placeholder.empty()= []
uploaded_files
def delete_uploaded_files():
if "uploaded_files" in st.session_state and st.session_state["uploaded_files"] != []:
= 0
file_count for file_path in st.session_state["uploaded_files"]:
try:
os.remove(file_path)+= 1
file_count except Exception as e:
= st.empty()
error_placeholder f"Error deleting {file_path}: {str(e)}")
error_placeholder.error(1) # Display error for 1 seconds
time.sleep(
error_placeholder.empty()f"{file_count} file(s) deleted.")
st.write(= []
st.session_state.uploaded_files
def get_last_tool_names(messages):
"""Returns the tool names from the most recent tool call in the messages."""
= []
tool_names for message in reversed(messages):
if message["role"] == "assistant" and "tool_calls" in message:
for fn in message["tool_calls"]:
= fn["function"].get("name")
name if name:
tool_names.append(name)break # Exit the loop after the first occurrence
return ", ".join(tool_names) if tool_names else "No tools used."
# Page configuration
st.set_page_config(="Scholaris",
page_title=":speech_balloon:",
page_icon="wide",
layout="collapsed",
initial_sidebar_state={
menu_items"About": "This is a graphical user interface for Scholaris, a conversational AI assistant for academic research.",
"Get Help": "https://github.com/nicomarr/scholaris/blob/main/nbs/02_ui.ipynb",
"Report a Bug": "https://github.com/nicomarr/scholaris/issues/new"
}
)
# Initialize the assistant if not already in session state
if "assistant" not in st.session_state:
try:
= Assistant(model="llama3.1", dir_path="./data")
st.session_state.assistant except Exception as e:
if "[Errno 61] Connection refused" in str(e):
f"""An error occurred: {e}. Please make sure Ollama is installed on your local computer and the server is running.
st.error( For troubleshooting, refer to the Ollama docs of GitHub:
[README](https://github.com/ollama/ollama/blob/main/docs/README.md)
[FAQ](https://github.com/ollama/ollama/blob/main/docs/faq.md).
""")
else:
f"An error occurred: {e}")
st.error(
st.stop()
# Initialize other session state variables
if "uploaded_files" not in st.session_state:
= []
st.session_state.uploaded_files
# Start conversation
if len(st.session_state.assistant.messages) < 2: # <2 because the assistant is initialized with a system message
"role": "assistant", "content": "How can I help you?"})
st.session_state.assistant.messages.append({
if st.session_state.assistant:
with st.sidebar:
# Set up the sidebar
"Scholaris")
st.title(str(st.session_state.assistant))
st.caption(# Conversation History Section
"---")
st.write("Conversation History")
st.subheader(="Download",
st.download_button(labelhelp="Download the conversation history as a JSON file.",
=download_conversation_history()[0],
data=download_conversation_history()[1],
file_name="text")
mime
if st.button(label="Reset", type="primary",
help="Click to restart the conversation.",
="reset_messages"):
key
st.session_state.assistant.clear_conversation_history()"role": "assistant", "content": "How can I help you?"})
st.session_state.assistant.messages.append({"Conversation history cleared!")
st.write(# st.rerun() # Rerun the script to update the chat interface and the sidebar
# Local Data Section
"---")
st.write("Local Data")
st.subheader(= st.session_state.assistant.dir_path
dir_path with st.expander("Data files"):
show_local_data(dir_path)
# File Upload Section
= st.file_uploader(
uploaded_files ="Upload files",
labeltype=['pdf','txt','md','markdown','py'],
=True,
accept_multiple_files
)if uploaded_files:
handle_file_uploads(uploaded_files)
# Delete Uploaded Files Section
if st.session_state.uploaded_files:
if st.button(
="Delete uploaded files",
labeltype="primary",
help="Only uploaded files will be deleted. Already existing files will not be deleted.",
="delete_data_files"
key
):
delete_uploaded_files()
"---")
st.write("Source code available on [GitHub](https://github.com/nicomarr/scholaris/blob/main/scholaris/ui.py)")
st.write(
# Main Chat Interface
for msg in st.session_state.assistant.messages: # Display chat messages from history on app rerun
if msg["role"] == "system" or msg["role"] == "tool": # Skip system message and tool returns
continue
elif msg["role"] == "assistant" and "content" not in msg: # Skip tool calls where no content is returned
continue
with st.chat_message(msg["role"]):
"content"])
st.markdown(msg[
if prompt := st.chat_input(): # Await user input
with st.chat_message("user"): # Display user message in chat message container
st.markdown(prompt)with st.spinner("Thinking...") as status: # Display status while assistant is processing
with st.chat_message("assistant"):
= st.session_state.assistant.chat(prompt=prompt, redirect_output=True)
stream try:
if stream:
= st.write_stream(stream)
response else:
"I'm sorry, I am unable to respond to that query.")
st.markdown(except Exception as e:
f"An error occurred: {e}")
st.error(with st.popover("Tools used"):
st.markdown(get_last_tool_names(st.session_state.assistant.messages))