top of page

検索結果

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

  • 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の費用がかかるため、他のソリューションと比較して高額になる可能性があります。

  • LangfuseとLLMを活用したPIIマスキング:トレース保存の詳細と実践的注意点

    PIIフィルター用LLMのトレース保存 こちらの記事 で、Langfuse の PII フィルターに LLM を利用する方法について検証しました。 実際の運用では、LLM呼び出しにかかる費用を正確に把握するため、PIIフィルターの実行を含めたトレース記録が求められることがよくあります。ここでは、Langfuse の mask オプションを利用する場合の、PIIフィルターLLMの呼び出しとメインのLLM呼び出しを同一トレース内で記録する方法と、その際の注意点について解説します。 トレース記録の構成イメージ 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.start_as_current_generation の input や generation.update の output に対して、自動的に masking_function が呼び出され、PIIがマスキングされた状態でトレースに保存されます。 注意点:再帰呼び出し(無限ループ)への対策 mask オプションで設定した masking_function 内で、さらに Langfuse のトレース保存(例: langfuse.start_as_current_generation)を行おうとすると、 無限ループ に陥る可能性があります。これは、トレース保存処理自体が mask オプションの対象となり、masking_function が再帰的に呼び出されてしまうためです。 この問題を避けるためには、masking_function の内部でトレースを保存する際に、多重呼び出しを防止する何らかの制御が必要です。 今回のサンプルコードでは、処理を簡潔にするため、グローバル変数 (require_mask) をフラグとして持たせ、masking_function 内部からの呼び出しに対してはフィルターを適用しないようにしています。 実際の運用では、mask オプションを利用せず明示的にマスキング用関数を呼び出す方法や、より堅牢な方法を検討することをお勧めします。 検証結果 今回は、以下の条件でトレースの確認を行いました。 入力値、LLMのプロンプト(PIIフィルター用):   こちらの記事 でも利用した、以下のダミーデータとプロンプトを利用 入力値 私の個人情報は以下の通りです: 氏名:山田 太郎 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 プロンプト 以下のテキストから、個人情報をマスクしてください。 # 条件 - マスク処理以外は行わない - テキストの前後に余分なテキストを追加しない - どのようなデータがマスクされたかわかるようにする(例:山田太郎 -> [氏名1]、000-0000-0000 -> [電話番号1]) - 同じ個人情報種別でも、異なる情報の場合は別物であるとわかるようにする(例:山田さんと佐藤さん -> [氏名1]さんと[氏名2]さん) # 置換対象の個人情報 - 氏名 - 生年月日 - 住所 - 電話番号 - メールアドレス - 運転免許証番号 - パスポート番号 - 社会保障番号 - クレジットカード情報(カード番号, 有効期限, セキュリティコード) - 銀行口座情報(銀行名, 支店名, 口座番号) # 対象のテキスト {{userMessage}} LLMのプロンプト(問い合わせ用): 以下のテキスト中に「山」が何度出てくるか数えて # 対象の文書 [ 入力値 ] 結果概要 問い合わせ用のLLMの下に、PIIフィルター用のLLM呼び出しがネストする形で保存できました。 Langfuseのトレース上では、LLM呼び出し(問い合わせ1回、input、output のフィルター用各1回ずつ)計3回分のトークン数や費用が確認できます。 LLM呼び出し(llm_called_trace): このトレースでは、問い合わせの LLM 問い合わせにかかった費用と、一連の流れ(問い合わせ+PIIフィルター)でかかった費用の両方が表示されます。 このLLM への問い合わせ結果を metadata の情報として登録したい場合、mask オプションが適用される作りになっているかを十分に確認する必要があります。上記のコード例では、文字列型以外の場合にマスキング処理が適用されないため、場合によっては metadata に個人情報が除去されていない状態で残ってしまう可能性があります。トレースの output としては個人情報が除去されていても、metadata には生データが残るリスクがあるため、input や output と metadata に保存する形式が異なっている場合は特に、保存内容には注意を払うことが必要です。 冒頭の例のコードでLLM からの結果を metadata に保存(一部抜粋) LLM呼び出し(1つ目のpii_filter_prompt): langfuse.start_as_current_generation( name="llm_called_trace", input=[ユーザの入力], ) 上記で設定している、input に対して masking_function が呼び出された結果です。 with langfuse.start_as_current_generation( name="pii_filter_prompt", prompt=[使用するプロンプト] ) as generation: # LLM呼び出し(マスキング処理) generation.update( output=[フィルター済みデータ], ) 今回はPII フィルターに対するトレースを保存する際に、 PIIフィルターを適用しないよう制御しています。そのため、 個人情報が除去されていない生データを残さないよう、input は保存しないようにします。 Langfuse で費用を表示したい場合には、モデルとトークン数の情報も必要になります。 GeminiAPIの場合は、LLM の結果情報に含まれているため、 generation.update の際に model, usage_details もあわせて保存すると今回の例のような形になります。 LLM呼び出し (2つ目のpii_filter_prompt) : # LLM呼び出し(入力に対する回答取得用) generation.update( output=[LLMの回答], ) 上記で設定している、output に対して masking_function が呼び出された結果です。ここでも mask オプションが適用されます。 input に対するトレース保存と同様に、input には何も設定しないことで、誤って生データがトレースに保存されることを防ぎます。 まとめ この記事では、Langfuseのmaskオプションを活用し、PII(個人特定情報)フィルターにLLMを用いる際のトレース記録方法とその注意点について深掘りしました。LLM呼び出しにかかるコストを正確に把握するためには、PIIフィルターの実行を含む一連の処理を同一トレース内で記録することが重要です。 Langfuseのmaskオプションは、inputやoutputデータがトレースに保存される際に自動的にマスキング関数を適用できる便利な機能です。しかし、オプションで指定した関数内部で再度Langfuseのトレース保存処理を行うと無限ループに陥る可能性があるため、実際に利用する際は適切かつ堅牢な処理となるよう注意が必要です。 また、metadataに個人情報を保存する際の注意点、PIIフィルターLLMのinputには生データを設定しないことの重要性についても解説しました。 これらの注意点を踏まえることで、PIIを適切に処理しつつ、Langfuseの強力なトレース機能を最大限に活用し、LLMアプリケーションの運用コストと安全性を両立させることができます。実際の運用では、本記事で紹介した内容を参考に、トレース設計を検討することをお勧めします。

  • Langfuseにおける個人情報(PII)マスキング:Gemini 2.5 Flash Liteで実現するLLMベースPIIフィルター

    LangfuseにおけるPIIマスキング手法の検討 前回の記事 では、Guardrails for Amazon Bedrockを利用したPIIマスキングについて紹介しました。個人情報を列挙した形のテストデータにおいては精度が高く、大半の情報は除去できましたが、一部、フィルターが準備されていないなど、追加の対応が必要な項目が残っていました。 また、この機能はAmazon Bedrockに依存するため、AWS以外のクラウドを利用している方にとっては導入のハードルがやや高くなると考えます。 そこで本記事では、PIIマスキングをLLM(大規模言語モデル)に任せるアプローチを検証します。 LLMを活用する利点は、プロンプトの調整によって非エンジニアでも容易に設定変更できること、そして文脈を考慮した判断が可能になる点です。(例えば、芸能人の名前や公開されている会社の住所は公開情報としてマスキングしないなど) さらに、PIIフィルター用のプロンプトもあわせてLangfuseで管理することで、コードのデプロイを都度行う必要や、クラウドのコンソール画面を触ることなく、非エンジニアでもプロンプトの調整を容易に行えるようになります。 今回のLLMモデルには、 Gemini 2.5 Flash-Lite を採用します。 Gemini 2.5 Flash Liteとは? Googleが提供する最新の基盤モデルファミリーであり、低レイテンシのユースケース向けに最適化された、最もバランスの取れたGeminiモデルです。 ただし、現在パブリックプレビュー中のため、利用できる場面は限られています。 なぜGemini 2.5 Flash Liteなのか 処理速度が高速で、料金も入力トークン100万あたり、$0.1、出力は$0.4と低コストで提供されています。料金は、Gemini 2.5 Flash と比較すると、おおよそ3分の1以下の費用に抑えられています。 PIIフィルターは呼び出し頻度が高くなるため、高速かつ低コストで利用できる点を高く評価し、今回採用しました。 PIIマスキングのテスト方法 前回、前々回と同様のアプリケーションコードと、テストデータを用いて検証を行います。 アプリケーションコード 前回同様、masking_function の内部を調整します。 client = genai.Client(api_key="GOOGLE_API_KEY") def masking_function(data: any, **kwargs) -> any: if isinstance(data, str): # マスキング処理 response = client.models.generate_content( model='models/gemini-2.5-flash-lite-preview-06-17', contents=data ) sanitized_data = response.text 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 検証結果1 上記の準備を行った上で、アプリケーションを実行します。 トレースを確認したところ、以下の結果が登録されていました。 ご提示いただいた個人情報につきまして、私はAIアシスタントであり、あなたの個人情報を直接保存・管理することはできません。 いただいた情報は、ご自身の確認のために役立つかもしれませんが、これらの情報は非常に機密性の高いものです。これらの情報を安易に他者に共有することは、悪用されるリスクを高めます。 特に、クレジットカード情報や銀行口座情報は、絶対に第三者に教えないでください。 もし、これらの情報を安全に管理したいのであれば、パスワードで保護された安全な場所(例えば、パスワードマネージャーなど)に保存することをお勧めします。 もし、私にこれらの情報について何か特別な操作(例えば、どこかに登録するなど)を求めていらっしゃるのであれば、その意図を具体的に教えていただけますでしょうか?ただし、どのような場合でも、私のシステムに直接個人情報を入力したり保存したりすることはできませんので、その点ご了承ください。 ダミーデータをそのままLLMに投入したため、データに対する具体的な指示や解凍形式が適切に含まれていませんでした。 比較を行う前に、この点を改善します。 LLMフィルタリング用のプロンプト設計 LLMから意図する回答を得るためには、プロンプトの改善が必要です。アプリケーションコードの修正を都度行うのは煩わしいため、PIIフィルター用のプロンプトも Langfuse で管理することにしました。 アプリケーションコード(プロンプト管理版) Langfuse で管理しているプロンプトを利用する形になるよう調整します。 # Langfuse で管理しているプロンプトの取得 pii_prompt_template = langfuse.get_prompt("pii_filter_prompt", label=langfuse_config.prompt_label) client = genai.Client(api_key="GOOGLE_API_KEY") def masking_function(data: any, **kwargs) -> any: if isinstance(data, str): prompt_content = pii_prompt_template.compile(userMessage=data) # マスキング処理 response = client.models.generate_content( # モデル情報もプロンプトの config から取得 model=pii_prompt_template.config.get("model", "models/gemini-2.5-flash-lite-preview-06-17") , contents=prompt_content ) sanitized_data = response.text return sanitized_data else: return data プロンプト設定 先のコード修正により、 pii_filter_prompt という名称のプロンプトが利用可能になりました。Langfuse 側で該当の名前のプロンプトを作成し、調整を行います。 今回の検証では、以下の点を満たす PII フィルター用のプロンプトを設定します。 過去2回で対象とした項目を網羅できること どのようなデータがマスクされたかわかること 入力値以外の応答文等が入らないこと 3点目については、簡易的なプロンプトで個人情報がマスクされるかを試したところ、下記のように入力値にない応答文が含まれる結果となりました。 これでは、入力値からPII情報のみを抽出・マスキングするという本来の目的を達成できないため、この点をプロンプトの条件に追加しています。 承知いたしました。以下のように個人情報をマスクしました。 私の個人情報は以下の通りです: 氏名:**** **** Name: **** **** (後略) Langfuse 上でプロンプトの調整を行いつつ、最終的に、今回の検証では以下のプロンプトをPIIフィルター用として設定しました。 以下のテキストから、個人情報をマスクしてください。 # 条件 - マスク処理以外は行わない - テキストの前後に余分なテキストを追加しない - どのようなデータがマスクされたかわかるようにする(例:山田太郎 -> [氏名1]、000-0000-0000 -> [電話番号1]) - 同じ個人情報種別でも、異なる情報の場合は別物であるとわかるようにする(例:山田さんと佐藤さん -> [氏名1]さんと[氏名2]さん) # 置換対象の個人情報 - 氏名 - 生年月日 - 住所 - 電話番号 - メールアドレス - 運転免許証番号 - パスポート番号 - 社会保障番号 - クレジットカード情報(カード番号, 有効期限, セキュリティコード) - 銀行口座情報(銀行名, 支店名, 口座番号) # 対象のテキスト {{userMessage}} こちらの状態で、PIIフィルターの検証を再度行います。 検証結果2 前回の Guardrails for Amazon Bedrock、前々回のllm-guard を利用した際の結果と比較していきます。 LLM利用 (Gemini 2.5 Flash-Lite) llm-guard(デフォルト設定) Guardrail for Amazon Bedrock 氏名(日本語) [氏名1] [REDACTED_PERSON_1][REDACTED_PERSON_2][REDACTED_PERSON_3] {NAME} 氏名(英語) [氏名2] [REDACTED_PERSON_4] {NAME} 生年月日 [生年月日1] - - 住所 [住所1] - {ADDRESS} 電話番号(日本) [電話番号1] / [電話番号2] 03-1234-[REDACTED_PHONE_NUMBER_1] {PHONE} 電話番号(海外) [電話番号3] / [電話番号4] [REDACTED_PHONE_NUMBER_2] {PHONE} メールアドレス [メールアドレス1] [REDACTED_EMAIL_ADDRESS_1] {EMAIL} 運転免許証番号 [運転免許証番号1] - {DRIVER_ID} パスポート番号 [パスポート番号1] - {US_PASSPORT_NUMBER} 社会保障番号 [社会保障番号1] [REDACTED_US_SSN_RE_1] 987-65-4321  {US_SOCIAL_SECURITY_NUMBER} クレジットカード(カード番号) [クレジットカード番号1] [REDACTED_CREDIT_CARD_RE_1] {CREDIT_DEBIT_CARD_NUMBER} クレジットカード(カード番号以外) 有効期限:[クレジットカード有効期限1] セキュリティコード:[クレジットカードセキュリティコード1] - 有効期限:{CREDIT_DEBIT_CARD_EXPIRY} セキュリティコード:{CREDIT_DEBIT_CARD_CVV} 銀行口座情報 銀行名:[銀行名1] 支店名:[銀行支店名1] 口座番号:[銀行口座番号1] Bank Name: [銀行名2] Branch: [銀行支店名2] Account Number: [銀行口座番号2] - 銀行名:みずほ銀行 支店名:新宿支店 口座番号:{US_BANK_ACCOUNT_NUMBER} Bank Name: Mizuho Bank Branch: {ADDRESS} Branch Account Number: {US_BANK_ACCOUNT_NUMBER} llm-guard、Guardrails for Amazon Bedrock との比較において、いずれかと差異のある項目のみ背景色を明るくしています。 前回確認した、Guardrails においては、フィルターが対応している項目においてはおおむね問題なく検知できているという結果でしたが、今回の LLM を利用したフィルターについては、過去2回で検証した結果と比較して、検出漏れ・誤検出が少ない結果となりました。 今回は LLM を利用するケースが優位に見える結果になりましたが、検証に利用しているダミーデータは自然な文脈が存在せず、個人情報が明示的に記載されているため、自然な文脈で利用した場合にはまた違う結果となる可能性があります。 また、今回提示しているアプリケーションコードでは、PIIフィルター処理そのものに対するトレース保存は行っていませんが、この処理に対してもトレースを保存することで、より詳細なコスト感を把握できるようになります。 PIIフィルターのプロンプトに関しては、Langfuseの評価機能を活用することで、さらなる制度の工場が期待できます。 まとめ 本記事では、LLM(Gemini 2.5 Flash-Lite)を利用したPIIマスキングについて検証しました。Langfuseのトレース情報から個人情報を確実に除去するという目的のもと、PIIの除去については、概ね問題なく行える見込みです。 ただし、今回の検証は限定的な条件下での確認であるため、実際にアプリケーションで利用する際には、さらなる詳細な検証とプロンプトの改善が必要不可欠です。また、前回の記事でも触れましたが、LLMを利用するため、本実装のままではコスト面などから実際のアプリケーションで利用するには最適ではない可能性もあります。プロンプトや実装のアプローチについて、多角的に比較・検討することをお勧めします。

  • Langfuseにおける個人情報(PII)マスキング:Guardrails for Amazon Bedrockの活用

    LangfuseにおけるPIIマスキング手法の検討 前回の記事 では、llm-guardを用いたPIIマスキングについて検証しました。llm-guardは柔軟なモデル選択が可能である一方、日本語のPII検出にはまだ課題が残ることが分かりました。 そこで、今回はその代替手段として、Guardrails for Amazon Bedrockの機密情報フィルター(Maskモード)の利用を検討します。 Guardrails for Amazon Bedrock とは? Guardrails for Amazon Bedrock は、Amazon Bedrock 上に構築される生成 AI アプリケーションに対して、以下の主要なガードレール(保護機能)を提供します。これらのガードレールは、内部的にAmazon Bedrockの基盤モデル(FM)の推論能力を活用してコンテンツの評価やフィルタリングを行っています。ユーザーがガードレール内で直接LLMモデルを指定することはできませんが、高度なAIを活用したコンテンツモデレーションが実現されています。 コンテンツフィルター: 憎悪、侮辱、性的、暴力、不法行為、プロンプト攻撃などのカテゴリに対してフィルタリング強度を調整できま 禁止トピック: 特定の話題に関するプロンプトや応答をブロックできます ワードフィルター: 指定した単語が含まれる入出力をブロックまたはマスキングできます 機密情報フィルター (Sensitive Information Filters): 氏名、メールアドレス、電話番号、クレジットカード番号などの PII を含む入出力を検出・ブロック・マスキングする機能です 特に「機密情報フィルター」は、事前に定義された PII タイプ(31種類以上)に対応しており、正規表現(RegEx)を使用してカスタムの機密情報を定義することも可能です。 また、PII の処理方法として、以下の2つのモードを選択できます。 BLOCK: 機密情報が検出された場合、コンテンツ全体をブロックし、カスタムメッセージを返します MASK: コンテンツに含まれる機密情報をマスキング(編集)し、[NAME]、[EMAIL]のような識別子タグに置き換えます なぜGuardrails for Amazon Bedrockなのか? Guardrails for Amazon Bedrockには、基盤モデルを呼び出すことなく、ガードレールのルールのみを適用してコンテンツを評価・マスキングできる独立したAPI(bedrock_runtime.apply_guardrail)が存在します。このAPIを利用することで、LLMの推論プロセスとは分離して、Guardrailsが提供するPIIマスキング機能のみを検証・活用できます。 このapply_guardrailメソッドを活用し、Langfuseのトレースにおけるマスキング処理を実現します。 PIIマスキングのテスト方法 今回は、以下の手順でGuardrails for Amazon Bedrockの機密情報フィルター(Maskモード)の動作を検証します。 事前準備 AWS上に有効なGuardrailsを作成する必要があります。今回は正規表現によるカスタム制約は検証せず、標準設定で利用可能な機密情報フィルターに焦点を当てます。 事前定義されているPIIフィルターは こちら に記載があります。 上記のページを参考に、コンソールまたはAPIから利用したい機密情報フィルターを設定していきます。今回は PII のマスキングを行うため、フィルターの動作は「マスク」を選択します。 参考 前回マスキング対象として期待した項目と、llm-guardのAnonymize機能が対応しているとされる項目、Guardrails form Amazon Bedrockの機密情報フィルターで相応すると思われる項目を並べてみました。 llm-guard と比較すると、Guardrails for Amazon Bedrock は標準で設定可能なPIIフィルターが充実していることが分かります。 PII llm-guard Guardrail for Amazon Bedrock 氏名 人名 NAME 生年月日 - - 住所 - ADDRESS 電話番号 電話番号 PHONE メールアドレス メールアドレス EMAIL 運転免許証番号 - DRIVER_ID パスポート番号 - US_PASSPORT_NUMBER 社会保障番号 米国社会保障番号(SSN) US_SOCIAL_SECURITY_NUMBER (クレジットカード情報) カード番号 クレジットカード CREDIT_DEBIT_CARD_NUMBER (クレジットカード情報) 有効期限 - CREDIT_DEBIT_CARD_EXPIRY (クレジットカード情報) セキュリティコード - CREDIT_DEBIT_CARD_CVV (銀行口座情報)銀行名 - - (銀行口座情報)支店名 - - (銀行口座情報)口座番号 - US_BANK_ROUTING_NUMBER Guardrailsを利用すれば、生年月日と銀行口座情報の銀行名、支店名を除き期待される項目は網羅できそうです。 今回の検証では、誤検知が起こらないかを確認する意味合いも含め、上記に含まれない項目を含めた31種をフィルターとして設定しました。 アプリケーションコード 前回同様、簡単なコードを用いて、プロンプトの入力とLLMの回答を模擬的に生成し、それぞれをLangfuseに登録する形式でテストを行いました。 前回のコードに対し、マスキング処理部分と初期化部分に手を加えます。今回はGuardrails for Amazon Bedrockのapply_guardrailメソッドを利用するように変更しています。 bedrockRuntime = boto3.client( 'bedrock-runtime', region_name="*********", aws_access_key_id="**********************", aws_secret_access_key="**********************" ) guardrail_id = "[あなたのガードレールのID]" guardrail_version = "DRAFT" # またはバージョン番号 def masking_function(data: any, **kwargs) -> any if isinstance(data, str): responce = bedrockRuntime.apply_guardrail( guardrailIdentifier=guardrail_id, guardrailVersion=guardrail_version, source="INPUT", # 今回はシンプルにするため、常に"INPUT"として評価する content=[ { "text": { "text": data } } ] ) # outputsからマスキング済みテキストを取得 if 'outputs' in responce and len(responce['outputs']) > 0: sanitized_data = responce['outputs'][0].get('text', data) return sanitized_data else: # outputsがない場合は元のデータを返す return data return data テストデータ 前回利用した以下のダミーデータを、今回もそのまま利用します。 私の個人情報は以下の通りです: 氏名:山田 太郎 Name: Taro Yamada 生年月日:1985年3月15日 Birthday: March 15, 198 住所:東京都新宿区西新宿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)それぞれで処理した際のPII項目ごとの置換結果を比較します。 llm-guard(デフォルト設定) Guardrail for Amazon Bedrock 氏名(日本語) [REDACTED_PERSON_1][REDACTED_PERSON_2][REDACTED_PERSON_3] {NAME} 氏名(英語) [REDACTED_PERSON_4] {NAME} 生年月日 - - 住所 - {ADDRESS} 電話番号(日本) 03-1234-[REDACTED_PHONE_NUMBER_1] {PHONE} 電話番号(海外) [REDACTED_PHONE_NUMBER_2] {PHONE} メールアドレス [REDACTED_EMAIL_ADDRESS_1] {EMAIL} 運転免許証番号 - {DRIVER_ID} パスポート番号 - {US_PASSPORT_NUMBER} 社会保障番号 [REDACTED_US_SSN_RE_1] 987-65-4321  {US_SOCIAL_SECURITY_NUMBER} クレジットカード(カード番号) [REDACTED_CREDIT_CARD_RE_1] {CREDIT_DEBIT_CARD_NUMBER} クレジットカード(カード番号以外) - 有効期限:{CREDIT_DEBIT_CARD_EXPIRY} セキュリティコード:{CREDIT_DEBIT_CARD_CVV} 銀行口座情報 - 銀行名:みずほ銀行 支店名:新宿支店 口座番号:{US_BANK_ACCOUNT_NUMBER} Bank Name: Mizuho Bank Branch: {ADDRESS} Branch Account Number: {US_BANK_ACCOUNT_NUMBER} llm-guardのマスキング結果は、前回記事のデフォルト設定時のものです。 一部誤検出や期待通りに検出されていないものもありますが、概ね期待通りの結果が得られました。 結果詳細(誤検出・検出漏れ項目の抜粋) 社会保障番号:987-65-4321 Social Security Number: {US_SOCIAL_SECURITY_NUMBER} 英語版は適切にマスキングされましたが、日本語版においては英語版と同様のフォーマットを渡していますが、マスキングされませんでした。 本項目については、米国向けのフィルターとして準備されているため、日本語の文脈では対応できない可能性があります。 銀行口座情報: 銀行名:みずほ銀行 支店名:新宿支店 口座番号:{US_BANK_ACCOUNT_NUMBER} Bank Account Information: Bank Name: Mizuho Bank Branch: {ADDRESS} Branch Account Number: {US_BANK_ACCOUNT_NUMBER} 英語版の支店名が住所として誤検出されていました。 今回は文脈を含まない情報でテストしましたが、実際の自然な文脈中で銀行支店名が出現した場合の挙動は別途検証が必要です。 今回の検証では、口座番号は正常に検出されていますが、利用したフィルターは米国の銀行口座番号として準備されています。実際に利用する際は、日本の形式に適合するか、もう少し検証してみる必要はありそうです。 まとめ 今回はGuardrails for Amazon BedrockのPIIマスキング機能について検証しました。 テストの結果、概ね Guardrails for Amazon Bedrock で PII の除去が出来ました。今回の検証では、限定的な条件での確認となるため、実際にアプリケーションで利用する際は、自然なテキスト中に含まれている場合や、全角文字が混在する場合など、日本語特有の表現が含まれている場合の挙動については、さらに詳細な検証が必要となります。 一方、現状ではカバーしきれていない情報もいくつかは残っています。 しかし、フィルターが準備されていない生年月日や、検知が上手くいかなかった社会保障番号(類似する情報であるマイナンバー)においては、ある程度フォーマットが定まっている情報になります。これらに対しては、正規表現フィルターを併用することで、PII除去の確実性を高められるでしょう。 検討課題 今回の検証では Langfuse の mask オプションを使用して実装しましたが、運用を考慮すると、今回の実装が適切とは限りません。 mask オプションは Langfuse にデータが渡されるたびに実行されるため、APIコール数が想定以上に増加し、コストに影響する可能性があります。実際、llm-guard のようなツールとは異なり、今回利用した機密情報フィルターについては、 Bedrockの料金ページ に記載の通り、1,000テキストユニットあたり0.10 USD発生します。(2025年7月時点) また、mask オプションを利用することで、エラーハンドリングが複雑になってしまう側面もあります。API エラーが発生した場合に Langfuse のトレース処理全体に影響を及ぼす懸念も無視できません。 そのため、Langfuse のトレース保存前に、アプリケーションコード内で明示的に apply_guardrail を呼び出し、その結果を input や output として Langfuse に渡す方法など、様々な実装アプローチを比較・検討することをおすすめします。

  • 自動化ツール n8n と Langfuse の連携

    n8nとは何か n8nは「nodemation」の略称で、ドラッグ&ドロップ操作や各ノードの設定によってワークフローを作成できる自動化ツールです。300以上の組み込みノードを提供しており、Slack、Gmail、Notion、カレンダー、Webhookなど、様々なサービスとの連携が可能です。コードを書くことなく複雑な自動化フローを構築できる一方で、JavaScript や最近はPython を使用したコードの実行をフローに埋め込むことなどにも対応しています。 Dify との違いは何? 両方とも基本的にOSSベースのノーコードツールと位置付けてよいと思いますが、Difyは「生成AIアプリを作る」ツール、n8nは「生成AIを含むシステムを繋いで業務を処理する」ツールという違いがあると思います。特に実際のオフィスワークにおいては、生成AIをはじめとして JIRA やCRM, mail, Slack などと連携することが求められるケースも多く、n8n はそのようなケースで非常に有用であると考えられます。またコードを書くこともできるので、痒いところに手が届くところも人気の理由です。 ちなみに n8n は Dify よりも歴史が長く、その分で安定しているという意見もある一方で、現在利用者や人気という点ではほぼ互角に見えます (ただし日本ではローカライズが早かった Dify の方が採用が多いように思われます) Github starsの推移、n8nの歴史は長いが スター数は近い n8n と Langfuse 連携の価値 そんな n8n と Langfuse を連携させる理由の一つは、もちろんTraceを取得することによる処理の可視化です。レイテンシーやコスト、システムの中でどのような処理が行われているかを可視化することは運用上で大きな意味を持ちます。またLLM as a judge などの評価にも利用することも可能となります。 もう一つの価値はプロンプト管理です。n8n の生成AIアプリケーションのノードの中にプロンプトをハードコードしても使い回しや世代管理などをすることができず、管理も煩雑になり、生産的ではありません。以降では、実際の設定方法について記載していきます。 n8n サンプル構成概要 今回の説明には、サンプルとして下図のシンプルなフローを用います。 [構成環境] Version 1.99.1 OSS Self-hosted on CloudRun + CloudSQL (PostgreSQL), [サンプルフロー] n8n フロー チャットでの入力を受け、このフローは以下の動作をします。 Code ノードがプロンプトを Langfuse から取得 そのプロンプトを AI Agentノードの System prompt に入れ、チャットに入力された内容が user prompt として入れられる その情報が Langchain ノード (Langfuse LLM) に入り、そこから今回はVertexAIのModelを呼び出す (これは OpenAI でも Bedrock でも何でも同じです) ひとつずつコードと設定を見てみましょう。 Codeノード コードサンプル const { Langfuse } = require("langfuse"); const langfuseClient = new Langfuse(); return langfuseClient.getPrompt("wording").then(prompt => { const variables = $input.item.json.variables || {}; const systemPrompt = prompt.compile(variables); return { json: { systemPrompt: systemPrompt, userMessage: $input.item.json.chatInput, langfusePrompt: prompt, promptVersion: prompt.version, promptName: "wording", sessionId: $input.item.json.sessionId } }; }); まず langfuse を読み込み、その後に wording という 名前のプロンプトを langfuseから取って、それを SystemPrompt に渡しているだけです。その後、各変数をセットしています。ちなみに Langfuse の host などは環境変数に入れておくなどする必要があり、langfuseパッケージもコンテナの中にモジュールをインストールしておく必要がありますのでご注意ください。また今回は特に使っておりませんが、prompt.compile に変数を渡してあげることで、 Langfuse の中のプロンプトに変数を渡してあげることもできます。 AI Agentノード このノードにはコードは書く必要はありません。Prompt の中に User messageを定義し、System Message として、1. Codeノードで取得した内容を反映させておきます。 前のノードを実行するとこの画像の左側ペインに変数がでるので、それをドラッグ&ドロップして設定するのが簡単 Langchain ノード コードサンプル const { CallbackHandler } = require("langfuse-langchain"); const model = await this.getInputConnectionData("ai_languageModel", 0); const inputData = $input.item.json; const langfuseHandler = new CallbackHandler({ sessionId: inputData.sessionId || `session_${Date.now()}`, userId: inputData.userId || "anonymous", tags: ["n8n", "ai-agent", "production"] }); model.callbacks = model.callbacks || []; model.callbacks.push(langfuseHandler); return model; まず langfuse-langchain パッケージから CallbackHandler クラスをインポートし、次にLLMモデルを取得します。この例ではGoogle Vertex Chat model かモデル名と 0 (最初の接続) を指定してとってきます。とってきたjson からデータを展開し、sessionIDやUserIDを入れております。ついでに n8n などのタグも設定しています。 そしてmodelに callback を追加して、model オブジェクトを AI Agent ノードに渡します。 以上で、やるべきことは終わりです。 Langfuse のTraceを見てみると以下のように記録されているはずです。Langchainを使ってるので、勝手にTokenなども取られています。 プロンプトには "言葉遣いは荒々しい野武士のようにしてください" と指定しており、それをそのままSystem promptとして指定してある。 いかがでしたでしょうか。n8n は簡単に使えるツールですが、生成AIの利用もサポートされており、Langfuseとの組み合わせも実現可能です。ご興味あるかたは、ぜひ試してみてください。

  • Langfuseにおける個人情報(PII)のマスキング

    LangfuseにおけるPIIマスキングの必要性 チャットボットのようなアプリケーションでは、ユーザーが意図せず個人情報(PII)を入力してしまう可能性があります。 個人情報保護 の観点から、これらの情報がLangfuseのトレースにそのまま出力されるのは望ましくありません。 そこで、トレース上で 個人情報 をマスキングした状態で確認できるよう、どのような手段が考えられるか検証しました。 個人情報(PII)の定義と具体例 具体的に 個人情報(PII) に該当する項目は、一般的に以下のものが挙げられます。 氏名 住所 電話番号 メールアドレス 顔写真 身分証番号(運転免許証、パスポートなど) 生年月日 社会保障番号 クレジットカード情報 銀行口座情報 今回はテキスト入力ベースのアプリケーションを想定しているため、顔写真のような画像データは検証対象外とします。 LangfuseにおけるPIIマスキング手法の検討 Langfuseの公式サイトで紹介されている Masking of Sensitive LLM Data を参考に、マスキング手法を検討します。 氏名や住所は正規表現での対応が難しいため、今回は Example 2 で紹介されている llm-guard を試してみました。 氏名や住所のような複雑な情報は正規表現での対応が難しいため、今回はExample 2で紹介されている llm-guard を試用しました。 今回試用した llm-guard のAnonymize機能は、現在以下の個人情報(PII)の検出に対応しています。 クレジットカード 人名 電話番号 URL メールアドレス IPアドレス UUID 米国社会保障番号(SSN) 暗号資産ウォレット番号 IBANコード 今回マスキング対象として期待している項目は以下の通りです。 氏名 生年月日 住所 電話番号 メールアドレス 運転免許証番号 パスポート番号 社会保障番号 クレジットカード情報 カード番号 有効期限 セキュリティコード 銀行口座情報 銀行名 支店名 口座番号 PIIマスキングのテスト方法 簡単なコードを用いて、プロンプトの入力とLLMの回答を模擬的に生成し、それぞれをLangfuseに登録する形式でテストを行いました。 アプリケーションコード 今回の検証では、PythonアプリケーションからLangfuseにトレースを投入するケースを対象としています。マスキング処理は、Langfuseの初期化時にmaskオプションに対して、処理を行う関数を設定することで実現できます。 vault = Vault() def create_anonymize_scanner(): scanner = Anonymize( vault=vault, ) return scanner def masking_function(data: any, **kwargs) -> any: if isinstance(data, str): scanner = create_anonymize_scanner() sanitized_data, is_valid, score = scanner.scan(data) return sanitized_data return data # テスト用の入力テキスト input_text = """[[ダミーデータを含むプロンプト]]""" # Langfuseの初期化 langfuse = Langfuse( public_key="xxxxxxxxxxxxxxxxxxxxxxxx", secret_key="xxxxxxxxxxxxxxxxxxxxxxxx", host="http://xxxxxxxxxxxxxxxx", mask=masking_function, ) # LLM呼び出しのトレース with langfuse.start_as_current_generation( name=f"test_step", input=input_text, ) as generation: # LLMからの実際の出力を格納 response_content = input_text # 現在はダミーデータを使用 generation.update(output=response_content) テストデータ テスト用のプロンプトとLLMの出力結果として、Cursorを用いて上記の個人情報(PII)具体例を含むダミーデータを以下の通り生成しました。インプットとアウトプットの双方に個人情報が含まれるとトレース上での問題となるため、今回は同じテストデータを利用し、両方のデータにマスキングが適用されるかを確認します。 私の個人情報は以下の通りです: 氏名:山田 太郎 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 最低限の引数での動作確認 上記のコードを実行したところ、何らかの置換が行われていることが確認できました。 検証結果:インプットとアウトプットの比較 インプットの結果をpt1_input.txt、アウトプットの結果をpt1_output.txt として比較しました。 inputとoutputのいずれに渡した場合でも、マスキング処理が適応されていることが確認できました。 検証結果:各個人情報の検出状況 プロンプトとして与えた各個人情報について、どのように処理されたかひとつずつ確認していきます。 氏名:△ 氏名:[REDACTED_PERSON_1][REDACTED_PERSON_2][REDACTED_PERSON_3] Name: [REDACTED_PERSON_4] 検知・置換自体は行われていますが、日本語名が意図せず三分割されている点が課題です。 生年月日:× 住所:× これらは llm-guard の現在の検出対象外のため、想定通りの結果です。 電話番号: × 電話番号:03-1234-[REDACTED_PHONE_NUMBER_1] Phone: [REDACTED_PHONE_NUMBER_2] 2種類の電話番号を「/」区切りで記載していましたが、一つの電話番号とみなされているようです。いずれにしても、日本の電話番号形式への対応はまだ課題があります。 メールアドレス:○ 運転免許証番号:× パスポート番号:× 社会保障番号:○ これらの項目については、当初の想定通りの検出結果となりました(運転免許証番号、パスポート番号は検出対象外のため×、メールアドレス、社会保障番号は検出対象のため○)。 クレジットカード情報:△ クレジットカード情報: カード番号:[REDACTED_CREDIT_CARD_RE_1] 有効期限:12/25 セキュリティコード:123 カード番号は問題なく検出できましたが、有効期限やセキュリティコードはマスキングされませんでした。 ドキュメント ではVisa、American Express、Diners Clubに対応とあるため、他のカード会社への対応状況も確認が必要です。 銀行口座情報:× こちらも llm-guard の現在の検出対象外のため、想定通りの結果です。 各種設定や検出モデルの変更による検証 オプションの設定や検出に利用するモデルを変更することにより、対応している項目に関しては精度が向上する可能性があると考え、合わせて確認してみました。 日本語設定の試行 Anonymizeの言語設定は標準では英語となっています。こちらを日本語に変更可能か試行しました。 しかし、 llm-guard が現在サポートしているのは英語('en')と中国語('zh')のみであることが確認されました。このため、日本語の PII 検出において、言語設定によるアプローチは現状では利用できません。 recognizer_confの変更による比較検証 Anonymizeでは、recognizer_confパラメータで検出モデルを指定できます。コードを以下のように変更し、llm_guard.input_scanners.anonymize_helpers で定義されているモデルを順に試行します。 scanner = Anonymize( vault=vault,   recognizer_conf=[[ ここの値を変更 ]] ) 定義されているモデルは以下の7種類になります。 BERT_BASE_NER_CONF(dslim/bert-base-NER) BERT_LARGE_NER_CONF(dslim/bert-large-NER) BERT_ZH_NER_CONF(gyr66/bert-base-chinese-finetuned-ner) DISTILBERT_AI4PRIVACY_v2_CONF(Isotonic/distilbert_finetuned_ai4privacy_v2) DEBERTA_AI4PRIVACY_v2_CONF(Isotonic/deberta-v3-base_finetuned_ai4privacy_v2) MDEBERTA_AI4PRIVACY_v2_CONF(Isotonic/mdeberta-v3-base_finetuned_ai4privacy_v2) DEBERTA_LAKSHYAKH93_CONF(lakshyakh93/deberta_finetuned_pii) 各モデルにおいて、先に試行したデフォルト設定時の差分に重点を置いて確認していきます。 デフォルト設定時にマスキングされなかったものについては、「-」で表示しています。 (マスキングの数値のみが異なっている場合は、同様の検出が行われたと判断しています) 氏名(日本語) 氏名(英語) 生年月日 住所 電話番号(日本) 電話番号(海外) メールアドレス 運転免許証番号 パスポート番号 社会保障番号 クレジットカード(カード番号) クレジットカード(カード番号以外) 銀行口座情報 デフォルト設定(参考) [REDACTED_PERSON_1][REDACTED_PERSON_2][REDACTED_PERSON_3] [REDACTED_PERSON_4] - - 03-1234-[REDACTED_PHONE_NUMBER_1] [REDACTED_PHONE_NUMBER_2] [REDACTED_EMAIL_ADDRESS_1] - - [REDACTED_US_SSN_RE_1] [REDACTED_CREDIT_CARD_RE_1] - - BERT_BASE_NER_CONF 山田 太郎 [REDACTED_PERSON_1][REDACTED_PERSON_2]mada - - 03-1234-5678 / 090-9999-8888 [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] [REDACTED_EMAIL_ADDRESS_1] - - [REDACTED_US_SSN_RE_1] [REDACTED_CREDIT_CARD_RE_1] - - BERT_LARGE_NER_CONF 山田 太郎 [REDACTED_PERSON_3] - - 03-1234-5678 / 090-9999-8888 [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] [REDACTED_EMAIL_ADDRESS_1] - - [REDACTED_US_SSN_RE_1] [REDACTED_CREDIT_CARD_RE_1] - - BERT_ZH_NER_CONF [REDACTED_PERSON_4] Taro Yamada - - 03-1234-5678 / 090-9999-8888 [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] [REDACTED_EMAIL_ADDRESS_1] - - [REDACTED_US_SSN_RE_1] [REDACTED_CREDIT_CARD_RE_1] - - DISTILBERT_AI4PRIVACY_v2_CONF 山田 太郎 Taro Yamada - - 03[REDACTED_PHONE_NUMBER_3] [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] [REDACTED_EMAIL_ADDRESS_1] 運転免許証番号:[REDACTED_PHONE_NUMBER_4] Driver's License: DL-[REDACTED_PHONE_NUMBER_5]12 - [REDACTED_US_SSN_RE_1] [REDACTED_CREDIT_CARD_RE_1] - - DEBERTA_AI4PRIVACY_v2_CONF [REDACTED_PERSON_5][REDACTED_PERSON_6][REDACTED_PERSON_7] [REDACTED_PERSON_3] - - 03-1234-[REDACTED_PHONE_NUMBER_6] [REDACTED_PHONE_NUMBER_7] [REDACTED_EMAIL_ADDRESS_1] - - [REDACTED_US_SSN_RE_1] [REDACTED_CREDIT_CARD_RE_1] - - MDEBERTA_AI4PRIVACY_v2_CONF [REDACTED_PERSON_8] 太郎 Taro [REDACTED_PERSON_9] - - [REDACTED_PHONE_NUMBER_8] / [REDACTED_PHONE_NUMBER_9] [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] [REDACTED_EMAIL_ADDRESS_1] - - [REDACTED_US_SSN_RE_1] [REDACTED_CREDIT_CARD_1] - - DEBERTA_LAKSHYAKH93_CONF 山田 太郎 [REDACTED_PERSON_1][REDACTED_PERSON_2][REDACTED_PERSON_3] [REDACTED_IBAN_CODE_1] [REDACTED_CRYPTO_2] 03-1234-5678 / 090-9999-8888 [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] [REDACTED_EMAIL_ADDRESS_1] Email: [REDACTED_EMAIL_ADDRESS_2] - [REDACTED_IBAN_CODE_2] Passport Number: TK1234567 社会[REDACTED_IBAN_CODE_3] Social Security Number: [REDACTED_US_SSN_RE_1] [REDACTED_CREDIT_CARD_RE_1] - [REDACTED_CRYPTO_3]- [REDACTED_CRYPTO_4] [REDACTED_IP_ADDRESS_2] 各モデルの比較検証結果 BERT_BASE_NER_CONF 氏名:山田 太郎  Name: [REDACTED_PERSON_1][REDACTED_PERSON_2]mada 日本語氏名はマスキングされず、英語氏名は部分的にマスキングされる形になりました。 電話番号:03-1234-5678 / 090-9999-8888 Phone: [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] 電話番号も全てマスキングされない結果です。 ただし、海外向けフォーマットにおいては、適切に2つ分として判断されているようです。 BERT_LARGE_NER_CONF 氏名:山田 太郎 Name: [REDACTED_PERSON_3] 日本語氏名はマスキングされませんでしたが、英語氏名は綺麗にマスキングされました。 電話番号:03-1234-5678 / 090-9999-8888 Phone: [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] 電話番号については、BERT_BASE_NER と同じ形になりました。 BERT_ZH_NER_CONF 氏名:[REDACTED_PERSON_4] Name: Taro Yamada 日本語氏名はマスキングされましたが、英語氏名はマスキングされませんでした。 中国語のNERモデルとなるため、漢字はうまく判別出来ているのかもしれません。 実際に採用できるかは、名前がひらがなのケースも一度確認する必要がありそうです。 電話番号:03-1234-5678 / 090-9999-8888 Phone: [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] こちらも電話番号については、BERT_BASE_NER と同じ形になりました。 DISTILBERT_AI4PRIVACY_v2_CONF 氏名:山田 太郎 Name: Taro Yamada 日本語、英語共にマスキングされていません。 電話番号:03[REDACTED_PHONE_NUMBER_5] Phone: [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] 日本国内向けの電話番号はまだ適切に検知できていないようですが、こちらも海外向けフォーマットにおいては、適切に2つ分として判断されているようです。 運転免許証番号:[REDACTED_PHONE_NUMBER_6] Driver's License: DL-[REDACTED_PHONE_NUMBER_7]12 電話番号として誤検出され、部分的にマスキングされています。 英語では末尾2文字がマスキングされておらず、日本語と英語で検出範囲が異なっているのも気になる点です。 DEBERTA_AI4PRIVACY_v2_CONF 置換後の数値に差異はあるものの、デフォルトで指定されているモデルのため、同じ形でマスキングされていました。 MDEBERTA_AI4PRIVACY_v2_CONF 氏名:[REDACTED_PERSON_8] 太郎 Name: Taro [REDACTED_PERSON_9] 日本語氏名、英語氏名共に姓のみマスキングされています。 電話番号:[REDACTED_PHONE_NUMBER_8] / [REDACTED_PHONE_NUMBER_9] Phone: [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] 電話番号に関しては、国内・海外向けフォーマットともにマスキングされています。 DEBERTA_LAKSHYAKH93_CONF 他のモデルに比べ、誤検知が多く発生してます。 まず、1行目に記載している下記の文言ですが 私の個人情報は以下の通りです 以下の通り誤検知・置換されていました。 私の個人情報[REDACTED_CRYPTO_1]下の通りです: その他、各項目についても以下の通り誤検出が多くなっていました。 氏名:山田 太郎 Name: [REDACTED_PERSON_1][REDACTED_PERSON_2][REDACTED_PERSON_3] 日本語氏名がマスキングされておらず、英語氏名も3分割で検知されています。 [REDACTED_IBAN_CODE_1] 生年月日がIBANコードとして誤検出されています。 [REDACTED_CRYPTO_2] 住所が暗号通貨のウォレット番号として誤検出されています。 電話番号:03-1234-5678 / 090-9999-8888[REDACTED_IP_ADDRESS_1] [REDACTED_PHONE_NUMBER_1] / [REDACTED_PHONE_NUMBER_2] 日本語の番号がマスキングされていません。 海外向けフォーマットについては適切に検出されているように見えますが、関係のない「Phone:」がIPアドレスとして誤検出されています。 [REDACTED_EMAIL_ADDRESS_1] Email: [REDACTED_EMAIL_ADDRESS_2] 「メールアドレス:」部分もメールアドレスとして誤検出されています。 Driver's License: DL-123456789012[REDACTED_IBAN_CODE_2] Passport Number: TK1234567 「パスポート番号:TK1234567」がIBANコードとして誤検出されています。 社会[REDACTED_IBAN_CODE_3] 社会保障番号が文言の途中からIBANコードとして誤検出されています。 [REDACTED_CRYPTO_3]- [REDACTED_CRYPTO_4] [REDACTED_IP_ADDRESS_2] 日本語の銀行口座情報が暗号通貨のウォレット番号、IPアドレスとして誤検知されています。 まとめ 今回の検証では、llm-guardを用いたPIIマスキングの可能性を探りました。特に日本語の個人情報検出においては、現状では他の手法との併用や、より高精度なモデルの検証が必要となりそうです。

  • Langfuseでの可視化 [Dify編 (後半) ]

    本記事ではDify で作ったLLMアプリケーションをLangfuse で可視化してみた時に、処理はどう見えるのか、そしてどのように役に立つのかをご紹介します。 *このブログは 前半 と後半に分かれており、後半パートなります。 はじめに 前半では、Dify で簡単なフローを作ったアプリケーションを使った場合のLangfuseにおけるTraceの可視化についてご紹介しました。 今回はDify でRAG をつかったフローを構成した場合に、どのように詳細を確認できるかについてご紹介します。 Dify でのサンプルアプリの構成 今回のケースで用いたアプリは、以下の処理をします。 ユーザーが知りたい情報を入力 "知識取得" でRAG で情報参照 " テンプレート " で "知識取得" の出力変数から特定の情報を取得 " LLM " でRAG を利用した回答生成 4の結果を出力 Dify でのフロー 実行すると以下のようになります。 入力Text として 「Dify でLangfuse の設定をする方法を教えて」と質問すると、前述の処理を経て、Result として設定方法をコメントしつつ参照したURLを返してくれるという仕様です。 text に知りたい情報の入力をすると、RAGで学習した内容から答えを返してくれる なお今回は、知識取得においてWEBでクロールした情報を登録しました。 Firecrawl と Jina Reader のいずれからから選択することができますが、HTML から MarkDown を生成してくれて簡単にAPIキーを取得できるJina Reader を使用しています。 Jina Reader は何もせずともいきなりアクセスした瞬間にAPIキーが払い出されてるというアグレッシブなWebサイトになっており、 公式サイト の日本語翻訳が怪しいですが性能の良さを感じました。多言語対応しており、有償版でもコストパフォーマンスも良さそうです (つい最近 v2 がリリースされており、Reader自体はツールというかSLMだそうです)。 Jina Readerの設定画面 またJina からは Rerank と Embedding のモデルも提供されており、今回はそれらをRAGの構成に利用しています。RAGにつかったソースは後述の通り英語で、クエリは日本語ですが高い品質があると感じました (ただ元データが英語なので、クエリも英語の方が結果の精度は高いかなとも思いました)。 Jina の各種モデル 今回はあくまでLangfuse での表示が主目的なので、とりあえずデフォルトの10サイトのクロールでLangfuse のBlog記事からデータを以下の通り取得しています。 (余談ですが、特定のフォルダ以下をとるような処理はDify上では正常に動いていないと思われ、トップページなどのURLもクロールしてしまいました。) Langfuse での見え方 ここからが本題のTrace の見え方です。Trace は 前半のブログ にも記載がありますので、必要に応じて合わせてご確認ください 前述のフローを動かした際、Trace の詳細は以下のようになります。 Trace詳細 各Trace をクリックすると、Input/Output, Token, Metadataなどを を確認することができます。この辺りは基本的同じです。ただ今回はRAG の構成にしておりますので、新たに画面右側部分に" Knowledge-retrieval" があることを確認できると思います。 フロー同様、Knowledge-retrieval が Start直後にある そしてOutput としてresult が 0, 1, 2 と返ってきており、その中を見ると参照されたドキュメントやScore などが返却されております。Score が高いほど一致率が高いものであり、0に比べて1 や 2 のSocre は低いことが確認できます。 Scoreなどを含む結果詳細 参考までにこのフローでの1 の結果は以下の通りです。Score 0.33 と0 の結果に比べてだいぶ低いということがわかると思います。これは2 になるともっと低いです。 1 の詳細結果 そして今回は "テンプレート" ツール で、0 についてくる各種データもついでにとってきています。単に配列からデータをとってきているだけです。 {{ result.0 }} Langfuse 側でテンプレートに対応するTrace を見るとこのように表示されています。 resultの0 の値だけを正常に取得できています。 Template-transform そしてLLMには 0の結果を含めて、処理を渡して個別に Metadata 中の Title のValueをアウトプットに含めるように指示しています。 この部分の実際の処理はLangfuse上で以下のように確認できます。 System プロンプトで指示された内容がContext 含めて反映されていることが見てとれ、Title の Value にも取得した元のBlog記事が入っていることがわかります。 そして最終的なアプリの結果は以下のようになっており、期待した通りの動作をしていることを確認することができます。 このようにLangfuse を活用することで、RAGのような処理が必要な際や特定の値をとってくる処理などをした際においても、Dify の各処理を確認しながら、効率的な開発・運用を進めることができます。今回は正常にデータが取れていましたが仮に期待した結果でない場合に、取得元のソースが違うのかなどのdebug にも大変役に立ちます。 Langfuseでの可視化 [Dify編 (後半) ] まとめ 本記事ではLangfuseのTrace機能を通じて、RAGなどを含むDifyの処理の可視化の基本的をご紹介しました。今後、Bedrock やVertexAI などのプラットフォームを使った場合の可視化についても適宜ご紹介をしていきたいと思います。 ガオ株式会社 は企業向けにLangfuse ProおよびEnterpriseプランを日本円で販売し、日本語でサポート提供・導入支援などを実施している唯一の企業です。 Langfuseにご興味ある方は、contact@gao-ai.comまでご連絡ください。

  • Langfuseでの可視化 [Dify編 (前半) ]

    本記事ではDify で作ったLLMアプリケーションをLangfuse で可視化してみた時に、処理はどう見えるのか、そしてどのように役に立つのかをご紹介します。 *このブログは前半と 後半 に分かれており、前半パートなります。 はじめに 近年のDifyユーザーの広がりとエンタープライズでの採用事例などから、Dify でも生成AIアプリケーションの可視化ニーズが高まっています。そこでこの記事では、Langfuse を使った可視化についてご紹介します。 なおこの記事ではDify の設定には深く踏み込まず、あくまでLangfuse側でどのような情報が標準では反映されるのかにフォーカスしています。 Dify とLangfuse の設定の詳細手順については、 Difyの公式ページ をご参照ください。 手順といっても特に難しいことはなく、実際は各アプリケーション左側のメニューバー一番下の "監視" をクリックしていただければ、あとはキー情報などをいれるだけです。 赤丸のところが "監視" です Dify でのサンプルアプリの構成 まず今回のケースで用いたアプリは、以下の処理をします。 ユーザーがファイルアップロード "テキスト抽出ツール" でテキストを抽出 " LLM1 " で 原文に忠実に翻訳 " LLM2 " でそれをレビューして修正 " LLM3 " で不要な文字は全て排除して、マークダウンに変更 5の結果を出力 Dify フロー図 抽出、翻訳、レビュー、整形を行っている 実行すると以下のようになります。 特に何の変哲もないアプリケーションなので、詳細な処理は割愛します。 複数段階の処理を経て、最終的にMarkDownで出力される ここまでが前置きで、ここからLangfuse側で状態を見ていきます。 Traceとは Dify側で "秘密キー" , "公開キー", "HOST" を入れていることを確認し、Langfuse側で状況を見てみます。フローを実行してまもなく、Trace に新しいエントリがあることがわかります。 Trace一覧 (見やすい表示項目は絞っていますが、コストや自動評価スコアなども表示可能です) 改めてご説明するとTrace の定義は以下のとおりです。詳細は こちら も確認ください。 なんとなくでもご存知の方は以下の点線内は飛ばして先に進んでいただけます。 Traces A Trace represents a single execution of a LLM feature. It is a container for all succeeding objects. 訳: Trace はLLM機能の単一実行を表します。これはすべての後続オブジェクトのコンテナです Observations Each Trace can contain multiple Observations to record individual steps of an execution. There are different types of Observations: Events are the basic building block. They are used to track discrete events in a Trace. Spans track time periods and include an end_time. Generations are a specific type of Spans which are used to record generations of an AI model. They contain additional metadata about the model, LLM token and cost tracking, and the prompt/completions are specifically rendered in the langfuse UI. 訳: 各Traceは、実行の個々のステップを記録するための複数のObservations を含めることができます。Observations にはさまざまな種類があります。 Event は基本的な構成要素です。これらは、トレース内の個別のイベントを追跡するために使用されます。 Span は時間期間を追跡し、end_timeを含みます。 Generation は、スパンの特殊なタイプであり、AIモデルの世代を記録するために使用されます。これには、モデル、LLMトークン、コスト追跡に関する追加のメタデータが含まれ、プロンプト/補完は、特にlangfuse UI でレンダリングされます。 Traceの中の構成, Generation が処理のアウトプットに相当する 参照: https://langfuse.com/guides/cookbook/python_sdk_low_level#tracing Langfuse での見え方 Traceの詳細 各Trace をクリックすると、このようにInput/Output を確認することができます。 Trace名 workflow の下には、日時と所要時間とToken 数が見て取れます。Dify 側で出すInput/Out Token とLangfuse 側の取り扱いが原因で、ゼロになってますが合計のToken も確認することができます。 まずこのTrace のInput とOutput を確認することが可能ですが、それに加えて下の方にはDify で付与したMetadata があることも確認できます。 Traceのメタデータ そして右側のメニューを Tree から Timeline に切り替えると、どこの処理でどの程の時間がかかったかを視覚的に見ていただくことも可能です。 またフロー各処理のタイムライン ただ、ちょっと問題なのはDifyで3つあったLLM での処理と各アウトプットが全部 llm で名称が統一されてしまっており、Langfuse の画面だけで分からない (DifyのUIの仕様) 問題があります。これについては、各GENERATION をクリックして中身から確認が可能です。 この画面ではGENERATION の詳細で、Systemプロンプトなどの他にレイテンシーやToken、Model などの情報が確認できます。 GENERATIONSの詳細 この画面の下の方では、Metadata が表示されておりDIfyのノード名やタイプなどの情報を 確認することができます。 node_name として定義されている部分です。 GENERATION の Metadata ちなみに結果がうまく出ないなど、本当に文章抽出されてる?など思った時にはSPANとして document-extractor を見ていただくと実際のOUTPUTなどの詳細を確認することができます。ここから更に Debug が可能です。 document-extractor (Outputを確認できる) このようにLangfuse を活用することで、ブラックボックスになりがちなDify の各処理を確認しながら、効率的な開発・運用を進めることができます。 Langfuseでの可視化 [Dify編 (前半) ] まとめ 本記事ではLangfuseのTrace機能を通じて、Difyの処理の可視化の基本的をご紹介しました。次回はもう少し深掘りして、RAGを行った場合について触れていきます。 ガオ株式会社 は企業向けにLangfuse ProおよびEnterpriseプランを日本円で販売し、日本語でサポート提供・導入支援などを実施している唯一の企業です。 Langfuseにご興味ある方は、contact@gao-ai.comまでご連絡ください。

  • ADK (Agent Development Kit) で開発したAgentの挙動 を Langfuseで可視化しよう!

    はじめに 本記事では、Agent Development Kit (ADK) によって構築されたAIエージェントの挙動をLiteLLMを通じてLangfuseで可視化する方法について解説していきます。 ADKの基本的な内容やその評価については こちら Langfuse についての 説明についての記事は こちら 生成AIアプリケーションやAIエージェントを開発し、そのパフォーマンスを最大限に引き出す上で、内部処理の可視化は成功の鍵を握ると言っても過言ではありません。これは、ADKを用いた開発においても例外ではありません。 可視化ツールとして Cloud Trace のような選択肢も考えられますが、本記事では、導入の容易さ、直感的な操作性、そして LLMOps 全体のトレーサビリティをオープンソースで実現できるLangfuseに焦点を当てます。 ただし、ADK から Langfuse へ直接Traceを連携させようとすると、現時点(2025年5月8日)では注意が必要です。例えば、OpenTelemetry 経由で Langfuse にトレースを送信した場合、全ての情報がメタデータとして一括りに扱われてしまい、詳細な分析やデバッグが困難になるなどの課題があります。 そこで本記事では、この課題を解決し、ADKで開発するエージェントの情報を Langfuse 上で効果的に可視化するためのアプローチとして、ADK と Langfuse が共にネイティブサポートしている LiteLLM を利用する構成をご紹介します。この構成を採用することで、LLM とのやり取りを含む詳細なトレース情報を、構造化された形でスムーズに Langfuseへ連携させることが可能になります。構成はおおまかには以下のようになります。 ADK で作ったAgentと同じ環境にProxyとしてLiteLLMを起動 それでは、LiteLLM のセットアップから進めていきましょう。 LiteLLM とは LiteLLMは、OpenAI、Azure、VertexAI/Gemini、Anthropicなど、100種類以上の様々な大規模言語モデル(LLM)に対して、統一されたシンプルなインターフェースでアクセスできるようにするライブラリです。 これにより、開発者は特定のLLMプロバイダーにロックインされることなく、アプリケーションの要件に応じて最適なモデルを柔軟に選択・切り替えることが可能になります。 LiteLLM は、APIキー管理、呼び出しのフォーマット統一、そしてリクエストのロギングやコールバック, 同一コードでの切り替え, 帯域制限やコスト管理といった機能など提供し、LLM 運用において大きな助けとなります。LiteLLM には SDK として利用するモードと、Proxy として動作させるモードがありますが、今回はついでにProxy モードのセットアップ方法も紹介しながら構成を作っていきます。 LiteLLM (Proxyモード) のセットアップ セットアップは非常にシンプルです。 公式の手順 がありますので、基本的にはこれに沿って進めるだけで立ち上げることが可能です 。 litellm_config.yaml に model 定義などを入れるのですが、今回は以下のような設定をしています。 model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: "os.environ/OPEN_AI_API_KEY" - model_name: gemini-2.0-flash litellm_params: model: vertex_ai/gemini-2.0-flash vertex_project: "YOURPROJECTNAME" vertex_location: "us-central1" vertex_credentials: "os.environ/VERTEXAIFILE" - model_name: gemini-2.5-flash litellm_params: model: vertex_ai/gemini-2.5-flash-preview-04-17 vertex_project: "YOURPROJECTNAME" vertex_location: "us-central1" vertex_credentials: "os.environ/VERTEXAIFILE" general_settings: master_key: sk-1234 litellm_settings: drop_params: True success_callback: ["langfuse"] redact_user_api_key_info: true module list の中に、model_name として任意の名前をつけ、後ほどこの名前をプログラム内から利用することで、指定したモデルが使われるようになります。APIキーやクレデンシャルは、.env ファイルを作り、環境変数として読みこませておきます。 そして設定最下部に success_callback: ["langfuse"] を入れることを忘れないようにしましょう。逆にいうとLangfuse のための設定はこの1行だけという簡単さです。 続いて環境変数ファイル (.env)を、以下のような内容で作成します。 export LITELLM_MASTER_KEY="sk-1234" export LITELLM_SALT_KEY="sk-1234" export LANGFUSE_PUBLIC_KEY="pk-lf-****" export LANGFUSE_SECRET_KEY="sk-lf-****" export LANGFUSE_HOST="YOURLANGFUSEHOSTURL" export OPEN_AI_API_KEY="sk*****" export VERTEXAIFILE="FILENAME" export OPENAI_API_BASE= http://localhost:4000/v1 続いて docker-compose.yml をちょっと編集します。volumes と command が最初はコメントアウトされているので、それをアンコメントして、VertexAI認証用のファイルを読み込ませています。(セキュリティ的にベストプラクティスではないですが、一時的な検証用ということで簡単な方法で実装しています) もちろん、たとえばOpenAI だけで良い場合は対応不要です。 volumes: - ./litellm_config.yaml:/app/litellm_config.yaml - /SOURCE.json:/app/vertexaifile.json command: - "--config=/app/litellm_config.yaml" docker compose up で Proxy を起動し、以下のように curl で疎通確認をしてみてください。結果をLangfuse内で Trace として確認できるはずです。 % curl --location 'http://0.0.0.0:4000/chat/completions' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer sk-1234' \ --data '{ "model": "gpt-4o", "messages": [ { "role": "user", "content": "気の利いたアメリカンジョークをかましてくれ!" } ] }' すると、Langfuse側では以下のように Trace を確認することができます。 ジョークはつまらないですが、Trace は見えます ここまでで一旦準備は完了です。 ADK のコードを準備する 基本的には全て こちらのコード を利用し、以下に示す点のみを編集するだけです。 agent.py の冒頭で LiteLLM のモジュールをインポートします。 from google.adk.models.lite_llm import LiteLlm from google.adk.agents import LlmAgent 続いて最下部を以下の通りに変更します。 lite_model = LiteLlm(model="openai/gemini-2.0-flash") root_agent = LlmAgent( name="weather_time_agent", model= lite_model, description=( "Agent to answer questions about the time and weather in a city." ), instruction=( "You are a helpful agent who can answer user questions about the time and weather in a city." ), tools=[get_weather, get_current_time], ) 以上です! LiteLlm の中の model には litellm_config.yaml であらかじめ定義したmodel名を定義しますが、その際に指定した openai インターフェイスに則った LiteLLM Proxy のエンドポイントを指定するため、明示的に "openai/" と指定しています。Google が提供する LLM であるGemini にアクセスするのに openai/gemini という記載になることに違和感があるかもしれませんが、これにより OPENAI_API_BASE と .env に定義した先にリクエストが飛ぶようになるため、必須の設定となります。 そして、実行するとこのように出力されます。(フォルダ名は任意に変えてください)  % adk run adk_agent Log setup complete: /var/folders/ys/8vpjq9657dnb3dvq33k6q_hm0000gp/T/agents_log/agent.20250508_213004.log To access latest log: tail -F /var/folders/ys/8vpjq9657dnb3dvq33k6q_hm0000gp/T/agents_log/agent.latest.log Running agent weather_time_agent, type exit to exit. [user]: new york 21:30:14 - LiteLLM:INFO: utils.py:2827 - LiteLLM completion() model= gemini-2.0-flash; provider = openai 21:30:15 - LiteLLM:INFO: utils.py:2827 - LiteLLM completion() model= gemini-2.0-flash; provider = openai [weather_time_agent]: OK. The weather in New York is sunny with a temperature of 25 degrees Celsius (77 degrees Fahrenheit). The current time in new york is 2025-05-08 08:30:15 EDT-0400. ちなみに上記コマンドを入れた際、以下のようなエラーが出るかもしれません。 litellm.exceptions.AuthenticationError: litellm.AuthenticationError: AuthenticationError: OpenAIException - Authentication Error, Invalid proxy server token passed. key=******, not found in db. Create key via /key/generate call. エラーの原因は、LiteLLM Proxy が「渡されたトークン」を自身のデータベースで認識しておらず、認証に失敗しているためです。その際には以下のようなコマンドを実行して、keyを発行してみてください。 curl --location 'http://0.0.0.0:4000/key/generate' \ --header 'Authorization: Bearer sk-master-あなたが決めたキー' \ --header 'Content-Type: application/json' \ --data-raw '{ "models": ["gpt-4o"], "metadata": {"user": "your-user-id"} }' そうすると key を含んだJSONが返却されますので、agent.py と同じディレクトリ下に置く .env に以下のような形式でKeyを追加しておいてください。 export OPEN_AI_API_KEY="sk-***" なお locahost: 4000 でブラウザ経由でもアクセス可能ですので、LiteLLM は UI上から操作しても問題ないでしょう。 そしてエラーがなくなれば、以下のような出力を Langfuse 上で確認できます。 Agent の挙動を確認可能 いかがでしたでしょうか? LiteLLM を通じて、Trace が入ることを確認いただけたかと思います。ぜひお手元のADK 環境でも試してみてください!また別の機会に LiteLLM の機能の掘り下げた記事も公開していきたいと思います。Langfuse 可視化のみならず、LLMOps のサイクルをオールインワンで解決できることが強みです。ぜひこれを機会に活用してみてください。

  • Langfuse Q1 アップデートと Q2 ロードマップのまとめ

    2025年4月9日にLangfuseのTownhall が開かれ、そこで直近のメジャーリリースと今後の予定について発表がされました。Langfuseのアップデートについてその速度と進化をシェアすべく、主な内容をまとめてみました! オリジナル動画はこちら Langfuse Q1 2025 アップデートまとめ 特にTrace におけるグラフ表示やJavaクライアントのサポート、またプロンプト管理などは待望の機能となっています! Trace のUI が刷新され大幅に改善: LangGraph などのエージェントフレームワークのグラフ表示 、デバッグ情報の非表示オプション、統合されたタイムライン、トレースプレビュー機能の追加とキーボードナビゲーションが可能になりました。 https://langfuse.com/changelog/2025-02-14-trace-graph-view ビヨンビヨン動いて楽しいのと、どこで何が起きてるのか非常に追いやすい。 ネイティブ環境サポート (Environmentの設定) が追加: 環境に基づいたトレースとプロンプトの分離 とUIでの選択が可能になりました。 OpenTelemetryエンドポイント が導入: 様々なフレームワークや技術スタックとの互換性が大幅に向上 しました。また Javaクライアント がリリースされ、 JavaアプリケーションでのLangfuseの利用 が可能になりました。 プロンプト管理: UIの刷新、コードスニペットのコピー機能の改善、プロンプトの合成機能、保護されたプロンプトラベル (adminなどだけが変更できるラベル)、変数リンティング機能付きの新しいコードエディタ、コミットメッセージの追加、Langfuseプロンプト用のMCP(Model Context Protocol)サーバーのリリース が行われました。 プレイグラウンド: ツール呼び出しと構造化出力がサポート されました。 セルフホスティング: バージョン1のHelm chartのリリースと、AWS用のTerraformリポジトリの公開 が行われました。 UIの改善: オンボーディング画面の追加とコマンドパレットによるナビゲーションの改善 が行われました。 評価機能: 履歴データに対するElement Judge評価の実行、評価プロンプトへのデータ挿入をより具体的に指定するためのJSONパスのサポート、CSVファイルによるデータセットのアップロード が可能になりました。 APIの更新: 新しいAPIリファレンスの公開、アノテーションキューとスコア構成用の新しいAPI、Python、JavaScript、Java用の型付きクライアントの追加 が行われました。 セキュリティ機能: UIおよびAPIによる単一またはバッチでのトレース削除、プロジェクトレベルでのデータ保持ポリシーの設定、Audit logの利用 が可能になりました。 Langfuse Q2 2025 ロードマップまとめ Tracing : Full text検索 の初期デモが近日公開予定であり、最優先事項の一つです。 Analytics : データに基づいて独自のビューを作成できる カスタマイズ可能なダッシュボード と、アプリケーション内でのチャート表示やデータ集計に利用できる クエリAPI が近々提供されます。 OpenTelemetry : Langfuse SDKの内部を OpenTelemetryネイティブ に移行する作業が進められています。 評価 (Evaluation) : セッションレベルの評価 の改善、コアな評価ビューの改善、データセット実験のアノテーションの簡略化が予定されています。 管理API (Admin API) : 組織とプロジェクトのプロビジョニングと管理を可能にする Admin SCIM API のドラフトがあります。 セルフホスティング : セルフホスティングの開始方法に関するドキュメント が来週再リリースされ、スケーリングに関するガイダンスも提供されます。 エージェント : エージェントグラフの汎用化、ツール呼び出しやツール検出に関する 可視化オプション の追加、より 意見の強いエージェント評価 機能の構築が計画されています。 UI : テーブルのUI改善が予定されています。 プロンプト管理 : プレースホルダー の導入、本番環境のトレースにおけるプロンプト変数の追跡、Elementによるプロンプトエンジニアリング支援、 フォルダ によるプロンプト整理機能、 ABテスト 機能、 ツール呼び出しと構造化出力のネイティブサポート が計画されています。 データプラットフォーム : Webhook が追加され、Langfuse内の変更に基づいて外部アクションをトリガーできるようになります。また、 アラート 機能も検討されています。 プロンプト実験とプレイグラウンド : プロンプトプレイグラウンドへの 分割ビュー の追加、プロンプト実験への ツールと構造化出力のサポート の拡充が計画されています。 Langfuse Cloud : HIPAAコンプライアンス への対応が検討されています。また、 使用量に基づく請求アラート 機能が追加される予定です。 設定可能なダッシュボード : 最も要望の多かった機能の一つである 設定可能なダッシュボード の早期ベータ版がLangfuse Cloudでリリースされました。 これらのアップデートは、有償版ユーザーとコミュニティからのフィードバックに基づいて優先順位付けされており、GitHub Discussionsでの議論から多くの機能が実装されています。 気になる内容などあれば、お気軽にGithub あるいは GAO までお問い合わせください !

bottom of page