Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ cython_debug/
*/__pycache__/*
.chainlit/translations/
code/.chainlit/translations/
**/.chainlit/translations/
storage/logs/*
vectorstores/*

Expand Down
2 changes: 1 addition & 1 deletion apps/ai_tutor/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import hashlib

# set config
config = config_manager.get_config().dict()
config = config_manager.get_config()

# set constants
GITHUB_REPO = config["misc"]["github_repo"]
Expand Down
185 changes: 100 additions & 85 deletions apps/ai_tutor/chainlit_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
from langchain_community.callbacks import get_openai_callback
from datetime import datetime, timezone
from config.config_manager import config_manager
from edubotics_core.chat.agentic.agent import Agent

from config.prompts import prompts

USER_TIMEOUT = 60_000
SYSTEM = "System"
Expand All @@ -40,7 +43,8 @@
ERROR = "Error"

# set config
config = config_manager.get_config().dict()
config = config_manager.get_config()
print(config)


async def setup_data_layer():
Expand Down Expand Up @@ -81,6 +85,8 @@ def __init__(self, config):
Initialize the Chatbot class.
"""
self.config = config
# Initialize Agent instance
self.agent = Agent(config=config, prompts=prompts)

@no_type_check
async def setup_llm(self):
Expand Down Expand Up @@ -172,10 +178,14 @@ async def make_llm_settings_widgets(self, config=None):
cl.input_widget.Select(
id="retriever_method",
label="Retriever (Default FAISS)",
values=["FAISS", "Chroma", "RAGatouille", "RAPTOR"],
initial_index=["FAISS", "Chroma", "RAGatouille", "RAPTOR"].index(
config["vectorstore"]["db_option"]
),
values=["FAISS", "Chroma", "RAGatouille", "RAPTOR", "MVS"],
initial_index=[
"FAISS",
"Chroma",
"RAGatouille",
"RAPTOR",
"MVS",
].index(config["vectorstore"]["db_option"]),
),
cl.input_widget.Slider(
id="memory_window",
Expand Down Expand Up @@ -297,14 +307,15 @@ async def start(self):
}

memory = cl.user_session.get("memory", [])
self.llm_tutor = LLMTutor(self.config, user=self.user)
# Replace LLMTutor usage with Agent if needed
# self.llm_tutor = LLMTutor(self.config, user=self.user)
# self.chain = self.llm_tutor.qa_bot(memory=memory)

self.chain = self.llm_tutor.qa_bot(
memory=memory,
)
self.question_generator = self.llm_tutor.question_generator
cl.user_session.set("llm_tutor", self.llm_tutor)
cl.user_session.set("chain", self.chain)
# cl.user_session.set("llm_tutor", self.llm_tutor)
# cl.user_session.set("chain", self.chain)

self.agent.set_thread_id(cl.context.session.thread_id)
cl.user_session.set("agent", self.agent)

async def stream_response(self, response):
"""
Expand Down Expand Up @@ -344,6 +355,7 @@ async def main(self, message):

# update user info with last message time
user = cl.user_session.get("user")

await reset_tokens_for_user(
user,
self.config["token_config"]["tokens_left"],
Expand All @@ -355,7 +367,11 @@ async def main(self, message):

# see if user has token credits left
# if not, return message saying they have run out of tokens
if user.metadata["tokens_left"] <= 0 and "admin" not in user.metadata["role"]:
if (
user.metadata["tokens_left"] <= 0
and "admin" not in user.metadata["role"]
and config["chat_logging"]["log_chat"]
):
current_datetime = get_time()
cooldown, cooldown_end_time = await check_user_cooldown(
user,
Expand Down Expand Up @@ -425,84 +441,83 @@ async def main(self, message):
),
}

response = cl.Message(content="")
with get_openai_callback() as token_count_cb:
if stream:
res = chain.stream(user_query=user_query_dict, config=chain_config)
res = await self.stream_response(res)
else:
res = await chain.invoke(
user_query=user_query_dict,
config=chain_config,
)
token_count += token_count_cb.total_tokens

answer = res.get("answer", res.get("result"))

answer_with_sources, source_elements, sources_dict = get_sources(
res, answer, stream=stream, view_sources=view_sources
)
answer_with_sources = answer_with_sources.replace("$$", "$")
async for chunk in self.agent.stream(message.content):
content = chunk["content"]
tokens = chunk["total_tokens"]
await response.stream_token(content)

actions = []
token_count = tokens

if self.config["llm_params"]["generate_follow_up"]:
cb_follow_up = cl.AsyncLangchainCallbackHandler()
config = {
"callbacks": (
[cb_follow_up]
if cl_data._data_layer and self.config["chat_logging"]["callbacks"]
else None
)
}
with get_openai_callback() as token_count_cb:
list_of_questions = await self.question_generator.generate_questions(
query=user_query_dict["input"],
response=answer,
chat_history=res.get("chat_history"),
context=res.get("context"),
config=config,
)

token_count += token_count_cb.total_tokens

for question in list_of_questions:
actions.append(
cl.Action(
name="follow up question",
value="example_value",
description=question,
label=question,
)
)
answer = response.content
sources = self.agent.get_sources()

# # update user info with token count
tokens_left = await update_user_from_chainlit(user, token_count)

answer_with_sources += (
'\n\n<footer><span style="font-size: 0.8em; text-align: right; display: block;">Tokens Left: '
+ str(tokens_left)
+ "</span></footer>\n"
)

await cl.Message(
content=answer_with_sources,
elements=source_elements,
author=LLM,
actions=actions,
).send()
if len(sources) > 0:
sources_text = "\n\nSources: \n" + "\n".join(
[f"- {source}" for source in sources]
)
response.content += sources_text

await response.send()

# answer_with_sources, source_elements, sources_dict = get_sources(
# res, answer, stream=stream, view_sources=view_sources
# )
# answer_with_sources = answer_with_sources.replace("$$", "$")

# actions = []

# if self.config["llm_params"]["generate_follow_up"]:
# cb_follow_up = cl.AsyncLangchainCallbackHandler()
# config = {
# "callbacks": (
# [cb_follow_up]
# if cl_data._data_layer and self.config["chat_logging"]["callbacks"]
# else None
# )
# }
# with get_openai_callback() as token_count_cb:
# list_of_questions = await self.question_generator.generate_questions(
# query=user_query_dict["input"],
# response=answer,
# chat_history=res.get("chat_history"),
# context=res.get("context"),
# config=config,
# )

# token_count += token_count_cb.total_tokens

# for question in list_of_questions:
# actions.append(
# cl.Action(
# name="follow up question",
# value="example_value",
# description=question,
# label=question,
# )
# )

# # # update user info with token count
# tokens_left = await update_user_from_chainlit(user, token_count)

# answer_with_sources += (
# '\n\n<footer><span style="font-size: 0.8em; text-align: right; display: block;">Tokens Left: '
# + str(tokens_left)
# + "</span></footer>\n"
# )

# await cl.Message(
# content=answer_with_sources,
# elements=source_elements,
# author=LLM,
# actions=actions,
# ).send()

async def on_chat_resume(self, thread: ThreadDict):
# thread_config = None
steps = thread["steps"]
k = self.config["llm_params"][
"memory_window"
] # on resume, alwyas use the default memory window
conversation_list = get_history_chat_resume(steps, k, SYSTEM, LLM)
# thread_config = get_last_config(
# steps
# ) # TODO: Returns None for now - which causes config to be reloaded with default values
cl.user_session.set("memory", conversation_list)
await self.start()
thread_id = thread["id"]
self.agent.set_thread_id(thread_id)
self.agent.populate_conversation_history(thread)

@cl.header_auth_callback
def header_auth_callback(headers: dict) -> Optional[cl.User]:
Expand Down
7 changes: 6 additions & 1 deletion apps/ai_tutor/config/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
LLAMA_CLOUD_API_KEY = os.getenv("LLAMA_CLOUD_API_KEY")
HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_TOKEN")
COHERE_API_KEY = os.getenv("COHERE_API_KEY")

LITERAL_API_KEY_LOGGING = os.getenv("LITERAL_API_KEY_LOGGING")
LITERAL_API_URL = os.getenv("LITERAL_API_URL")

HUGGINGFACE_TOKEN = os.getenv("HUGGINGFACE_TOKEN")


CHAINLIT_URL = os.getenv("CHAINLIT_URL")
EMAIL_ENCRYPTION_KEY = os.getenv("EMAIL_ENCRYPTION_KEY")

Expand Down
5 changes: 3 additions & 2 deletions apps/ai_tutor/config/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@
),
"prompt_with_history": {
"normal": (
"You are an AI Tutor for the course DS598, taught by Prof. Thomas Gardos. Answer the user's question using the provided context. Only use the context if it is relevant. The context is ordered by relevance. "
"If you don't know the answer, do your best without making things up. Keep the conversation flowing naturally. "
"You are an AI Tutor for the course DS542, taught by Prof. Thomas Gardos. Answer the user's question using the provided context. Only use the context if it is relevant. The context is ordered by relevance. "
"If you don't know the answer, do not make things up, just say you don't know and ask the user to rephrase. Keep the conversation flowing naturally. "
"Use chat history and context as guides but avoid repeating past responses. Provide links from the source_file metadata. Use the source context that is most relevant. "
"Render math equations in LaTeX format between $ or $$ signs, stick to the parameter and variable icons found in your context. Be sure to explain the parameters and variables in the equations."
"Speak in a friendly and engaging manner, like talking to a friend. Avoid sounding repetitive or robotic.\n\n"
"Do not get influenced by the style of conversation in the chat history. Follow the instructions given here."
"Chat History:\n{chat_history}\n\n"
"Context:\n{context}\n\n"
"Answer the student's question below in a friendly, concise, and engaging manner. Use the context and history only if relevant, otherwise, engage in a free-flowing conversation.\n"
"If the provided context is not relevant, just say you don't know and ask the user to attach the relevant documents. Do not make things up."
"Student: {input}\n"
"AI Tutor:"
),
Expand Down
Loading