LangChain+Chromaを使ってLangChainの専門家を作ってみる

# はじめに
csvに対する処理を自然言語で実装してみたい
↓
そのためには、多様な命令に対して必要な処理をモデル自身に考えさせる必要がありそう？
↓
モデル自身に考えさせる技術について調べるにはAuto-GPTのソースコードを読み解く必要がありそう？
↓
LangChainのUseCasesにもある方法を使ってソースコードを読み込んでみよう

こんな感じの思いつきでとりあえずやってみました。

# 環境
Python 3.11.3
mac os 13.2.1（intel）


# 作成手順
基本的にはLangChainのUseCasesを元に処理をまとめただけです。

## 1.仮想環境構築

    python3 -m venv .venv
    . .venv/bin/activate

## 2.ライブラリインストール

    pip install openai chromadb langchain tiktoken

## 3.ディレクトリ準備
    
    mkdir source_code　#ソースコードの保存先
    mkdir vectors #ソースコードのベクトルデータの保存先

## 4.git clone

    cd source_code
    git clone https://github.com/hwchase17/langchain.git
    cd ../

## 5.API_KEYの設定

```
touch .env
```

```.env
OPENAI_API_KEY={YOUR_OPENAI_API_KEY}
```

## 6.embedding用クラスの作成
```
touch embedding.py
```   

```embedding.py
import os

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import CharacterTextSplitter
from langchain.document_loaders import TextLoader


class Embedding:

    def __init__(self, root_dir, persist_directory) -> None:
        self.root_dir = root_dir
        self.docs = []
        self.texts = []
        self.persist_directory = persist_directory
        self.embedding = OpenAIEmbeddings()
        
    def load(self):
        for dirpath, dirnames, filenames in os.walk(self.root_dir):
            for file in filenames:
                try:
                    loader = TextLoader(os.path.join(dirpath, file), encoding='utf-8')
                    self.docs.extend(loader.load_and_split())
                except Exception as e:
                    pass
    
    def split(self):
        text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
        self.texts = text_splitter.split_documents(self.docs)

    def init_vector(self) -> Chroma:
        self.load()
        self.split()
        # ベクトルデータの初期化
        chroma_index = Chroma.from_documents(self.docs, embedding=self.embedding, persist_directory=self.persist_directory)
        # ベクトルデータをディレクトリに保存
        chroma_index.persist()
        return chroma_index
```

## 7.実行ファイルの作成

```main.py
import os
import pdb
from dotenv import load_dotenv

import openai

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationalRetrievalChain

from embedding import Embedding

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
root_dir = f'{os.getcwd()}/source_code/langchain/langchain'
persist_directory = f"{os.getcwd()}/vectors/langchain"


def main():
    # 対象ソースコードのベクトルデータがあれば既存データをそのまま使う
    if not os.path.exists(persist_directory):
        embeddings = Embedding(root_dir=root_dir, persist_directory=persist_directory)
        vectordb = embeddings.init_vector()
    else:
        vectordb = Chroma(persist_directory=persist_directory, embedding_function=OpenAIEmbeddings())
    
    # retrieverの設定
    retriever = vectordb.as_retriever()
    retriever.search_kwargs["distance_metric"] = "cos"
    retriever.search_kwargs["fetch_k"] = 100
    retriever.search_kwargs["maximal_marginal_relevance"] = True
    retriever.search_kwargs["k"] = 7

    model = ChatOpenAI(model_name="gpt-3.5-turbo")
    qa = ConversationalRetrievalChain.from_llm(model, retriever=retriever)

    chat_history = []
    print("Chat Start!!")
    while True:
        query = input()
        if query == "exit":
            print("Exit!!")
            break
        
        result = qa({"question": query, "chat_history": chat_history})
        chat_history.append((query, result['answer']))
        print(f"**Answer**: {result['answer']} \n")

if __name__ == "__main__":
    main()
```

## 8.実行

入力
```
$python main.py
```

出力
```
Using embedded DuckDB with persistence: data will be stored in: {YOUR_ENVIRONMENT}/oss_reading_project/vectors/langchain
Chat Start!!
# 文章をここに入力
```

# 改善案

- トークン上限への対策
現時点だと質問文を工夫しないと、トークン上限エラーが頻発する
 [参考になりそうな動画](https://www.youtube.com/watch?v=f9_BWhCI4Zo&feature=youtu.be)

- ソースコード取得方法を修正
[git cloneの関数化に使えそう](https://python.langchain.com/en/latest/modules/chains/index_examples/vector_db_text_generation.html)
[ドキュメントの要約](https://book.st-hakky.com/docs/langchain-long-document/)

# まとめ
自分で作ってみて初めてわかったのだが、トークン管理が思ったよりシビアだった。

# 参考
- [LangChain Code Understanding](https://python.langchain.com/en/latest/use_cases/code.html)
- [Haccy LangChain の インデックスについて](https://book.st-hakky.com/docs/indexes-of-langchain/)