第5回　StreamlitとLangChainとLM studioを使って自分専用LLMウェブアプリを作りたい！

# 第5回
今回は、資料を元に回答するwebアプリを作成しました。
参考URL:https://blog.streamlit.io/langchain-tutorial-4-build-an-ask-the-doc-app/
GitHub:https://github.com/s1932072/LangChain-tutorial-4

```Python:main
import streamlit as st
from langchain.llms import OpenAI
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.embeddings import HuggingFaceEmbeddings
import os

os.environ["TOKENIZERS_PARALLELISM"] = "false"

class CustomHuggingFaceEmbeddings(HuggingFaceEmbeddings):
    def embed_documents(self, documents):
        texts = [doc.text.replace("\n", " ") if hasattr(doc, 'text') else str(doc) for doc in documents]
        return super().embed_documents(texts)

def generate_embeddings(texts, llm):
    return [llm.get_embedding(text) for text in texts]

def generate_response(uploaded_file, query_text):
    if uploaded_file is not None:
        documents = [uploaded_file.read().decode()]
        text_splitter = CharacterTextSplitter()
        texts = text_splitter.create_documents(documents)
        embeddings = CustomHuggingFaceEmbeddings(model_name="oshizo/sbert-jsnli-luke-japanese-base-lite")
        llm = OpenAI(base_url="http://localhost:1234/v1", api_key="not-needed")
        db = Chroma.from_documents(texts, embeddings)
        retriever = db.as_retriever(search_kwargs={"k": 1})
        qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=retriever)
        responses = []
        for text in texts:
            responses.append(qa.run(text))
        return qa.run(query_text)

st.set_page_config(page_title='🦜🔗 Ask the Doc App')
st.title('🦜🔗 Ask the Doc App')

uploaded_file = st.file_uploader('Upload an article', type='txt')
query_text = st.text_input('Enter your question:', placeholder='Please provide a short summary.', disabled=not uploaded_file)

result = []
with st.form('myform', clear_on_submit=True):
    submitted = st.form_submit_button('Submit', disabled=not (uploaded_file and query_text))
    if submitted:
        with st.spinner('Calculating...'):
            response = generate_response(uploaded_file, query_text)
            result.append(response)

if len(result):
    st.info(result[0])

```

今回はある程度参考URLのプログラムから変更を加えました。

OpenAI APIからlocal modelへの変更。
EmbeddingにHuggingFaceEmbeddingを使用。

以下が今回使用したプログラムについてChatGPT4による説明です。



このプログラムは、Streamlitを用いて作成されたウェブアプリケーションで、「Ask the Doc App」という名前がつけられています。このアプリは、アップロードされた文書から質問に対する回答を探す機能を提供します。プログラムは複数の部分に分かれており、それぞれの部分について説明します。

### 1. ライブラリのインポート

```python
import streamlit as st
from langchain.llms import OpenAI
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.embeddings import HuggingFaceEmbeddings
import os
```

- `streamlit` (別名 `st`) は、ウェブアプリケーションを簡単に作成するためのPythonライブラリです。
- `langchain` は、自然言語処理を行うためのライブラリで、このコードではOpenAIのAPIを使用するためのクラスやテキスト分割、ベクトルストア、質問応答チェーンなどが含まれています。
- `os` ライブラリは環境変数を設定するために使用されています。

### 2. 環境変数の設定

```python
os.environ["TOKENIZERS_PARALLELISM"] = "false"
```

- この行は、Hugging Faceのトークナイザーが並列処理を行わないようにするための設定です。これは、マルチプロセッシング環境でのデッドロックを避けるために必要です。

### 3. カスタム HuggingFaceEmbeddings クラス

```python
class CustomHuggingFaceEmbeddings(HuggingFaceEmbeddings):
    def embed_documents(self, documents):
        texts = [doc.text.replace("\n", " ") if hasattr(doc, 'text') else str(doc) for doc in documents]
        return super().embed_documents(texts)
```

- `CustomHuggingFaceEmbeddings` は、`HuggingFaceEmbeddings` を拡張したカスタムクラスです。このクラスは、ドキュメントの埋め込みを生成する際に、改行をスペースに置き換えるカスタム動作を行います。

### 4. 埋め込み生成関数

```python
def generate_embeddings(texts, llm):
    return [llm.get_embedding(text) for text in texts]
```

- この関数は、与えられたテキストのリストに対して、指定されたローカルLLM（LangChain Large Language Model）を使用して埋め込みを生成します。

### 5. レスポンス生成関数

```python
def generate_response(uploaded_file, query_text):
    # ...
```

- この関数はアップロードされたファイルから文書を読み込み、その文書をチャンクに分割してから、それぞれのチャンクに対して質問応答（QA）処理を行います。結果は `responses` リストに追加され、最後に `qa.run(query_text)` で得られた結果が返されます。

### 6. Streamlitウェブアプリのセットアップ

```python
st.set_page_config(page_title='🦜🔗 Ask the Doc App')
st.title('🦜🔗 Ask the Doc App')
# ...
```

- Streamlitを使用してウェブアプリのUIを設定しています。ページのタイトルやアップロードフォーム、質問入力欄などが設定されています。

### 7. ファイルアップロードと質問入力

```python
uploaded

_file = st.file_uploader('Upload an article', type='txt')
query_text = st.text_input('Enter your question:', placeholder='Please provide a short summary.', disabled=not uploaded_file)
```

- ユーザーがテキストファイルをアップロードし、質問を入力できるようにするためのUI要素です。

### 8. レスポンスの処理

```python
result = []
with st.form('myform', clear_on_submit=True):
    # ...
    if submitted:
        with st.spinner('Calculating...'):
            response = generate_response(uploaded_file, query_text)
            result.append(response)
if len(result):
    st.info(result[0])
```

- ユーザーが「Submit」ボタンを押すと、選択されたファイルと入力された質問を基にレスポンスを生成し、画面上に表示します。

![スクリーンショット 2024-01-01 22.02.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3631067/cb75d6fd-7558-4dc2-1a32-82881ca4a9a5.png)



# 感想
このプログラムで、参考資料を元にした回答ができるようになりました。
しかし、１つの質問に対して時間がかかります。さらに、全く同じ質問をしても回答がブレます。これに関しては複数回出力を行わせて、よく出現する文章を最終結果とすれば良いか....
また、与えるテキストの量が多いとエラーを吐きます。これは私が使用しているLocal modelが一度に入力できるtolken数に限りがあるからです。この問題の解決にも取り組もうと思います。これができれば私のやりたいネット記事の要約もできそうです！

# 次回
この調子でStreamlitの記事を実装して遊んでいきます
それではまた！