Spaces:
Sleeping
Sleeping
import uuid | |
from embedding_loader import * | |
from initialize_db import QdrantClientInitializer | |
from pdf_loader import PDFLoader | |
from IPython.display import display, Markdown | |
import gradio as gr | |
from langchain_core.messages import HumanMessage, AIMessage | |
from langchain.memory import ConversationBufferMemory | |
from langchain_core.chat_history import InMemoryChatMessageHistory | |
from qdrant_client import QdrantClient, models | |
from db_operations import DatabaseOperations | |
from openai import AzureOpenAI | |
import json | |
from qdrant_client.http import models as rest | |
import time | |
from fastembed.sparse.bm25 import Bm25 | |
from fastembed.late_interaction import LateInteractionTextEmbedding | |
from pydantic import BaseModel, Field | |
from langchain_core.load import dumpd, dumps, load, loads | |
from langchain.schema import Document | |
dense_embedding_model = import_embedding() | |
late_interaction_embedding_model = LateInteractionTextEmbedding("colbert-ir/colbertv2.0") | |
bm25_embedding_model = Bm25("Qdrant/bm25", language="turkish") | |
AZURE_OPENAI_KEY = os.getenv('azure_api') | |
os.environ['AZURE_OPENAI_KEY'] = AZURE_OPENAI_KEY | |
openai.api_version = "2024-08-01-preview" # change it with your own version | |
openai.azure_endpoint = os.getenv('azure_endpoint') | |
model = "gpt-4o-mini" # deployment name on Azure OPENAI Studio | |
client = AzureOpenAI(azure_endpoint = openai.azure_endpoint, | |
api_key=AZURE_OPENAI_KEY, | |
api_version=openai.api_version) | |
obj_qdrant = QdrantClientInitializer() | |
qclient = obj_qdrant.initialize_db() | |
obj_loader = PDFLoader() | |
# ----- | |
def retriever_db(client, query, collection_name, CAR_ID): | |
dense_query_vector = list(dense_embedding_model.embed_documents([query]))[0] | |
sparse_query_vector = list(bm25_embedding_model.query_embed(query))[0] | |
late_query_vector = list(late_interaction_embedding_model.query_embed(query))[0].tolist() | |
prefetch = [ | |
models.Prefetch( | |
query=dense_query_vector, | |
using="sfr-mistral", | |
limit=30, | |
), | |
models.Prefetch( | |
query=models.SparseVector(**sparse_query_vector.as_object()), | |
using="bm25", | |
limit=30, | |
), | |
models.Prefetch( | |
query=late_query_vector, | |
using="colbertv2.0", | |
limit=30, | |
), | |
] | |
results = client.query_points( | |
collection_name, | |
prefetch=prefetch, | |
query=models.FusionQuery( | |
fusion=models.Fusion.RRF, | |
), | |
with_payload=True, | |
query_filter=models.Filter( | |
must=[ | |
models.FieldCondition(key="car_id", match=models.MatchValue(value=CAR_ID)) | |
]), | |
limit=10, | |
) | |
# retrieved_chunks = [doc.payload for doc in results.points] | |
retrieved_chunks = [Document(metadata=doc.payload["metadata"], page_content=doc.payload["page_content"]) for doc in results.points] | |
return retrieved_chunks | |
## new version | |
def chat_gpt(prompt=None, history=[], model=model, client=client, tools=[None]): | |
if prompt is None: | |
messages = history | |
else: | |
history.append({"role": "user", "content": f"{prompt}"}) | |
messages = history | |
completion = client.chat.completions.create( | |
model=model, | |
messages=messages, | |
tools=tools, | |
tool_choice="required", | |
temperature=0.0 | |
) | |
return completion | |
retrieval_functions = [ | |
{ | |
"type": "function", | |
"function":{ | |
"name": "get_section_titles", | |
"description": """Use this function to get the section, subsection and subsubsection titles from a user manual table of content.""", | |
"parameters": { | |
"type": "object", | |
"properties": { | |
"section_title": { | |
"type": "string", | |
"description": "Title of the section in the table of content", | |
}, | |
"sub_section_title": { | |
"type": "string", | |
"description": "Title of the subsection in the table of content", | |
}, | |
"sub_sub_section_title": { | |
"type": "string", | |
"description": "Title of the subsubsection in the table of content", | |
} | |
}, | |
"required": ["section_title", "sub_section_title", "sub_sub_section_title"], | |
} | |
} | |
} | |
] | |
def get_section_content(section_title, sub_section_title, sub_sub_section_title, content_path): | |
with open(content_path, "r") as file: | |
doc_section_content = json.loads(file.read()) | |
response = None | |
try: | |
content = doc_section_content["TableOfContents"][section_title][sub_section_title][sub_sub_section_title]["content"] | |
pages = doc_section_content["TableOfContents"][section_title][sub_section_title][sub_sub_section_title]["pages"] | |
response = Document(metadata={"page": pages, | |
"section_title": section_title, | |
"sub_section_title": sub_section_title, | |
"sub_sub_section_title": sub_sub_section_title}, | |
page_content = content) | |
except: | |
pass | |
return response | |
def get_lead_result(question): | |
hizmet_listesi = {"Bakım": """Check-Up, Periyodik Bakım, Aks Değişimi, Amortisör Değişimi, Amortisör Takozu Değişimi, Baskı Balata Değişimi, Benzin Filtresi Değişimi, | |
Debriyaj Balatası Değişimi, Direksiyon Kutusu Değişimi, Dizel Araç Bakımı, Egzoz Muayenesi, Fren Kaliperi Değişimi, El Freni Teli Değişimi, | |
Fren Balatası Değişimi, Fren Disk Değişimi, Hava Filtresi Değişimi, Helezon Yay Değişimi, Kampana Fren Balatası Değişimi, | |
Kızdırma Bujisi Değişimi, Rot Başı Değişimi, Rot Kolu Değişimi, Rotil Değişimi, Silecek Değişimi, Süspansiyon, Triger Kayışı Değişimi, | |
Triger Zinciri Değişimi, V Kayışı Değişimi, Yağ Filtresi Değişimi, Yakıt Filtresi Değişimi, Havayastığı Değişimi""", | |
"Yağ ve Sıvılar": """Şanzıman Yağı Değişimi, Dizel Araçlarda Yağ Değişimi, Yağ Değişimi, Fren Hidrolik Değişimi, Antifriz Değişimi,""", | |
"Akü": """Akü Şarj Etme, Akü Değişimi""", | |
"Klima": """Oto Klima Kompresörü Tamiri, Oto Klima Tamiri, Araç Klima Temizliği, Araç Klima Bakteri Temizliği, Klima Gazı Dolumu, Klima Dezenfeksiyonu, Polen Filtresi Değişimi""", | |
"Elektrik": """Servis Uyarı Lambası Sıfırlama,Buji Kablosu Değişimi, Arıza Tespit, Göstergelerin Kontrolü, Far Ayarı ve Ampul Değişimi, Buji Değişimi, Sigorta Değişimi""", | |
"Lastik/ Jant": """Lastik Jant Satış, Lastik Değişimi, Balans Ayarı, Rot Ayarı, Rotasyon, Lastik Tamiri, Hava Kontrolü, Nitrojen Dolumu, Supap Değişimi, Lastik Saklama (Lastik Oteli), Jant Sökme Takma,""", | |
"Diğer": """Cam Tamiri""", | |
"Hibrit Araçlar": "Hibrit Araç Aküsü"} | |
lead_functions = [ | |
{ | |
"type": "function", | |
"function": { | |
"name": "grade_service_relevance", | |
"description": "Grade the relevance of services to a user question", | |
"parameters": { | |
"type": "object", | |
"properties": { | |
"binary_score": { | |
"type": "string", | |
"description": "Services are relevant to the question, 'yes' or 'no'", | |
"enum": ["yes", "no"] | |
} | |
}, | |
"required": ["binary_score"] | |
} | |
} | |
} | |
] | |
# System message | |
system_message = """Soruyu cevaplarken: | |
1- Önce soruyu düşün. | |
2- Kullanıcının sorduğu soru, hizmet listesinde sunulan hizmetlerle alakalı mı? | |
Alakalı ise "yes", değilse "no" olarak cevap ver.""" | |
def service_grader_relevance(hizmet_listesi: str, question: str) -> dict: | |
completion = client.chat.completions.create( | |
model=model, | |
messages=[ | |
{"role": "system", "content": system_message}, | |
{"role": "user", "content": f"Provided services: \n\n {hizmet_listesi} \n\n User question: {question}"} | |
], | |
tools=lead_functions, | |
tool_choice={"type": "function", "function": {"name": "grade_service_relevance"}} | |
) | |
tool_call = completion.choices[0].message.tool_calls[0] | |
return json.loads(tool_call.function.arguments) | |
result = service_grader_relevance(hizmet_listesi, question) | |
return result['binary_score'] | |
def chat_gpt_nofn(prompt=None, history=[], model=model, client=client): | |
if prompt is None: | |
messages = history | |
else: | |
history.append({"role": "user", "content": f"{prompt}"}) | |
messages = history | |
completion = client.chat.completions.create( | |
model=model, | |
messages=messages, | |
stream=True) | |
return completion | |
def format_chat_prompt(chat_history): | |
prompt = [] | |
print(chat_history) | |
for turn in chat_history: | |
user_message, ai_message = turn | |
prompt.append({"role": "user", "content": user_message}) | |
prompt.append({"role": "assistant", "content": ai_message}) | |
return prompt | |
class GradeDocuments(BaseModel): | |
"""Binary score for relevance check on retrieved documents.""" | |
binary_score: str = Field(description="Documents are relevant to the question, 'yes' or 'no'") | |
def grade_document_with_openai(document: str, question: str) -> GradeDocuments: | |
system_message = """ | |
You are a grader assessing relevance of a retrieved document to a user question. | |
Consider the following when making your assessment: | |
- Does the document directly or indiretly address the user's question? | |
- Does it provide information or context that is pertinent to the question? | |
- Does it discuss relevant risks, benefits, recommendations, or considerations related to the question? | |
If the document contains keyword(s) or semantic meaning related or partially related to the question, grade it as relevant. | |
Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question. | |
""" | |
# """ | |
# You are a grader assessing relevance of a retrieved document to a user question. | |
# Consider the following when making your assessment: | |
# - Does the documents encapsulate the car related feature mentioned the question? | |
# - Does the documents directly or indirectly address the user's question? | |
# - Does it provide information or context that is pertinent to the question? | |
# - Does it discuss relevant risks, benefits, recommendations, or considerations related to the question? | |
# If the documents contains keyword(s) or semantic meaning related or partially related to the question, grade it as relevant | |
# Give a binary score 'yes' or 'no' to indicate whether the documents is relevant to the question. | |
# """ | |
response = client.chat.completions.create( | |
model=model, | |
messages=[ | |
{"role": "system", "content": system_message}, | |
{"role": "user", "content": f'Retrieved document: \n\n {document} \n\n User question: {question}'} | |
] | |
) | |
score = response.choices[0].message.content | |
return GradeDocuments(binary_score=score.strip()) | |
liked_state = gr.State(None) | |
last_interaction = gr.State(None) | |
def chat(question, manual, history, liked, graded_doc_history): | |
history = history or [] | |
graded_doc_history = graded_doc_history or [] | |
conv = format_chat_prompt(history) | |
# print("History: ", history) | |
# print("CONV: ", conv) | |
# print("Graded_doc_history: ", graded_doc_history) | |
manual_list = {"Toyota_Corolla_2024_TR": -8580416610875007536, | |
"Renault_Clio_2024_TR":-5514489544983735006, | |
"Fiat_Egea_2024_TR":-2026113796962100812} | |
collection_list = {"Toyota_Corolla_2024_TR": "HYBRID_TOYOTA_MANUAL_COLLECTION_EMBED3", | |
"Renault_Clio_2024_TR": "HYBRID_RENAULT_MANUAL_COLLECTION_EMBED3", | |
"Fiat_Egea_2024_TR": "HYBRID_FIAT_MANUAL_COLLECTION_EMBED3"} | |
collection_name = collection_list[manual] | |
toc_name = "ToC_" + manual + ".txt" | |
start_time = time.time() | |
with open("ToCs/" + toc_name, "r") as file: | |
content = json.loads(file.read()) | |
print("ToCs:--- %s seconds ---" % (time.time() - start_time)) | |
# start_time = time.time() | |
# db = obj_loader.load_from_database(embeddings=embeddings, collection_name=collection_name) | |
# print("DB Load:--- %s seconds ---" % (time.time() - start_time)) | |
start_time = time.time() | |
for i in range(3): | |
first_hop = f"""Soruyu cevaplarken: | |
1- Önce soruyu düşün. | |
2- Kullanıcının sorduğu sorunun konu başlıkları neler olabilir? | |
3- Sorulan soru bir arızaya işaret ediyor olabilir mi? | |
4- Bu konu başlıkları kullanım kılavuzu içindekiler tablosu başlıkları ile alakalı mı? | |
5- Alakalı olabilecek tüm başlıkları türet. | |
Buna göre, aşağıda vereceğim kullanım kılavuzu içindekiler tablosu (başlıklar) bilgisini kullanarak bu içeriğe erişmek için uygun fonksiyonları üret. | |
Eğer herhangi bir içeriğe ulaşamazsan, bunu belir ve sorunun cevabı hakkında yorum yapma. | |
Kullanım Kılavuzu İçindekiler Tablosu: | |
{content} | |
""" | |
# conv = [{"role": "system", "content": f"{first_hop}"}] | |
# conv.append({"role": "system", "content": f"{first_hop}"}) | |
# first_hop_response = chat_gpt(prompt=f"Soru: {question}", history=conv, tools=retrieval_functions) | |
# conv.append(first_hop_response.choices[-1].message) | |
first_hop_response = chat_gpt(prompt=f"Soru: {question}", | |
history=[{"role": "system", "content": f"{first_hop}"}], | |
tools=retrieval_functions) | |
tool_calls = first_hop_response.choices[-1].message.tool_calls | |
arg_list = [] | |
if tool_calls: | |
for tool_call in tool_calls: | |
function_name = tool_call.function.name | |
args = json.loads(tool_call.function.arguments) | |
arg_list.append(args) | |
print(f"Will call {function_name} with arguments {args}") | |
break | |
else: | |
print("No tool calls") | |
print("First_hop:--- %s seconds ---" % (time.time() - start_time)) | |
path = "Contents/" + manual + ".json" | |
start_time = time.time() | |
documents = [] | |
for i in arg_list: | |
documents.append(get_section_content(str(i['section_title']), str(i['sub_section_title']), str(i['sub_sub_section_title']), content_path=path)) | |
# break | |
# print("Documents:", documents) | |
CAR_ID = manual_list[manual] | |
documents_db = retriever_db(qclient, question, collection_name, CAR_ID) | |
for i in documents_db: | |
documents.append(i) | |
print("Retriever:--- %s seconds ---" % (time.time() - start_time)) | |
# print("Documents: ", documents) | |
start_time = time.time() | |
graded_doc = [] | |
for r in documents: | |
grade = grade_document_with_openai(r, question) | |
if grade.binary_score == "yes": | |
graded_doc.append(r) | |
# graded_doc_history.append(r) | |
print(r.metadata["page"]) | |
print("GRADED DOC:", graded_doc) | |
print("GRADED DOC HISTORY", graded_doc_history) | |
# if not graded_doc: | |
# documents = [] | |
# CAR_ID = manual_list[manual] | |
# documents_db = retriever_db(qclient, question, collection_name, CAR_ID) | |
# for i in documents_db: | |
# documents.append(i) | |
# for r in documents: | |
# grade = grade_document_with_openai(r, question) | |
# if grade.binary_score == "yes": | |
# graded_doc.append(r) | |
# graded_doc_history.append(r) | |
print("Onaylanan Doküman Sayısı:", len(graded_doc)) | |
# print(graded_doc_history) | |
print("Grade Doc:--- %s seconds ---" % (time.time() - start_time)) | |
start_time = time.time() | |
if get_lead_result(question).lower() == 'yes': | |
lead = """ Her zaman tamirci yerine geçen "servis", "yetkili servis", ""... servisi", ""... yetkili servisi", "bakım" gibi ifadeler yerine yalnızca "OtoBakım Servisi" yazmalısın. | |
Cevabın sonunda, "Nasıl devam edeceğinizden emin değilseniz." ifadesiyle başlayarak, kullanıcılara hızlı ve uygun fiyatlı çözümler için OtoBakım mağazalarını ziyaret etmelerini teklif etmelisin.""" | |
print('Lead: yes') | |
else: | |
lead = 'Her zaman tamirci yerine geçen "servis", "yetkili servis", ""... servisi", ""... yetkili servisi", "bakım" gibi ifadeler yerine yalnızca "OtoBakım Servisi" yazmalısın. Sorunun tipine göre OtoBakım servislerini ziyaret edebileceklerini söylemelisin.' | |
print('Lead: no') | |
print("Lead Check:--- %s seconds ---" % (time.time() - start_time)) | |
start_time = time.time() | |
marka = manual.split("_")[0] | |
model = manual.split("_")[1] | |
yıl = manual.split("_")[2] | |
prompt = f""" | |
Sen, yalnızca {yıl} {marka} {model} marka araba ile ilgili sorunlara ve araç bilgilerine odaklanan, ARVI adında uzman bir asistansın. | |
Amacın, araba sorunları, bakım, onarımlar, teknik özellikler ve diğer araçla ilgili konularla ilgili sorulara eğer dokümanlarda (yani kullanım kılavuzunda) yeterli bilgi varsa doğru, yardımcı, net ve yorum yapmadan cevaplar vermektir. | |
Temel nezaket etkileşimlerine uygun ve kibar bir şekilde yanıt vermek için tasarlandın. | |
Soruları yanıtlarken aşağıdaki adımları izle: \n | |
- Dokümanlar soruyla ilgiliyse, soruyu yanıtlamak için dokümanlardan yararlan. | |
- Sorulara cevap verirken sana sağlanan bilgilerdeki uyarılara, tehlikelere vurgu yap ve öne çıkar. | |
- Soruları yanıtlarken yorum yapma, kişisel görüşlerini belirtme ve önceki bilgilerini kullanma. | |
- Dokümandakiler dışında terim ve bilgileri kullanma. | |
- Eğer dokümanlarda bir işlemin nasıl yapıldığı adım adım anlatılıyorsa, bu adımları direkt şekilde ekle. | |
- Kullanıcıya doğrudan cevap ver. | |
- Cevaplar kısa ama anlamlı ve yeterli olsun. | |
- Cevabın sonunda kullanılan kaynakları listelerken şu kurallara uy: | |
1. Başlık yapısı tamamen aynı olan kaynakları (section_title -> sub_section_title -> sub_sub_section_title) tek bir referans altında birleştir. | |
2. Başlık boş olsa bile mutlaka referansta göster | |
3. Boş başlıklar için -> işaretinden sonra boşluk bırak | |
3. Her başlık yapısı sadece bir kez yazılmalı | |
4. Aynı başlığa ait tüm sayfa numaralarını tek bir "Sayfa:" satırında göster | |
5. Sayfa numaralarını şu kurallara göre formatla: | |
- Ardışık sayfalar için tire kullan (örn: **-**) | |
- Ardışık olmayan sayfalar için virgül kullan (örn: **, **, **) | |
- Tekrar eden sayfa numaralarını eleme | |
6. Format şu şekilde olmalı: | |
Kaynaklar: | |
a. [section_title -> sub_section_title -> sub_sub_section_title] // dolu başlık örneği | |
Sayfa: X-Z ya da A, B, ... | |
b. Sayfa: K, L, ... // boş başlık örneği | |
. | |
. | |
. | |
Ek yönerge: {lead} | |
Son Kontrol: | |
- Cevabın doğruluğunu ve tamlığını kontrol et. | |
- Gereksiz bilgi veya yorum olup olmadığını kontrol et. | |
- Referansların doğru eklendiğinden emin ol. | |
Sorulan soru bir önceki soru ile bağlantılı olabilir. Bu durumda önceki soruyla yeni soruyu beraber değerlendir. | |
Kullanıcı detaylı bilgi, daha çok, başka vb. ifadeler kullanmışsa, daha detaylı bilgi ver. | |
Eğer Dokümanlar boş ise ve Geçmiş Dokümanlar soru ile alakasızsa: sorulan soruyla uyumlu bir şekilde, kullanım kılavuzunda bu bilginin olmadığını, soruyu neden yanıtlayamadığını belirt ve soru hakkında yorum yapma. | |
Soru çok genel ise, spesifik bilgi iste. | |
Geçmiş Dokümanlar: {graded_doc_history} | |
Dokümanlar: {graded_doc} | |
""" | |
for r in graded_doc: | |
graded_doc_history.append(r) | |
conv.append({"role": "system", "content": f"{prompt}"}) | |
final_response = chat_gpt_nofn(prompt=f"Soru: {question}", history=conv) | |
# final_response = chat_gpt_nofn(prompt=prompt, history=conv) | |
partial_response = "" | |
print("Answer:--- %s seconds ---" % (time.time() - start_time)) | |
for chunk in final_response: | |
try: | |
if chunk.choices[0].delta.content is not None: | |
partial_response += chunk.choices[0].delta.content | |
yield partial_response, history + [(question, partial_response)], graded_doc_history | |
except: | |
pass | |
response = partial_response | |
# conv.append({"role": "user", "content": prompt}) | |
conv.append({"role": "assistant", "content": response}) | |
history.append((question, response)) | |
print("Answer:--- %s seconds ---" % (time.time() - start_time)) | |
# Store the last interaction without saving to the database yet | |
last_interaction.value = { | |
"question": question, | |
"response": response, | |
"manual": manual, | |
"point_id": uuid.uuid4().hex | |
} | |
yield response, history, graded_doc_history | |
def save_last_interaction(feedback): | |
if last_interaction.value: | |
DatabaseOperations.save_user_history_demo( | |
qclient, | |
"USER_COLLECTION_EMBED3_v3", | |
last_interaction.value["question"], | |
last_interaction.value["response"], | |
dense_embedding_model, | |
last_interaction.value["point_id"], | |
last_interaction.value["manual"], | |
feedback | |
) | |
last_interaction.value = None | |
manual_list = ["Toyota_Corolla_2024_TR", "Renault_Clio_2024_TR", "Fiat_Egea_2024_TR"] | |
with gr.Blocks() as demo: | |
chatbot = gr.Chatbot(height=600) | |
manual = gr.Dropdown(label="Kullanım Kılavuzları", value="Toyota_Corolla_2024_TR", choices=manual_list) | |
textbox = gr.Textbox() | |
graded_doc_history = gr.State([]) | |
clear = gr.ClearButton(components=[textbox, chatbot, graded_doc_history], value='Clear console') | |
def handle_like(data: gr.LikeData): | |
liked_state.value = data.liked | |
if liked_state.value is not None: | |
feedback = "LIKE" if liked_state.value else "DISLIKE" | |
save_last_interaction(feedback) | |
# def gradio_chat(question, manual, history, graded_doc_history): | |
# save_last_interaction("N/A") # Save previous interaction before starting a new one | |
# chat_generator = chat(question, manual, history, liked_state.value, graded_doc_history) | |
# final_response = "" | |
# final_history = history | |
# for partial_response, updated_history, doc_history in chat_generator: | |
# final_response += partial_response | |
# final_history = updated_history | |
# yield "", final_history | |
# return "", final_history | |
def gradio_chat(question, manual, history, graded_doc_history): | |
save_last_interaction("N/A") | |
chat_generator = chat(question, manual, history, liked_state.value, graded_doc_history) | |
final_history = history | |
final_doc_history = graded_doc_history or [] | |
for partial_response, updated_history, doc_history in chat_generator: | |
final_history = updated_history | |
final_doc_history = doc_history | |
yield "", final_history, final_doc_history | |
return "", final_history, final_doc_history | |
textbox.submit(gradio_chat, | |
[textbox, manual, chatbot, graded_doc_history], | |
[textbox, chatbot, graded_doc_history]) | |
chatbot.like(handle_like, None, None) | |
demo.queue() | |
demo.launch() | |