LoginSignup
6
4

LangChainゆる勉強会(1)

Last updated at Posted at 2024-02-11

はじめに

LangChainの安定版V0.1.0がリリースされたのを記念して、
LangChainゆる勉強会を開催します。

LangChainとは?

LangChain is a framework for developing applications powered by language models. It enables applications that:

Are context-aware: connect a language model to sources of context (prompt instructions, few shot examples, content to ground its response in, etc.)
Reason: rely on a language model to reason (about how to answer based on provided context, what actions to take, etc.)

LangChainは言語モデルを利用したアプリケーション開発のためのフレームワークです。
以下のようなアプリケーションを可能にします:

コンテキストを認識する:言語モデルをコンテキストのソース(プロンプトの指示、いくつかの例、応答の根拠となるコンテンツなど)に接続する。
推論する:言語モデルに依存して推論する(提供されたコンテキストに基づいてどのように答えるか、どのようなアクションを取るか、など)。

LangChainに用意されている6つのモジュール

  • Model I/O 言語モデルを扱いやすくする

    • プロンプト準備、言語モデルの呼び出し、結果の受け取りの3つのステップを簡単に実装するための機能
  • Retrieval 未知のデータを扱えるようにする

    • LLMは未知のデータ(学習していないデータ)を扱えません
    • この問題に対応するのがRetrieval-Augmented Generation(RAG)です
  • Memory 過去の対話を短期・長期で記憶する

    • 前の文脈を踏まえた対話形式で言語モデルに回答させるには、それまでの会話をすべて送信する必要がある
    • 会話履歴の保存と、読み込みを簡単にする機能を提供
  • Chains 複数の処理をまとめる

    • LangChainでは多数のモジュールや別の機能を組み合わせつつ1つのアプリケーションを作成
    • このモジュールは組み合わせを簡単にする機能を提供します
  • Agents 自律的に外部と干渉して言語モデルの限界を超える

    • ReActやOpenAI Function Callingという手法を用い、言語モデルでは対応できないタスクをおこなう
  • Callbacks さまざまなイベント発生時に処理を行う

    • ログ出力や外部ライブラリとの連携

参考書

以下の書籍を参考にしています。

LangChain完全入門 生成AIアプリケーション開発がはかどる大規模言語モデルの操り方
Screenshot 2024-01-11 at 9.44.06.png

書籍のソースコードはここにあります。
https://github.com/harukaxq/langchain-book

今回の環境の特徴

多くの場合、LangChainから接続するLLMは、OpenAI社のAPIを使うと思います。(そのような記事が圧倒的に多いです)
しかし、今回は、私の環境の都合でAmazon Bedrockを利用します。

なお、以下の書籍ではLangChainのバージョンは、0.0.261を利用していますが、せっかくなので、最新安定版の0.1.0を利用します。

環境のセットアップ

今回は以下の環境を使っています。

なお、poetry、Pythonともにasdfからインストールできます。

ここでは、asdfがインストールされている前提で、Python、Poetryのインストールから説明します。

Pythonインストール(asdfから)

基本的には以下のサイトの手順でOKです。
https://dev.classmethod.jp/articles/asdf-python-introduction/

# Python のプラグインとリポジトリを確認します
% asdf plugin list all | grep python
(出力↓)
python                       *https://github.com/danhper/asdf-python.git

# プラグインをインストールします。
% asdf plugin add python https://github.com/danhper/asdf-python.git

# プラグインがインストールできたか確認します。
% asdf plugin list
(出力↓)
python

# インストール可能なバージョン一覧を確認
% asdf list all python
(出力↓)
ズラズラと出てきます。

# 今回はPython3.11.6をローカルにインストールします。
% asdf install python 3.11.6
% asdf local python 3.11.6
% python --version
(出力↓)
Python 3.11.6

poetryインストール(asdfから)

poetryのインストールもPythonと基本的な手順は一緒です。


% asdf plugin-add poetry
% asdf list-all poetry
(出力↓)
(中略)
1.7.1
% asdf install poetry 1.7.1
% asdf local poetry 1.7.1
% poetry --version
(出力↓)
Poetry (version 1.7.1)

poetryのプロジェクト初期化


# poetry初期化(基本的に全部デフォルトで良い)
% poetry init

# 仮想環境の使用
% poetry config virtualenvs.in-project true --local

# pythonバージョン確認
% poetry run python --version
% poetry env list

LangChainのインストール

以下のようにしてv0.1.0を指定します。

poetry add langchain==0.1.0

この結果は、以下のようにpyproject.tomlに書き込まれます。

pyproject.toml
[tool.poetry]
name = "impress-book-v0-1-0"
version = "0.1.0"
description = ""
authors = ["Akira Abe"]
readme = "README.md"
packages = [{include = "impress_book_v0"}]

[tool.poetry.dependencies]
python = "^3.11"
langchain = "0.1.0"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

環境変数などの定義

.envを作成しAWSのアクセスキーなどを登録します。

.env
AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=YYYYYYYYYYYYYYYYYYYY
AWS_DEFAULT_REGION=us-east-1
MODEL_ID=anthropic.claude-v2

警告
.envはgitにコミットしないでください!

Model I/Oのサンプル実装

このタイミングで他に必要なライブラリをインストールします。

poetry add python-dotenv==1.0.0
poetry add boto3==1.34.16
poetry add langchain-community==0.0.11

まずは非常に単純な、LLMを呼び出すケース

language_models.py
import os
from dotenv import load_dotenv
from langchain_community.chat_models import BedrockChat
from langchain_core.messages import HumanMessage
import langchain
langchain.verbose = True

load_dotenv()

chat = BedrockChat(
    model_id=os.getenv("MODEL_ID"),
    streaming=True,
    model_kwargs={"temperature": 0.5, "max_tokens_to_sample" : 2048}
  )

result = chat([HumanMessage(content="Hello, how are you?")])

print(result.content)

プロンプトテンプレートを使ったケース

prompt_and_language_model.py
import os
from dotenv import load_dotenv
from langchain_community.chat_models import BedrockChat
from langchain_core.messages import HumanMessage
from langchain_core.prompts import PromptTemplate
import langchain
langchain.verbose = True

load_dotenv()

# BedrockChatを初期化する
chat = BedrockChat(
    model_id=os.getenv("MODEL_ID"),
    streaming=True,
    model_kwargs={"temperature": 0.5, "max_tokens_to_sample" : 2048}
  )

# PromptTemplateを使って、文章を生成する
prompt = PromptTemplate(
  template="{product}はどこの会社が開発した製品ですか?",
  input_variables=[
    "product"
  ]
)

result = chat(
  [
    HumanMessage(content=prompt.format(product="iPhone")),
  ]
)

print(result.content)

OutputParserを用いてLLMからの出力をパースするケース

pydantic_output_parser.py
import os
from dotenv import load_dotenv

from langchain_community.chat_models import BedrockChat
# from langchain_core.output_parsers import PydanticOutputParser, OutputFixingParser
from langchain.output_parsers import PydanticOutputParser, OutputFixingParser
from langchain_core.messages import HumanMessage
from pydantic import BaseModel, Field, ValidationInfo, field_validator
import langchain
langchain.verbose = True

load_dotenv()

# BedrockChatを初期化する
chat = BedrockChat(
    model_id=os.getenv("MODEL_ID"),
    streaming=True,
    model_kwargs={"temperature": 0.5, "max_tokens_to_sample" : 2048}
  )

# Pydanticのモデルを定義する
class Smartphone(BaseModel): 
    release_date: str=  Field(description="スマートフォンの発売日") #← Fieldを使って説明を追加する
    screen_inches: float = Field(description="スマートフォンの画面サイズ(インチ)")
    os_installed: str = Field(description="スマートフォンにインストールされているOS")
    model_name: str = Field(description="スマートフォンのモデル名")

    @field_validator("screen_inches") #← validatorを使って値を検証する
    def validate_screen_inches(cls, field: int, info: ValidationInfo): #← validatorの引数には、検証するフィールドと値が渡される
        assert info.config is not None #← configがNoneでないことを確認する
        if field <= 0: #← screen_inchesが0以下の場合はエラーを返す
            raise ValueError("Screen inches must be a positive number")
        return field

# PydanticOutputParserを定義する
parser = OutputFixingParser.from_llm(
    parser = PydanticOutputParser(pydantic_object=Smartphone),
    llm=chat
) #← PydanticOutputParserをSmartPhoneモデルで初期化する 

result = chat([ #← Chat modelsにHumanMessageを渡して、文章を生成する
    HumanMessage(content="Androidで最近リリースしたスマートフォンを1個挙げて"),
    HumanMessage(content=parser.get_format_instructions())
])

parsed_result = parser.parse(result.content) #← PydanticOutputParserを使って、文章をパースする

print(f"モデル名: {parsed_result.model_name}")
print(f"画面サイズ: {parsed_result.screen_inches}")
print(f"OS: {parsed_result.os_installed}")
print(f"発売日: {parsed_result.release_date}")

Retrievalのサンプル実装

このタイミングでchainlitなどのライブラリをインストールします。

poetry add spacy==3.7.2
poetry add pymupdf==1.23.8
poetry add tiktoken==0.5.2
poetry add chromadb==0.4.22
poetry add chainlit==0.6.402

# spacyの日本語対応モデルをダウンロードする
poetry run python -m spacy download ja_core_news_sm

PDFの内容を元に回答するケース

chat_3.py
import os
from dotenv import load_dotenv

import chainlit as cl
from langchain_community.chat_models import BedrockChat
from langchain.document_loaders import PyMuPDFLoader
from langchain.embeddings import BedrockEmbeddings
from langchain_core.prompts import PromptTemplate
from langchain_core.messages import HumanMessage
from langchain.text_splitter import SpacyTextSplitter
from langchain_community.vectorstores import Chroma

import langchain
langchain.verbose = True
load_dotenv()

# Chat開始時に検索対象のPDFをアップロードするようにする。
embeddings = BedrockEmbeddings()

chat = BedrockChat(
    model_id=os.getenv("MODEL_ID"),
    streaming=True,
    model_kwargs={"temperature": 0.5, "max_tokens_to_sample" : 2048}
  )
prompt = PromptTemplate(template="""文章を元に質問に答えてください。 

文章: 
{document}

質問: {query}
""", input_variables=["document", "query"])

text_splitter = SpacyTextSplitter(chunk_size=300, pipeline="ja_core_news_sm")

@cl.on_chat_start
async def on_chat_start():
    files = None #← ファイルが選択されているか確認する変数

    while files is None: #← ファイルが選択されるまで繰り返す
        files = await cl.AskFileMessage(
            max_size_mb=20,
            content="PDFを選択してください",
            accept=["application/pdf"],
            raise_on_timeout=False,
        ).send()
    file = files[0]

    if not os.path.exists("tmp"): #← tmpディレクトリが存在するか確認
        os.mkdir("tmp") #← 存在しなければ作成する
    with open(f"tmp/{file.name}", "wb") as f: #← PDFファイルを保存する
        f.write(file.content) #← ファイルの内容を書き込む

    documents = PyMuPDFLoader(f"tmp/{file.name}").load() #← 保存したPDFファイルを読み込む
    splitted_documents = text_splitter.split_documents(documents) #← ドキュメントを分割する

    database = Chroma( #← データベースを初期化する
        embedding_function=embeddings,
        # 今回はpersist_directoryを指定しないことでデータベースの永続化を行わない
    )

    database.add_documents(splitted_documents) #← ドキュメントをデータベースに追加する

    cl.user_session.set(  #← データベースをセッションに保存する
        "database",  #← セッションに保存する名前
        database  #← セッションに保存する値
    )

    await cl.Message(content=f"`{file.name}`の読み込みが完了しました。質問を入力してください。").send() #← 読み込み完了を通知する

@cl.on_message
async def on_message(input_message):
    print("入力されたメッセージ: " + input_message)
    database = cl.user_session.get("database") #← セッションからデータベースを取得する
    documents = database.similarity_search(input_message)
    documents_string = ""

    for document in documents:
        documents_string += f"""
    ---------------------------
    {document.page_content}
    """

    result = chat([
        HumanMessage(content=prompt.format(document=documents_string,
                                           query=input_message)) #← input_messageに変更
    ])
    await cl.Message(content=result.content).send()

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4