こんにちは。注文を取る時にタブレットだけだと味気ないですね。LLMを使いましょう。
今回はローカルLLM(ちょっとなぜかGPT3.5のAPIのクレカが通らなくて。。。)+ RAG + Gradioを使いました。
ローカルLLMはChatGPTのようなものを手元で実現できます。ChatGPTのシステムを使うには都度課金が必要ですから大変ですよね。
RAGはLLMにメニューを覚え込ませます。AIはお店のメニューを知りませんから、メニューを渡してみてもらう必要があります。また、品切れ情報だったりと更新される情報もあるので、それらも確認してもらう必要がありますね。
Gradioは簡単にウェブアプリが作れます。スマホから注文をしてもらうのに、Pythonのコードを叩いてもらうわけには行きませんので。
LLMはMistral7Bを使いました。ちょっと日本語変ですが、いいでしょう。
from transformers import AutoTokenizer, pipeline
model_id = "mistralai/Mistral-7B-Instruct-v0.2"
tokenizer = AutoTokenizer.from_pretrained(model_id)
pipe = pipeline("text-generation", model=model_id, tokenizer=tokenizer, device=0, max_new_tokens=300)
そして、今回のRAGですが、DBはCSVから取り込みました。どうしても日本語が取り込めなくて、結局pandas経由で入れました。
from langchain_community.document_loaders import DataFrameLoader
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings.huggingface import HuggingFaceEmbeddings
from langchain_text_splitters import CharacterTextSplitter
import pandas as pd
df = pd.read_csv("list5.csv")
loader = DataFrameLoader(df, page_content_column="name")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)
embeddings = HuggingFaceEmbeddings(
model_name="intfloat/multilingual-e5-large"
)
db = FAISS.from_documents(docs, embeddings)
retriever = db.as_retriever()
print(db.index.ntotal)
今回はlist5.csvというファイルにメニューが書いてあります。おすすめや値段、料理の説明などが書いてあります。プロンプトはちょっとくどい感じですが書いてみました。
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain.llms import HuggingFacePipeline
llm = HuggingFacePipeline(pipeline=pipe)
template = """ここはタイ料理レストラン「ブルーキャット」で、あなたは店員です。お客さんのオーダーに答えていくつか注文をとってもらえますか?注文以外の会話は次のコンテクストを参照しなくていいです。日本語で答えてください。お店にある料理は次のコンテクストの料理です。:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
query = "トムヤムクンはありますか?"
answer = chain.invoke(query)
if "Answer:" in answer:
answer = answer.split("Answer:")[1]
if "Question:" in answer:
answer = answer.split("Question:")[0]
print(answer)
出力が安定しないので、不要な文字を削除したかったですが完璧とは行きませんでした。今後調整が必要ですね。
次にGradioです。今回は出力から不要な文字を削除する分岐を入れました。
import gradio as gr
import os
def add_text(history, text):
history = history + [(text, None)]
return history, gr.Textbox(value="", interactive=False)
def bot(history):
query = history[-1][0]
response = chain.invoke(query)
if "Answer:" in response:
response = response.split("Answer:")[1]
if "Question:" in response:
response = response.split("Question:")[0]
history[-1][1] = ""
for character in response:
history[-1][1] += character
yield history
with gr.Blocks() as demo:
chatbot = gr.Chatbot([])
with gr.Row():
txt = gr.Textbox(
scale=4,
show_label = False,
container = False
)
clear = gr.Button("Clear")
txt_msg = txt.submit(add_text, [chatbot, txt], [chatbot, txt], queue = False).then(bot, chatbot, chatbot)
txt_msg.then(lambda: gr.Textbox(interactive = True), None, [txt], queue = False)
clear.click(lambda: None, None, chatbot, queue=False)
demo.queue()
demo.launch(share=True)
結果、、、
いい感じの応答システムができました。メニューの内容はきちんと覚えていて説明してくれました。実際にレストランとかで使ってみたいですね。売り上げは上がるんでしょうか。