Skip to content

Commit

Permalink
Contexts, Multi Step Retrieval
Browse files Browse the repository at this point in the history
  • Loading branch information
Niklas Kerkhoff committed Nov 22, 2024
1 parent 03c2a31 commit 136f469
Show file tree
Hide file tree
Showing 45 changed files with 640 additions and 178 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
.DS_STORE
deployment/env
deployment/env
kit-deployment
3 changes: 2 additions & 1 deletion deployment/template-env/.tutor-assistant.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
DATA_DIR=/var/lib/tutor-assistant/data
HOST=tutor-assistant
OPENAI_API_KEY=<...>
OPENAI_API_KEY=<...>
OPENAI_ORGANIZATION=<...>
6 changes: 6 additions & 0 deletions deployment/tutor-assistant-nginx-proxy.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ http {
location /api/ {
proxy_pass http://tutor-assistant-app-service:8080/api/;
proxy_read_timeout 300s;

proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
}

location /auth/ {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,14 @@ class ChatService(
private fun getContextFromJson(json: String): MessageContext {
val root = objectMapper.readTree(json)
return MessageContext(
root.getOrNull("kwargs").getOrNull("metadata").getOrNull("source")?.asText(),
root.getOrNull("kwargs").getOrNull("metadata").getOrNull("page")?.asInt(),
root.getOrNull("kwargs").getOrNull("page_content")?.asText(),
root.getOrNull("kwargs").getOrNull("metadata").getOrNull("originalKey")?.asText()
tutorAssistantId = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("tutorAssistantId")?.asText(),
title = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("title")?.asText(),
originalKey = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("originalKey")?.asText(),
isCalendar = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("isCalendar")?.asBoolean(),
heading = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("heading")?.asText(),
page = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("page")?.asInt(),
content = root.getOrNull("kwargs").getOrNull("page_content")?.asText(),
score = root.getOrNull("kwargs").getOrNull("metadata").getOrNull("score")?.asDouble(),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import jakarta.persistence.Embeddable

@Embeddable
data class MessageContext(
val source: String?,
val tutorAssistantId: String?,
val title: String?,
val originalKey: String?,
val isCalendar: Boolean?,
val heading: String?,
val page: Int?,
@Column(columnDefinition = "text")
val content: String?,
val originalKey: String?
val score: Double?
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ data class FileDocumentDto(
val title: String,
val loaderType: String,
val collection: String?,
val fileStoreId: UUID
) {
constructor(fileDocument: FileDocument) : this(
id = fileDocument.id,
title = fileDocument.title,
loaderType = fileDocument.loaderType,
collection = fileDocument.collection
collection = fileDocument.collection,
fileStoreId = fileDocument.fileStoreId
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ spring:
application:
name: tutor-assistant-app-service

servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB

datasource:
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
Expand Down
6 changes: 5 additions & 1 deletion tutor-assistant/resources/prompt_templates/base_template.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
Du bist ein hilfreicher Assistent.

Hier ein paar allgemeine Informationen, die zum Beantworten der Fragen des Benutzers relevant sein könnten:
- Wir haben heute folgendes Datum: 06.11.2024
- Wir haben heute folgendes Datum: {date}
- Die Benutzer sind studentische Tutoren für eine Programmieren-Vorlesung. Sie bringen anderen Studenten programmieren bei.
- Es geht um die Programmiersprache Java.
- Wenn nicht anders gefordert, schreibe angefragten Code in Java. Wenn nicht anders gefragt, schreibe deine Antworten auf {language}.
- Schreibe Code immer auf Englisch. Kommentare im Code dürfen auf der Standardsprache sein.
- Wenn du den Eindruck hast, dass eine Frage völlig am Thema vorbeigeht, beantworte sie dennoch so gut es geht, weise aber darauf hin, dass du dafür nicht entwickelt wurdest.
- Sprich den Benutzer mit "Du" an

Benutzer Markdown, um deine Antworten zu strukturieren. Markiere insbesondere wichtige Phrasen, damit der Benutzer auf den ersten Blick das Ergebnis sieht.
Wenn der Benutzer nach bestimmten Daten fragt, markiere diese fett.
Wenn du die Antwort nicht weißt, markiere die Stelle fett, an der du das schreibst.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Wichtige Termine und Fristen mit Zeit und Ort: Übungsblätter, Prüfungen, Veranstaltungen
Termine
Original file line number Diff line number Diff line change
@@ -1,5 +0,0 @@
Dies ist ein Chatverlauf.
Untersuche, welche groben Themen in der letzten Nachricht des Benutzers vorkommen. Benenne sie jeweils mit einem Begriff.
Gib möglichst wenig Begriffe aus. Sie sollen nur die Hauptthemen der Anfragen abdecken. Häufig wird nur ein Begriff notwendig sein.
Verwende die anderen Nachrichten nur, um den Kontext der letzten Nachricht zu verstehen.
Ganz wichtig: Beantworte nicht die Frage. Gib nur die Begriffe zu den groben Themen aus. Trenne sie mit Semikolons.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Dies ist ein Chatverlauf.
Untersuche, welche groben Themen in der letzten Nachricht des Benutzers vorkommen. Benenne sie jeweils mit einem Begriff.
Gib möglichst wenig Begriffe aus. Sie sollen nur die Hauptthemen der Anfragen abdecken. Häufig wird nur ein Begriff notwendig sein.
Verwende die anderen Nachrichten nur, um den Kontext der letzten Nachricht zu verstehen.
Ganz wichtig: Beantworte nicht die Frage. Gib nur die Begriffe zu den groben Themen aus. Trenne sie mit Semikolons.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Dies ist ein Chatverlauf.
Ich muss Anfragen an einen Vectorstore machen, sodass die richtigen Dokumente abgerufen werden, sodass die letzte Frage des Benutzers beantwortet werden kann.
Gib mir Anfragen, die ich stellen soll. Trenne die Anfragen mit einem Semikolon.
Gib mir möglichst wenig Anfragen, um Zeit zu sparen. Die Anfragen sollen dennoch alles Wichtige abdecken. In vielen Fällen wird eine Anfrage reichen.
Ganz wichtig: beantworte nicht die Frage, sondern gib nur die Anfragen aus.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Dies ist ein Chatverlauf. Es soll eine Antwort auf die letzte Nachricht des Benutzers erstellt werden.
Um eine Antwort zu generieren, müssen erstmal die notwendigen Dokumente aus einem Vectorstore abgerufen werden.

Ich möchte, dass du mir Anfragen ausgibst, die ich an den Vectorstore senden kann. Beachte dabei folgendes:
- Die Anfragen sollen beschreiben, was der Benutzer möchte. Zudem sollen sie so formuliert sein, dass bei einer Ähnlichkeitssuche die richtigen Dokumente übereinstimmen.
- Überlege dir, welche Informationen du bräuchtest und formuliere dahingehend Anfragen.
- Es soll die letzte Nachricht des Benutzers beantwortet werden. Beziehe jedoch den Chat-Verlauf mit ein, wenn es für den Kontext wichtig ist.
- Es sollen möglichst wenig Anfragen generiert werden. Je mehr Anfragen, desto länger muss der Benutzer warten.
- Die Anfragen sollen sehr verschieden sein. Bei ähnlichen Anfragen kämen dieselben Dokumente zurück, damit gäbe es Redundanz.
- Verwende unter keinen Umständen dieselben Begriffe in mehreren Anfragen.
- Die Anfragen sollen Dokumente identifizieren, nicht die Frage beantworten.
- Häufig wird nur eine Anfrage benötigt.
- Formuliere nur kurze Anfragen. Sie sollen nur wenige Wörter lang sein, wenn überhaupt mehr als ein Wort.
- Versuche wirklich den Kern der Frage des Benutzers zu erfassen und entsprechende Anfragen zu generieren. Gib das Thema aus und nicht, was die Frage dazu ist.
- Benutzer die Begriffe des Benutzers.
- Trenne die Anfragen mit einem Semikolon

Es ist wirklich super wichtig, dass du nur ganz wenige Anfragen ausgibst. Gib wirklich nur dann mehrere aus, wenn es absolut notwendig ist, um den Kontext der Nachricht zu erfassen. Meistens ist dies nicht der Fall!!!
Verwende auf gar keinen Fall dasselbe Wort in mehreren Anfragen. Das bedeutet, dass man nur eine Anfrage braucht!!!

Ganz wichtig: beantworte nicht die Frage, sondern gib nur die Anfragen aus.
26 changes: 26 additions & 0 deletions tutor-assistant/resources/prompt_templates/multi_steps/first.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Dies ist ein Chat-Verlauf. Es soll eine Antwort auf die letzte Nachricht des Benutzers erstellt werden.
Dazu findest du im Folgenden einen Kontext. Es gibt zwei Möglichkeiten:

1. Du kannst die Nachricht des Benutzers mit Sicherheit richtig beantworten:
Wenn du die Nachricht des Benutzers richtig beantworten kannst, dann gib die Antwort aus.
Bevor du deine Antwort gibst, erkläre genau, warum du diese Antwort gibst!
Nutze Markdown, um deine Antworten übersichtlich zu gestalten. Markiere insbesondere das finale Ergebnis fett.
Starte die Ausgabe der Antwort mit !!!RESPONSE!!!

2. Du kannst die Nachricht nicht oder nicht sicher beantworten:
Gib keine Antwort auf die Nachricht aus! Stattdessen gib Phrasen aus, die ich an meinen Vectorstore stellen kann. Ich suche damit nach einem anderen Kontext und frage dich später damit nochmal. Du sollst also Anfragen ausgeben, die ich an den Vectorstore senden kann. Beachte dabei folgendes:
- Die Anfragen sollen beschreiben, was der Benutzer möchte. Zudem sollen sie so formuliert sein, dass bei einer Ähnlichkeitssuche die richtigen Dokumente übereinstimmen.
- Überlege dir, welche Informationen du bräuchtest und formuliere dahingehend Anfragen.
- Es soll die letzte Nachricht des Benutzers beantwortet werden. Beziehe jedoch den Chat-Verlauf mit ein, wenn es für den Kontext wichtig ist.
- Es sollen möglichst wenig Anfragen generiert werden. Je mehr Anfragen, desto länger muss der Benutzer warten.
- Die Anfragen sollen sehr verschieden sein. Bei ähnlichen Anfragen kämen dieselben Dokumente zurück, damit gäbe es Redundanz.
- Häufig wird nur eine Anfrage benötigt.
- Formuliere nur kurze Anfragen. Sie sollen nur wenige Wörter lang sein, wenn überhaupt mehr als ein Wort.
- Versuche wirklich den Kern der Frage des Benutzers zu erfassen und entsprechende Anfragen zu generieren. Gib das Thema aus und nicht, was die Frage dazu ist.
- Benutze die Begriffe des Benutzers.
- Trenne die Anfragen mit einem Semikolon
- Starte die Ausgabe der Anfragen mit !!!QUERIES!!!
- Ganz wichtig: beantworte nicht die Frage, sondern gib nur die Anfragen aus.

Der Kontext:
{context}
14 changes: 14 additions & 0 deletions tutor-assistant/resources/prompt_templates/multi_steps/last.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Wenn du dir bei einer Antwort unsicher bist, teile sie mit, aber schreibe dazu, dass du dir unsicher bist.
Wenn du eine Antwort gar nicht weißt, teile dies dem Benutzer mit statt irgendetwas Falsches zu antworten.

Verwende folgenden Kontext und die bisherige Konversation, um eine Antwort zu generieren, die dem Benutzer bestmöglich hilft.

Bevor du deine Antwort gibst, erkläre genau, warum du diese Antwort gibst!

Nutze Markdown, um deine Antworten übersichtlich zu gestalten. Markiere insbesondere das finale Ergebnis fett.
Wenn du dir nicht sicher bist oder die Antwort nicht weißt, markiere auch das fett.

Ganz wichtig: Starte die Ausgabe deiner Antwort mit !!!RESPONSE!!!

Der Kontext:
{context}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from tutor_assistant.controller.config.domain_config import config
from tutor_assistant.controller.utils.api_utils import check_request_body
from tutor_assistant.controller.utils.data_transfer_utils import json_output
from tutor_assistant.controller.utils.langchain_utils import stream_chain
from tutor_assistant.controller.utils.langchain_utils import stream_chain, stream_response
from tutor_assistant.domain.chats.message_chain_service import MessageChainService
from tutor_assistant.domain.chats.message_multi_steps_chain_service import MessageMultiStepsChainService
from tutor_assistant.domain.chats.summary_chain_service import SummaryChainService
from tutor_assistant.utils.string_utils import shorten_middle

Expand All @@ -23,12 +24,14 @@ async def _message(request: Request):

config.logger.info(f'POST /chats/message: len(message):{len(user_message_content)};len(history):{len(history)}')

chain = MessageChainService(config).create(user_message_content, history)
# chain = MessageChainService(config).create(user_message_content, history)

response = MessageMultiStepsChainService(config).load_response(user_message_content, history)

config.logger.info('Starting event-stream')

return StreamingResponse(
stream_chain(chain), media_type="text/event-stream"
stream_response(response), media_type="text/event-stream"
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,36 @@
from langchain_core.prompts import ChatPromptTemplate

from tutor_assistant.controller.config.domain_config import config
from tutor_assistant.domain.chats.message_multi_steps_chain_service import MessageMultiStepsChainService

router = APIRouter()


@router.post('/demo/multi-steps')
async def _get_multi_steps():
# user_message_content = 'Was mache ich in Artemis bei Exited Prematurely'
# history = []
# MessageMultiStepsChainService(config).load_response(user_message_content, history)

# user_message_content = 'Welches Datum haben wir heute?'
# history = []
# MessageMultiStepsChainService(config).create(user_message_content, history)
#
# user_message_content = 'Wie viele Übungsblätter gibt es?'
# history = []
# MessageMultiStepsChainService(config).create(user_message_content, history)
#
user_message_content = 'Was mache ich bei Exited Prematurely'
history = []
response = MessageMultiStepsChainService(config).load_response(user_message_content, history)

for item in response:
if 'context' in item:
print(item['context'])
if 'answer' in item:
print(item['answer'])


@router.get("/demo/messages")
async def _get_demo_messages():
template = ChatPromptTemplate.from_messages(
Expand All @@ -20,7 +46,6 @@ async def _get_demo_messages():
)



@router.post('/demo/meta-docs')
async def _meta_docs():
documents = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async def _add_document(request: Request):

loader = get_loader(loader_creators, title, loader_type, loader_params)

ids = DocumentService(config).add(loader, original_key, is_calendar)
ids = DocumentService(config).add(loader, title, original_key, is_calendar)

config.logger.info(f'Result: {ids}')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def get_logger() -> logging.Logger:
logger = logging.getLogger('tutor-assistant')
logger.setLevel(logging.DEBUG)

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s')

console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
Expand Down
15 changes: 10 additions & 5 deletions tutor-assistant/tutor_assistant/controller/config/domain_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

from tutor_assistant.controller.config._logging_config import get_logger
from tutor_assistant.controller.utils.model_utils import get_remote_ollama_chat_model
from tutor_assistant.controller.utils.resource_utils import load_resources
from tutor_assistant.domain.domain_config import DomainConfig
from tutor_assistant.domain.vector_stores.chroma_repo import ChromaRepo
from tutor_assistant.domain.vector_stores.faiss_repo import FaissRepo

_use_base_retriever = True

_embeddings = OpenAIEmbeddings(model='text-embedding-3-large')

_store = ChromaRepo(f"{os.getenv('DATA_DIR')}/chroma_ws2324_with_meta_docs_index", _embeddings)

if _use_base_retriever:
_store = ChromaRepo(f"{os.getenv('DATA_DIR')}/chroma_ws2324_no_meta_docs_index", _embeddings)

config = DomainConfig(
ChatOpenAI(model='gpt-4o', temperature=0),
# get_remote_ollama_chat_model('llama3.1:8b'),
_embeddings,
# FaissRepo(f"{os.getenv('DATA_DIR')}/faiss_index", _embeddings),
ChromaRepo(f"{os.getenv('DATA_DIR')}/chroma_index", _embeddings),
_store,
load_resources(f'{os.getcwd()}/resources'),
get_logger(),
"Deutsch"
"Deutsch",
_use_base_retriever
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from typing import Iterator

from langchain_core.documents import Document
from langchain_core.runnables import Runnable
Expand All @@ -7,7 +8,11 @@


def stream_chain(chain: Runnable, answer_key='answer', context_key='context'):
for item in chain.stream({}):
yield from stream_response(chain.stream({}), answer_key, context_key)


def stream_response(response: Iterator, answer_key='answer', context_key='context'):
for item in response:
if context_key in item:
yield from _handle_context(item[context_key], context_key)
elif answer_key in item:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from tutor_assistant.controller.utils.data_transfer_utils import messages_from_history
from tutor_assistant.controller.utils.langchain_utils import escape_prompt
from tutor_assistant.domain.documents.retrievers.hybrid_retriever import HybridRetriever
from tutor_assistant.domain.documents.retrievers.queries_loader_retriever import QueriesLoaderRetriever
from tutor_assistant.domain.domain_config import DomainConfig
from tutor_assistant.domain.utils.templates import prepend_base_template

Expand All @@ -23,12 +23,16 @@ def create(self, user_message_content: str, history: list[dict[str, str]]) -> Ru
chat_prompt = self._get_chat_prompt(messages)

model_chain = self._get_model_chain(chat_prompt)
retriever = HybridRetriever(self._config)
retriever = QueriesLoaderRetriever(self._config)


# retriever_chain = (lambda _: user_message_content) | self._get_self_query_retriever_chain(user_message_content)
# retriever_chain = (lambda _: user_message_content) | self._get_base_retriever_chain(user_message_content)
retriever_chain = (lambda _: messages) | retriever

if self._config.use_base_retriever:
retriever_chain = (lambda _: self._search_with_score(user_message_content))
# retriever_chain = (lambda _: user_message_content) | self._get_self_query_retriever_chain(user_message_content)


return (
RunnablePassthrough
.assign(context=retriever_chain)
Expand Down Expand Up @@ -79,3 +83,25 @@ def _get_self_query_retriever_chain(self, query: str) -> RunnableSerializable[An
print(retriever.invoke(query))

return (lambda _: query) | retriever

def _search_with_score(self, query: str) -> list[Document]:
self._config.logger.info(f'Base Retriever: Running for "{query}')
try:
docs, scores = zip(
*self._config.vector_store_manager.load().similarity_search_with_score(
query,
k=4
)
)
except Exception as e:
print('Exception:', e)
return []
result = []
doc: Document
for doc, np_score in zip(docs, scores):
score = float(np_score)
doc.metadata['score'] = score
if np_score < 2:
result.append(doc)

return result
Loading

0 comments on commit 136f469

Please sign in to comment.