Büyük Dil Modellerini Kullanmanın Hızlı ve Etkili Yolu: Langchain 🦜🔗

Güldeniz Bektaş
TurkNet Technology
Published in
11 min readDec 7, 2023

--

Source: Langchain

ChatGPT uzun süredir hayatımızda, günlük işlerimize entegre olmayı başardı. Sırf bundan ötürü dahi oldukça başarılı olduğunu öne sürmek çok abartılı bir ifade şekli olmaz. Aldığımız hataları Google’a yazarak sayfalarca sonucu incelemek yerine ChatGPT’ye yazarak kendimize vakit kazandırıyoruz ya da her gün monoton bir şekilde yazdığımız mailleri ChatGPT ile yeniden şekillendirebiliyoruz. Neredeyse çoğu alanda günlük işlerimize yerleşmiş bu model nasıl bu kadar iyi olabiliyor? Biz GPT veya benzeri modelleri nasıl kullanabiliriz? Bu yazıda bunlara cevap vereceğiz. Hatta daha fazla ileri giderek bu modelleri kendi kullanım amacınız için detaylandırabileceğinize değindikten sonra bir uygulama ile tüm öğrenilenleri uygulamalı olarak göreceğiz 🤗

O zaman konunun temeline ufaktan bir giriş yapalım.

Nedir bu LLM?

LLM (Büyük Dil Modelleri, Large Language Models) çok fonksiyonlu yapay zeka dil modellerine verilen bir genel addır. Derin öğrenme metodolojisinin, metinleri anlamlandırma, sınıflandırma vb. konulardaki karşılığı olan LLMler klasik Doğal Dil İşleme (NLP) modellerine kıyasla çok daha büyük modeller, çok daha fazla sayıda parametreler kullanılarak eğitilmesi gereken bir yapay zeka modelidir. LLMler çok fonksiyonlu olması sayesinde tek bir büyük modelin birçok farklı metin işleme problemini çözebildiği bir yapı sağlar. GPT, BARD, LLaMa
günümüzde geliştirilen LLMlerin başında gelenlerdir.

Modele giren input büyüklüğü ve çeşidinin fazla olması modelin daha fazla bilgi öğrenmesini sağlar. LLMlerin en büyük avantajı budur. Eğitildikleri veri çoğu NLP modeline göre daha kapsamlı ve büyüktür. Bu sebeplerle alınan çıktılar daha detaylı ve anlamlı olur. Tabii, kullanıcı olarak bu modelleri kullanmak tatlı gelse de eğitildikleri verinin ve modelin büyüklüğü sebebi ile eğitim ve fine tune işlemleri maliyetli olabilir 😅

Bu kadar popüler olan modelleri kendi projeleriniz için kullanmak istemeniz çok doğal. Ve her birini kendi hedeflerimiz doğrultusunda eğip bükmek zor olabilir. Neyse ki open source topluluğu o kadar mükemmel ki tüm bu LLM modelleri tek bir yerden kullanabiliyoruz. Hem de tek yapmamız gereken pip install langchain.

Langchain mi?

Langchain LLMlerin özelleştirilip uygulama bazlı yapılarda kullanılabilecek hale getiren open-source bir framework. Python ve JavaScript/TypeScript kütüphaneleri bulunuyor.

GPT, LLaMa gibi büyük dil modellerini uygulamalarda kullanmak için özelleştirmemiz gerekiyor. Örnek vermek gerekirse, ChatGPT ile sohbet ederken arkada çalışan GPT modelinin nasıl yanıtlar üretmesi gerektiği OpenAI tarafından standart hale getirilmiş vaziyette. Diğer yandan bu kadar büyük bir modele kendi spesifik sorunuzu sormanız gerektiğinde, öğrendiği ihtimalleri sınırlandırmak için promptunuzu (yönlendirme) detaylar ile süslediğiniz çok oluyordur. Doğru konudaki, doğru noktaları bulabilmesi için. Veya bir diğer örnek, ChatGPTye kim olduğunu sorduğunuz takdirde size GPT tabanlı dil modeli olduğunu söyleyecektir çünkü bildiği şey budur. Tam da bunun gibi sebeplerden modelin kim olduğunu, nasıl yanıtlar vermesi gerektiğini; kısacası 5N1K’yı kendimiz tanımlamak isteyebiliriz. Farklı alternatifleri olsa da hem birçok farklı dil modelini desteklemesi, hem de sağladığı özelliklerin çeşitliliği bakımından
Langchain tam olarak işimizi gören bir framework. Öyle ki GPT gibi LLMlere istediğimiz kimliği yükleyerek, sonuçları buna göre alabiliriz.

Peki bu ‘chain’ ne anlama geliyor olabilir?

Lang + Chain🦜🔗

Langchain LLMleri birçok farklı amaca yönelik özelleştirme seçeneğini chain (zincir) yapısıyla oluşturabilmemizi sağlıyor. Langchain modülünde, bilgi çıkarımı (retrieval) konusu üzerinde farklı zincirler bulunuyor. Bir soru — cevap sohbeti için en uygun yapılardan bir tanesi Retrieval QA Chain yapısıdır. Burada bir bilgi çıkarımı yapılacak kaynağını dil modelinin veri kaynağı olarak kullanmasını sağlıyor.

Bunun dışında ise bir memory nesnesi yardımıyla konuşma geçmişini de LLM’e vererek girilen önceki metinlerde refere edilmiş kısımların anlaşılması konusunda veya daha genel şekliyle bir konuşmanın sürekliliğini sağlayacak şekilde çalışabiliyor. Retrieval QA Chain’in konuya uygun olmasının bir diğer sebebi de istediğimiz şekliyle düzenlenmiş bir promptu kullanmamıza olanak sağlıyor olması.

Zincir yapısı ile bir konuşmanın şeklini belirleyen adımları belirtebiliyoruz. Aynı zamanda kendi hedefimiz doğrultusunda LLMin bilgi haznesini de sınırlandırabiliyoruz. Örnekle, biraz sonra oluşturacağımız chatbot LLM ile ilgili konulara cevap verebiliyor olacak. Bunu sağlamanın bir yolu da var. Böylece modelin gelen sorular ile halüsinasyon görmesini engelleyerek, sadece bizim verdiğimiz bilgiye odaklanmasını sağlayabiliyoruz. Aksi takdirde, bu kadar büyük bir modeli tek bir konuya kısıtlamak chatbot performansını düşürecektir. Aynı zamanda elinizdeki
konuya dair yeterince veri ile eğitilmemiş olabilir.

Bu da bizi bir diğer konumuza getiriyor 😎

Knowledge Base 📚

Knowledge base bir LLM özel bir amaca uyarlanırken kullanmasını istediğimiz bağlamı oluşturan veridir. LLM ile oluşturduğumuz uygulama, Langchain’in de bize sağladığı bilgi çıkarıcılar (retrievals) yardımıyla ilgili veriden beslenerek çalışabiliyor. Bu sayede model, istemediğimiz bir açık kaynaktan getirebileceği bilgi kirliliği oluşturabilecek veriyi kullanmıyor ve yanlış bir bilgi oluşmasının önüne geçebiliyoruz.

Knowledge base’i PDF veya metin belgelerinden aktarabileceğiniz gibi, bir web sitesini crawl ederek de oluşturabilirsiniz. Langchain bu bilgi haznesini oluşturabilmeniz için size birçok seçenek sağlıyor. Peki bu verileri direkt modele mi veriyoruz? Tabii ki de hayır. Bildiğiniz gibi bu modeller sadece sayıdan anlıyor. Verimizi anlayıp işleyebilmek için ufak bir ön işlemeden sonra, bu verileri veri tiplerine uygun olarak depolayabileceğimiz bir veritabanına atmamız gerekiyor. Ve böylece şimdi knowledge basein işlenmesi ve bir LLMde kullanılabilir hale getirilebilmesi için gerekli olan Embedding ve Vektör Veritabanı kavramlarına geçiyoruz.

Embedding & Vektör Veritabanı

Embedding yapısı genel olarak yeniden ifade etme anlamıyla kullanılır. Metin işlemede ise yazılı metinleri makinenin anlamlandırabileceği bir şekilde sayılara çevirme gibi çok temel ve kritik bir işin genel adıdır. Sayılara çevrilen metnin vektörize hale getirip tutulmasıyla embeddingler oluşturuluyor. Bu sayede elimizdeki metni sayısal bir değer şeklinde tutabiliyoruz ve metinlerin birbiri arasındaki ilişkiyi ölçebilir duruma geliyoruz. Kullanıcı tarafından sorulan sorular da aynı şekilde embedding’e çevriliyor ve knowledge base’imizdeki verilerle arasındaki benzerlikler bu sayede kurulabiliyor.

Langchain’in eklentisiyle yalnızca OpenAI API anahtarı kullanarak bir metni OpenAI embeddinglerine çevirebiliyoruz. Bunun nedeni gelen metni GPT’nin öğrendiği kelime vektörlerine uygun bir şekilde çevirmek. Bu embeddingler de vektör dblere yazılarak modelin knowledge base’ini oluşturuyor.

Vektör veritabanları embeddingleri depolayabileceğimiz, metin embeddingleri için ideal bir veritabanı mimarisidir. Knowledge base’i oluşturan veriyi bu yapıda tutuyoruz. Vektör yapısı kendi içinde her bir kaydı bir vektör şeklinde tutuyor. Günümüzdeki vektör veritabanları akıllı indexlemelerle ve hızlı algoritmalarla yüz binlerce vektör içeren veritabanlarına milisaniyeler mertebesinde sorgulayabiliyor. Son yıllarda LLMlerin yaygınlaşmasıyla farklı vektör veritabanları ortaya çıktı. Bu veritabanlarına örnek olarak milvus, qdrant, faiss verilebilir. En yaygın
kullanılan vektör veritabanlarına Langchain kütüphaneleri destek sağlıyor. Böylece hem veriler bu vektör veritabanına yüklenirken hem de model çalıştırıldığında Langchain’in ilgili modülleri kullanılabiliyor.

Ne kadar kolay değil mi? Langchain büyük dil modellerini rahatça kullanabilmemiz için çoğu toolu sağlıyor. Artık modeli kendi belirlediğimiz bilgi ile yüklediğimize göre, geriye yapmamız gereken önemli bir madde daha kalıyor. Modeli istediğimiz kalıba sokmak. Yukarıda bahsettiğimiz gibi modelin bazı özelliklerine karışmamız gereken durumlar olacaktır. Hangi kişiliğe bürünmesini istediğimiz, cevapların sonuna mutlaka eklemesini istediğimiz cümleler ya da en önemlisi cevabı knowledge basede bulamadığı zamanlarda uydurmak yerine ne yapması gerektiği gibi direktifleri prompt template ile belirtmemiz gerekiyor.

Prompt Template ⌨✍🏽

Bir büyük dil modelini özel bir amaçla kullanmak istediğimizde, modele nasıl davranması gerektiği hakkında bir direktifler listesi vermemiz gerekiyor. Burada Prompt Template devreye giriyor. Prompt Template yardımıyla kullandığımız dil modeline takip etmesi gereken kuralları tarif ediyoruz.

Prompt Template düz bir kurallar listesi şeklinde verilebildiği gibi bir soru — cevap konuşma yapısı kurmak istediğimizde ihtiyacımız olan memory yapısını modele Prompt Template ile verebiliyoruz. Bunun yanında input variable olarak içeriği (context) ve kullanıcının sorduğu soruyu prompt template’e veriyoruz.

Memory derken peki?

Langchain Memory 🧠

Memory yapıları bir konuşma için kullanılması gereken yapıların başında geliyor. Bir konuşma sırasındaki kullanıcıdan gelen ve modelden gelen cevapları tutarak, bir sonraki soru sorulduğunda cevap verilirken önceki soruların ve cevapların dikkate alınmasını sağlıyor. Soru ve cevapların nasıl tutulacağı ve modele tekrar verileceği kısmında özelleştirmeler de
bulunmakla birlikte yapının temel hali işimizi oldukça görüyor.

Aynı ChatGPT ile bir soru üzerinden uzun süre konuşmamız gibi. Verdiğimiz bilgileri ve soruları tutarak kendini yenileyen ve aynı zamanda tekrar eden bir konuşma sürdürebiliyoruz. Sürekli soruları baştan yazdığınızı düşünsenize? 😔

Teorinin sonuna gelelim artık, ne dersiniz? 😉

LLaMa ve Streamlit ile Chatbot

Langchain ve LLM bilgilerimizi tazelediğimize göre bunu ufak bir uygulama ile pekiştirebiliriz. Şimdiye kadar Langchain’in genel yapısı, kendi problemimiz için kullanacağımız modeli nasıl manipüle edebileceğimiz gibi, önemli ve kendi projenizi geliştirebilmeniz için yeterli alt yapıyı sağlayacak bilgilerden bahsettik. Tek tek göz korkutucu gelebilirler ama merak etmeyin bir uygulama ile tüm bu korkuları yok edeceğiz.

Konumuz Large Language Models olduğuna göre uygulamamızı LLM’ler hakkında sorulara cevap verecek bir şekilde hazırlayabiliriz. Kendi oluşturacağımız knowledge base ile LLaMa modelini sadece LLM ile ilgili sorulara yanıt verebilecek hale getireceğiz. Tüm adımları madde madde açıklıyor olacağım ☺️

Adım 1 — Vektör Veritabanı Oluşturmak

By Author

Kullanacağımız modelin LLM hakkında sorulara cevap verebilmesini istiyoruz. Bu yüzden ona bu konu hakkındaki temel bilgileri sağlamamız gerekiyor. Bunu istediğiniz veri türünde yapabilirsiniz: ister websitesini crawl edersiniz, ister kendiniz metin olarak verebilirsiniz. Langchain’in en güzel yanlarından biri istediğiniz yönteme göre bir metodunun bulunması. Bizim durumumuzda Langchain’den istediğimiz şey göndereceğimiz PDF’leri parse etmesi. Öncelikle arxiv kütüphanesi yardımıyla makaleleri indireceğiz. Daha sonra bu makaleleri chunklara (yığın) ayıracağız ve modelimize uygun şekilde embeddinglerini çıkardıktan sonra Faiss’e yükleyeceğiz. İşte bu kadar basit, şimdi kod üzerinde gösterelim.

Verileri indirmek için bu kodu çalıştırmamız yeterli.

import arxiv
# papers about LLMs
paper_ids = [‘2308.10620’, ‘2307.06435’, ‘2303.18223’, ‘2307.10700’, ‘2310.11207’, ‘2305.11828’]
for paper_id in paper_ids:
search = arxiv.Search(id_list=[paper_id])
paper = next(search.results())
print(paper.title)
paper.download_pdf(“data/”)

paper_ids bizim indirmek istediğimiz makalelerin Arxiv idlerini içeriyor. Konu LLM olduğu için tüm makaleler LLM literatür taraması üzerine. Daha fazlasını eklemek isterseniz ya da bambaşka bir konu ile ilgili bir chatbot geliştirmek isterseniz makale idlerini ekleyebilir veya değiştirebilirsiniz.

Makalelerimiz data klasörü altına indirilmiş olacak. Tüm makalelerin pdf hallerini orada bulabilirsiniz. Şimdi sırada bu makaleleri Faiss’e yüklemek için gerçekleştirmemiz gereken işlemler var. Aşağıdaki kodda veriyi PyPDFLoader ile okuduktan sonra bu metinleri 500 karakterli chunklara ayırıyoruz. Hemen sonrasında ise bu chunkları embedding vektörlerine
çevirebilmek için HuggingFace’in sentence-transformer’ını kullanıyoruz. Bu metodlar GPU isteyebileceği için device’ı CPU olarak ayarlamayı unutmayın.

from langchain.document_loaders import DirectoryLoader, TextLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS
# define what documents to load
loader = DirectoryLoader(path=’data’, glob=”*.pdf”, loader_cls=PyPDFLoader)
# interpret information in the documents
documents = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=500,
chunk_overlap=50)
texts = splitter.split_documents(documents)
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2",
model_kwargs={‘device’: ‘cpu’})
# create the vector store database
db = FAISS.from_documents(texts, embeddings)
# save the vector store
db.save_local(“faiss”)

Adım 2 — Soru Cevap Zinciri Yaratmak

Yazı boyunca chatbotumuz için gerekli bilgi sistemini yaratmamı gerektiğinden ve bunun öneminden bahsettik. Gerekli bilgileri ArXiv aracılığı ile çekip, lokal depolama sistemine depoladıktan sonra bu bilgiyi kullanarak soruları cevaplayacak yapıyı kurabiliriz. Buna geçmeden önce başka bir önemli noktayı daha eklememiz gerekiyor. Yine daha önce bahsettiğimiz gibi chatbotu istediğimiz kalıba sokmamız gerekiyor. Nasıl cevap vereceği, cevap verirken hangi dili kullanacağı, neye nasıl cevap vermesi gerektiği gibi bilgileri prompt template aracılığı ile belirleyeceğiz. Bu adımları tamamladıktan sonra işimizin çoğu bitmiş olacak 😎

By author

LLaMa open source bir model bu yüzden bu modeli indirmeniz gerekiyor. Modeli bu linkten indirebilirsiniz. Model 2.87 GB büyüklüğünde.

Bu modeli CTransformers metodu ile yükleyeceğiz çünkü bu transformer modeli C dili yapılmış.

from langchain.llms import CTransformers
def load_llm():
“””load the llm”””
llm = CTransformers(model=’models/llama-2–7b-chat.ggmlv3.q2_K.bin’,
# model available here: https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/tree/main
model_type=’llama’,
config={‘max_new_tokens’: 256, ‘temperature’: 0})
return llm

Kullanabilmek için vektörlerimizi de yüklememiz gerekiyor.

def load_vector_store(): 
# load the vector store
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2",
model_kwargs={‘device’: ‘cpu’})
db = FAISS.load_local(“faiss”, embeddings)
return db

Spesifik bir konumuz olduğu için chatbotumuza prompt template ekleyeceğiz. Bunu yapmak zorunda değilsiniz fakat bahsettiğim gibi chatbotumuzun odaklanmasını istediğimiz konu belirli bir alan olduğu için prompt template eklememiz en doğru karar olacaktır. Yine aynı şekilde modelin bizim belirlediğimiz konunun dışına çıkmasını ve cevap uydurmasını engellemek için, paperlar tarafından cevabı verilemeyen sorulara bilmiyorum şekline yanıt vermesi gerektiğini ekliyoruz.

from langchain import PromptTemplate
def create_prompt_template():
# prepare the template that provides instructions to the chatbot
template = “””Use the provided context to answer the user’s question.
If you don’t know the answer, respond with “I do not know”.
Context: {context}
History: {history}
Question: {question}
Answer: “””
prompt = PromptTemplate(
template=template,
input_variables=[‘context’, ‘question’])
return prompt

Şimdi soru cevap zincirini oluşturabiliriz.

from langchain.chains import RetrievalQA
def create_qa_chain():
“””create the qa chain”””
# load the llm, vector store, and the prompt
llm = load_llm()
db = load_vector_store()
prompt = create_prompt_template()
# create the qa_chain
retriever = db.as_retriever(search_kwargs={‘k’: 2})
qa_chain = RetrievalQA.from_chain_type(llm=llm,
chain_type=’stuff’,
retriever=retriever,
return_source_documents=True,
chain_type_kwargs={‘prompt’: prompt})
return qa_chain

Cevap döndürecek fonksiyonu da ekleyelim.

def generate_response(query, qa_chain):
# use the qa_chain to answer the given query
return qa_chain({‘query’:query})[‘result’]

İşte bu kadar 🤩

Adım 3 — Arayüz Hazırlamak

Chatbotumuzun cevap verebilmesi için gereken tüm şeyler hazırlandı. Şimdi sıra bunu kullanıcıya güzel bir web arayüzü ile sunmak. Böylece bu kodlar arasında kaybolmak zorunda kalmayacak kimse. Bu arayüzü yapmak için Streamlit kütüphanesini kullanacağız. Kolay ve temiz bir arayüz olacak. Hem de bu arayüzü oluşturmak Streamlit sayesinde çok kolay.

import time
import streamlit as st
from langchain.llms import CTransformers
from langchain import PromptTemplate
from langchain.chains import RetrievalQA
from langchain.memory import ConversationBufferMemory
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS
from langchain.document_loaders import PyPDFLoader, DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

st.write(“### Welcome to LLM Bot!”)
st.write(
“You can ask me anything about LLM’s! So excited to answer your questions :)”)

streaming=True

# Initialize chat history
if “messages” not in st.session_state:
st.session_state.messages = []
# Display chat messages from history on app rerun
for message in st.session_state.messages:
with st.chat_message(message[“role”]):
st.markdown(message[“content”])

@st.cache_resource()
def load_llm():
# load the llm with ctransformers
llm = CTransformers(model=’model/llama-2–7b-chat.ggmlv3.q2_K.bin’, # model available here: https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGML/trmodel_type=’llama’,
config={‘max_new_tokens’: 256, ‘temperature’: 0})
return llm

@st.cache_resource()
def load_vector_store():
# load the vector store
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2",
model_kwargs={‘device’: ‘cpu’})
db = FAISS.load_local(“faiss”, embeddings)
return db

@st.cache_data()
def load_prompt_template():
# prepare the template we will use when prompting the AI
template = “””Use the provided context to answer the user’s question.
If you don’t know the answer, respond with “I do not know.”.

Context: {context}
History: {history}
Question: {question}
Answer:
“””
prompt = PromptTemplate(template=template,
input_variables=[‘context’, ‘question’]
)
return prompt

def create_qa_chain():
# load the llm, vector store, and the prompt
llm = load_llm()
db = load_vector_store()
prompt = load_prompt_template()
# create the qa_chain
retriever = db.as_retriever(search_kwargs={'k': 2})
qa_chain = RetrievalQA.from_chain_type(llm=llm,
chain_type='stuff',
retriever=retriever,
return_source_documents=True,
chain_type_kwargs={'prompt': prompt,
"memory": ConversationBufferMemory(
memory_key="history",
input_key="question")})
return qa_chain

def generate_response(query, qa_chain):
# use the qa_chain to answer the given query
return qa_chain({'query':query})['result']

def get_user_input():
# get the user query
input_text = st.text_input('Ask me anything about Large Language Models!', "", key='input')
return input_text

# create the qa_chain
qa_chain = create_qa_chain()
prompt = st.chat_input("Type a message...")

if prompt:
with st.chat_message("user"):
st.markdown(prompt)
st.session_state.messages.append({"role": "user", "content": prompt})
if streaming:
res = generate_response(query=prompt, qa_chain=qa_chain)
with st.chat_message("Assistant"):
message_placeholder = st.empty()
full_response = ""
buffer = ""
for chunk in res.split():
full_response += chunk + " "
time.sleep(0.05)
# Add a blinking cursor to simulate typing
message_placeholder.markdown(full_response + "▌")
message_placeholder.markdown(full_response)
st.session_state.messages.append({"role": "assistant", "content": full_response})

else:
pass

Bu arayüz ile soruları girdikten sonra aynı ChatGPT’de olduğu gibi cevaplarınızı gerçek zamanlı yazılıyormuş gibi alabileceksiniz.

Çalıştırmak için bu komutu kullanmanız yeterli:


streamlit run app.py

Ve işte bu kadar 🙂 Chatbotumuz arayüz ile birlikte hazır. Tüm kodu bu Github reposunda bulabilirsiniz.

Nasıl kullanabileceğiniz de ayrıntılı bir şekilde README.md dosyasında
bulunuyor. Birkaç örnek görelim 😊

By author

LLM sorularınıza cevap verebilecek bir chatbotunuz var artık. Kendiniz geliştirmek için daha fazla makale, chat geçmişi özelliği ekleyebilir ve arayüz için farklı bir yöntem kullanabilirsiniz. Repo üzerinde kendi denemelerinizi yapmayı unutmayın 😉🏃🏽‍♂️

Daha fazla yazıda görüşmek üzereee 😎

Yazarlar:

Güldeniz Bektaş, AI Engineer — Linkedin
Ekrem Yıldız, AI Engineer — Linkedin

Github Repo

--

--