top of page

検索結果

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

  • Langfuseデータセット構築ガイド:UI・CSV・SDKの徹底比較

    先日、新規アプリケーションのプロンプトを検討するにあたり、トレースデータ(ログ)が存在しない状態からデータセットを作成する必要がありました。 ある程度のデータ量を用意したかったため、手動入力を避ける方法(SDK や CSV)を調査・検証しました。 本記事では、 基本となる UI での登録手順と、今回試した一括登録の手順をそれぞれ整理し、使い勝手や特徴を比較した備忘録 として残します。 利用バージョン Langfuse : v3.127.0 OSS Python SDK : 3.9.0 全体の流れ Dataset が利用できるまでに以下の手続きが必要です。 データセット(dataset)の作成 データセットアイテム(items)の作成 本記事は、公式ドキュメントのこちらの 記事 を参考に実施しました。 1.データセット(dataset)の作成 UIを利用する方法 Datasets へ遷移し、[+ New Dataset] をクリックすることで新規のデータセットが作成できます。 Name のみ指定し、[Create dataset] で作成完了です。 特に複雑な操作も無く、直感的に作成できました。 SDKを利用する方法 今回はPython SDKを利用したので、Python のサンプルコードとなります。 こちらも既に アプリケーション内で Langfuse を利用したことがある方であれば、特に迷うことなく利用できるのではないかと思います。 langfuse.create_dataset( name=[データセット名], ) name をキーとした UPSERT が行われる仕様のようです。description や metadata を追加で指定したり、既に設定されている値を変更したりすると、データセットが更新されたことを UI 上で確認できました。 ただし、オプション未指定(または None )の場合は更新されず、既に設定されているものがそのまま残る挙動を確認しました。 また、データセット名がキーとなっているため、データセット名自体の変更に SDK は利用できません。名称を変更したい場合は、UI から操作する必要があります。 2,データセット(items)の作成 UIを利用する方法 作成したデータセットに対し、UI または CSV でアイテムの追加が可能です。 追加したいデータセットをクリックすると、デフォルトでは Runs タブが表示されるため、 Items タブに切り替えます。ここで UI での追加と、CSV の追加が行えます。 UIで一つずつ追加する [+New item] から追加します。 JSON 形式で記述する必要がありますが、ひとまずは Input のみ指定すれば [Add to dataset] で追加できます。 JSON 形式のハードルが高いことを除けば、こちらも複雑な操作は必要なく、概ね直感的な操作で作成出来ました。 CSVを利用して一括で追加する [Upload CSV] をクリックすると CSV のアップロード画面が表示されます。 CSV を用いたアップロードでは、先の UI 同様、 Input, Expected Output, Metadata のみが登録可能です。なお、CSVアップロードによる一括での UPSERT はできないようです。 CSV 登録の特徴として、「CSV のどの項目を各フィールドに割り当てるか」を UI 上でマッピング出来ることが挙げられます。この機能により、事前に アップロードフォーマットに合わせた CSV 形式への加工や、値をわざわざ JSON 形式に変換する必要が基本的にはない点は大きなメリットです。 例として、以下のような CSV を 作成し、UIから取り込んでみました。 id name num 1 apple 100g 2 egg 20 項目をすべて Input にマッピングします。 すると、ヘッダ行と値が適切に設定された JSON として Input に入力されました。 形式をあまり意識せずに登録できるため、既存データの CSVをとりあえず投げ込んでつくる、といった方法も取れます。個人的には、各種CSVアップロードはフォーマットの調整に時間がかかることが多いので、とても嬉しい機能でした。 もちろん、データセットからダウンロードしたファイルもそのままアップできます。Input として、ダウンロードした CSV の Input を割り当てれば、{ input: {...} } のような形式にならず、元のデータセットアイテムと同じ形式で登録されました。 SDKを利用する方法 データセット同様、特に難しい点はありませんでした。 id が省略されている場合は INSERT、指定されている場合は UPSERT となります。 langfuse.create_dataset_item( id=[ID], dataset_name=[データセット名], input=[入力データの内容], ) id は省略可能ですが、登録時に指定しておいた方が明示的に管理できるため、SDK を利用して作成する場合は、指定しておく方が利便性が高いと思われます。 省略した場合はUIからの登録同様、自動的にランダムな ID が付与されます。 指定する際の注意点 他のデータセットで既に利用している ID は利用できません。 (異なる組織、またはプロジェクトであれば利用可能です) CSVからの一括更新が出来ないため、実際は ID を指定しての1つずつの処理にはなりますが、データを全てアーカイブしたい場合など、まとめて更新を行うことが想定される場合には SDK を利用すると良さそうです。 まとめ 今回、UI と SDK の両方を触ってみて、データセットアイテムにある程度のデータ量が必要な場合は以下のように使い分けるのが良さそうだと感じました。 UI (CSV) データの更新が不要な場合 CSV マッピング機能が優秀 時間をかけずにデータを投入したい 登録にあたり一切の開発が不要 SDK データの更新が必要な場合 データ量が膨大な場合 CSV での処理に不安がある量の場合 手元のデータが複雑で、Input 用に何かしらの加工を行う必要がある場合 特に CSV アップロード時のマッピング機能は、データ前処理の手間が省けるので、今後は積極的に使っていきたいと思います。

  • Observation Types で mask オプション内での再起呼び出しを回避する

    以前、Langfuse の mask オプションを利用する際のトレース保存方法について解説しました。( 該当記事 ) 当時、 mask オプションに設定した関数内でトレースを保存しようとすると、該当の関数が再帰的に呼び出されてしまう問題がありました。該当記事内では、グローバル変数を用いて制御しましたがあまりスマートな方法とは言えません。 しかし、この課題をよりスマートに解決出来そうなアップデートが行われました。 実際にどのように解決していけるか、試してみたいと思います。 以前のコードと課題 以前のコードでは、masking_function が再帰的に呼び出されないよう、グローバル変数で制御していました。 require_mask = True def masking_function(data: any, kwargs) -> any: global require_mask if require_mask and isinstance(data, str): try: require_mask = False # PII フィルター適用時のトレース保存 with langfuse.start_as_current_generation( name="pii_filter_prompt", prompt=[使用するプロンプト] ) as generation: # LLM呼び出し(マスキング処理) generation.update( output=[フィルター済みデータ], ) finally: require_mask = True return [フィルター済みデータ] langfuse = Langfuse( public_key="**************", secret_key="****************", host="*****************", mask=masking_function, ) # LLM呼び出しのトレース with langfuse.start_as_current_generation( name="llm_called_trace", input=[ユーザの入力], ) as generation: # LLM呼び出し(入力に対する回答取得用) generation.update( output=[LLMの回答], ) このアプローチでは、上記のような簡単なコードであればさほど難しくはありませんが、規模が大きくなるにつれ、コードや管理が複雑になってしまいます。 Langfuse アップデート:Observation Typesの追加 2025年8月末頃のアップデートで、Observation Types が追加されました。( 該当記事 ) これまでも、 span や generation はありましたが、 guardrail や evaluator など、多くの種類が追加されました。 こちらのアップデート記事を読んだ際に、Observation Types = "guardrail" としてトレースを保存すれば、Langfuse で良い感じに処理して再帰的に mask オプションの関数が呼ばれることは無くなるのでは?と期待しました。 そこで、実際に以前の実験時に利用した、上記のコードを基に再起呼び出しを防ぐ対応が不要になるか試してみました。 1.Observation Types = "guardrail" の利用 事前準備 Observation Types の追加は Python SDK 3.3.1 以上でなければ対応していないため、利用するSDKをアップグレードします。 また、トレースの保存方法が start_as_current_generation から start_as_current_observation (as_type="generation") と少々変更されているため、合わせて調整します。 # 念のための制御用 loop = 0 def masking_function(data: any, kwargs) -> any: global loop if loop > 5: return data if isinstance(data, str): loop += 1 # PII フィルター適用時のトレース保存 with langfuse.start_as_current_observation( as_type="guardrail", name=f"in masking function: {loop}", prompt=[使用するプロンプト] ) as guardrail: # マスキング処理 sanitized_data = f"sanitized data:{data}" guardrail.update(output=sanitized_data) return sanitized_data else: return data # Langfuseの初期化 langfuse = Langfuse( public_key="**************", secret_key="****************", host="*****************", mask=masking_function, ) # LLM呼び出しのトレース with langfuse.start_as_current_observation( as_type="generation", name="test_type_guardrails", input=[ユーザの入力], ) as generation: # LLM呼び出し(入力に対する回答取得用) generation.update( output=[LLMの回答], ) 無限ループに陥らないよう、念のため複数回再帰的に呼び出されたタイミングで終了するようにしました。 こちらで実行してみます。 結果 残念ながら、 observation type = "guardrail" の指定だけでは、再帰的に実行されてしまう状態は変わらないようです。 2.OpenTelemetry の利用 Langfuseは内部でOpenTelemetryを利用しており、これを使うことでmasking_functionの呼び出し元のトレース情報を取得できることがわかりました 。 トレース情報からObservationTypesを取得し、判断に利用すれば、グローバル変数を利用する形よりはいくらかスマートに対応できます。 修正 from opentelemetry import trace as otel_trace_api from langfuse._client.attributes import LangfuseOtelSpanAttributes // 中略 def masking_function(data: any, kwargs) -> any: current_observation_type = None try: current_span = otel_trace_api.get_current_span() if current_span and current_span != otel_trace_api.INVALID_SPAN: current_observation_type = current_span.attributes.get( LangfuseOtelSpanAttributes.OBSERVATION_TYPE, "unknown" ) except Exception as e: print(f"Error getting current observation info: {e}") return data # guardrailの場合はマスキングを行わない if current_observation_type == "guardrail": return data if isinstance(data, str): with langfuse.start_as_current_observation( as_type="guardrail", name="in masking function", prompt=[使用するプロンプト] ) as guardrail: # マスキング処理 sanitized_data = f"sanitized data:{data}" guardrail.update(output=sanitized_data) return sanitized_data else: return data opentelemetry.trace.get_current_span() を利用して、現在のトレースの情報を取得します。 今回は、マスク用の関数内以外では as_type = "guardrail" の指定を利用しないこと、マスク用の関数内で別のObservation Typeを設定しないことを前提としています。そのため、単純に取得した observation type が guardrail の場合は、masking_function内での呼びだしとみなし、処理をせずに入力値をそのまま返す形にしています。 結果 無事、Observation Typeを取得し、制御することに成功しました! これにより、 mask オプションを利用する際も、グローバル変数を利用せずトレースの保存を制御できることが確認出来ました。 まとめ Observation Types の追加によって、mask関数の再帰呼び出しが自動的に回避されるといった変更はありませんでした。しかし、期待した方法とは異なりますが、Observation TypesとOpenTelemetryを組み合わせることで、これまでよりもはるかにスマートな方法で、トレース保存を制御できることが確認できました 。

  • A2A × ADKの"観測粒度"を設計する - Langfuse & Cloud Trace でトレース構造を可視化 -

    はじめに 2025年4月9日、GoogleがAgent2Agent(A2A)プロトコルを発表 してから半年以上が経過し、多くの開発者がマルチエージェントシステムの構築に取り組んでいます。 A2Aは複雑なコンポーネント構成とエージェント間通信を持つため、 処理フローをトレースとして可視化することがLLM Opsにおいて重要 です。 本記事では、LangfuseとCloud Traceを使用してA2A × ADKエージェントの挙動を観測し、実用的な分析のための観測粒度の最適化方法を解説します。 実装環境 使用したライブラリ 本記事で使用したライブラリとそのバージョンは以下の通りです。 requirements.txt google-adk[a2a] google-genai langfuse python-dotenv opentelemetry-instrumentation-google-genai opentelemetry-exporter-gcp-logging opentelemetry-exporter-gcp-monitoring opentelemetry-exporter-otlp-proto-grpc opentelemetry-instrumentation-vertexai>=2.0b0 実際にインストールされたバージョン(pip list) - Python: 3.12.4 - google-genai: 1.39.1 - langfuse: 3.5.2 - opentelemetry-instrumentation-google-genai: 0.1.5 - opentelemetry-instrumentation-vertexai: 0.1.11 - python-dotenv: 1.1.1 エージェント構成 今回は、 ADK Multi-tool Agent Sample をベースに、東京の天気と現在時刻を取得するシンプルなエージェントを構築しました。2つのツール( get_weather と get_current_time )を持つエージェントです。 from datetime import datetime from zoneinfo import ZoneInfo from google.adk.agents import Agent def get_weather(city: str) -> dict: """指定された都市の天気情報を取得""" if city.lower() == "東京": return { "status": "success", "report": ( "東京の天気は晴れで、気温は25度です。" ), } else: return { "status": "error", "error_message": f"'{city}'の天気情報は利用できません。", } def get_current_time(city: str) -> dict: """指定された都市の現在時刻を取得""" if city.lower() == "東京": tz_identifier = "Asia/Tokyo" else: return { "status": "error", "error_message": ( f"'{city}'のタイムゾーン情報は利用できません。" ), } tz = ZoneInfo(tz_identifier) now = datetime.now(tz) report = ( f'{city}の現在時刻は{now.strftime("%Y-%m-%d %H:%M:%S %Z%z")}です。' ) return {"status": "success", "report": report} root_agent = Agent( name="weather_time_agent", model="gemini-2.5-flash-lite", description=( "東京の天気と現在時刻を取得するエージェントです。" ), instruction=( "東京の天気と現在時刻を取得するエージェントです。" ), tools=[get_weather, get_current_time], ) A2Aの実装パターン A2Aには主に2つの実装パターンがあります。「Exposingパターン」と「Consumingパターン」です。今回は両方の方法でエージェントを実装し、トレースの挙動を確認しました。 共通設定 両パターンで共通して使用するサーバー設定は以下の通りです。 import uvicorn from google.adk.agents import ADKAgentExecutor from a2a.server import A2AStarletteApplication, DefaultRequestHandler from a2a.storage import InMemoryTaskStore request_handler = DefaultRequestHandler( agent_executor=ADKAgentExecutor(agent=root_agent), task_store=InMemoryTaskStore(), ) server = A2AStarletteApplication( agent_card=public_agent_card, http_handler=request_handler, ) uvicorn.run(server.build(), host='0.0.0.0', port=9999) Exposingパターン Exposingパターンは、自作のエージェントをA2Aプロトコルで公開する方式です。自社で開発したエージェントを外部に公開し、他のチームやシステムが利用できるエージェントサービスを提供する際に使用します。 A2Aサーバーは、エージェントのメタデータを記述したAgent Cardを自動的に生成し、well-known URLで公開します。クライアントはこのAgent Cardを参照することで、エージェントの機能や入出力形式を理解できます。 from google.adk.a2a import to_a2a # ADKエージェントをA2Aサーバーとして公開 a2a_app = to_a2a(root_agent, port=8001) 実装の詳細は、 ADK公式ドキュメントのクイックスタート(Exposing) を参考にしました。 Consumingパターン Consumingパターンは、既存のA2Aエージェントをリモートから利用する方式です。外部ベンダーが提供するA2Aエージェントを活用する際に使用します。 from google.adk.a2a import RemoteA2aAgent, AGENT_CARD_WELL_KNOWN_PATH from google.adk.agents import Agent weather_time_agent = RemoteA2aAgent( name="weather_time_agent", description=( "東京の天気と現在時刻を取得するエージェントです。" ), agent_card=f"http://localhost:8001/a2a/weather_time_agent{AGENT_CARD_WELL_KNOWN_PATH}", ) root_agent = Agent( model="gemini-2.5-flash-lite", name="root_agent", instruction=( "日本語で回答して" ), sub_agents=[weather_time_agent], ) 実装の詳細は、 ADK公式ドキュメントのクイックスタート(Consuming) を参考にしました。 Agent Card Agent Cardは、エージェントのメタデータを定義するJSONファイルで、Consumingパターンにおいて重要な役割を果たします。以下は今回使用したAgent Cardの例です。 { "capabilities": {}, "defaultInputModes": ["text/plain"], "defaultOutputModes": ["text/plain"], "description": "東京の天気と現在時刻を取得するエージェントです。", "name": "weather_time_agent", "skills": [ { "id": "weather_time_checking", "name": "Weather and Time Checking", "description": "東京の天気と現在時刻を取得します。", "tags": ["weather", "time"] } ], "url": "http://localhost:8001/a2a/weather_time_agent", "version": "1.0.0" } Langfuseでのトレース取得 Langfuseは、LLMアプリケーションのトレーシングと評価に特化したオープンソースツールです。OpenTelemetryと統合されており、A2A × ADKエージェントのトレースを簡単に取得できます。 基本的な実装方法 A2AやADKを使用する場合、Langfuseでトレースを取得する方法はシンプルで、Langfuse Clientを初期化するだけで、自動的にトレースが収集されます。※LANGFUSE_HOSTなどの環境変数はあらかじめ設定しておきます from langfuse import get_client langfuse = get_client() これだけで、A2AやADKが生成するすべてのspanが自動的にLangfuseに送信されます。明示的にspanの設定を記述する必要がないのは、OpenTelemetryのインストルメンテーションライブラリが自動的にspanを生成するためです。 ExposingパターンとConsumingパターンの違い ExposingとConsuming両方のパターンでトレースを取得しましたが、構造や内容に差は見受けられませんでした。 バッチ処理での結果 まず、バッチ処理でのトレース結果を見てみましょう。 Langfuseのトレース表示(バッチ) トレース構造の特徴 バッチ処理では、 1つのトレース内に大量のspanが生成 されます。具体的には以下の構成になっています。 A2A関連のspan: 約35個 ADK関連のspan: 約5個 合計: 約40個のspan これらのspanは階層的に配置され、エージェントの処理フローを詳細に記録しています。 ストリーミング処理での結果 次に、ストリーミング処理でのトレース結果を確認します。 Langfuseのトレース表示(ストリーミング) トレース構造の特徴 ストリーミング処理では、バッチ処理とは大きく異なるトレース構造が生成されます。 複数の大きなトレース : それぞれがストリーミングレスポンスの断片に対応 各トレースには約40個のspan(A2A: 35個 + ADK: 5個)が含まれる 約30個の独立した小さなトレース : 各トレースのspan数は1~3個と少ない バッチ処理の各spanがトレースとして独立したような構造です Langfuse ストリーミング処理での小さなトレース バッチとの違い spanの名前から判断すると、ストリーミング処理では バッチ処理の各処理単位が独立したトレースになっている ことがわかります。これは、ストリーミングの性質上、処理が分割されて実行されるためです。 バッチ vs ストリーミングの数値比較 項目 バッチ ストリーミング 大きなトレース数 1個 1個~ 大きなトレース1つあたりのspan数 約40個 約40個 小さな独立トレース数 0個 約30個 トレース内容の詳細分析 トレースの内容を詳しく見ていくと、いくつかの興味深い特徴が見えてきます。 EventQueue関連spanの多さ トレースを観察すると、 その大半をEventQueue関連のspanが占有 していることがわかります。生成AIのリクエスト/レスポンス以外の処理で、膨大な数のspanが生成されています。 これらのspanは、A2AやADKの内部処理に関連しています。詳細な動作については、ライブラリの実装コードを参照する必要がありますが、イベント駆動型のアーキテクチャによる処理フローであることが考えられます。 EventQueue.deque_event の連続発生 特に注目すべき点として、 0.5秒継続する EventQueue.deque_event  spanが連続で発生 しています。これは処理待ちやイベントキューの処理に起因すると推測されます。 重要な注意点として、実際のエージェントのレスポンス速度は速い可能性があります。 しかし、イベント処理が具体的に何を行っているか不明なため、LangfuseのUIに表示されるLatencyの値は、その内部処理時間を含んで大きくなってしまいます。この現象が、パフォーマンス分析を複雑にする要因の一つです。 複数の EventQueue.deque_event の処理で0.5秒費やされている様子 観測からの示唆 A2A × ADKのトレース構造を観察した結果、A2A層は内部イベント処理が非常に詳細に出力されることが分かりました。これはA2Aの可視化が成功している証拠ですが、一方で LLM Ops的観点では情報過多となり、分析効率を下げる要因 にもなります。 具体的には: - A2A関連のspanが34~35個と非常に多く、トレース全体の大部分を占める - LLMの推論処理やツール実行といった、アプリケーションレベルで重要な情報が埋もれてしまう - EventQueueの内部処理など、通常のデバッグでは不要な詳細情報が大量に含まれる そこで次に、「A2Aの内部動作を理解した上で、どこまで観測すべきか」という「観測粒度の設計」を紹介します。 Cloud Traceでのトレース取得 Cloud Traceは、Google Cloudが提供する分散トレーシングサービスです。OpenTelemetryと統合されており、A2A × ADKエージェントのトレースをGoogle Cloudの観測可能性プラットフォームに送信できます。 実装方法 Cloud Traceを使用するには、OpenTelemetryの初期化とGoogle Cloud Observabilityの設定が必要です。 Google Cloud 公式 ドキュメント に従って実装しました。 import google.auth import google.auth.transport.requests import grpc from opentelemetry import trace, logs, metrics from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from opentelemetry.sdk._logs import LoggerProvider from opentelemetry.sdk._logs.export import BatchLogRecordProcessor from opentelemetry.exporter.cloud_logging import CloudLoggingExporter from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader from opentelemetry.exporter.cloud_monitoring import CloudMonitoringMetricsExporter from opentelemetry.sdk.resources import Resource from opentelemetry.semconv.resource import ResourceAttributes from opentelemetry.instrumentation.vertexai import VertexAIInstrumentor from opentelemetry.instrumentation.google_genai import GoogleGenAiSdkInstrumentor from opentelemetry.exporter.otlp.proto.grpc._auth import AuthMetadataPlugin SERVICE_NAME = ResourceAttributes.SERVICE_NAME # OpenTelemetry初期化 def initialize_opentelemetry() -> None: """OpenTelemetryの初期化とGoogle Cloud Observabilityの設定""" credentials, project_id = google.auth.default() resource = Resource.create( attributes={ SERVICE_NAME: "multi-tool-agent", # The project to send spans to "gcp.project_id": project_id, } ) # Set up OTLP auth request = google.auth.transport.requests.Request() auth_metadata_plugin = AuthMetadataPlugin(credentials=credentials, request=request) channel_creds = grpc.composite_channel_credentials( grpc.ssl_channel_credentials(), grpc.metadata_call_credentials(auth_metadata_plugin), ) # Set up OpenTelemetry Python SDK tracer_provider = TracerProvider(resource=resource) tracer_provider.add_span_processor( BatchSpanProcessor( OTLPSpanExporter( credentials=channel_creds, endpoint="https://telemetry.googleapis.com:443/v1/traces", ) ) ) trace.set_tracer_provider(tracer_provider) logger_provider = LoggerProvider(resource=resource) logger_provider.add_log_record_processor( BatchLogRecordProcessor(CloudLoggingExporter()) ) logs.set_logger_provider(logger_provider) reader = PeriodicExportingMetricReader(CloudMonitoringMetricsExporter()) meter_provider = MeterProvider(metric_readers=[reader], resource=resource) metrics.set_meter_provider(meter_provider) # Load instrumentors # ADK uses Vertex AI and Google Gen AI SDKs. VertexAIInstrumentor().instrument() GoogleGenAiSdkInstrumentor().instrument() print(f"OpenTelemetryが初期化されました。Google Cloud Project ID: {project_id}") # OpenTelemetryの初期化を実行 initialize_opentelemetry() この設定により、A2A × ADKエージェントのすべてのトレースがCloud Traceに送信されます。 取得結果 Cloud Traceで取得したトレースを確認してみましょう。 Cloud Traceのトレース表示 Cloud Traceで取得したトレース構造は、Langfuseで取得したものと一致しています: バッチ処理: 1 つのトレースに大量のspan(39個) ストリーミング処理: 複数の大きなトレース + 多数の小さな独立トレース OpenTelemetryという共通の基盤を使用しているため、同じトレースデータが異なるUIで表示されているだけです。それぞれのツールの利点を活かして、目的に応じて使い分けることができます。 観測粒度の最適化(フィルタリング) ここまで見てきたように、A2A × ADKエージェントは非常に多くのspanを生成します。これは詳細な情報を得られる反面、重要なspanが埋もれてしまうという問題があります。 課題 具体的な課題は以下の通りです: A2A関連のspan(34~35個)がトレースを複雑化 ADKの重要なspan(5個)が埋もれてしまう 分析やデバッグの効率が低下 例えば、LLMの推論処理だけを見たい場合、A2Aの内部処理に関する膨大なspanは不要です。このような場合、Langfuseの 計測スコープフィルタリング機能 が非常に有用です。 Langfuseの計測スコープフィルタリング機能 計測スコープとは 計測スコープ(Instrumentation Scope) は、インストルメンテーションライブラリがspanに付与するメタデータです。どのライブラリがspanを生成したかを識別するための情報で、Langfuse UIでは metadata.scope.name  として表示されます。 今回のA2A × ADKエージェントでは、主に以下の計測スコープが使用されています: a2a-python-sdk : A2A関連のspan gcp.vertex.agent : ADK関連のspan この情報を活用することで、特定のライブラリが生成したspanをフィルタリングできます。 フィルタリングの実装 基本的な設定方法 Langfuseのクライアント初期化時に、 blocked_instrumentation_scopes パラメータを指定することで、特定の計測スコープのspanをフィルタリングできます。 from langfuse import Langfuse # フィルタリング機能使用時 langfuse = Langfuse( blocked_instrumentation_scopes=["a2a-python-sdk", "gcp.vertex.agent"] ) A2Aのみを削除のパターンの場合 最も実用的なパターンは、 A2A関連のspanのみを削除する 方法です。 A2Aの内部処理はOpenTelemetryで詳細にトレースされますが、LLM推論やADKツール呼び出しの可視化を主目的とする場合は、 A2Aの内部spanを除外した"要約的トレース"が実務上最適 です。これはA2Aの動作を十分理解した上で、可視化の粒度を最適化するというアプローチです。 langfuse = Langfuse(blocked_instrumentation_scopes=["a2a-python-sdk"]) a2a-python-sdk を除外したトレース 結果: ADKの5つのspanのみが残り、シンプルなトレース になります LLMの推論処理や各ツールの実行状況が明確に可視化されます ただし、これらのspanが 名前のないトレースの子として配置 されます トレース一覧で区別しにくくなるという問題がありますが、 LLMの処理部分が見えているため、多くの場合で有効な方法です フィルタリングの注意点 Langfuse公式ドキュメントの計測スコープフィルタリングの警告 トレースツリーの破壊問題 Langfuse公式ドキュメント には、以下の警告が記載されています: 「ブロックされたライブラリとブロックされていないライブラリの範囲がネストされている場合、特定のライブラリをブロックするとトレースツリーの関係が壊れる可能性がある」 これは、A2A × ADKのように ライブラリのspanがネストされている場合 に発生する問題です。親spanがフィルタリングされると、子spanが孤立してしまうのです。 名前のないトレース問題 親spanがフィルタリングされたため、子spanが孤立し、名前のないトレースの子として配置されてしまいます。これにより、トレース一覧で区別しにくくなります。 推奨されるフィルタリングパターン 目的に応じて、以下のパターンから選択することをおすすめします: フィルタリングパターンと結果: パターン A2A span ADK span トレース構造 用途 フィルタなし ◯ ◯ 正常 全体把握・詳細デバッグ A2Aのみ削除 × ◯ 破壊 ※1 LLM処理分析(推奨) ADKのみ削除 ◯ × 正常 LLM処理が見えないため非推奨 両方削除 × × - ※2 手動トレース設定時(高度な用途) ※1 子spanが孤立し、「名前のないトレース」の子になる。 ※2 自動生成トレースが無効になる。 パターン別の詳細 1. フィルタなし(デフォルト) すべてのspanを含めて全体像を把握したい場合 システム全体のフローを理解したい初期段階 詳細なデバッグが必要な場合 2. A2Aのみ削除(実用的) LLMの推論処理に焦点を当てたい場合 ツールの実行状況を明確に可視化したい場合 トレース構造は破壊され名前のないトレースになりますが、実用上は妥協可能な範囲 3. ADKのみ削除(非推奨) A2Aの内部処理を詳細に見たい場合 ただし、 LLMの処理部分が見えなくなる ため、このパターンはあまり採用する意味がありません 4. 両方削除(高度な用途) 自動生成されるトレースを完全に無効化 トレースの手動設定&送信ができる ため、細かくトレースの中身を設定したい場合は 一番推奨できる パターン より細かい粒度でトレースをカスタマイズしたい上級者向け まとめ トレーシングのポイント A2A × ADKエージェントのトレーシングについて、以下の重要なポイントを確認しました: Langfuse/Cloud Trace両方でOpenTelemetry経由の自動トレース取得が可能 Langfuse Clientの初期化だけで、簡単にトレースが収集される インストルメンテーションライブラリによる自動span生成が機能 バッチとストリーミングで大きく異なるトレース構造 バッチ: 1つのトレースに約39個のspan ストリーミング: 複数の大きなトレース + 約30個の小さな独立トレース EventQueue関連のspanが大部分を占める トレースの大半がA2Aの内部処理に関連 EventQueue.deque_event の連続発生がLatency表示値に影響(実際のレスポンス速度とは別) フィルタリング機能の活用 Langfuseの計測スコープフィルタリング機能を効果的に活用するためのポイント: 計測スコープによるフィルタリングで不要なspanを削除可能 blocked_instrumentation_scopes パラメータを使用 a2a-python-sdk や gcp.vertex.agent などの計測スコープを指定 トレース構造の破壊に注意が必要 ネストされたspanの親をフィルタリングすると、子spanが孤立 名前のないトレース問題が発生する可能性 目的に応じた適切なフィルタリング設定を選択 LLM処理分析: A2Aのみ削除(最も実用的) 全体把握: フィルタなし 高度なカスタマイズ: 両方削除 + 手動トレース設定 今後の展望と注意事項 A2A × ADKエージェントのトレーシングには、まだ解明されていない部分もあります: EventQueueの詳細な動作解明 なぜこれほど多くのspanが生成されるのか 内部処理の最適化の可能性 ストリーミング時のspan分割メカニズムの理解 どのようなロジックでトレースが分割されるのか パフォーマンスへの影響 より効率的なトレース分析手法の確立 カスタムメトリクスの追加 トレースの可視化方法の改善 注意: A2Aのトレース構造は公式が今後変更する可能性があります。 本記事の内容は現時点(2025年10月)での実装に基づいており、将来的にライブラリのアップデートによってトレース構造やspan数が変わる可能性があることにご注意ください。 A2A × ADKエージェントの観測可能性を高めることで、より効率的なシステム開発とデバッグが可能になります。本記事で紹介したトレーシングとフィルタリングの技術が、皆さんのエージェント開発の一助となれば幸いです。 参考資料 本記事の実装にあたり、以下の公式ドキュメントを参考にしました: ADK公式ドキュメント - クイックスタート(Exposing) ADK公式ドキュメント - クイックスタート(Consuming) ADK Multi-tool Agent Sample Cloud Trace - AI Agent ADK のインストルメンテーション Langfuse公式ドキュメント - 計測スコープのフィルタリング

  • もう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アプリケーション開発環境を構築してみてください!

bottom of page