APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

実践LangChain: tagsの行方とコールバック関数

はじめに

クラウド事業部の大久保と申します。

LangChainが世に出てから2年、言い換えるとLLMアプリケーションの普及が始まってから2年以上経った現在においてプロダクションとして世に送り出されているものも増えており、実践的な書籍もそろっているところはあるかと思います。

私自身、実案件で設計する中で各種媒体の情報は有益で全体感をつかむためには役に立ってはいるものの、 本番向けの実装を検討する中でAPI Referenceを直接読みに行く場面が多いです。 そこで、実践LangChainと自分なりにフレーズを定期的に細かなTipsを記事として書いていきたいと思います。

以下のコードはjupyter notebookを実行したときのものです。 import分については前のコードを参照しております。

対象読者

  • プロダクションでLangChainを扱おうとしている方
  • 特に共通処理や運用周りとの連携を検討する方

テーマ

今日のテーマは tags です。

細かすぎて「なんのこっちゃ?」という話かもしれませんが、LangChainで各種LLMを実行する際、Modelのクラスに tags というプロパティを付与することで、実行結果に任意のメタデータを付加することができます。

from dotenv import load_dotenv 
from langchain_community.chat_models.azure_openai import AzureChatOpenAI

load_dotenv()

tags = ["internal", "azure"]
llm = AzureChatOpenAI(azure_deployment="gpt-4o-mini", tags=tags)

response = llm.invoke("hello")

これは主にトレースに使われるもので、LangSmithや各種トレーサーでは自然な形で取得され、表示されます。

PoCの段階ではあまり気にしないかもしれませんが、本番環境でセキュリティや運用面を検討する際には、非常に重要なメタデータです。運用に関心がある方であれば、ピンとくる部分かと思います。


実行結果の中身を確認する

では、実際の出力を見てみましょう。

print(type(response))
print(response)
<class 'langchain_core.messages.ai.AIMessage'>
content='Hello! How can I assist you today?' additional_kwargs={} response_metadata={'token_usage': {...}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_b705f0c291', 'finish_reason': 'stop', 'logprobs': None} id='run-37066f7f-7a43-41b7-9325-6e18b6066fcb-0'

「なんと、ありません」

メタデータなのだから、出力結果に含まれているだろうと思った皆さん。残念ながら、ここには出てこないんです。

みなさん一緒に、tagsの在処を探してみましょう。


tags の在処

公式ドキュメントにもある通り、出力結果である AIMessage には tags は含まれません。

AIMessage — 🦜🔗 LangChain documentation

では、どこにあるのでしょうか?

答えは以下のコードで明らかになります。

from langchain_core.callbacks import BaseCallbackHandler
from typing import Any

class TagsPrintCallbackHandler(BaseCallbackHandler):
    def on_llm_end(self, output: Any, run_id: str, parent_run_id: str = None, **kwargs: Any) -> None:
        tags = kwargs.get("tags", [])
        print(f"[on_llm_end] run_id={run_id}")
        print(f"tags: {tags}")

handler = TagsPrintCallbackHandler()
tags = ["internal", "azure"]
llm_for_tags = AzureChatOpenAI(azure_deployment="gpt-4o-mini", tags=tags, callbacks=[handler])
response = llm_for_tags.invoke("hello")
[on_llm_end] run_id=7f4e60a2-2801-472a-a822-988fed3760fe  
tags: ['internal', 'azure']

このコードは、LLMによる出力が終わった後に、LangChainの config に含まれるパラメータを表示するカスタムコールバックです。

ご覧のように、tags はコールバック関数内で扱われています。


コールバックイベントについて

LangChainでは、コールバックイベントは以下の形式で定義されています:

on_[runnable_type]_(start|stream|end)

runnable_type は以下のいずれかです:

  • llm:チャットモデル以外のLLM
  • chat_model:チャットモデル用
  • promptChatPromptTemplate など
  • tool@tool デコレータなどで定義されたツール
  • chain:大半の Runnable はこれに該当

イベントカテゴリは以下のとおり:

  • start:処理の開始
  • stream:ストリーミング中
  • end:処理の終了

それぞれのイベントでは、異なる情報(ペイロード)が渡されます。詳しくは以下を参照:

Callbacks | 🦜️🔗 LangChain

BaseStreamEvent — 🦜🔗 LangChain documentation


chain 間の tags の伝播について

LangChainの特徴である chain を考えると、tags は本当に前後処理だけで完結しているのか、気になりませんか?

私も気になって、夜も眠れませんでした。

そこで、簡単な chain を作って、configtags が含まれるか確認してみます。

from langchain_core.runnables import RunnableLambda

def print_config(input_from_llm, config=None):
    print("Input from llm:", input_from_llm)
    print("Config:", config)

find_tags = RunnableLambda(print_config)
chain = llm | print_config

chain.invoke("hello")
Input from llm: ...
Config: {'tags': [], 'metadata': {}, 'callbacks': ..., ...}

tags は空ですね!

スコープコントロールがしっかりしていて、非常に好感が持てます。

ただし、Chainの間で一貫した tags を持たせたい場面もあります。

chain.invoke("hello", config={"tags": ["chain:azure"]})
Config: {'tags': ['chain:azure'], ...}

このように、明示的に config を渡せば、Chainの中でも tags を伝播させることが可能です。


ユースケースについて

主に以下のようなユースケースが考えられます:

  • 本番環境でのObservabilityやSecurity対応
  • 実行時のメタデータとしてログや監視フィルターに利用
  • LangGraphでのマルチエージェント構成で、内部用LLM出力をフロントに返さない識別子として活用

また、より高度で複雑なアプリケーションになればなるほど、細かいメタデータが運用上必要になります。

本番実装を目指すのであれば、こういった「細かい話」も知っておいて損はありません。

おわりに

私達クラウド事業部はクラウド技術を活用したSI/SESのご支援をしております。

www.ap-com.co.jp

また、一緒に働いていただける仲間も募集中です! ご興味持っていただけましたらぜひお声がけください。

www.ap-com.co.jp