Open8

LangChain

philosophynotephilosophynote

https://python.langchain.com/docs/concepts/#agents

Agents
By themselves, language models can't take actions - they just output text. A big use case for LangChain is creating agents. Agents are systems that use an LLM as a reasoning engine to determine which actions to take and what the inputs to those actions should be. The results of those actions can then be fed back into the agent and it determine whether more actions are needed, or whether it is okay to finish.

LangGraph is an extension of LangChain specifically aimed at creating highly controllable and customizable agents. Please check out that documentation for a more in depth overview of agent concepts.

There is a legacy agent concept in LangChain that we are moving towards deprecating: AgentExecutor. AgentExecutor was essentially a runtime for agents. It was a great place to get started, however, it was not flexible enough as you started to have more customized agents. In order to solve that we built LangGraph to be this flexible, highly-controllable runtime.

If you are still using AgentExecutor, do not fear: we still have a guide on how to use AgentExecutor. It is recommended, however, that you start to transition to LangGraph. In order to assist in this, we have put together a transition guide on how to do so.

エージェント
言語モデルはそれ自身ではアクションを起こすことができません。
LangChainの大きなユースケースはエージェントの作成です。 エージェントは、LLMを推論エンジンとして使い、どのアクションを取るべきか、またそのアクションの入力はどうあるべきかを決定するシステムです。 LangGraphはLangChainの拡張で、特に高度に制御可能でカスタマイズ可能なエージェントを作ることを目的としています。 エージェントの概念のより深い概要については、そのドキュメントをご覧ください。 LangChainには、非推奨のレガシーなエージェント概念があります: AgentExecutorです。 AgentExecutor は基本的にエージェントのランタイムでした。 しかし、よりカスタマイズされたエージェントを持つようになると、柔軟性に欠けていました。 それを解決するために、私たちはこの柔軟で高度に制御可能なランタイムとしてLangGraphを開発しました。 もし、まだAgentExecutorをお使いであれば、恐れることはありません。 しかし、LangGraphへの移行を始めることをお勧めします。 そのために、移行ガイドを用意しました。

philosophynotephilosophynote

一般的なフローは次のようになります:モデルは、入力と以前の観察に対してどのようなステップを取るべきか「考えます」。 そして、モデルは利用可能なツールからアクションを選択します(または、ユーザーに応答することを選択します)。 モデルは、そのツールの引数を生成します。 エージェントのランタイム(エクゼキュータ)は、選択されたツールを解析し、生成された引数で呼び出します。 エクゼキュータは、ツールの呼び出し結果を観察としてモデルに返します。 このプロセスは、エージェントが応答を選択するまで繰り返されます。

philosophynotephilosophynote

LangChainを使って構築するアプリケーションの多くは、LLMコールを複数回呼び出す複数のステップを含みます。 このようなアプリケーションがますます複雑になるにつれて、チェーンやエージェントの内部で何が起こっているかを正確に検査できることが重要になります。 そのための最良の方法がLangSmithです。

philosophynotephilosophynote

LCELについて

https://python.langchain.com/docs/concepts/#langchain-expression-language-lcel

LangChain表現言語(LCEL)は、LangChainコンポーネントを連鎖させる宣言的な方法です。 LCELは、最も単純な "prompt + LLM "チェーンから、最も複雑なチェーンまで、コードを変更することなく、プロトタイプを本番で使用できるように、初日から設計されました(本番で100ステップのLCELチェーンを成功させた人を見てきました)。 LCELを使いたくなる理由をいくつか挙げてみましょう:

  • ファーストクラスのストリーミング・サポート: LCELでチェーンを構築すると、可能な限り最良のtime-to-first-token(出力の最初のチャンクが出てくるまでの経過時間)が得られます。 例えば、LLMからストリーミング出力パーサーに直接トークンをストリーミングし、LLMプロバイダーが生のトークンを出力するのと同じ速度で、解析されたインクリメンタルな出力チャンクが戻ってきます。

  • 非同期のサポート: LCELで構築されたチェーンは、同期API(例:プロトタイピング中のJupyterノートブック)と非同期API(例:LangServeサーバー)の両方で呼び出すことができます。 これにより、プロトタイプと本番で同じコードを使用することができ、優れたパフォーマンスと、同じサーバーで多数の同時リクエストを処理することができます。

  • 最適化された並列実行: LCELチェーンに並列実行可能なステップがある場合(例えば、複数のリトリーバーから文書を取得する場合)、可能な限りレイテンシを小さくするために、同期と非同期の両方のインターフェイスで自動的に実行されます。

  • 再試行とフォールバック: LCELチェーンのどの部分に対しても、リトライとフォールバックを設定できます。 これは、スケール時のチェーンの信頼性を高める素晴らしい方法です。 現在、リトライ/フォールバックのストリーミング・サポートの追加に取り組んでいます。

  • 中間結果へのアクセス: より複雑なチェーンの場合、最終的な出力が出る前であっても、中間ステップの結果にアクセスすることは、しばしば非常に便利です。 これはエンドユーザに何か起こっていることを知らせたり、チェーンのデバッグに使うことができます。 中間結果をストリームすることができ、すべてのLangServeサーバーで利用可能です。

  • 入出力スキーマ 入出力スキーマは、チェーンの構造から推測されるPydanticスキーマとJSONSchemaスキーマを、すべてのLCELチェーンに与えます。 これは入力と出力の検証に使用でき、LangServeの不可欠な部分です。

  • シームレスなLangSmithトレース チェーンが複雑になるにつれ、各ステップで何が起きているかを正確に理解することがますます重要になってきます。 LCELでは、全てのステップが自動的にLangSmithに記録され、最大限の観測とデバッグが可能です。

LCELは、LLMChainやConversationalRetrievalChainのようなレガシーなサブクラス化されたチェーンに対して、動作の一貫性とカスタマイズを提供することを目的としています。 これらのレガシーチェーンの多くは、プロンプトのような重要な詳細を隠しており、より多様な実行可能モデルが出現するにつれて、カスタマイズがますます重要になっています。 現在、これらのレガシーチェーンのいずれかを使用している場合は、移行方法についてこのガイドを参照してください。 LCELで特定のタスクを実行する方法については、関連するハウツーガイドを参照してください。

philosophynotephilosophynote

https://zenn.dev/os1ma/articles/acd3472c3a6755

LCEL では、chain = prompt | model のように、プロンプトや LLM を | で繋げて書き、処理の連鎖 (Chain) を実装します。

旧:chain = LLMChain(prompt=prompt, llm=llm)

from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate

prompt = PromptTemplate.from_template("""料理のレシピを考えてください。

料理名: {dish}""")

model = ChatOpenAI(model_name="gpt-3.5-turbo-1106", temperature=0)

chain = prompt | model

result = chain.invoke({"dish": "カレー"})
print(result.content)
材料:
- 牛肉または鶏肉 300g
- 玉ねぎ 1個
<以下略>
philosophynotephilosophynote

LLM に料理のレシピを生成させて、その結果を Recipe クラスのインスタンスに変換する、という流れを実施してみます。

from dotenv import load_dotenv
from langchain.chat_models import ChatOpenAI
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import PromptTemplate
from pydantic import BaseModel, Field

class Recipe(BaseModel):
    ingredients: list[str] = Field(description="ingredients of the dish")
    steps: list[str] = Field(description="steps to make the dish")

output_parser = PydanticOutputParser(pydantic_object=Recipe)
prompt = PromptTemplate.from_template(
    """料理のレシピを考えてください。

{format_instructions}

料理名: {dish}""",
    partial_variables={"format_instructions": output_parser.get_format_instructions()},
)

model = ChatOpenAI(model="gpt-3.5-turbo-1106").bind(
    response_format={"type": "json_object"}
)

chain = prompt | model | output_parser

result = chain.invoke({"dish": "カレー"})
print(type(result))
print(result)
<class '__main__.Recipe'>
ingredients=['カレールー', '肉(豚肉、鶏肉、牛肉など)', 'じゃがいも', 'にんじん', '玉ねぎ', 'にんにく', '生姜', 'トマト缶', '水', '塩', 'コショウ', 'サラダ油'] steps=['1. じゃがいもとにんじんを洗い、皮をむいて食べやすい大きさに切る。', '2. 玉ねぎ、にんにく、生姜をみじん切りにする。', '3. 鍋にサラダ油を熱し、にんにくと生姜を炒める。', '4. 肉を加えて炒め、色が変わったら玉ねぎを加える。', '5. じゃがいもとにんじんを加えて炒める。', '6. トマト缶を加え、水を注ぎ入れて煮込む。', '7. カレールーを加えて溶かし、塩とコショウで味を調える。', '8. ご飯の上にカレーをかけて完成。']
philosophynotephilosophynote

ルールベース

LLM を使ったアプリケーションでは、LLM の応答に対してルールベースでさらに処理を加えたり、何らかの変換をかけたいことも多いです。
LCEL では、Chain の連鎖に任意の処理 (関数) を加えることができます。

たとえば、LLM の生成したテキストに対して、小文字を大文字に変換する処理を連鎖させたい場合は、以下のように実装できます。

from langchain.schema.runnable import RunnableLambda

def upper(inp: str) -> str:
    return inp.upper()

chain = prompt | model | output_parser | RunnableLambda(upper)

文書をベクトル検索したりしてプロンプトに含めることで、社内文書などの LLM が本来知らない情報をもとに回答させることができる、という手法です。

RAG を LCEL で実装するため、まずは retriever (LangChain における文書を検索するインタフェース) を準備します。


from langchain.chat_models import ChatOpenAI
from langchain.embeddings import OpenAIEmbeddings
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough
from langchain.vectorstores.faiss import FAISS

texts = [
    "私の趣味は読書です。",
    "私の好きな食べ物はカレーです。",
    "私の嫌いな食べ物は饅頭です。",
]
vectorstore = FAISS.from_texts(texts, embedding=OpenAIEmbeddings())

retriever = vectorstore.as_retriever(search_kwargs={"k": 1})

prompt = ChatPromptTemplate.from_template(
    """以下のcontextだけに基づいて回答してください。

{context}

質問: {question}
"""
)

model = ChatOpenAI(model="gpt-3.5-turbo-1106", temperature=0)

output_parser = StrOutputParser()

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model
    | output_parser
)