top of page

検索結果

空の検索で33件の結果が見つかりました。

  • もうRAG評価で迷わない!Ragas最新メトリクス解説と実践的改善ガイド

    はじめに RAG(Retrieval-Augmented Generation)は、外部知識を参照してLLMの回答精度を向上させる強力な技術です。 しかし、多くの開発者が「RAGを作ったはいいものの、その性能をどう客観的に評価すればいいのか分からない」という壁に直面しています。 検索結果に不要な情報が混じっている気がする。 たまに事実と異なる回答(ハルシネーション)を生成してしまう。 改善したいが、検索と生成のどちらがボトルネックなのか切り分けられない。 この記事では、RAG評価で広く使われているオープンソースのライブラリ Ragas を用いて、これらの課題を解決する方法を解説します。 Ragasの主要な評価メトリクスを理解し、自作RAGシステムの課題を特定して改善サイクルを回すための、具体的なノウハウを掴んでいきましょう。 なお、Ragasは標準メトリクス中心の評価フレームワークであり、ドメイン固有の要件(トーン、規制対応など)にはカスタム評価が必要になる場合があります(本稿末尾の「Ragasでカバーしきれない観点とカスタム評価」を参照してください)。 Ragasの評価フロー例 本稿の対象読者 RAG/LLMアプリケーションの開発・評価・運用に携わる方 RAGの品質を定量的に測定し、継続的に改善したい方 なぜRAG評価は難しいのか? RAGシステムが期待通りに機能しない原因は、主に以下の3つのいずれかに分類できます。 ノイズ(Noise): 質問と無関係な情報を検索してしまい、回答の精度が下がる。 回収漏れ(Missed Information): 回答に必要な重要な情報を取り逃してしまい、不完全な回答になる。 幻覚(Hallucination): 検索した情報から逸脱し、事実に基づかない回答を生成してしまう。 これらの原因を特定するには、RAGのパイプラインを「検索(Retrieval)」と「生成(Generation)」のフェーズに分解し、それぞれの品質を測るメトリクスが必要です。それを実現するのがRagasです。 Ragasとは Ragasとは、LLMを利用したRAGパイプラインの健全性を測るための様々なメトリクスを提供します。 ここでは特に重要な5つのコアメトリクスを、「検索品質」と「生成品質」の2つのカテゴリに分けて解説します。 なお、Ragasには本稿で扱っていないメトリクス(例: `Context Entities Recall`、`Noise Sensitivity`、マルチモーダル関連 など)も複数存在します。 本稿では、RAGの実務で特に使用頻度が高いメトリクスに絞って解説しています。メトリクス一覧は公式の「Available Metrics」をご参照ください。 https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/ Ragasへの入力 Ragasで検索、生成の品質を測るためには以下のようなデータを入力する必要があります。 利用するメトリクスによって必須入力が異なります。 question: ユーザーからの質問 answer: RAGシステムが生成した回答 contexts: 回答の根拠として検索されたコンテキスト ground_truth: 人間が用意した正解の回答 検索品質を測る まずは、ユーザーの質問に対して適切な情報を集められているかを確認します。 各メトリクスの目的、必須入力、課題を発見できるか、また、各メトリクスの詳細について以下にまとめます。 メトリクス 目的 必須入力 どんな課題を発見できるか? Context Relevance 検索した文脈(コンテキスト)が、そもそも質問に関係あるか question, contexts 検索結果に全く無関係な情報が混じっていないか。 基本的な検索アルゴリズムの適合性を確認。 Context Precision 検索した文脈の中に、回答に"本当に必要な"情報がどれだけあるか question, contexts, ground_truth 関連はあるが冗長な情報が多く、ノイズになっていないか。検索結果のS/N比を確認。 Context Recall 回答に必要な情報を、漏れなく検索できているか question, contexts, ground_truth 複雑な質問に対し、回答の根拠となる情報が不足していないか。情報の網羅性を確認。 ▼ 各指標の詳細 Context Relevance ( コンテキスト の関連性) 解釈: スコアが低い場合、検索エンジンがユーザーの質問意図を全く理解できていない可能性があります。 典型的な落とし穴: 曖昧なクエリに対して、無関係なドキュメントを大量に返してしまっているケースがあります。 Context Precision( コンテキスト の適合率) 解釈: スコアが高いほど、検索結果のノイズが少ないことを意味します。低い場合は、検索範囲が広すぎる(拾いすぎ)可能性があります。 典型的な落とし穴: 検索でヒットしたドキュメントのチャンク(分割単位)が長すぎて、質問に関係ない部分までコンテキストに含まれてしまうケースがあります。 Context Recall ( コンテキスト の再現率) 解釈: スコアが高いほど、必要な情報を漏れなく集められていることを意味します。低い場合は、検索アルゴリズムが見つけるべき情報を見つけられていません。 典型的な落とし穴: 専門用語の言い換えや同義語に対応できず、重要な情報を含むドキュメントを検索できていないケースがあります。 生成品質を測る 次に、集めた情報に基づいて、LLMが質の高い回答を生成できているかを確認します。 メトリクス 目的 必須入力 どんな課題を発見できるか? Faithfulness 回答が、検索した文脈に忠実か(ハルシネーションの抑制) question, answer, contexts LLMが検索結果を無視して、勝手な情報を生成していないか。 回答の信頼性を測る最重要指標。 Answer Relevancy 回答が、ユーザーの質問の意図に沿っているか question, answer 回答が冗長だったり、的外れな内容になったりしていないか。 ユーザー体験に直結する指標。 ▼ 各指標の詳細 Faithfulness (忠実度) 解釈: 最も重要な指標の一つです。スコアが低い場合、たとえ流暢な回答でも、それは信頼できない「ハルシネーション」である可能性が高いです。 典型的な落とし穴: `Context Recall`が低くて情報が不足しているのに、LLMが無理に回答を生成しようとしてハルシネーションを起こす。原因は生成ではなく検索側にあることも多いです。 Answer Relevancy (回答の関連性) 解釈: スコアが低い場合、ユーザーは「質問に答えてくれていない」と感じるでしょう。 典型的な落とし穴: 曖昧な質問に対して、LLMが安全策をとり、一般的で当たり障りのない回答を生成してしまうケースがあります。 【実践】Ragasを使ってみる 1. データの用意 まず、評価用のデータセットを準備します。最初は10〜30件程度の小さなセットで傾向を掴むのがおすすめですが、本サンプルコードでは2件のデータセットを作成します。 前述の通りRagasにはメトリクスごとに対して必須入力に合わせて、以下のようなデータセットを用意します。 question : ユーザーからの質問 answer : RAGシステムが生成した回答 contexts : 回答の根拠として検索された文脈(文字列のリスト) ground_truth : (`Context Recall`の評価に必要)人間が用意した正解の回答 2. 実行コード例 事前に以下のコマンドで必要なライブラリをインストールし、OpenAIのAPIキーを設定しておきます。 pip install ragas==0.3.5 datasets==4.1.1 export OPENAI_API_KEY=your_api_key Note : 本稿ではRagas 0.3.5を使用しています。バージョンによってAPIが異なる場合がありますので、ご注意ください。 コードの例は下記の通りです。このプログラムを実行すると評価用データセットに対するRagsの評価が出力されます。 from datasets import Dataset from ragas import evaluate from ragas.metrics import ( ContextRelevance, context_precision, context_recall, faithfulness, answer_relevancy, ) # 評価用データセット(辞書形式) data_samples = { 'question': [ '製品Aの保証期間は?', 'ソフトウェアBの推奨OSは?' ], 'answer': [ '製品Aの保証期間は2年です。', 'Windows 11またはmacOS Sonomaが推奨されます。' ], 'contexts' : [ ['製品Aの保証は購入日から2年間有効です。', '配送は通常3営業日以内に行われます。'], ['ソフトウェアBはWindowsの場合はWindows 11で動作します。', 'macOSの場合はmacOS Sonomaです。'] ], 'ground_truth': [ '製品Aの保証期間は購入日から2年間です。', 'ソフトウェアBを利用するための推奨OSは、Windows 11またはmacOS Sonomaです。' ] } dataset = Dataset.from_dict(data_samples) # 評価の実行 result = evaluate( dataset, metrics=[ ContextRelevance(), context_precision, context_recall, faithfulness, answer_relevancy, ] ) # 結果をDataFrameで表示 df_result = result.to_pandas() print(df_result) 3. LangfuseでRagasスコアを記録する Ragasで算出したスコアをLangfuseに記録することで、個々のリクエストのトレースデータと評価結果を紐づけ、継続的な改善サイクルを実現できます。 Langfuseの公式ドキュメントでは、Ragas評価をLangfuseに連携する方法が紹介されています。  公式ドキュメント: https://langfuse.com/docs/scores/model-based-evals/ragas   詳細な実装方法については上記の公式ドキュメントをご参照いただきたいですが、ここでは簡単に説明します。 基本的な流れは以下の通りです: トレースの実行 : RAGアプリケーションを実行し、その過程(検索、生成など)をLangfuseでトレース(追跡・記録)します。 評価の準備 : Langfuseからトレースされたデータ(質問、コンテキスト、回答など)を取得し、Ragas評価フレームワークに渡します。 スコアの算出 : Ragasを使い、忠実性(faithfulness)や回答の関連性(answer_relevancy)といったメトリクスに基づいて評価スコアを計算します。 結果の連携 : 算出されたスコアを、対応するトレースに紐付ける形でLangfuseに送信し、ダッシュボード上で結果を可視化・分析します。 結果をどう読み解き、次の一手につなげるか? 評価スコアが出たら、次はその数値を元に改善アクションを考えます。 以下によくある課題(症状)と、確認すべきメトリクス、そして具体的な対策をまとめました。 課題(症状) 確認すべきメトリクス 主な対策案 回答に嘘や間違いが多い   Faithfulness が低い ・まず`Context Recall`を確認。情報不足が原因の可能性。 ・根拠を明示させるプロンプトにする。 ・不明な場合は「分かりません」と答えるよう指示する。 無関係な情報が多く、回答が冗長 Context Precision が低い Answer Relevancy が低い ・検索結果の再ランキング(Re-ranking)を導入する。 ・検索で取得するドキュメント数(k)を減らす。 ・ドキュメントのチャンクサイズを短くする。 回答が不十分・情報不足 Context Recall が低い ・検索で取得するドキュメント数(k)を増やす。 ・クエリ拡張(ユーザーの質問を複数のクエリに変換)を試す。 ・ベクトル検索とキーワード検索を組み合わせる。 質問と関係ない回答が返ってくる   Context Relevance が低い ・埋め込みモデル(Embedding Model)の再検討。 ・ユーザーの質問の意図を明確にする前処理を追加する。 Ragasでカバーしきれない観点とカスタム評価 Ragasは、RAGシステムの基本的な性能を標準的に評価するための強力なツールですが、実際のビジネス応用では、以下のような制約があります。 Ragasの制約 事前に定義された評価メトリクスに基づく標準的な評価 ドメイン固有の要件(業界特有のルール、コンプライアンス要件など)への対応が限定的 評価基準の詳細な調整が困難 ブランドトーンや表現スタイルなど、定性的な評価軸への対応 このような場合には、オリジナルの評価基準を作成することが有効です。 例えば、Langfuseを活用することで、以下のようなカスタム評価が可能になります: 金融分野での規制要件への適合性評価 医療分野での専門用語の正確性評価 LangfuseとRagasの使い分けや、カスタム評価基準の作成方法については、リンク先の記事 : Langfuse で LLM 評価を効率化!活用方法徹底解説 で詳しく解説されていますので、ぜひご参照ください。 まとめ:Ragasで課題を可視化し、Langfuseで改善を加速させる 本稿では、Ragasの主要メトリクスが、RAGシステムの課題をいかに的確に浮かび上がらせるかを解説しました。これは、データドリブンなRAG改善における、欠かすことのできない第一歩です。 しかし、スコアを算出するだけでは、改善活動は始まりません。そのスコアを「いつ、誰が、何を改善した結果なのか」という文脈と共に記録し、チームで共有して初めて、評価は次のアクションに繋がります。 そのための最適な基盤が Langfuse です。 LangfuseにRagasの評価スコアを記録することで、個々のリクエストのトレースデータと評価結果が完全に紐づきます。 例えば、「Context Precisionが低い」という課題が見つかった場合は、Langfuse上でその原因となった検索コンテキストを即座に確認し、次の改善策を立てられます。また、改善後のスコアも再び記録し、A/B比較を実施できます。 このように、Ragasが「What(何を改善すべきか)」を教え、Langfuseが「How(どう改善サイクルを回すか)」を支えることができます。 推測に頼る評価から脱却し、RagasとLangfuseによる継続的な改善サイクルを、ぜひ今日から始めてみてください。

  • Strands Agents と ADK でリモートMCPサーバーを使ったAgentを作り、その処理をLangfuseで可視化する

    はじめに 本記事では、Strands Agents とADK の二つのフレームワークを使用したシンプルなエージェント (両方ともLangfuse のMCPサーバーを使う) が、Langfuseを使ってどのように可視化されるのかをクイックに紹介する記事です。最近のアップデートで標準でグラフも出たりして便利です。タイトルが長すぎて、単にリフレーズしただけの冒頭文になってしまいました。 利用するMCPサーバー 今回はLangfuse のドキュメントを参照してくれるMCPサーバーを利用します。 開発の時にMCPクライアント (Cursor など) に入れておくと開発が捗りますが、本題ではないので今回はこれ自体の説明は割愛します。詳細はこちらのリンクをご覧ください。 https://langfuse.com/docs/docs-mcp Strands Agents のサンプルコード こちらはターミナル上で実行することを想定しているものです。Strands Agents はこのようなコードを大変シンプルに短く書けるのが非常に魅力的だと思います。 サンプルコード #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.10" # dependencies = [ #   "strands-agents[openai]", #   "mcp", #   "langfuse", # ] # /// import os, sys from strands import Agent from strands.models.openai import OpenAIModel from mcp.client.streamable_http import streamablehttp_client from strands.tools .mcp.mcp_client import MCPClient from langfuse import observe, get_client SYSTEM_PROMPT = (     "You are a helpful assistant for Langfuse developers.\n"     "- For Langfuse questions, proactively use MCP tools from the docs server: "     "call `searchLangfuseDocs` first, then `getLangfuseDocsPage` for details.\n"     "- Prefer up-to-date docs. Keep answers concise; include code when useful." ) # モデルとMCPはプロセスで1回だけ初期化 model = OpenAIModel(     client_args={"api_key": os.environ["OPENAI_API_KEY"]},     model_id=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),     params={"temperature": 0}, ) mcp = MCPClient(lambda: streamablehttp_client(" https://langfuse.com/api/mcp ")) @observe(name="agent-run", as_type="generation")  def run_agent(prompt: str) -> str:     with mcp:  # MCPセッション(with内で有効)         agent = Agent(model=model, tools=mcp.list_tools_sync(), system_prompt=SYSTEM_PROMPT)         res = agent(prompt)         return getattr(res, "message", str(res)) def main():     prompt = " ".join(sys.argv[1:]) or input("> ")     out = run_agent(prompt)               print(out)     get_client().flush()              if name == "__main__":     main() そして特に何もせずとも Langfuse にはこんな感じでTraceとして可視化されます。 OutputはTrace の中に Content として入っています。 Strands Agents の Trace いい感じでグラフも自動生成されます。処理の部分をクリックすると、その詳細が表示されます。例えば、 execute_tool searchLangfuseDocs というツール実行をクリックすると、実際にはユーザーの質問 (Datasetの作り方を知りたい) がどんな query になってて ("Langfuse dataset creation") 飛んで、どんな情報が取れて ... みたいな内容を確認して、デバックすることができます。 ADK (Agent Deployment Kit) のサンプルコード こちらも同様にCLIで動作して、同じ動作をします。こちらは OpenAI ではなくて、VertexAI上のGeminiを利用しています。OpenAI を使おうとすると LiteLLMが必要ですが、本題ではないのでおとなしくGeminiを使います。2.5-flashは速くて安くて便利です。 サンプルコード #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.10" # dependencies = [ #   "google-adk", #   "google-genai", #   "langfuse>=3", #   "mcp", # ] # /// import os, sys, asyncio from typing import Optional, List, Literal from datetime import timedelta from google.adk.agents import Agent from google.adk.runners import InMemoryRunner from google.genai import types from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client from langfuse import observe, get_client SYSTEM_PROMPT = (     "You are a helpful assistant for Langfuse developers.\n"     "- For Langfuse questions, proactively use MCP tools from the docs server: "     "call `lf_docs(mode=\"search\", query=...)` first, then "     "`lf_docs(mode=\"get\", pathOrUrl=...)` for details.\n"     "- Prefer up-to-date docs. Keep answers concise; include code when useful." ) MCP_URL = (os.getenv("LF_MCP_URL") or " https://langfuse.com/api/mcp").strip() async def mcp call(tool_name: str, **arguments) -> str:     for k, v in list(arguments.items()):         if isinstance(v, str):             arguments[k] = v.strip().replace("\r", "")     async with streamablehttp_client(url=MCP_URL) as (r, w, _):         async with ClientSession(r, w) as session:             await session.initialize()             resp = await session.call _tool(                 tool_name,                 arguments=arguments,                 read_timeout_seconds=timedelta(seconds=60),             )     texts: List[str] = []     for c in getattr(resp, "content", []) or []:         texts.append(getattr(c, "text", "") or "")     return "\n".join(t for t in texts if t).strip() async def lf_docs(     mode: Literal["search", "get"],     query: Optional[str] = None,     pathOrUrl: Optional[str] = None, ) -> str:     if mode == "search":         if not query:             return "Error: 'query' is required for mode='search'."         return await mcp call("searchLangfuseDocs", query=query)     # mode == "get"     if not pathOrUrl:         return "Error: 'pathOrUrl' is required for mode='get'."     return await mcp call("getLangfuseDocsPage", pathOrUrl=pathOrUrl) ROOT = Agent(     name="adk_mcp_cli_vertex",     model=os.getenv("ADK_MODEL", "gemini-2.5-flash"),     description="CLI agent using Langfuse Docs MCP tools (Vertex AI Gemini 2.5 Flash)",     instruction=SYSTEM_PROMPT,     tools=[lf_docs], ) @observe(name="adk-cli-turn", as_type="generation") async def run_once(prompt: str) -> str:     runner = InMemoryRunner(agent=ROOT, app_name=ROOT.name)     session = await runner.session_service.create_session(app_name=ROOT.name, user_id="cli-user")     user_msg = types.Content(role="user", parts=[types.Part(text=prompt)])     final = "(no answer)"     async for event in runner.run _async(user_id="cli-user", session_id=session.id, new_message=user_msg):         if hasattr(event, "is_final_response") and event.is _final_response():             if event.content and event.content.parts :                 final = event.content.parts [0].text or final     return final async def amain():     prompt = " ".join(sys.argv[1:]) or input("> ")     out = await run_once(prompt)     print(out)     get_client().flush()  if name == "__main__":     asyncio.run (amain()) こちらがTraceです。こちらも構造化されて、グラフ図も同様に出てきます。 Traceの中のデータの入り方などは異なりますが、基本的には同じです。 ADK での Trace 同様に図も出してくれてわかりやすい まとめ 今回のサンプルコードは @observe を利用して、Traceを作っていますがSpan の構成やUser, Session, 命名など細かく設定をすることも可能です。ぜひ色々と試して見てください(MCP サーバーの活用も便利だと思います!)。

  • Dify Langfuseプラグインがv0.0.2にアップデート!プロンプトの変数置換に対応

    LLMアプリケーション開発プラットフォーム「Dify」と、LLMオブザーバビリティツール「Langfuse」を連携させるためのカスタムプラグイン「Dify Langfuse Plugin」が、v0.0.2にアップデートされました。 今回のアップデートの目玉は、 Langfuseからプロンプトを取得する「Get Prompt」ツールに、プロンプト内の変数を動的に置換する機能が追加された 点です。これにより、Difyのワークフロー上で、より柔軟かつ再利用性の高いプロンプト管理が実現可能になります。 本記事では、この新しいアップデート内容を中心に、Dify Langfuseプラグインの魅力と使い方を詳しくご紹介します。 リリース情報はこちらからご確認いただけます。 GitHub Release v0.0.2 Dify Langfuseプラグインとは? Dify Langfuseプラグインは、Difyのワークフロー内からLangfuseで管理しているプロンプトを直接呼び出したり、検索・更新したりするためのカスタムツールです。 このプラグインを使うことで、煩雑になりがちなプロンプトのバージョン管理やチームでの共有をLangfuseに集約し、Difyからは常に最適なプロンプトを呼び出してアプリケーションを構築、といった効率的な開発フローが実現できます。 プラグインの基本的な機能や導入メリットについては、以下の記事で詳しく解説しています。 【v0.0.2】Get Promptツールが変数置換に対応! 今回のアップデートで最も強力なのが、Get Promptツールの機能強化です。 従来、Langfuseから取得できるのは静的なプロンプト本文のみでした。しかし、v0.0.2からは、プロンプト内に {{variable_name}} のような形式で変数を定義しておき、Dify側から値を渡して動的にプロンプトを生成できるようになりました。 具体的な使い方 新しく追加されたツール変数 variables に、置換したい変数をJSON形式で指定します。 例えば、Langfuseに以下のようなプロンプトが保存されているとします。 こんにちは{{name}}さん、{{country}}へようこそ! このプロンプトをGet Promptツールで取得する際に、variables パラメータに次のようなJSON文字列を渡します。 {"name": "太郎", "country": "日本"} すると、ツールからの出力 text は、変数が置換された以下の文字列になります。 こんにちは太郎さん、日本へようこそ! これにより、Difyのワークフロー内で得られた変数(ユーザーの入力など)を使って、Langfuseで一元管理されたプロンプトテンプレートを動的にカスタマイズすることが可能になります。 変数置換 前 のプロンプトの例 変数置換 後 のプロンプトの例 アップデートされたGet Promptツールの詳細 以下は、Get Promptツールの入力パラメータと出力の詳細です。 入力パラメータ パラメータ名 説明 必須 デフォルト値 name Langfuseで管理されているプロンプトの一意な名前。 はい - label 取得したいプロンプトバージョンに付与されたラベル。version との同時指定不可。 いいえ production version 取得したいプロンプトの特定のバージョン番号。label との同時指定不可。 いいえ - variables プロンプト内の変数を置換するためのJSON文字列。形式:{"variable_name": "value"} いいえ - 出力 変数が正しく置換されると、text には置換後のプロンプト本文が、json には処理の詳細を含むメタデータが返されます。 prompt キー はこれまで通り変数置換前のプロンプトが、 processed_prompt キー には変数置換後のプロンプトが入ります。 出力例: { "text": "こんにちは太郎さん、日本へようこそ!", "files": [], "json": [ { "name": "greeting_prompt", "version": 2, "prompt": "こんにちは{{name}}さん、{{country}}へようこそ!", "processed_prompt": "こんにちは太郎さん、日本へようこそ!", "variables_applied": true, "labels": ["production", "latest"], ... } ] } json出力に processed_prompt キーを追加 まとめ:より高度なLLMアプリケーション開発へ 今回のDify Langfuseプラグインのアップデートにより、プロンプトの管理と活用の幅が大きく広がりました。 ぜひ、この機会にDify Langfuseプラグインをアップデート(または新規インストール)して、より効率的で高度なLLMアプリケーション開発にお役立てください。 インストールと詳細はこちらのGitHubリポジトリのREADMEから https://github.com/gao-ai-com/dify-plugin-langfuse

  • Terraform で実現する Langfuse on AWS

    はじめに 本記事では、この Langfuse 環境を AWS 上に構築する方法について解説します 2025/05/22 に Langfuse の公式ドキュメントにおいて、AWS 向けの Terraform 構成が公開されました。この公式ドキュメントに記載された手順をベースとし、実際に環境を構築する際の具体的なステップや留意点、さらに実運用を見据えたポイントなどを、弊社の知見を交えながらご紹介します。 本記事の Google Cloud バージョンは こちら 公式の AWS システム構成 Langfuse 環境を AWS 上に Terraform で構築するにあたり、最も信頼できる情報源は Langfuse の公式ドキュメントです。公式で AWS 向けの Terraform 構成が提供開始され、これにより導入のハードルが大きく下がりました。 まず、Langfuse 公式サイトの AWS 向けセルフホスティングガイド をご確認いただくことを強く推奨します。この公式ガイドには、Terraform で環境構築する手順が載っています。 主要コンポーネントとAWS プロダクトのマッピング 公式ドキュメントで推奨されている構成、および Langfuse の一般的なコンポーネントが AWS のどのプロダクトに対応するかを以下にまとめます。 Langfuse コンポーネント AWS プロダクト 主な目的・役割 Langfuse Web, Worker Amazon Elastic Kubernetes Service (以下EKS) Langfuseサーバーのコンテナをホスティングします。 Redis Amazon ElastiCache キャッシュ・キューに使用します。 Postgres - OLTP Amazon Aurora PostgreSQL 認証情報などトランザクションデータを格納するストレージです。 ClickHouse - OLAP コンピュート: EKS ストレージ: Amazon EFS トレースなどを格納するストレージです。 Blob Storage Amazon S3 生のイベントやマルチモーダルファイルを保管します。 Terraformコードの提供形態 Langfuse の GitHub リポジトリ には、これらの AWS リソースを効率的にプロビジョニングするための Terraform 設定ファイル群(HCL コード)が提供されています。ユーザーは提供されたコードをベースに、ドメインや構成オプションを指定することで、推奨構成を迅速にデプロイできます。 構成図 GitHub リポジトリにある構成図は以下のとおりです。この構成は本番環境を想定した最低限のコンポーネントになっていると考えます。 システム構成図 引用: langfuse-terraform-aws 費用の概算 最低限のランニングコストを試算しました。( リンク ) 月額約 450$ となっています。 Terraform での環境構築で知っておきたい Tips 集 Langfuse の公式ドキュメントには、AWS 上に Terraform を用いて環境を構築するための手順が詳細に記載されています。基本的にはこのドキュメントに従って進めることで、迷うことなく環境をセットアップできるでしょう。 本セクションでは、実際に構築する過程で気づいた点や、よりスムーズに進めるための Tips などをまとめてご紹介します。 構築の大まかな流れ 公式ドキュメントで提供されている Terraform コードを利用した構築は、主に以下のステップで進められます。 API を有効化します。 設定 HCL ファイルを用意します。ドメインを指定します。 terraform init をして、先に DNS 設定だけ apply します。 全てのリソースを apply します。 所要時間 : 全体のデプロイには、環境にもよりますが 20 分〜 60 分程度かかります。 構築時の Tips と留意点 💡 実際に公式ドキュメントの手順に沿って構築を進める中で、いくつか留意しておくと良い点や、カスタマイズのヒントがありました。 既知の問題について 弊社で何回か試したときは、ステップ4で以下のエラーが出て apply  失敗しました。 terraform apply ... ╷ │ Warning: Helm release "" was created but has a failed status. Use the `helm` command to investigate the error, correct it, then run Terraform again. │ │ with module.langfuse.helm_release.langfuse, │ on ../../langfuse.tf line 121, in resource "helm_release" "langfuse": │ 121: resource "helm_release" "langfuse" { │ ╵ ╷ │ Error: context deadline exceeded │ │ with module.langfuse.helm_release.langfuse, │ on ../../langfuse.tf line 121, in resource "helm_release" "langfuse": │ 121: resource "helm_release" "langfuse" { │ ╵ これは公式ドキュメントで 既知の問題 として対処法が示されています。以下のコマンドを実行後に再び apply  すれば成功します。 # Connect your kubectl to the EKS cluster aws eks update-kubeconfig --name langfuse # Restart the CoreDNS and ClickHouse containers kubectl --namespace kube-system rollout restart deploy coredns kubectl --namespace langfuse delete pod langfuse-clickhouse-shard0-{0,1,2} langfuse-zookeeper-{0,1,2} 設定ファイルのテンプレート活用 公式ドキュメントには設定ファイルの記述例がありますが、リポジトリ内の examples/quickstart/quickstart.tf に類似の構成ファイルが含まれている場合があります。こちらを参考にしたり、コピーして自身の環境に合わせて修正したりすると、設定ファイル作成の手間を省けることがあります。 Aurora と ElastiCache のコスト最適化 Terraform のデフォルト設定では、Aurora の容量や ElastiCache のインスタンス数が多い状態でプロビジョニングされる場合があり、特に検証用途では 料金が高額になる可能性 があります。開発・検証環境など、そこまで高い性能や障害耐性を求めない場合は、Langfuse モジュールの設定で以下のように調整することで、コストを大幅に抑えることができます。 module "langfuse" { # ... 他の変数 ... postgres_max_capacity = 1.0 cache_instance_count = 1 } アクセスキー・リージョン未設定エラーへの対処 terraform apply 実行時などに、「アクセスキーが設定されていない」や「リージョンが設定されていない」といったエラーメッセージが表示されることがあります。これは、AWS プロバイダが対象のプロジェクトやリージョンを認識できていない場合に発生します。以下の環境変数を設定するか、Terraform のプロバイダ設定で明示的に指定してください。 export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY" export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY" export AWS_REGION="YOUR_REGION"   または、プロバイダブロックに記述します。 provider "aws" { access_key = "YOUR_ACCESS_KEY_ID" secret_key = "YOUR_SECRET_ACCESS_KEY" region = "YOUR_REGION" } ロードバランサー周りの設定 Terraform で構築された環境でのロードバランサーは HTTP と HTTPS の2つのリスナーがあります。HTTP リスナーは HTTP から HTTPS へリダイレクトするためのものです。 HTTPS リスナーからターゲットグループへアクセスし、ポート3000に設定して Langfuse Web へリクエストをルーティングします。 まとめ 本記事では、公式ドキュメントおよび Terraform コードを利用して、AWS 上に Langfuse を構築する手順と、その過程で得られた実践的な Tips をご紹介しました。 公式に提供されている Terraform 構成を活用することで、EKS、Aurora といった AWS のマネージドサービス群を効率的かつ再現性高くプロビジョニングできることをご確認いただけたかと思います。特に、インフラ構成をコードで管理する Infrastructure as Code (IaC) のアプローチは、環境構築の迅速化だけでなく、構成変更の追跡やチーム内での共有を容易にし、属人化を防ぐ上でも大きなメリットがあります。 Langfuse は活発に開発が続けられており、LLM アプリケーション開発の効率化と品質向上に寄与する機能が今後も追加されていくことが期待されます。本記事が、皆様の AWS 上での Langfuse 導入、そして LLM を活用したイノベーション推進のお役に立てれば幸いです。ぜひ、公式ドキュメントと合わせてご活用いただき、実際の開発プロジェクトでお試しください。

  • Terraform で実現する Langfuse on Google Cloud

    はじめに 本記事では、この Langfuse 環境を Google Cloud 上に構築する方法について解説します 2025/05/22 に Langfuse の公式ドキュメントにおいて、Google Cloud 向けの Terraform 構成が公開されました。この公式ドキュメントに記載された手順をベースとし、実際に環境を構築する際の具体的なステップや留意点、さらに実運用を見据えたポイントなどを、弊社の知見を交えながらご紹介します。 本記事の AWS バージョンは こちら 公式の Google Cloud システム構成 Langfuse 環境を Google Cloud 上に Terraform で構築するにあたり、最も信頼できる情報源は Langfuse の公式ドキュメントです。公式で Google Cloud 向けの Terraform 構成が提供開始され、これにより導入のハードルが大きく下がりました。 まず、Langfuse 公式サイトの Google Cloud 向けセルフホスティングガイド をご確認いただくことを強く推奨します。この公式ガイドには、Terraform で環境構築する手順が載っています。 主要コンポーネントとGoogle Cloudプロダクトのマッピング 公式ドキュメントで推奨されている構成、および Langfuse の一般的なコンポーネントが Google Cloud のどのプロダクトに対応するかを以下にまとめます。 Langfuse コンポーネント Google Cloud プロダクト 主な目的・役割 Langfuse Web, Worker Google Kubernetes Engine Langfuseサーバーのコンテナをホスティングします。 Redis Memorystore for Redis キャッシュ・キューに使用します。 Postgres - OLTP Cloud SQL for PostgreSQL 認証情報などトランザクションデータを格納するストレージです。 ClickHouse - OLAP Google Kubernetes Engine トレースなどを格納するストレージです。 Blob Storage Google Cloud Storage 生のイベントやマルチモーダルファイルを保管します。 Terraformコードの提供形態 Langfuse の GitHub リポジトリ には、これらの Google Cloud リソースを効率的にプロビジョニングするための Terraform 設定ファイル群(HCL コード)が提供されています。ユーザーは提供されたコードをベースに、ドメインや構成オプションを指定することで、推奨構成を迅速にデプロイできます。 想定される構成図 これらのコンポーネントから想定される構成は以下のようになります。これは弊社が以前投稿した記事: Google CloudでLangfuse v3の構築手順(推奨設定/GKE) と同じ構成となっています。 システム構成図 本番運用を見据えたアーキテクチャ選択について セルフホストする際のインストールパターンは多岐に渡り、使用する場面に合わせて選択するのが良いです。これらセルフホスティングのパターンについては こちら の記事で解説しています。 公式 Terraform のシステム構成では、Langfuse Web, Langfuse Workder, ClickHouse に Google Kubernetes Engine (GKE)  を使用した構築を選択しています。この選択の理由と、他の主要なホスティングオプションとの比較を以下に示します。 Google Kubernetes Engine (GKE) 強み : 本番グレードのオートスケーリング、詳細なネットワーク制御、高可用性を実現します。将来的な機能拡張やデータ増大にも柔軟に対応できる、最も堅牢な選択肢です。 考慮点 : 高機能かつ柔軟である反面、 運用コストは他の選択肢より高くなる 傾向があり、Kubernetes の運用知識と管理スキルが求められます。 Cloud Run 特性 : サーバーレスで迅速なデプロイが可能であり、運用負荷とコストを抑えたい場合に適しています。オートスケーリングの機能も備わっています。 GKE との比較 : GKEほどの高度なカスタマイズ性や、複数ポート利用といった要件への対応は限定的です。 Compute Engine (GCE) 特性 : docker compose コマンドにより簡単にデプロイ可能です。 GKE との比較 : コンテナ化された Langfuse アプリケーションの運用(特にClickHouse のコンテナ)においては、GKE のようなオーケストレーション機能がないため、スケーリング、自己修復、ローリングアップデートといった運用効率で劣ります。 費用の概算 最低限のランニングコストを試算しました。( リンク ) 月額約 570$ となっています。 Terraform での環境構築で知っておきたい Tips 集 Langfuse の公式ドキュメントには、Google Cloud 上に Terraform を用いて環境を構築するための手順が詳細に記載されています。基本的にはこのドキュメントに従って進めることで、迷うことなく環境をセットアップできるでしょう。 本セクションでは、実際に構築する過程で気づいた点や、よりスムーズに進めるための Tips などをまとめてご紹介します。 構築の大まかな流れ 公式ドキュメントで提供されている Terraform コードを利用した構築は、主に以下のステップで進められます。 API を有効化します。 設定 HCL ファイルを用意します。ドメインを指定します。 terraform init をして、先に DNS 設定と GKE クラスターだけ apply します。 全てのリソースを apply します。 所要時間 : 全体のデプロイには、環境にもよりますが 20 分〜 60 分程度かかります。 構築時の Tips と留意点 💡 実際に公式ドキュメントの手順に沿って構築を進める中で、いくつか留意しておくと良い点や、カスタマイズのヒントがありました。 設定ファイルのテンプレート活用 公式ドキュメントには設定ファイルの記述例がありますが、リポジトリ内の examples/quickstart/quickstart.tf に類似の構成ファイルが含まれている場合があります。こちらを参考にしたり、コピーして自身の環境に合わせて修正したりすると、設定ファイル作成の手間を省けることがあります。 リソースの削除保護について デフォルト設定では、安全のために Cloud SQL インスタンスなどの主要リソースに 削除保護 ( deletion_protection = true ) が有効 になっていることがあります。検証目的などで頻繁にリソースの作成・削除を行いたい場合は、Langfuse の Terraform モジュールで明示的に指定することで、Terraform による削除が可能になります。 module "langfuse" { # ... 他の変数 ... deletion_protection = false } Cloud SQL インスタンスのコスト最適化 Terraform のデフォルト設定では、Cloud SQL インスタンスが比較的高性能なマシンタイプ( db-perf-optimized-N-2 )でプロビジョニングされる場合があり、特に検証用途ではオーバースペックで 料金が高額になる可能性 があります。開発・検証環境など、そこまで高い性能を求めない場合は、Langfuse モジュールの設定で以下のようにマシンタイプやアベイラビリティタイプを調整することで、コストを大幅に抑えることができます。 module "langfuse" { # ... 他の変数 ... database_instance_tier = "db-f1-micro" database_instance_availability_type = "ZONAL" database_instance_edition = "ENTERPRISE" } Cloud DNS を別プロジェクトで管理している場合 Langfuse の Terraform 構成に Cloud DNS ゾーンやレコード作成が含まれている場合で、DNS 管理を別の Google Cloud プロジェクトで行っているケースでは、dns.tf ファイル内の "Create Cloud DNS zone" と "Create DNS A record for the load balancer" の2つのDNS 関連のリソース定義をコメントアウト、または削除する必要があります。 terraform apply が完了後、コンソールで Aレコードを編集して HTTPS の方の Load Balancer のフロントエンドの IP アドレスにします。 GCS HMAC キー発行時の認証について Google Cloud コンソールからはユーザーアカウントで HMAC キーを発行できますが、Terraform(サービスアカウント経由)でユーザーアカウントの HMAC キーを直接発行することはできません。サービスアカウント自身の HMAC キーを発行することは可能です。組織ポリシーでサービスアカウントから HMAC キーを発行を制限している場合は注意が必要です。 プロジェクトID・リージョン未設定エラーへの対処 terraform apply 実行時などに、「プロジェクトが設定されていない」や「リージョンが設定されていない」といったエラーメッセージが表示されることがあります。これは、Google Cloud プロバイダが対象のプロジェクトやリージョンを認識できていない場合に発生します。以下の環境変数を設定するか、Terraform のプロバイダ設定で明示的に指定してください。 export GOOGLE_PROJECT="YOUR_PROJECT_ID" export GOOGLE_REGION="YOUR_REGION"   または、プロバイダブロックに記述します。 provider "google" { project = "YOUR_PROJECT_ID" region = "YOUR_REGION" } ロードバランサー周りの設定 Terraform で構築された環境でのロードバランサーは HTTP ロードバランサーと HTTPS ロードバランサーの2つあります。HTTP ロードバランサーは HTTP から HTTPS へリダイレクトするためのものです。 HTTPS ロードバランサーには2つのバックエンドがあります。これは、ポート3000でアクセスできるならポート指定するパスルール設定のようです。 まとめ 本記事では、公式ドキュメントおよび Terraform コードを利用して、Google Cloud 上に Langfuse を構築する手順と、その過程で得られた実践的な Tips をご紹介しました。 公式に提供されている Terraform 構成を活用することで、GKE、Cloud SQL for PostgreSQL といった Google Cloud のマネージドサービス群を効率的かつ再現性高くプロビジョニングできることをご確認いただけたかと思います。特に、インフラ構成をコードで管理する Infrastructure as Code (IaC) のアプローチは、環境構築の迅速化だけでなく、構成変更の追跡やチーム内での共有を容易にし、属人化を防ぐ上でも大きなメリットがあります。 本記事でご紹介した Tips、特に Cloud SQL インスタンスのサイジング調整などは、コストを意識した運用を行う上で重要なポイントです。これらを参考に、ご自身のユースケースや予算に合わせて環境を最適化してください。 Langfuse は活発に開発が続けられており、LLM アプリケーション開発の効率化と品質向上に寄与する機能が今後も追加されていくことが期待されます。本記事が、皆様の Google Cloud 上での Langfuse 導入、そして LLM を活用したイノベーション推進のお役に立てれば幸いです。ぜひ、公式ドキュメントと合わせてご活用いただき、実際の開発プロジェクトでお試しください。

  • Dify のプロンプト管理を劇的に改善!Langfuse プラグインのご紹介

    1. はじめに 本記事では Dify の Langfuse プラグインをご紹介いたします。 Dify でアプリを開発する際、ワークフローに直接プロンプトを書き込んでいくと、「前のプロンプトの方が良かったけど消しちゃった」「チームで同じプロンプトをスムーズに共有したいけど…」といったお悩みが出てきませんか? Dify 上でプロンプトが増えてくると、バージョン管理や再利用が難しくなりがちです。これが開発効率やアプリケーションの品質に響いてしまうことも。もし、プロンプトを Dify のワークフローから切り離し、専用のツールで一元的に、かつバージョン管理しながら集中的に扱えたら、こうした課題はずいぶん楽になるはずです。 そこで注目したいのが、LLM アプリケーションのトレーシングやプロンプト管理に特化したオープンソースツール「Langfuse」です。 実は、皆さんがお使いの Dify は、既に Langfuse の非常に強力な「トレース機能」との連携を標準でサポートしています!  Dify での Langfuse のトレース連携 この Dify と Langfuse のトレース連携の具体的な設定方法や活用事例については、 Langfuseでの可視化 [Dify編 (前半) ]  でも詳しく解説されていますので、ぜひそちらも併せてご覧いただき、皆さんのアプリケーション監視や改善にお役立てください。 (ブログ記事へのリンク: https://www.gao-ai.com/post/dify編-前半-langfuseでの可視化 ) Langfuse はトレース機能だけでなくプロンプトのバージョン管理、変更履歴の追跡を効率的に行う機能もあります。しかし、Langfuse でプロンプトをしっかり管理していても、それを Dify のような LLM アプリケーション開発プラットフォームで利用するには、ひと手間必要でした。 そこで今回、この課題を解決するために、 Dify から Langfuse で管理されているプロンプトを直接利用したり検索したりできるカスタムプラグインを開発し、OSS として GitHub で公開しました。   ▶ 本プラグインの GitHub リポジトリは こちら このプラグインを使えば、Langfuse で厳密にバージョン管理されたプロンプトを、Dify のワークフローに簡単に組み込むことができます。 この記事では、私たちが開発したこの Dify 向け Langfuse プラグインが、どのように皆様のプロンプト管理を効率化し、アプリケーション開発をサポートできるのか、その機能や使い方をご紹介します。具体的には、「特定のプロンプトを取得するツール(Get Prompt)」と「プロンプトを検索するツール(Search Prompts)」、「プロンプトを更新するツール(Update Prompt)」の3つの主要な機能について解説していきます。 プロンプト管理に課題を感じている方、そして Langfuse の強力な管理機能を Dify で活かしたいと考えている方に、この記事がお役に立てれば幸いです。 2. 背景 このプラグインがどのような課題を解決し、どんなメリットをもたらすのかをご理解いただくために、まずは Dify と Langfuse、そしてそれらを連携させる価値について簡単にご説明します。 Dify とは Dify は、ノーコードまたはローコードで LLM を活用したアプリケーションを迅速に構築できるプラットフォームです。直感的な GUI を通じて、複雑なワークフローも比較的簡単に作成できるのが大きな魅力です。 Langfuse とは 次に Langfuse ですが、これはLLMアプリケーションの「オブザーバビリティ(可観測性)」を高めるための強力なオープンソースツールです。Langfuse の主な機能としては、アプリケーションの実行フローを詳細にトレース(追跡)してデバッグを容易にしたり、消費トークン数を管理したり、ユーザーの利用状況に関するデータを収集・分析して改善に繋げたりといったものが挙げられます。これにより、LLM アプリケーションが実際にどのように動作しているのか、どこに問題があるのかを把握しやすくなります。 しかし、Langfuse の魅力はそれだけではありません。 今回私たちが特に注目しているのは、Langfuse が提供する「プロンプト管理機能」です。  Langfuse を使えば、作成したプロンプトをバージョンごとに記録し、変更履歴を管理し、さらには異なるバージョンのプロンプトを比較・評価することも可能です。これにより、プロンプトを単なるテキストではなく、適切に管理・改善していくべき「資産」として扱えるようになります。 記事: Langfuse によるプロンプト管理の魅力 Dify × Langfuse 連携の価値 Dify の手軽なアプリケーション構築能力と、Langfuse の堅牢な LLM 運用機能を組み合わせることで、開発プロセス全体に以下のような戦略的な価値がもたらされます。 プロンプト開発の独立性と迅速性の向上: プロンプトのライフサイクルを Dify のワークフローから分離し、Langfuse で一元管理することで、プロンプトの修正や実験がアプリケーション本体の変更・再デプロイを必要としません。これにより、迅速なイテレーションと改善が実現します。 プロンプト品質と再利用性の一貫した担保: Langfuse による厳密なバージョン管理は、過去の優れたプロンプトへの確実なロールバックを可能にし、チーム内やプロジェクト間でのプロンプト共有と再利用を効率化し、アプリケーション全体の品質維持に貢献します。 データ駆動型プロンプトエンジニアリングの推進: Langfuse のトレース機能や評価基盤と連携することで、各プロンプトのパフォーマンスを客観的に把握しやすくなります。これにより、データに基づいた継続的な改善サイクルを構築し、プロンプトの最適化を促進できます。 このように、Dify と Langfuse の連携は、単にツールを繋ぐだけでなく、LLM アプリケーション開発の質と効率を一段階引き上げるポテンシャルを秘めているのです。本プラグインは、その連携を具体的に実現するための一助となります。 3. Langfuse プラグインの概要 Langfuse プラグインは Dify におけるプロンプト管理を、Langfuse と連携して効率化するために開発されました。Difyのワークフローから Langfuse 上のプロンプトを手軽に扱えるようにすることが主な目的です。 プラグインは、主に以下の3つのツールで構成されています。これらのツールの詳細については、GitHub の README-ja をご覧ください。 Get Prompt ツール:  Langfuse から特定のプロンプト(本文やメタデータ)を取得します。 Search Prompts ツール:  Langfuse 内のプロンプトを様々な条件で検索します。 Update Prompts ツール : Langfuse の特定のプロンプトを新しいバージョンとして更新します。 なお、本プラグインを通じて Langfuse の機能を利用するためには、 Langfuse の API キーが必須となります。  API キーの取得方法や Dify への具体的な設定手順については、次のセクションでご説明します。 4. 導入方法と使い方 このセクションでは、開発した Langfuse プラグインを Dify に導入し、実際にワークフローで活用するための手順と基本的な使い方についてご説明します。 このプラグインはオープンソースソフトウェアとして、以下の GitHub リポジトリで公開されています。 --- 🚀 Dify-Langfuse連携プラグイン GitHubリポジトリ 🚀 https://github.com/gao-ai-com/dify-plugin-langfuse --- 準備: Langfuse でプロンプトの作成 Langfuse でプロンプトを作成する手順は 公式ドキュメント をご覧ください。 プラグインのインストール 本プラグインは、Dify のプラットフォーム機能を利用して、GitHub リポジトリから直接インストールすることができます。 Dify のプラグイン管理ページへ移動: Dify にログイン後、画面右上にある[プラグイン]からプラグイン管理ページを開き、[プラグインをインストールする]、[GitHub]を続けてクリックします。 リポジトリアドレスの入力: インストールするプラグインの GitHub リポジトリアドレスを入力するフィールドが表示されますので、ここに、 https://github.com/gao-ai-com/dify-plugin-langfuse を入力します。 バージョンとパッケージファイルの選択・インストール: リポジトリが認識されると、利用可能なバージョン番号とパッケージを選択する画面に進みます。適切なものを選択し、指示に従ってインストールを完了してください。 必要な設定:Langfuse APIキーとエンドポイント プラグインをインストール後、Langfuse と連携するためにはLangfuse API キーとエンドポイントを設定する必要があります。 Langfuse の [Settings] から [API Keys] で API キーを発行します。 Langfuse で API キーを発行 Dify プラグインのページから Langfuse プラグインを選択し、[認証する]をクリックします「Langfuse 公開鍵」「Langfuse 秘密鍵」「Langfuse Host」を設定します。 Langfuse の認証 基本的な使い方 このLangfuse連携プラグインのツールをDifyのワークフローに組み込むことで、プロンプトの検索、取得、更新をシームレスに行うことができます。以下に基本的な利用フローの例を示します。 Search Prompts ツールで目的のプロンプトを探す 特定のプロンプト名が不明な場合や、条件に合うプロンプトのリストから選びたい場合にこのツールを使用します。 ツールから Langfuse プラグインの Search prompts ツールを選択します。 入力パラメータを設定します。例えば、「customer_support_faq というラベルがついた最新のプロンプト」を探したい場合は、label フィールドに customer_support_faq を入力します。 このツールを実行すると、条件に一致するプロンプトのメタデータ(プロンプト名、バージョン情報など)がリスト形式で出力されます。 Get Prompt ツールでプロンプト本文を取得 実際に使用するプロンプトの本文を取得します。 ツールから Langfuse プラグインの Get Prompt ツールを選択します。 取得するプロンプトの入力パラメータを設定します。 このツールを実行すると、指定されたプロンプトの本文が text 出力として、メタデータが json 出力として得られます。 LLMノードで取得したプロンプトを利用 Get Prompt ツールで取得したプロンプト本文を、LLM ノードで使用します。 LLM ノードを追加または選択します。 LLM ノードのプロンプト入力設定部分(例: システムプロンプトやユーザープロンプトのフィールド)に、Get Prompt ツールの出力を参照する変数を挿入します。 Get Prompt ツールの使用例 Update Prompt ツールでプロンプトを更新 特定のプロンプト本文を更新します。 ツールから Langfuse プラグインの Update prompt ツールを選択します。 更新するプロンプトの入力パラメータを設定します。 このツールを実行すると、Langfuse に新しいプロンプトバージョンが作成され、指定したラベルやタグが設定されます。 ※Get Prompt ツールと併用する際にはタグの設定間違えのないよう注意してください。 これで、Langfuse で管理されたプロンプトを Dify のワークフロー内で動的に利用する基本的な流れが完成です。これにより、プロンプトの変更を Langfuse 側で行うだけで、Dify のワークフローは変更せずに最新のプロンプトを適用できるようになります。 5. 活用シナリオ この Langfuse 連携プラグイン、特に Get Prompt ツールをDifyのワークフローに組み込むことで、皆さんのアプリケーション開発はより効率的で、柔軟になります。いくつかの具体的な活用シナリオを見ていきましょう。 シナリオ1:安全かつ迅速なプロンプトの改善サイクル Dify で運用中のプロンプトを改善したいけれど、直接編集はリスクがあり、ワークフロー複製も手間になります。 このプラグインがあれば: Get Prompt ツールで取得取得したプロンプトを元に改善案を練り、その新しいプロンプトテキストを Update Prompt ツールを使って Langfuse の該当プロンプトの新しいバージョンとして登録します。その際、例えば staging  や developer-test-v2  といったラベルを新しいバージョンに付与します。 その後、Dify のテスト用ワークフローで改めて Get Prompt ツールを使い、その新しいラベルを指定して改善版プロンプトを読み込み、効果を検証します。期待通りの結果が得られれば、Langfuse 側でそのバージョンに production  ラベルを付け替えるか、あるいは本番用ワークフローが参照するラベルを更新します。 これにより、 Dify 上で直接プロンプトを編集・テストし、その成果を即座に Langfuse のバージョン管理下に置き、安全に本番環境へ反映する という、よりダイレクトで迅速な改善サイクルが実現します。問題発生時のロールバックも Langfuse のバージョン履歴から容易に行えます。 シナリオ2:A/Bテストによる最適なプロンプトの追求 複数のプロンプト案の効果を Dify で比較したいけれど、準備が大変だと感じていませんか。 このプラグインがあれば: 比較したい複数のプロンプト案を Dify 上で作成し、それぞれを Update Prompt ツールを使って Langfuse の同じプロンプト名に対して異なるラベル(例: test-A, test-B)を付与した新しいバージョンとして登録します。 その後、Dify 側で Get Prompt ツールを使い、リクエストに応じて呼び出す label  を動的に切り替えて A/Bテストを実施します。それぞれの結果の品質を Langfuse のトレース機能やスコアリング機能(Langfuse 本体の機能)と組み合わせて分析すれば、 実際の利用データに基づき最適なプロンプトを選定できます。  Dify の柔軟性と Langfuse の管理・評価機能を活かし、データドリブンなプロンプト改善が可能です。 シナリオ3:チームでの効率的なプロンプト共同開発・運用 チームでの Dify 開発時、プロンプト管理が属人化したり、最新版の共有が難しかったりしませんか。 このプラグインがあれば: Langfuse をチーム共通の「プロンプトリポジトリ」として活用します。プロンプトエンジニアが Langfuse でプロンプトのベースラインを作成・管理する一方で、Dify を利用する各開発メンバーも、 日々の開発やテストの中で改善したプロンプトや新しいアイデアを Update Prompt ツールを使い、自身の名前やタスクID を含む一時的なラベルを付けて Langfuse に新しいバージョンとして気軽に登録・共有できます。 これにより、個々の改善案が埋もれることなく Langfuse に集約され、チームレビューを経て正式なバージョンやラベル(例: stable, beta)に昇格させることが可能になります。Search Prompts ツールでこれらの試行錯誤中のプロンプトも検索・発見できるため、チーム全体の知見共有と開発効率が大幅に向上します。 これらのシナリオはほんの一例です。皆さんのアイデア次第で、このプラグインは Dify での LLM アプリケーション開発をさらに強力にサポートしてくれるはずです。 6. 制限事項と今後の展望 この Dify 向け Langfuse 連携プラグインをより快適にご利用いただくために、現時点での主な制限事項と、今後の機能拡張に関するアイデアについて触れておきます。 現時点での主な制限事項 本プラグインをご利用いただくにあたり、いくつかの制限事項がございます。主な点として、Get Prompt ツールと Update Prompt ツールは現状、Langfuse 上で text タイプとして保存されているプロンプトのみに対応しており、chat タイプは対象外です。また、Get Prompt ツールでラベルとバージョン番号の同時指定はできません。これは Langfuse API の仕様によるものです。 今後の展望に関するアイデア このプラグインをさらに便利にするためのアイデアとして、以下のような機能拡張が考えられます。これらは現時点での構想であり、将来的な実現をお約束するものではありませんが、可能性としてご紹介します。 カスタムトレースを定義できるオリジナル LLM ノードの作成 Langfuse へのトレース情報をより詳細かつ柔軟に送信できる、このプラグイン専用のLLM 実行ノードがあれば、Dify のワークフロー内での処理と Langfuse 上のトレースデータとの連携を一層深め、より高度な分析やデバッグに役立つかもしれません。 chat タイプのプロンプトへの対応 Get Prompt ツールが chat タイプのプロンプトにも対応するようになれば、Langfuse で管理できるプロンプトの形式が広がり、より多様な LLM アプリケーションの構築に貢献できる可能性があります。 Langfuse データセットとの連携 Langfuse のデータセット機能を Dify から活用できるようになれば、例えば、Langfuse に登録された評価用データセットを Dify から参照し、異なるプロンプトバージョンでの出力をバッチテストするようなワークフローを構築するなど、プロンプトの品質評価や改善サイクルをさらに効率化できるかもしれません。 7. まとめ 本記事では、Dify と Langfuse を連携させ、プロンプト管理を格段に向上させるカスタムプラグインをご紹介しました。このプラグインを利用することで、皆さんが普段お使いのDify の手軽なアプリケーション開発フローはそのままに、Langfuse が持つ堅牢なプロンプトのバージョン管理やトレーサビリティといった強力な機能の恩恵を受けることができます。これにより、プロンプト管理の煩雑さから解放され、品質向上、開発サイクルの迅速化、そしてチームでの効率的な共同作業が期待できます。 Dify での開発効率をさらに高めたい、あるいはプロンプト管理を本格的に導入したいとお考えの Dify ユーザーの皆様にとって、本プラグインはきっとお役に立てるはずです。 このプラグインはオープンソースソフトウェアとして GitHub で公開しています。ぜひ一度お試しいただき、その効果を実感していただければと思います。 実際にプラグインを使ってみたご感想、改善点のご提案、バグのご報告、さらには機能追加に関するプルリクエストなど、皆様からの積極的なフィードバックやコントリビューションを心より歓迎いたします。コミュニティと共に、このプラグインをより実践的で強力なツールへと育てていければ幸いです。 この記事が、皆さんの Dify と Langfuse を活用したLLMアプリケーション開発の一助となることを願っています。

  • 【実践】Model ArmorでVertex AIのAIセキュリティを実装する

    AIアプリのセキュリティ問題について AIを利用したアプリケーションが急速に普及する一方で、悪意あるプロンプトでAIをハッキングしようとする動きも出てきています。 これは、従来のSQLインジェクションのような攻撃と同様、またはそれ以上に深刻なリスクをもたらします。AIアプリケーションのセキュリティ対策の重要性が高まっている今、どのような対策を取ることが出来るか検討していきます。 アプリケーションのセキュリティ対策とModel Armorの選択 しかし対策と言っても、ハックの手法は日々進化しており、自力で全てに対応するのは困難です。また、アプリケーション本来の開発に専念したい開発者にとって、セキュリティ対策に多くの時間を割くのは悩ましい課題です。そこで、専門的なサービスを活用するのが最も効率的な解決策と言えます。 数あるサービスの中でも、Model Armorは現在、 Google Cloudサービスとの統合をPre-GA(一般提供開始前) で提供しており、これから本格的な活用が期待されます。Google Cloud Nextでも言及されており、Google Cloudのサービスとシームレスに統合できる点が大きな魅力です。 こうした背景から、今回はModel Armorを実際に試してみることにしました。 Model Armorの役割と機能 Model Armorは、AIアプリケーションと大規模言語モデル(LLM)の間に位置し、悪意ある入力をブロックするゲートウェイのような役割を果たします。自前で様々な対策を検討するのに比べ、簡単な設定を行うだけで、プロンプトインジェクションや機密情報の流出といったリスクへのセキュリティを強化できるのが大きな利点です。 特に、Google Cloudの主要なAIプラットフォームであるVertex AIとの連携が非常に容易なため、既存のサービスに手軽にセキュリティを加えたい開発者にとって、Model Armorは強力な選択肢となるでしょう。 実際の使用例:フロア設定とテンプレートの使い分け Model Armorには、プロジェクト全体に適用する フロア設定 と、より柔軟に制御できる テンプレート という2つの適用方法があります。それぞれの使い方と違いを、実際に試した例を交えて解説します。 フロア設定を使ってみる まずは、最も手軽な フロア設定 から試してみました。これは、特定のプロジェクト全体にModel Armorのポリシーを適用する設定です。 以下の公式ドキュメントを参考に設定を進めました。 参考: Model Armor のフロア設定を構成する フロア設定 今回は、以下の構成で実験してみます。 利用したコード 今回の検証用に作成した、モデルにリクエストを送信する際の簡易的なコードです。 from google import genai from google.genai import types import os from dotenv import load_dotenv load_dotenv() project_id = os.getenv("GOOGLE_CLOUD_PROJECT") client = genai.Client( vertexai = True, project = project_id, location = 'us-central1', http_options = types.HttpOptions() ) response = client.models.generate_content( model="gemini-2.5-flash-lite", contents="プロンプト" ) 結果 アプリケーションのレスポンスに検出結果は含まれておらず、これまでの結果とあまり違いは見られませんでしたが、 Cloud Logging には検出結果が出力されていました。 該当のログは以下のフィルターで簡単に絞り込めます。 jsonPayload.@type="type.googleapis.com/google.cloud.modelarmor.logging.v1.SanitizeOperationLogEntry" 確認したところ、今回の実験に利用したプロンプトは、特に問題が無いものとして認識されているようですが、何かしら検査が行われたことはログとして出力されています。 機密データの保護 - 検出タイプを「高度」に変更し、DLPで作成した検出・匿名化テンプレートを設定してみました。 以下の通り、ログ上では機密情報が自動的に[PERSON_NAME]のように匿名化されることを確認しました。 ([PERSON_NAME] を検出・匿名化対象としているため、氏名部分が置換されている) PIIが検出される状態において、Vertex AI の種類を「違反を検出し、ブロックする」に変更するとどうなるか確認してみます。 $python test.py xxxxxxxxxxx/google/genai/_common.py:474: UserWarning: MODEL_ARMOR is not a valid BlockedReason warnings.warn(f"{value} is not a valid {cls.__name__}") SDK(google-genai:v1.33.0)を使用した場合、UserWarning が発生しました。Pre-GAのため、SDKがまだ対応されていないものと思われます。 レスポンスからブロックされた理由を確認します。 # print(response.prompt_feedback.block_reason) BlockedReason.MODEL_ARMOR # print(response.prompt_feedback.block_reason_message) Blocked by Model Armor Floor Setting: The prompt violated SDP/PII filters. ログとしては、「検査のみ」の場合とほぼ同内容になっているようです。 ブロックする場合はエラーハンドリングが必要にはなりますが、検査のみであれば、アプリケーションのコードには一切の変更を行うことなく導入できる点は、既存のアプリケーションにModel Armorを導入する上で非常に大きなメリットだと感じました。 Model Armorのテンプレートを使ってみる 次に、よりきめ細かな制御が可能な テンプレート を試してみました。 テンプレートは、特定のAIリクエストに対して個別のセキュリティポリシーを適用したい場合に便利です。 実際に利用できる場面としては、プロジェクト内で複数のAIアプリケーションを運用している場合に、それぞれのユースケースに合わせて異なるセキュリティポリシーを適用したい場合に特に有効です。 テンプレートの作成は Model Armor templates console を参考に実施しました。 フロア設定と概ね同じ内容ですが、ブロックを行うか、検査のみとするかが選べない点がフロア設定との大きな差異です。 なお、テンプレートは フロア設定よりも厳しい制限 でなければ保存できません。 既にフロア設定が作成されている場合、フロア設定より緩い制限で保存しようとするとエラーが発生しました。 アプリケーションへの組み込み API のリファレンスを見る限りでは、generate_content にModel Armor のテンプレートを利用する設定を追加するだけでよさそうです。しかし、現時点(2025/09)ではSDKに該当の設定を追加することが出来ませんでした。 そのため、こちらは Python から curl でリクエストを飛ばす形にして検証しました。 テンプレートの適用は、通常のVertex AIへのリクエストに model_armor_config という設定を追加するだけです。 ただし、テンプレートを利用するには、事前にVertex AIのサービスアカウントに Model Armor ユーザー のロールを追加しておく必要があります。 結果 レスポンスとして、以下が返ってきました。フロア設定とあまり違いは無さそうですが、検出結果は即座に ブロック として返されました。 { "promptFeedback": { "blockReason": "MODEL_ARMOR", "blockReasonMessage": "The prompt violated SDP/PII filters." }, "usageMetadata": { "trafficType": "ON_DEMAND" } "modelVersion": "gemini-2.5-flash-lite", # 一部省略 } Cloud Loggingを確認してみます。 ログの内容としても、フロア設定の場合とあまり大きな差は無さそうです。 フロア設定が優先されて、テンプレートが適応されていない可能性も考えましたが、 resource.labels.template_id に利用したテンプレートのID(model-armor-test-template)が出力されていたため、問題なく適応されていると判断しています。 "resource": { "type": "modelarmor.googleapis.com/SanitizeOperation", "labels": { "location": "[location]", "resource_container": "projects/[project_number]", "template_id": "model-armor-test-template" } }, なお、フロア設定が適応されている場合は以下の出力でした。 "resource": { "type": "modelarmor.googleapis.com/SanitizeOperation", "labels": { "template_id": "FLOOR_SETTING--[n]" "resource_container": "projects/[project_number]", "location": "global" } } まとめと今後の展望 Model Armorのフロア設定は、既に運用中のAIアプリケーションにセキュリティ対策を導入する際に非常に手軽で強力な手段です。しかし、Pre-GAということもあり、SDKの修正が追い付いていない等、公開するアプリケーションで採用するには悩ましい点も少なくありません。 今後の正式リリースやアップデートで、より柔軟な設定が可能になることを期待し、引き続き動向を注視していきたいと思います。

  • Langfuseで解決する 自動化ツール n8n のプロンプト課題

    本記事は こちらの記事 の続編 (アップデート版) となります。 (改めて) n8nとは何か、プロンプト管理の課題 n8nは「nodemation」の略称で、ドラッグ&ドロップ操作や各ノードの設定によってワークフローを作成できる自動化ツールです。300以上の組み込みノードを提供しており、Slack、Gmail、Notion、カレンダー、Webhookなど、様々なサービスとの連携が可能です。コードを書くことなく複雑な自動化フローを構築できる一方で、JavaScript や最近はPython を使用したコードの実行をフローに埋め込むことなどにも対応しています。  同ツールを使用することで簡単に生成AIをワークフローに繋ぎ込むことができますが、 プロンプト管理の機構が n8n には存在しておらず (筆者が観測する限り)、フローごとに作るなどの手間が発生してしまいます。 Langfuseとの連携 その課題に解決すべく 前回の記事 では簡易的なコードで解決していましたが、記事を書いた直後に公式ノードがリリースされましたので、本ブログはそちらをご紹介します。 前回記事を書いた1週間後に Langfuse CEO の Marc が公式版を作り始めた 前回の構成はこちらでしたが、 Code ノードを公式のノードに置き換えます。 前回の記事の n8n フロー ゴールはこのような構成です。 Code を Langfuse公式の Get a prompt に変更 まず、任意の場所で Add node -> Langfuseを検索 -> Install してください。 右クリックなどして画面を表示。 検索欄に "Langfuse"と入れる 右上の "Install node" そうすると Langfuseノードがキャンパス上にできますので、クリックして中にキーを Credential と 取得する Prompt 名を指定しましょう。 Credentialのところをクリックで新規作成 利用する時には Langfuseノードを前後のノードと接続するだけです。例えば AI Agentノードと接続すると、このような画面でプロンプトが見えておりますので、それをSystem promptなどに指定するだけです。(User promptももちろん必要です) 左側のPromptから Drag & Drop で変数を入れるだけ テストでチャットしてみると、ちゃんとプロンプトを認識して動作してくれます。 そして前回同様に Trace も正常に反映されています。 プロンプトが反映された回答がきている Trace画面 いかがでしたでしょうか。今回のアップデートにより、さらに簡単に n8n からプロンプト管理ができるようになります。また前の記事のように簡単にTraceを取ることもできますので、n8n で生成AIを管理する方はぜひ試してしてみてください。

  • TerraformでLiteLLM ProxyをGoogle Cloud上に構築する

    はじめに 近年、LLMアプリケーションの開発において、複数のLLMプロバイダーを使い分ける必要性が高まっています。 OpenAIのGPT、AnthropicのClaude、GoogleのGeminiなど、それぞれ異なる特徴を持っており、それぞれの特徴に合わせてLLMを使い分けるケースも多くあります。 また、コスト最適化の観点から、簡単なタスクには安価なモデルを、複雑なタスクには高性能なモデルを使い分けることも重要です。 しかし、従来のアプローチでは各LLMプロバイダーごとに異なるAPI仕様に対応する必要があり、APIキーの分散管理、使用量・コストの把握困難、アプリケーション側での複雑な実装といった課題がありました。 LiteLLM Proxyは、複数のLLMプロバイダー利用に伴うAPI仕様の違いや管理の煩雑さといった課題を解決するLLM Gatewayソリューションです。 統一されたAPIインターフェースを提供することで、開発者は一つのエンドポイントからOpenAIやGoogle Geminiなど様々なLLMプロバイダーへシームレスにアクセスできます。 また、自動的な負荷分散やフェイルオーバー機能により高い可用性を確保し、詳細な使用量・コストの監視機能によって効率的なリソース管理も実現します。 さらに、LiteLLM ProxyはLangfuseとの連携によるトレース送信機能も標準で備えています。 簡単な設定を行うだけで、各LLMリクエストのトレースログを自動的にLangfuseへ送信し、可観測性や運用分析を強化できます。 LiteLLM Proxyの主要な特徴 LiteLLM Proxyは、複数のLLMプロバイダーを統一的に管理できるプロキシサーバーです。主な特徴は以下の通りです: 複数プロバイダー統合: OpenAI、Anthropic、Google、Azure OpenAIなど、様々なLLMプロバイダーを統一APIで利用可能 コスト・使用量管理: リクエスト数、トークン使用量、コストの追跡と制限設定 APIキー管理: 複数のAPIキーを一元管理し、セキュアに配布 負荷分散・フェイルオーバー: 複数のエンドポイント間でのリクエスト分散と自動フェイルオーバー ログ・監視: 詳細なリクエストログとメトリクスの収集 Langufseへのログ送信も可能 本記事の全体像 本記事では以下について解説します。 Terraformを使用して、Google Cloud上にLiteLLM Proxyを構築する手順を解説する LiteLLM Proxyを使用して複数のLLMプロバイダーへのアクセスを試す LiteLLM ProxyのWeb UIでの管理画面を確認する LiteLLM Proxyを利用した LLMプロバイダーアクセスのTrace情報がLangfuseへ送信されていることの確認する 構築する全体構成 以下の構成図に示すとおり、Google Cloud上にLiteLLM Proxy環境を構築します。 ※本構成は必要最小限の構成になっております。セキュリティ強度を高める場合はCloud Runサービスの前段に外部アプリケーションロードバランサーを構成し、IAPやCloud Armorを利用ください。 LiteLLM Proxy構成図 この構成では以下の要素を組み合わせています: LiteLLM Proxy Cloud Run上でコンテナとして稼働する中核コンポーネント Secret Manager: APIキーの安全な管理 Cloud SQL: プロキシの設定データとメタデータの保存 ※以下のコンポーネントの構築・準備手順はこの記事では割愛します。 Langfuse : LLMリクエストの可観測性とパフォーマンス監視  複数LLMプロバイダー: OpenAI、Googleへの統一アクセス 次のセクションから、実際の構築手順を解説していきます。 ※ 本記事ではTerraformのインストールやGoogle Cloud プロジェクトの作成等は割愛いたします。 Terraformによるインフラ構築 TerraformによりGoogle Cloud上にLiteLLM Proxy環境を構築します。 Terraformファイル構成 LiteLLM Proxy環境の構築に必要な主要ファイルを以下のように構成します . ├── main.tf # メインの設定ファイル(プロバイダー設定、リソース定義) ├── variables.tf # 変数定義(カスタマイズ可能な値) ├── outputs.tf # 出力値(デプロイ後に必要な情報) └── terraform.tfvars # 変数の実際の値(プロジェクト名や環境変数値を設定) 下記のとおり、`main.tf` `variables.tf` `outputs.tf` `terraform.tfvars` ファイルを作成します。 コードブロックは展開して確認ください。 メインリソース(`main.tf`) # Googleプロバイダー、プロジェクト設定 terraform { required_version = ">= 0.14" required_providers { google = { source = "hashicorp/google" version = "~> 6.0" } random = { source = "hashicorp/random" version = "~> 3.1" } } } provider "google" { project = var.project_id region = var.region zone = var.zone } # 変数とランダム値生成(セキュリティ関連) # Random password for database resource "random_password" "db_password" { length = 16 special = false upper = true lower = true numeric = true } # Random master key for LiteLLM resource "random_password" "master_key" { length = 32 special = true upper = true lower = true numeric = true } # Random password for LiteLLM UI resource "random_password" "ui_password" { length = 16 special = true upper = true lower = true numeric = true } # Random salt key for LiteLLM resource "random_string" "salt_key" { length = 16 special = false upper = true lower = true numeric = true } # Random prefix for database name resource "random_id" "db_prefix" { byte_length = 4 } # Local values - 条件分岐により変数またはランダム値を使用 locals { db_password = var.db_password != null ? var.db_password : random_password.db_password.result master_key = var.master_key != null ? var.master_key : random_password.master_key.result ui_password = var.ui_password != null ? var.ui_password : random_password.ui_password.result salt_key = var.salt_key != null ? var.salt_key : random_string.salt_key.result vertex_project_id = var.vertex_project_id != null ? var.vertex_project_id : var.project_id } # Google Project Servicesの有効化 resource "google_project_service" "required_apis" { for_each = toset([ "compute.googleapis.com", "run.googleapis.com", "sqladmin.googleapis.com", "secretmanager.googleapis.com", "vpcaccess.googleapis.com", "aiplatform.googleapis.com", ]) project = var.project_id service = each.value disable_dependent_services = false disable_on_destroy = false } # ネットワーク(VPC/サブネット/プライベート接続) resource "google_compute_network" "litellm_vpc" { name = "litellm-vpc" auto_create_subnetworks = false } resource "google_compute_subnetwork" "cloudrun_egress" { name = "cloudrun-egress" ip_cidr_range = "10.0.2.0/24" network = google_compute_network.litellm_vpc.id region = var.region private_ip_google_access = true } resource "google_compute_global_address" "psa_subnet" { name = "psa-subnet" purpose = "VPC_PEERING" address_type = "INTERNAL" prefix_length = 16 network = google_compute_network.litellm_vpc.id } resource "google_service_networking_connection" "private_service_connection" { network = google_compute_network.litellm_vpc.id service = "servicenetworking.googleapis.com" reserved_peering_ranges = [google_compute_global_address.psa_subnet.name] } # Cloud SQL(PostgreSQLデータベース) resource "google_sql_database_instance" "litellm_db_01" { database_version = "POSTGRES_16" name = "litellm-db-${random_id.db_prefix.hex}-1" region = var.region project = var.project_id settings { activation_policy = "ALWAYS" availability_type = "ZONAL" disk_autoresize = false disk_size = 10 disk_type = "PD_SSD" edition = "ENTERPRISE" pricing_plan = "PER_USE" tier = "db-f1-micro" ip_configuration { ipv4_enabled = false private_network = google_compute_network.litellm_vpc.id enable_private_path_for_google_cloud_services = true } location_preference { zone = var.zone } } deletion_protection = false depends_on = [google_service_networking_connection.private_service_connection] } resource "google_sql_database" "litellm_db" { name = "litellm_db" instance = google_sql_database_instance.litellm_db_01.name project = var.project_id } resource "google_sql_user" "litellm_user" { name = "litellm_user" instance = google_sql_database_instance.litellm_db_01.name password = local.db_password project = var.project_id } # Secret Manager(機密情報管理) resource "google_secret_manager_secret" "litellm_master_key" { secret_id = "litellm-master-key" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "litellm_master_key" { secret = google_secret_manager_secret.litellm_master_key.id secret_data = local.master_key } resource "google_secret_manager_secret" "litellm_database_url" { secret_id = "litellm-database-url" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "litellm_database_url" { secret = google_secret_manager_secret.litellm_database_url.id secret_data = "postgresql://${google_sql_user.litellm_user.name}:${local.db_password}@${google_sql_database_instance.litellm_db_01.private_ip_address}:5432/${google_sql_database.litellm_db.name}" } resource "google_secret_manager_secret" "litellm_ui_username" { secret_id = "litellm-ui-username" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "litellm_ui_username" { secret = google_secret_manager_secret.litellm_ui_username.id secret_data = var.ui_username } resource "google_secret_manager_secret" "litellm_ui_password" { secret_id = "litellm-ui-password" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "litellm_ui_password" { secret = google_secret_manager_secret.litellm_ui_password.id secret_data = local.ui_password } resource "google_secret_manager_secret" "litellm_salt_key" { secret_id = "litellm-salt-key" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "litellm_salt_key" { secret = google_secret_manager_secret.litellm_salt_key.id secret_data = local.salt_key } resource "google_secret_manager_secret" "litellm_proxy_admin_id" { secret_id = "litellm-proxy-admin-id" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "litellm_proxy_admin_id" { secret = google_secret_manager_secret.litellm_proxy_admin_id.id secret_data = var.proxy_admin_id } resource "google_secret_manager_secret" "langfuse_public_key" { secret_id = "langfuse-public-key" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "langfuse_public_key" { secret = google_secret_manager_secret.langfuse_public_key.id secret_data = var.langfuse_public_key } resource "google_secret_manager_secret" "langfuse_secret_key" { secret_id = "langfuse-secret-key" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "langfuse_secret_key" { secret = google_secret_manager_secret.langfuse_secret_key.id secret_data = var.langfuse_secret_key } resource "google_secret_manager_secret" "litellm_config_yaml" { secret_id = "litellm-config-yaml" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "litellm_config_yaml" { secret = google_secret_manager_secret.litellm_config_yaml.id secret_data = var.litellm_config_yaml } # OpenAI API Key Secret resource "google_secret_manager_secret" "openai_api_key" { secret_id = "openai-api-key" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "openai_api_key" { secret = google_secret_manager_secret.openai_api_key.id secret_data = var.openai_api_key } # Cloud Run Service resource "google_cloud_run_v2_service" "litellm_proxy" { name = "litellm-proxy" location = var.region project = var.project_id ingress = "INGRESS_TRAFFIC_ALL" invoker_iam_disabled = true template { execution_environment = "EXECUTION_ENVIRONMENT_GEN2" dynamic "volumes" { for_each = var.litellm_config_yaml != null ? [1] : [] content { name = "litellm-config" secret { secret = google_secret_manager_secret.litellm_config_yaml.secret_id items { path = "litellm_config.yaml" version = "latest" } } } } containers { # LiteLLM Proxy image from Docker Hub image = "docker.io/litellm/litellm:${var.litellm_version}" name = "litellm-proxy" args = var.litellm_config_yaml != null ? [ "--config", "/etc/litellm/litellm_config.yaml" ] : [] ports { container_port = 4000 name = "http1" } # Environment variables env { name = "LITELLM_LOG_LEVEL" value = "INFO" } env { name = "STORE_MODEL_IN_DB" value = "True" } env { name = "LANGFUSE_PUBLIC_KEY" value_source { secret_key_ref { secret = google_secret_manager_secret.langfuse_public_key.secret_id version = "latest" } } } env { name = "LANGFUSE_SECRET_KEY" value_source { secret_key_ref { secret = google_secret_manager_secret.langfuse_secret_key.secret_id version = "latest" } } } env { name = "LANGFUSE_HOST" value = var.langfuse_host } # Secret Manager references env { name = "DATABASE_URL" value_source { secret_key_ref { secret = google_secret_manager_secret.litellm_database_url.secret_id version = "latest" } } } env { name = "LITELLM_MASTER_KEY" value_source { secret_key_ref { secret = google_secret_manager_secret.litellm_master_key.secret_id version = "latest" } } } env { name = "UI_PASSWORD" value_source { secret_key_ref { secret = google_secret_manager_secret.litellm_ui_password.secret_id version = "latest" } } } env { name = "UI_USERNAME" value_source { secret_key_ref { secret = google_secret_manager_secret.litellm_ui_username.secret_id version = "latest" } } } env { name = "LITELLM_SALT_KEY" value_source { secret_key_ref { secret = google_secret_manager_secret.litellm_salt_key.secret_id version = "latest" } } } env { name = "PROXY_ADMIN_ID" value_source { secret_key_ref { secret = google_secret_manager_secret.litellm_proxy_admin_id.secret_id version = "latest" } } } # OpenAI API Key (conditional) dynamic "env" { for_each = var.openai_api_key != null ? [1] : [] content { name = "OPENAI_API_KEY" value_source { secret_key_ref { secret = google_secret_manager_secret.openai_api_key.secret_id version = "latest" } } } } # Resource limits resources { limits = { cpu = "1000m" memory = "1Gi" } startup_cpu_boost = true cpu_idle = true } dynamic "volume_mounts" { for_each = var.litellm_config_yaml != null ? [1] : [] content { name = "litellm-config" mount_path = "/etc/litellm" } } # Health check startup_probe { failure_threshold = 6 http_get { path = "/" port = 4000 } initial_delay_seconds = 60 period_seconds = 10 timeout_seconds = 10 } } # Scaling configuration scaling { max_instance_count = 1 min_instance_count = 0 } max_instance_request_concurrency = 80 service_account = google_service_account.lite_llm_proxy.email timeout = "300s" vpc_access { network_interfaces { network = google_compute_network.litellm_vpc.id subnetwork = google_compute_subnetwork.cloudrun_egress.id } egress = "PRIVATE_RANGES_ONLY" } } traffic { percent = 100 type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST" } deletion_protection = false depends_on = [ google_project_service.required_apis, google_sql_user.litellm_user, google_service_account.lite_llm_proxy, google_project_iam_member.litellm_proxy_cloudsql_client, google_project_iam_member.litellm_proxy_secret_accessor, ] } # IAM for Cloud Run service # Cloud Run IAM for load balancer access resource "google_cloud_run_service_iam_member" "litellm_proxy_public" { service = google_cloud_run_v2_service.litellm_proxy.name location = google_cloud_run_v2_service.litellm_proxy.location project = google_cloud_run_v2_service.litellm_proxy.project role = "roles/run.invoker" member = "allUsers" } # Vertex AI permission for Cloud Run service account resource "google_project_iam_member" "litellm_proxy_vertex_user" { project = local.vertex_project_id role = "roles/aiplatform.user" member = "serviceAccount:${google_service_account.lite_llm_proxy.email}" } resource "google_service_account" "lite_llm_proxy" { account_id = "lite-llm-proxy" description = "LiteLLM Proxy Service Account" display_name = "LiteLLM Proxy" project = var.project_id } # IAM binding for service account resource "google_project_iam_member" "litellm_proxy_cloudsql_client" { project = var.project_id role = "roles/cloudsql.client" member = "serviceAccount:${google_service_account.lite_llm_proxy.email}" } resource "google_project_iam_member" "litellm_proxy_secret_accessor" { project = var.project_id role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.lite_llm_proxy.email}" } 変数定義(`variables.tf`) variable "project_id" { description = "GCP Project ID" type = string } variable "region" { description = "GCP Region" type = string default = "asia-northeast1" } variable "zone" { description = "GCP Zone" type = string default = "asia-northeast1-a" } variable "db_password" { description = "Database password for LiteLLM user (optional)" type = string default = null sensitive = true } variable "master_key" { description = "LiteLLM master key (optional)" type = string default = null sensitive = true } variable "ui_username" { description = "UI username for LiteLLM admin" type = string default = "admin" } variable "ui_password" { description = "UI password for LiteLLM admin (optional)" type = string default = null sensitive = true } variable "salt_key" { description = "Salt key for LiteLLM (optional)" type = string default = null sensitive = true } variable "proxy_admin_id" { description = "Proxy admin ID" type = string default = "admin" } variable "langfuse_public_key" { description = "Langfuse public key" type = string default = null sensitive = true } variable "langfuse_secret_key" { description = "Langfuse secret key" type = string default = null sensitive = true } variable "langfuse_host" { description = "Langfuse host URL" type = string default = null } variable "vertex_project_id" { description = "Project ID where Vertex AI is used" type = string default = null } variable "litellm_config_yaml" { description = "Contents of litellm_config.yaml (optional)" type = string default = null sensitive = true } variable "litellm_version" { description = "LiteLLM container image version" type = string default = "v1.75.8-stable" } variable "openai_api_key" { description = "OpenAI API key (optional)" type = string default = null sensitive = true } 出力値定義(`outputs.tf`) output "cloud_run_url" { description = "Cloud Run service URL" value = google_cloud_run_v2_service.litellm_proxy.uri } output "database_private_ip" { description = "Cloud SQL instance private IP" value = google_sql_database_instance.litellm_db_01.private_ip_address } output "database_connection_name" { description = "Cloud SQL instance connection name" value = google_sql_database_instance.litellm_db_01.connection_name } output "database_name" { description = "LiteLLM database name" value = google_sql_database.litellm_db.name } output "service_account_email" { description = "LiteLLM Proxy service account email" value = google_service_account.lite_llm_proxy.email } 変数入力ファイル(`terraform.tfvars`) # Google CloudのプロジェクトID、リージョン、ゾーンを入力してください project_id = "your-project-id" region = "asia-northeast1" zone = "asia-northeast1-a" # Langfuse settings(LLM可観測性プラットフォーム) # Langfuse のパブリックキーとシークレットキー、ホストURLを入力してください langfuse_public_key = "pk-lf-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" langfuse_secret_key = "sk-lf-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" langfuse_host = "https://your-langfuse-instance.com" # LiteLLM Container version litellm_version = "v1.75.8-stable" # OpenAI API Key # OpenAI を利用する場合はOpenAI API Keyを設定してください。 openai_api_key = "sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # LiteLLM設定ファイル # LiteLLM設定は litellm_config.yaml に設定します。以下を参考に設定してください。 # Vertex AIでGeminiを利用する場合はproject_id,locationの設定、OpenAIを利用する場合はOpenAI API Keyの設定が必要な点を注意ください。 # サンプル設定を以下に示します。 litellm_config_yaml = <<EOT model_list: # Vertex AI Geminiモデル # Vertex AI を利用する場合はVertex AI Project ID,Locationを設定してください。 - model_name: "gemini-pro" litellm_params: model: "vertex_ai/gemini-1.5-pro-002" vertex_project: "your-project-id" vertex_location: "asia-northeast1" - model_name: "gemini-flash" litellm_params: model: "vertex_ai/gemini-1.5-flash-002" vertex_project: "your-project-id" vertex_location: "asia-northeast1" # OpenAIモデル(API Key設定時のみ) # OpenAI を利用する場合はOpenAI API Keyを設定してください。 - model_name: "gpt-4.1" litellm_params: model: "openai/gpt-4.1" api_key: os.environ/OPENAI_API_KEY - model_name: "gpt-4.1-mini" litellm_params: model: "openai/gpt-4.1-mini" api_key: os.environ/OPENAI_API_KEY litellm_settings: success_callback: ["langfuse"] failure_callback: ["langfuse"] EOT Terraformの実行手順 下記の手順に従って、Terraformを実行LiteLLM Proxy環境を構築します。 1. Terraform 初期化 # 作業ディレクトリに移動 cd /path/to/your/terraform # Terraform初期化 terraform init 2. 実行計画の確認 # 実行計画を確認 terraform plan 3. リソースの作成 # リソース作成実行 terraform apply # 確認プロンプトで "yes" を入力 4. デプロイの確認 # 出力値の確認 terraform output 次のセクションでは、デプロイされたLiteLLM ProxyのLLM Gateway機能を実際に試行してみます。 LLM Gateway機能の試行 LiteLLM Proxyがデプロイされたら、実際にLLM Gateway機能をテストしてみましょう。 基本的な動作確認 1. ヘルスチェック # Cloud RunのURL(outputsのcloud_run_urlで確認) CUSTOM_DOMAIN=$(terraform output -raw cloud_run_url) # ヘルスチェック curl -sS -X GET "${CUSTOM_DOMAIN}/" -o /dev/null -w "%{http_code}\n" # 200 が返ってくれば正常 2. 利用可能なモデル一覧の取得 # マスターキーを取得(実際の運用では環境変数に設定) MASTER_KEY=$(gcloud secrets versions access latest --secret="litellm-master-key") # モデル一覧を取得(ALB経由) curl -X GET "${CUSTOM_DOMAIN}/v1/models" \ -H "Authorization: Bearer ${MASTER_KEY}" \ -H "Content-Type: application/json" # モデル一覧が返ってくれば正常 OpenAI互換APIでのテスト LiteLLM ProxyはOpenAI APIと互換性があるため、OpenAIクライアントと同様に使用できます。 1. Chat Completions API(GPT-4.1-mini) # GPT-4.1-miniを使用したチャット curl -X POST "${CUSTOM_DOMAIN}/v1/chat/completions" \ -H "Authorization: Bearer ${MASTER_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4.1-mini", "messages": [ { "role": "user", "content": "Hello! Please introduce yourself." } ], "max_tokens": 200, "temperature": 0.7 }' 2. Chat Completions API(Google Gemini Flash) # Gemini Flashを使用したチャット curl -X POST "${CUSTOM_DOMAIN}/v1/chat/completions" \ -H "Authorization: Bearer ${MASTER_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "gemini-flash", "messages": [ { "role": "user", "content": "What are the advantages of using Google Cloud Run for hosting AI services?" } ], "max_tokens": 200, "temperature": 0.3 }' Web UIでの管理機能確認 LiteLLM ProxyにはWeb UIでの管理画面が用意されており、ブラウザで管理できます。 1. UIへのアクセス 以下のコマンドでUIへのアクセス情報が確認できます。 # UI認証情報を取得 UI_USERNAME=$(gcloud secrets versions access latest --secret="litellm-ui-username") UI_PASSWORD=$(gcloud secrets versions access latest --secret="litellm-ui-password") echo "UI URL: $(terraform output -raw cloud_run_url)/ui" echo "Username: ${UI_USERNAME}" echo "Password: ${UI_PASSWORD}" # 以下のように出力される UI URL: https://litellm-proxy-xxxxxxxx.a.run.app/ui Username: xxxxx Password: xxxxx 出力された情報をもとにWeb UIへログインします。 LiteLLM ログイン画面 2. UIで確認できる機能 LiteLLM のWeb UIからは以下のような情報が確認できます。 詳しくは公式ドキュメントを参照ください。( LiteLLM公式ドキュメントリンク ) リクエスト統計: 各モデルの使用回数、成功/失敗率 コスト追跡 : プロバイダー別のコスト情報 使用量制限 : ユーザーやAPIキー別の制限設定 ログ表示 : リアルタイムのリクエストログ モデル管理 : 利用可能モデルの一覧と設定 LiteLLM Web UI画面 LangfuseでのTrace確認 Langfuseでは、LiteLLM Proxyを利用した各種トレース情報が確認できます。 本手順で設定したLangfuseホストにアクセスし、Traceを確認します。 OpenAIのTrace確認 上記のCurlコマンドで確認したOpenAI/GTP へのアクセスのTraceを確認します。 Langfuse - OpenAI Trace確認 GeminiのTrace確認 上記のCurlコマンドで確認したVertexAI/Gemini へのアクセスのTraceを確認します。 Langfuse - Gemini Trace確認 まとめ 構築の利点の再確認 今回のTerraformとGoogle Cloud Runを使用したLiteLLM Proxy構築により、以下の利点が実現できました 1. 運用・管理面での利点 コスト一元管理: 複数のLLMプロバイダーのコストを統合して追跡・制御 APIキー管理: Secret Managerによる安全な機密情報管理 統一API: OpenAI互換APIによる既存コードの再利用性 2. インフラ面での利点 サーバーレス: インフラ管理不要で自動スケーリング 高可用性: Cloud Runのマネージドサービス利用 監視: Cloud Loggingによる包括的なログ管理 おわりに 本記事では、TerraformとGoogle Cloud Runを用いて、Google Cloud上にLiteLLM Proxyを構築する手順を解説しました。 構築したLiteLLM Proxyは、複数のLLMプロバイダーを一元的に管理し、コストや使用量を効率的に制御できる強力なLLMゲートウェイとして機能します。また、Cloud Runのサーバーレスな特性により、運用負荷を最小限に抑えつつ高い可用性を実現できます。 本記事では触れませんでしたが、LiteLLM Proxyにはコスト管理やLLMプロバイダーのフォールバック機能など、LLMアプリケーションの開発・運用に役立つ機能が他にも備わっています。 ぜひ、LiteLLM Proxyを活用して、効率的で柔軟なLLMアプリケーション開発環境を構築してみてください!

  • Langfuseの新しい連携元「mcp-use」

    こんにちは。ガオ株式会社の橘です。 今回は、Langfuseに加わった新しい連携元「mcp-use」と、その連携方法や内容についてご紹介します。 要約 mcp-useにLangfuse連携機能が搭載された これにより、mcp-useで構築したLLM Agentから、Langfuseに簡単にtrace連携を行い、分析・運用ができるようになった 簡単な設定で、LLM Agentの動作ログがLangfuseに連携できる 連携されたtraceの例 mcp-useとは mcp-useとは、mcp-use社が提供している、LLM agentの構築ライブラリです。たった数行のコード記述で、MCPサーバと接続してその機能を利用するLLM agentが構築できると謳われています。 2025年8月現在では、Python版とTypeScript版が提供されています。 詳しくは 公式ページ やGithubのページ( Python版 、 TypeScript版 )をご参照ください。 Langfuse連携の設定方法 以下の準備を行います。 Langfuse側 traceを連携する連携先organizationsとprojectを用意する 既存のものを使うか、新規に作成するかは要件に合わせて選択 手順は こちらの記事 等を参照 APIKEYの発行を行い、「public key, secret key, hostnameの値」を控える 手順は こちらの記事 等を参照 mcp-use側 mcp-useを使ったLLM Agentを構築する 我々は動作確認に、 公式が公開 しているquickstartを利用 1画面に収まるPythonコードで、MCPサーバを活用する自律推論LLM Agentが構築できるのはなかなか凄い 以下の環境変数に、Langfuse側で発行した(=控えておいた)値を設定する 環境変数名 設定する値 LANGFUSE_PUBLIC_KEY Langfuse側で発行したpublic key LANGFUSE_SECRET_KEY Langfuse側で発行したsecret key LANGFUSE_HOST Langfuseが稼働しているホスト名 基本的な準備は以上です。あとは、mcp-useが動作する環境からLangfuseが待ち受ける環境に対してHTTPS接続できる必要があるため、必要に応じてそのための設定(ファイアウォール設定変更等)を実施します(環境に大きく依存するため、本記事では割愛します)。 trace連携と結果確認 前述の準備をしてあれば、あとはmcp-useで作成したLLM Agentを動作させるだけで、自動的に(APIKEYを発行した organizations & project に)traceが連携されます。 これにより、LLM Agentがどのような入力に対し、どのMCPサーバを選択し、どんなやりとりをしたうえでユーザへの返答を構成したかが簡単にわかるようになります。また、時系列に沿ってtraceを追え、かつそれぞれの処理時間も可視化されるため、処理時間の長い応答のボトルネック調査なども容易に行うことができます。 MCP tool選択を行った際のtrace例 まとめ mcp-useがLangfuse連携に対応したことで、簡単にLLM Agentの挙動を確認できるようになりました。 これからLLM Agentを構築しようとしている方は、mcp-useとLangfuseの組み合わせも検討してみてはいかがでしょうか? 備考 LLM Agentの動作には、別途LLM APIを利用するためのAPIKEY等が必要です。詳細はmcp-useのドキュメントをご参照ください。 今回我々が検証したのはPython版のmcp-useのみで、TypeScript版mcp-useは未検証です。 mcp-useがLangfuse連携対応しているのは2025/08/19にリリースされたv1.3.10からなので、古いバージョンのmcp-useを使っている方は、まずmcp-useのアップデートを実施してください。 Langfuseのサイトでは「利用するための前提条件としてmcp-useのアカウントが必要」とありますが、2025/08/21現在ではmcp-useのアカウントを作らなくともtrace連携は問題なく動作しました。

  • GeminiコンテキストキャッシュとLangfuseで実現するコスト監視

    はじめに GoogleのVertex AI Geminiが提供するコンテキストキャッシュ機能は、大量のコンテキストを再利用することで、APIコストを大幅に削減できる強力なツールです。しかし、実際にどの程度のコスト削減効果があるのかを可視化するには、一手間加える必要があります。 本記事では、オープンソースのLLM監視プラットフォームであるLangfuseとGeminiのコンテキストキャッシュを組み合わせ、キャッシュ効果を定量的に監視し、コストの内訳を詳細に把握する方法を解説します。 必要なツール Google Cloud Platform: Vertex AI API 有効化済み Langfuse : OpenSourceの LLM Engineering Platform Python 実行環境: uv または pip でパッケージ管理 実現できること Before(従来) キャッシュを利用しているものの、具体的な効果が不明確  コストの内訳(通常入力 vs キャッシュ)が見えない レイテンシ改善効果が分からない After(本手法) 📊 Langfuse ダッシュボード表示例: ├── 入力トークン: 15 → $0.0000045 ├── キャッシュトークン: 1,189 → $0.0000891 ├── 出力トークン: 1,291 → $0.0032275 ├── 合計コスト: $0.0033211 └── レイテンシ: 自動測定 実装手順 ステップ 1: Langfuse でモデル定義を設定 ※ 今回は、gemini-2.5-flashを利用する前提で手順の説明をします。 1. Langfuse ダッシュボード にログイン 2. Settings > Modelsでgemini-2.5-flashを探し、右のCloneを選択 3. モデルにキャッシュの価格を追加する 今回は、gemini-2.5-flashにcached_tokensとして$0.000000075(2025年8月時点の金額)と入力 ステップ 2: コンテキストキャッシュを作成 ライブラリインストール google-generativeai langfuse python-dotenv 環境変数定義 (envファイル作成) ### .env LANGFUSE_PUBLIC_KEY=[パブリックキー] LANGFUSE_SECRET_KEY=[シークレットキー] LANGFUSE_HOST=[langfuseのホストURL] GOOGLE_CLOUD_PROJECT=[自身のGoogle Cloud Project ID] GOOGLE_CLOUD_LOCATION=global 大量のコンテキストデータを準備(例:長い文書、FAQ、マニュアル、PDF) 今回は長いテキストの文章をキャッシュに入れるようなサンプルになってます。 Vertex AI Clientでキャッシュを作成 📖 公式ドキュメント: Context Cache Create - Google Cloud from google.genai.types import Content, CreateCachedContentConfig, Part content_cache = client.caches.create( model="gemini-2.5-flash", config=CreateCachedContentConfig( contents=[Content(role="user", parts=[Part.from_text(text=long_context)])], ttl="3600s", # 1時間有効 ), ) print(f"キャッシュID: {content_cache.name}") キャッシュを作成するのには以下の制限があります。 最小キャッシュサイズ: 1,024 トークン以上のコンテンツが必要(Gemini 2.5 Flash) 最大コンテンツサイズ: 10MB まで TTL 制限: 最小 1 分、最大制限なし 制限詳細: Google Cloud 公式制限表 コンテキストキャッシュ作成時のprint文で表示された キャッシュID (例:`projects/xxx/locations/global/cachedContents/123`)を保存します。 次の呼び出しで使用するため、環境変数に入れておくと便利です。 # .env GEMINI_CACHE_ID=projects/xxx/locations/global/cachedContents/123 ステップ 3: Langfuse 統合コードの実装 3-1. 使用量データの正確な取得 Gemini APIでは、モデルを呼び出した後のレスポンス内にトークン使用数が含まれています。このレスポンスから得られたトークン数をLangfuseに渡す必要があります。 # キャッシュIDを使用してGemini呼び出し cache_id = "projects/xxx/locations/global/cachedContents/123" # ステップ2で取得 response = client.models.generate_content( model="gemini-2.5-flash", contents="あなたの質問をここに入力", config=GenerateContentConfig(cached_content=cache_id) ) # APIレスポンスから直接取得 usage_metadata = response.usage_metadata cached_tokens = getattr(usage_metadata, "cached_content_token_count", 0) input_tokens = getattr(usage_metadata, "prompt_token_count", 0) - cached_tokens output_tokens = getattr(usage_metadata, "candidates_token_count", 0) 補足 キャッシュから供給されたトークン数は、呼び出し後の response.usage_metadata.cached_content_token_count に入ります GenerateContentConfig(cached_content=cache_id) を付けて呼び出す必要があります。 prompt_token_count は「プロンプト全体(キャッシュ分を含む)」のトークン数です。 通常課金される入力トークンは、全体からキャッシュ分を引いた数で算出をしています。 3-2. Langfuse トレース実装 このサンプルで行っていること(流れ) @observe(..., as_type="generation") で関数を計測対象にし、レイテンシを自動取得 GenerateContentConfig(cached_content=cache_id) を指定して Gemini を呼び出し レスポンス usage_metadata から cached_content_token_count などの使用量を取得 課金対象の入力トークンを prompt_token_count - cached_content_token_countで算出 update_current_generation に model/input/output/usage_details/metadata を渡して Langfuse に送信 モデル定義の単価に基づき、Langfuse 側でコストが自動計算・可視化 サンプルソースコード import os from typing import Any from dotenv import load_dotenv from google import genai from google.genai.types import GenerateContentConfig, HttpOptions from langfuse import get_client, observe load_dotenv() def build_clients() -> tuple[genai.Client, Any]: _ = get_client() project_id = os.getenv("GOOGLE_CLOUD_PROJECT") location = os.getenv("GOOGLE_CLOUD_LOCATION") client = genai.Client( vertexai=True, project=project_id, location=location, http_options=HttpOptions(api_version="v1"), ) return client, _ @observe(name="gemini-cached-call", as_type="generation") def call_gemini_with_cache( client: genai.Client, cache_id: str, query: str, model_name: str = "gemini-2.5-flash", ) -> str: """Call Gemini with context cache and report usage to Langfuse.""" response = client.models.generate_content( model=model_name, contents=query, config=GenerateContentConfig(cached_content=cache_id), ) usage = response.usage_metadata cached_tokens = getattr(usage, "cached_content_token_count", 0) input_tokens = max(0, getattr(usage, "prompt_token_count", 0) - cached_tokens) output_tokens = getattr(usage, "candidates_token_count", 0) langfuse_client = get_client() langfuse_client.update_current_generation( model=model_name, input=query, output=(response.text or ""), usage_details={ "input": input_tokens, "cached_tokens": cached_tokens, "output": output_tokens, }, metadata={ "cache_id": cache_id, "provider": "vertex-ai", }, ) return response.text or "" def main() -> None: client, langfuse_client = build_clients() # Example placeholders for blog readers cache_id = os.getenv( "GEMINI_CACHE_ID", "projects/[Project ID]/locations/global/cachedContents/[cache_number]", ) query = "データサイエンスについて教えて" _ = call_gemini_with_cache(client, cache_id, query) # Ensure data is flushed to Langfuse backend in short-lived processes langfuse_client.flush() if __name__ == "__main__": main() Langfuseダッシュボード可視化 以下の画像のように、キャッシュトークンが別途コストとして出力されています。 命名の注意点 input, output という名前を入れた変数を設定すると、それぞれ Input cost, Output cost に分類されます。 例:cached_input_tokensと命名し、モデル価格をセットすると以下のようにinput costとして描画されるようになります。 キャッシュコストを独立して表示したい場合は cached_tokens など別の名前を使用してください。 cached_tokens は Other cost として独立したカテゴリで表示されます。 まとめ この手法により、以下を実現できます 透明性 : キャッシュ効果の可視化 自動化 : 手動計算不要のコスト監視 継続性 : 長期的なコスト最適化 スケール : 大規模システムでの運用 Gemini のコンテキストキャッシュと Langfuse を組み合わせることで、生成AIの「コスト」「レイテンシ」「再利用性」を定量的にコントロールできます。特に、長文コンテキストやPDF・動画等のマルチモーダルのデータの再利用が多いワークロードでは、入力コストの削減と応答時間の短縮が同時に見込め、AI アプリケーションの運用効率を大幅に向上させることができると思いますので、ぜひGemini のコンテキストキャッシュと Langfuseを利用して、生成AIアプリケーションの開発を進めていただけると嬉しいです。

  • Langfuseにおける個人情報(PII)マスキング:Sensitive Data Protection の活用

    これまで、LangfuseでのPIIマスキング手法として、 llm-guard 、 Guardrails for Amazon Bedrock 、そして LLM(Gemini 2.5 Flash Lite) によるマスキング手法を検討してきました。 今回は、Google Cloudの機密データ保護機能である Sensitive Data Protection(旧:Cloud Data Loss Prevention, Cloud DLP)  の利用について検討します。 Sensitive Data Protection とは? Sensitive Data Protection (旧称:Cloud Data Loss Prevention、Cloud DLP)は、Google Cloudが提供する 機密データ保護サービス です。データの検出、分類、匿名化といった強力な機能を提供し、個人情報(PII)をはじめとする機密情報の漏洩リスクを軽減します。 このサービスは、単なるパターンマッチングにとどまらず、機械学習や文脈分析を組み合わせることで、より高精度な機密データ検出を実現します。特に、 クレジットカード番号、社会保障番号、メールアドレス、電話番号 など、世界中の150種類以上の組み込みの InfoType(情報タイプ)  検出器をサポートしており、非常に広範な種類のPIIに対応できます。また、独自のビジネスニーズに合わせて カスタムInfoType を定義することも可能です。 Guardrails for Amazon Bedrockと同様に、APIを通じてPIIマスキングを実行できます。今回の検証では、 DLP API を利用してマスキングを行います。 Guardrails for Amazon Bedrock との違いは? これまでの検証で取り上げた Guardrails for Amazon Bedrock は、Sensitive Data Protection と目的が近いサービスです。どちらもLLMの入出力におけるPIIマスキングに利用できますが、いくつか重要な違いがあります。 特徴 \ ソリューション Sensitive Data Protection Guardrails for Amazon Bedrock 最適なユーザー Google Cloud環境でLLMアプリケーションを運用するユーザー AWS BedrockをLLMプラットフォームとして利用するユーザー 検出範囲と精度 汎用PII検出、150種類以上の高精度なInfoType(PIIに特化) Bedrockの入出力に特化、PIIを含む多様なフィルタタイプ 適用レイヤー アプリケーション層での柔軟な適用(API経由) Bedrock API呼び出し時(リアルタイム) リアルタイム性 API経由でリアルタイム処理が可能 リアルタイムで適用可能 Langfuse連携 API呼び出し結果をLangfuseへ記録可能 API呼び出し結果をLangfuseへ記録可能 その他特徴 高度な匿名化オプション、広範なPII検出器、 コンテキストを考慮した検出 Bedrockでのコンテンツモデレーションの一元化、 リアルタイムポリシー適用 Guardrails for Amazon BedrockはAmazon Bedrockと密接に統合されており、Bedrockを利用する際に特化したリアルタイム制御を提供します。一方、Sensitive Data ProtectionはGoogle Cloudの汎用的な機密データ保護サービスであり、より広範なPIIタイプに対応し、アプリケーション層で柔軟に組み込める点が特徴です。 今回の検証では、PII検出の対象をサンプルコード内で設定しますが、Guardrailsと同様に コンソール上での設定も可能 です。サービスとして運用する際は、コンソールで検出対象を追加・調整し、アプリケーションの修正やリリースなしでポリシーを更新できる テンプレートの利用を検討 することをおすすめします。 テスト方法 今回は以下の手順で、Sensitive Data ProtectionのPIIマスキング機能を検証していきます。 事前準備 まず、Sensitive Data Protection が提供する InfoType検出器(検出対象)  を確認します。検出器のリファレンスは こちら にあります。過去の検証と同様に、今回はカスタム検出器は利用せず、 組み込みのInfoType検出器のみ を使用します。 当初、すべてのInfoTypeを設定しようとしましたが、検出器の設定可能上限数(150個)を超過したため(総数216個)、 カテゴリがPIIに該当するものに限定 して設定することにしました。 多数のInfoTypeを手動でリスト化するのは大変なので、以下のPythonコードでAPIからPII関連のInfoTypeを自動で取得します。 # DLPクライアントを初期化(クォータプロジェクトを指定) dlp_client = dlp_v2.DlpServiceClient client_options={"quota_project_id": project_id} ) def get_info_types(): info_types_response = dlp_client.list_info_types( request={ 'parent': f"projects/{project_id}", 'filter': 'supported_by=INSPECT' # 検査可能なInfoTypeに絞る } ) included_info_types = [] pii_list = [] for info_type in info_types_response.info_types: if info_type.name in included_info_types: continue # categoriesを反復してPIIを探す is_pii = False for category in info_type.categories: if category.type_category.name == "PII": is_pii = True break; if is_pii: included_info_types.extend(info_type.specific_info_types) pii_list.append({ 'name': info_type.name, }) return pii_list 参考 マスキング対象として期待する項目と、llm-guardのAnonymize機能、Guardrails for Amazon Bedrockの機密情報フィルター、そしてSensitive Data ProtectionのInfoType検出器で対応すると思われる項目を比較表にまとめました。 PII llm-guard Guardrail for Amazon Bedrock Sensitive Data Protection 氏名 人名 NAME PERSON_NAME 生年月日 - - DATE_OF_BIRTH 住所 - ADDRESS GEOGRAPHIC_DATA 電話番号 電話番号 PHONE PHONE_NUMBER メールアドレス メールアドレス EMAIL EMAIL_ADDRESS 運転免許証番号 - DRIVER_ID DRIVERS_LICENSE_NUMBER パスポート番号 - US_PASSPORT_NUMBER PASSPORT 社会保障番号 米国社会保障番号(SSN) US_SOCIAL_SECURITY_NUMBER US_SOCIAL_SECURITY_NUMBER JAPAN_INDIVIDUAL_NUMBER(マイナンバー) (クレジットカード情報) カード番号 クレジットカード CREDIT_DEBIT_CARD_NUMBER CREDIT_CARD_DATA (クレジットカード情報) 有効期限 - CREDIT_DEBIT_CARD_EXPIRY CREDIT_CARD_DATA (クレジットカード情報) セキュリティコード - CREDIT_DEBIT_CARD_CVV CREDIT_CARD_DATA (銀行口座情報)銀行名 - - - (銀行口座情報)支店名 - - - (銀行口座情報) 口座番号 - US_BANK_ROUTING_NUMBER FINANCIAL_ID llm-guardと比べると検出項目は充実していますが、Guardrails for Amazon Bedrockでカバーしきれていなかった「 生年月日 」もSensitive Data Protectionでは検出できるようです。銀行口座情報の銀行名や支店名を除けば、期待されるほとんどのPII項目を網羅できる見込みです。 今回の検証では、誤検知が発生しないかも確認するため、PIIカテゴリに属する 85種類の検出器をすべて設定 しました。 アプリケーションコード 過去の記事と同様に、簡単なPythonコードを用いてプロンプトの入力とLLMの回答を模擬的に生成し、それぞれをLangfuseに登録する形式でテストを行います。 get_info_types関数とDLPクライアントの初期化部分を追記、masking_functionの中身をDLP APIを利用するように修正します。マスキング方法は、今回は 検出されたInfoTypeの名前で置換 する設定にします。 今回は行いませんが、「*」のような特定の文字で置き換えたり、固定文言の指定、設定した文言のいずれかと置換、といった設定も可能です。 def masking_function(data: any, **kwargs) -> any: if isinstance(data, str) and len(data) > 0: # 利用可能なPIIのInfoTypeを取得 info_types_list = get_info_types() request = { 'parent': f"projects/{project_id}", 'inspect_config': { 'info_types': info_types_list }, 'deidentify_config': { 'info_type_transformations': { 'transformations': [ { 'primitive_transformation': { 'replace_with_info_type_config': {} } } ] } }, 'item': {'value': data} } # DLPを使用したPIIマスキング response = dlp_client.deidentify_content( request=request ) sanitized_data = response.item.value return sanitized_data else: return data テストデータ 過去の記事と同様に、以下のダミーデータを流用してテストを行いました。 ダミーデータ 私の個人情報は以下の通りです: 氏名:山田 太郎 Name: Taro Yamada 生年月日:1985年3月15日 Birthday: March 15, 1985 住所:東京都新宿区西新宿2-8-1 Address: 2-8-1 Nishi-Shinjuku, Shinjuku-ku, Tokyo, Japan 電話番号:03-1234-5678 / 090-9999-8888 Phone: +81-3-1234-5678 / +81-90-9999-8888 メールアドレス:taro.yamada@example.com Email: taro.yamada@example.com 運転免許証番号:123456789012 Driver's License: DL-123456789012 パスポート番号:TK1234567 Passport Number: TK1234567 社会保障番号:987-65-4321 Social Security Number: 987-65-4321 クレジットカード情報: - カード番号:4111-2222-3333-4444 - 有効期限:12/25 - セキュリティコード:123 Credit Card Information: - Card Number: 4111-2222-3333-4444 - Expiry Date: 12/25 - Security Code: 123 銀行口座情報: - 銀行名:みずほ銀行 - 支店名:新宿支店 - 口座番号:1234567 Bank Account Information: - Bank Name: Mizuho Bank - Branch: Shinjuku Branch - Account Number: 1234567 検証結果 以下に、llm-guard(デフォルト設定)、Guardrails for Amazon Bedrock(apply_guardrail)、そして今回検証したSensitive Data Protectionそれぞれで処理した際のPII項目ごとの置換結果を比較します。 llm-guard (デフォルト設定) Guardrail for Amazon Bedrock Sensitive Data Protection 氏名(日本語) [REDACTED_PERSON_1][REDACTED_PERSON_2][REDACTED_PERSON_3] {NAME} [PERSON_NAME] 氏名(英語) [REDACTED_PERSON_4] {NAME} [PERSON_NAME] 生年月日 - - [DATE_OF_BIRTH] 住所 - {ADDRESS} [PERSON_NAME][GEOGRAPHIC_DATA] 電話番号(日本) 03-1234-[REDACTED_PHONE_NUMBER_1] {PHONE} [PHONE_NUMBER] / [PHONE_NUMBER] 電話番号(海外) [REDACTED_PHONE_NUMBER_2] {PHONE} [PHONE_NUMBER] / [PHONE_NUMBER] メールアドレス [REDACTED_EMAIL_ADDRESS_1] {EMAIL} [EMAIL_ADDRESS] 運転免許証番号 - {DRIVER_ID} [DRIVERS_LICENSE_NUMBER] DL-[DRIVERS_LICENSE_NUMBER] パスポート番号 - {US_PASSPORT_NUMBER} パスポート番号:[DRIVERS_LICENSE_NUMBER] Passport Number: [PASSPORT] 社会保障番号 [REDACTED_US_SSN_RE_1] 987-65-4321  {US_SOCIAL_SECURITY_NUMBER} 987-65-4321 クレジットカード(カード番号) [REDACTED_CREDIT_CARD_RE_1] {CREDIT_DEBIT_CARD_NUMBER} 4111-2222-3333-4444 クレジットカード(カード番号以外) - 有効期限:{CREDIT_DEBIT_CARD_EXPIRY} セキュリティコード:{CREDIT_DEBIT_CARD_CVV} 有効期限:12/25 セキュリティコード:123 銀行口座情報 - 銀行名:みずほ銀行 支店名:新宿支店 口座番号:{US_BANK_ACCOUNT_NUMBER} Bank Name: Mizuho Bank Branch: {ADDRESS} Branch Account Number: {US_BANK_ACCOUNT_NUMBER} 銀行名:[PERSON_NAME]銀行 支店名:[GEOGRAPHIC_DATA] 口座番号:[FINANCIAL_ID] Bank Name: Mizuho Bank Branch: Shinjuku Branch Account Number: [FINANCIAL_ID] llm-guard(デフォルト設定)とGuardrails for Amazon Bedrock(apply_guardrail)の結果は、以前の記事で検証した際のデータを記載しています。Sensitive Data Protection の検証において、上記2回と明示的に差異がある項目の背景色を明るくしています。 今回の検証で目立つ点としては、期待していたクレジットカード情報や社会保障番号が検出されませんでした。また、いくつかの誤検出も見られます。DLPの検出器には、 コンテキストの手がかり がある場合にのみ識別できるものもあるため、もう少し自然な文脈を与えたテキストであれば、結果は変わってくるかもしれません。 結果詳細(誤検出・検出漏れ項目の抜粋) 氏名 氏名:[PERSON_NAME]Name: [PERSON_NAME] 検出自体は問題なさそうですが、元のテキストの改行が消えてしまっています。 住所 住所:[PERSON_NAME][GEOGRAPHIC_DATA] 何かが人名として誤検出されてしまっています。また、どこまでが人名として検出されているかは不明なため推測にはなりますが、おそらく日本語住所+改行+Address:+英語住所がひとまとめに住所としてみなされているようです。 運転免許証番号 運転免許証番号:[DRIVERS_LICENSE_NUMBER] Driver's License: DL-[DRIVERS_LICENSE_NUMBER] 誤検出というほどではありませんが、「DL-」というプレフィックスが残ってしまっています。 パスポート番号 パスポート番号:[DRIVERS_LICENSE_NUMBER] Passport Number: [PASSPORT] 日本語版が運転免許証番号として誤検出されています。 社会保障番号 社会保障番号:987-65-4321 Social Security Number: 987-65-4321 期待とはことなり、PIIとして検出されませんでした。 クレジットカード情報 クレジットカード情報: - カード番号:4111-2222-3333-4444 - 有効期限:12/25 - セキュリティコード:123 Credit Card Information: - Card Number: 4111-2222-3333-4444 - Expiry Date: [CREDIT_CARD_EXPIRATION_DATE] - Security Code: 123 英語版の有効期限のみが検出されました。 銀行口座情報 銀行口座情報: - 銀行名:[PERSON_NAME]銀行 - 支店名:[GEOGRAPHIC_DATA] - 口座番号:[FINANCIAL_ID] Bank Account Information: - Bank Name: Mizuho Bank - Branch: Shinjuku Branch - Account Number: [JAPAN_BANK_ACCOUNT] 日本語の銀行名が個人名、支店名が住所として誤検出されています。 FINANCIAL_ID に JAPAN_BANK_ACCOUNT は含有されていますが、異なる検出器で置き換わっているのは気になります。 検出の精度は十分とは言えませんが、Guardrailsやllm-guardでは検出できなかった一部の項目(例:生年月日)を捉えることができました。 まとめ 今回はGoogle Cloudの Sensitive Data Protection を利用したPIIマスキング機能について検証しました。 テストの結果、一部の項目で優れた検出能力を示しましたが、完全に網羅できているわけではなく、誤検出も見られました。今回の検証で検出できなかったPII項目の一部は、llm-guardのような別のフィルタリングツールでカバーできる可能性があります。そのため、 Sensitive Data Protectionとllm-guardを併用 することで、コストを抑えつつカバー範囲を広げられるかもしれません。 また、今回の検証は限定的な条件での確認となります。実際にアプリケーションで利用する際は、自然なテキスト中にPIIが含まれている場合や、全角文字が混在する場合など、 日本語特有の表現が含まれている場合の挙動 については、さらに詳細な検証が必要不可欠です。 検討課題 Sensitive Data Protectionの料金体系 も考慮すべき点です。料金は、検出・変換されたデータ量(バイト数)に基づいて発生します。月間1GBまでの利用は無料ですが、それを超えると1GBあたり2~3 USDの費用がかかるため、他のソリューションと比較して高額になる可能性があります。

bottom of page