コンテキスト¶
Agent Development Kit (ADK) において、コンテキストとは、エージェントとそのツールが特定の操作中に利用できる重要な情報の集合体を指します。現在のタスクや会話のターンを効果的に処理するために必要な背景知識やリソースだと考えてください。
エージェントがうまく機能するためには、多くの場合、最新のユーザーメッセージ以上の情報が必要です。コンテキストは以下を可能にするため、不可欠です。
- 状態の維持: 会話の複数のステップにわたる詳細(例:ユーザーの好み、以前の計算結果、ショッピングカートのアイテム)を記憶します。これは主にセッション状態(session state)を通じて管理されます。
- データの受け渡し: あるステップ(LLMの呼び出しやツールの実行など)で発見または生成された情報を、後続のステップと共有します。ここでもセッション状態が鍵となります。
- サービスへのアクセス: 以下のようなフレームワークの機能と対話します。
- アーティファクトストレージ: セッションに関連付けられたファイルやデータのかたまり(PDF、画像、設定ファイルなど)を保存または読み込みます。
- メモリ: ユーザーに関連する過去の対話や外部の知識ソースから関連情報を検索します。
- 認証: ツールが外部APIに安全にアクセスするために必要な認証情報を要求および取得します。
- IDと追跡: 現在どのエージェントが実行されているか(
agent.name)を把握し、ロギングとデバッグのために現在のリクエスト-レスポンスサイクル(invocation_id)を一意に識別します。 - ツール固有のアクション: 現在の対話の詳細へのアクセスを必要とする、認証要求やメモリ検索など、ツール内での特化した操作を有効にします。
単一の完全なユーザーリクエストから最終レスポンスまでのサイクル(呼び出し (invocation))に関するこれらすべての情報をまとめる中心的な要素がInvocationContextです。しかし、通常、このオブジェクトを直接作成したり管理したりすることはありません。ADKフレームワークは、呼び出しが開始されるとき(例:runner.run_async経由で)にこれを作成し、関連するコンテキスト情報をエージェントコード、コールバック、ツールに暗黙的に渡します。
# フレームワークがコンテキストを提供する方法
from google.adk import Runner
# 1. エージェントとサービスで Runner を初期化します
runner = Runner(
app_name="my_app",
agent=my_root_agent,
session_service=my_session_service,
artifact_service=my_artifact_service,
)
# 2. ユーザー入力で run_async を呼び出します
# 注: run_async は Event を順次生成する非同期ジェネレーターです。
# フレームワークは内部で InvocationContext を生成し、
# エージェントコード、コールバック、ツールに暗黙的に渡します。
async for event in runner.run_async(
user_id="user123",
session_id="session456",
new_message=user_message
):
print(event.stringify_content())
# 開発者は、メソッド引数として渡されるコンテキストオブジェクトを扱います。
/* 概念的な疑似コード:フレームワークがコンテキストを提供する方法(内部ロジック) */
const runner = new InMemoryRunner({ agent: myRootAgent });
const session = await runner.sessionService.createSession({ ... });
const userMessage = createUserContent(...);
// --- runner.runAsync(...) の内部 ---
// 1. フレームワークがこの特定の実行のためのメインコンテキストを作成します
const invocationContext = new InvocationContext({
invocationId: "this-run-のための一意なID",
session: session,
userContent: userMessage,
agent: myRootAgent, // 開始エージェント
sessionService: runner.sessionService,
pluginManager: runner.pluginManager,
// ... その他必要なフィールド ...
});
//
// 2. フレームワークがエージェントの run メソッドを呼び出し、コンテキストを暗黙的に渡します
await myRootAgent.runAsync(invocationContext);
// --- 内部ロジックの終わり ---
// 開発者は、メソッド引数として渡されるコンテキストオブジェクトを扱います。
/* 概念的な疑似コード:フレームワークがコンテキストを提供する方法(内部ロジック) */
sessionService := session.InMemoryService()
r, err := runner.New(runner.Config{
AppName: appName,
Agent: myAgent,
SessionService: sessionService,
})
if err != nil {
log.Fatalf("Failed to create runner: %v", err)
}
s, err := sessionService.Create(ctx, &session.CreateRequest{
AppName: appName,
UserID: userID,
})
if err != nil {
log.Fatalf("FATAL: Failed to create session: %v", err)
}
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("\nYou > ")
if !scanner.Scan() {
break
}
userInput := scanner.Text()
if strings.EqualFold(userInput, "quit") {
break
}
userMsg := genai.NewContentFromText(userInput, genai.RoleUser)
events := r.Run(ctx, s.Session.UserID(), s.Session.ID(), userMsg, agent.RunConfig{
StreamingMode: agent.StreamingModeNone,
})
fmt.Print("\nAgent > ")
for event, err := range events {
if err != nil {
log.Printf("ERROR during agent execution: %v", err)
break
}
fmt.Print(event.Content.Parts[0].Text)
}
}
/* フレームワークがコンテキストを提供する方法 */
InMemoryRunner runner = new InMemoryRunner(agent);
Session session = runner
.sessionService()
.createSession(runner.appName(), USER_ID, initialState, SESSION_ID )
.blockingGet();
try (Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8)) {
while (true) {
System.out.print("\nYou > ");
String userInput = scanner.nextLine();
if ("quit".equalsIgnoreCase(userInput)) {
break;
}
Content userMsg = Content.fromParts(Part.fromText(userInput));
Flowable<Event> events = runner.runAsync(session.userId(), session.id(), userMsg);
System.out.print("\nAgent > ");
events.blockingForEach(event -> System.out.print(event.stringifyContent()));
}
}
コンテキストのタイプ¶
ADK は、エージェントの環境、状態、およびリソースを管理するための中心的なメカニズムとして Context クラスを使用します。Context はすべてのエージェントの相互作用の基礎として機能しますが、エージェントの実行フローのどこで使用されるかに応じて、機能と権限の適切なバランスを提供するように設計された、いくつかの特殊な「フレーバー」として現れます。
これらの特定のコンテキストタイプを使用することで、ADK はメモリ、セッション状態、認証情報などの必要な情報に、必要な時に必要な場所でエージェントが正確にアクセスできるようにします。
遭遇する主なコンテキストフレーバーは次のとおりです。
-
InvocationContext: サービスの参照やライフサイクル管理など、呼び出し全体の包括的なビューを提供するために、エージェントのコア実行時(_run_async_impl、_run_live_impl)に使用されます。 -
ReadonlyContext: インストラクションプロバイダー内など、変更が許可されないシナリオで使用される、基本的なコンテキスト詳細の軽量で制限されたビュー。 -
Context: エージェントのライフサイクルおよびモデルのコールバックで使用されます。セッション状態の読み書き、アーティファクトの管理、メモリサービスへのデータ挿入のための堅牢な機能セットを提供します。 -
ToolContext: ツールの実行およびツール関連のコールバック用に調整されています。Context の機能に加えて、認証フロー、メモリ検索、アーティファクト検出のための特殊なメソッドが含まれています。
Note
互換性について: Python と TypeScript では、CallbackContext と ToolContext は Context タイプに置き換えられました。CallbackContext クラスは、後方互換性を確保するための Context のエイリアスとして維持されます。既存のコードベースで CallbackContext に遭遇することがあるかもしれませんが、統合された完全な機能セットを利用するために、新規開発では Context クラスを使用することをお勧めします。
InvocationContext¶
- 使用場所: エージェントのコア実装メソッド(
_run_async_impl、_run_live_impl)内でctx引数として直接受け取ります。 - 目的: 現在の呼び出しの全体の状態へのアクセスを提供します。これは最も包括的なコンテキストオブジェクトです。
- 主な内容:
session(stateとeventsを含む)、現在のagentインスタンス、invocation_id、初期のuser_content、設定されたサービス(artifact_service、memory_service、session_service)への参照、およびライブ/ストリーミングモードに関連するフィールドへの直接アクセス。 - ユースケース: 主にエージェントのコアロジックがセッション全体やサービスに直接アクセスする必要がある場合に使用されますが、状態やアーティファクトの操作は、独自のコンテキストを使用するコールバック/ツールに委譲されることが多いです。また、呼び出し自体を制御するためにも使用されます(例:
ctx.end_invocation = Trueの設定)。
ReadonlyContext¶
- **使用場所:** 基本情報への読み取りアクセスのみが必要で、変更が許可されていないシナリオ(例:`InstructionProvider`関数)で提供されます。他のコンテキストのベースクラスでもあります。
- **目的:** 基本的なコンテキスト詳細の安全な読み取り専用ビューを提供します。
- **主な内容:** `invocation_id`、`agent_name`、および現在の`state`の読み取り専用*ビュー*。
=== "Python"
```python
# 例:ReadonlyContextを受け取るInstruction provider
from google.adk.agents.readonly_context import ReadonlyContext
def my_instruction_provider(context: ReadonlyContext) -> str:
# 読み取り専用アクセスの例
# state プロパティは状態の読み取り専用 MappingProxyType ビューを提供します
user_tier = context.state.get("user_tier", "standard")
# context.state['new_key'] = 'value' # TypeError: 'mappingproxy' object does not support item assignment
return f"{user_tier} ユーザーのリクエストを処理してください。"
```
=== "TypeScript"
```typescript
// 疑似コード:ReadonlyContextを受け取る Instruction provider
import { ReadonlyContext } from '@google/adk';
function myInstructionProvider(context: ReadonlyContext): string {
// 読み取り専用アクセスの例
// state オブジェクトは読み取り専用です
const userTier = context.state.get('user_tier') ?? 'standard';
// context.state.set('new_key', 'value'); // 失敗するか、エラーを投げます
return `${userTier} ユーザーのリクエストを処理してください。`;
}
```
=== "Go"
```go
import "google.golang.org/adk/agent"
// Pseudocode: Instruction provider receiving ReadonlyContext
func myInstructionProvider(ctx agent.ReadonlyContext) (string, error) {
// Read-only access example
userTier, err := ctx.ReadonlyState().Get("user_tier")
if err != nil {
userTier = "standard" // Default value
}
// ctx.ReadonlyState() has no Set method since State() is read-only.
return fmt.Sprintf("Process the request for a %v user.", userTier), nil
}
```
=== "Java"
```java
// 例: ReadonlyContext を受け取る Instruction provider
import com.google.adk.agents.ReadonlyContext;
public String myInstructionProvider(ReadonlyContext context) {
// 読み取り専用アクセスの例
// state() はセッション状態の変更不可ビューを返します
String userTier = (String) context.state().getOrDefault("user_tier", "standard");
// context.state().put("new_key", "value"); // UnsupportedOperationException
return userTier + " ユーザーのリクエストを処理してください。";
}
```
CallbackContext と Context¶
- **使用場所:** エージェントのライフサイクルコールバック(`before_agent_callback`、`after_agent_callback`)およびモデルインタラクションコールバック(`before_model_callback`、`after_model_callback`)に`callback_context`として渡されます。
- **目的:** *特にコールバック内で*、状態の検査と変更、アーティファクトとの対話、および呼び出し詳細へのアクセスを容易にします。
- **主な機能(`ReadonlyContext`に追加):**
- **変更可能な`state`プロパティ:** セッション状態の読み取り*と書き込み*を許可します。ここで行われた変更(`callback_context.state['key'] = value`)は追跡され、コールバック後にフレームワークによって生成されるイベントに関連付けられます。
- **アーティファクトメソッド:** 設定された`artifact_service`と対話するための`load_artifact(filename)`および`save_artifact(filename, part)`メソッド。
- 直接の`user_content`アクセス。
*(注: TypeScript では `CallbackContext` と `ToolContext` は単一の `Context` 型に統合されています。)*
=== "Python"
```python
# 例: Context を受け取るコールバック(TypeScript では CallbackContext が Context に統合されます)
from google.adk.agents.context import Context
from google.adk.models import LlmRequest
from google.genai import types
from typing import Optional
def my_before_model_cb(context: Context, request: LlmRequest) -> Optional[types.Content]:
# 状態の読み書きの例
call_count = context.state.get("model_calls", 0)
context.state["model_calls"] = call_count + 1 # 状態を変更(delta を追跡)
# オプションでアーティファクトをロード
# config_part = context.load_artifact("model_config.json")
print(f"呼び出し {context.invocation_id} のためのモデルコール #{call_count + 1} を準備中")
return None # モデルコールの続行を許可
```
=== "TypeScript"
```typescript
// 疑似コード: Context を受け取るコールバック
import { Context, LlmRequest } from '@google/adk';
import { Content } from '@google/genai';
function myBeforeModelCb(context: Context, request: LlmRequest): Content | undefined {
// 状態の読み書きの例
const callCount = (context.state.get('model_calls') as number) || 0;
context.state.set('model_calls', callCount + 1); // 状態を変更
// オプションでアーティファクトをロード
// const configPart = await context.loadArtifact('model_config.json');
console.log(`呼び出し ${context.invocationId} のためのモデルコール #${callCount + 1} を準備中`);
return undefined; // モデルコールの続行を許可
}
```
=== "Go"
```go
import (
"google.golang.org/adk/agent"
"google.golang.org/adk/model"
)
// Pseudocode: Callback receiving CallbackContext
func myBeforeModelCb(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
// Read/Write state example
callCount, err := ctx.State().Get("model_calls")
if err != nil {
callCount = 0 // Default value
}
newCount := callCount.(int) + 1
if err := ctx.State().Set("model_calls", newCount); err != nil {
return nil, err
}
// Optionally load an artifact
// configPart, err := ctx.Artifacts().Load("model_config.json")
fmt.Printf("Preparing model call #%d for invocation %s\n", newCount, ctx.InvocationID())
return nil, nil // Allow model call to proceed
}
```
=== "Java"
```java
// 例: CallbackContext を受け取るコールバック
import com.google.adk.agents.CallbackContext;
import com.google.adk.models.LlmRequest;
import com.google.adk.models.LlmResponse;
import io.reactivex.rxjava3.core.Maybe;
public Maybe<LlmResponse> myBeforeModelCb(CallbackContext callbackContext, LlmRequest request) {
// 状態の読み書きの例
int callCount = (int) callbackContext.state().getOrDefault("model_calls", 0);
callbackContext.state().put("model_calls", callCount + 1); // 状態を変更(delta を追跡)
// オプションでアーティファクトをロード
// Maybe<Part> configPart = callbackContext.loadArtifact("model_config.json");
System.out.println("呼び出し " + callbackContext.invocationId() + " のためのモデルコール " + (callCount + 1) + " を準備中");
return Maybe.empty(); // モデルコールの続行を許可
}
```
ToolContext¶
- **使用場所:** `FunctionTool`を支える関数や、ツールの実行コールバック(`before_tool_callback`、`after_tool_callback`)に`tool_context`として渡されます。
- **目的:** `CallbackContext`が提供するすべてに加えて、認証の処理、メモリの検索、アーティファクトの一覧表示など、ツールの実行に不可欠な特化したメソッドを提供します。
- **主な機能(`CallbackContext`に追加):**
- **認証メソッド:** 認証フローをトリガーする`request_credential(auth_config)`と、ユーザー/システムによって提供された認証情報を取得する`get_auth_response(auth_config)`。
- **アーティファクトの一覧表示:** セッションで利用可能なアーティファクトを発見する`list_artifacts()`。
- **メモリ検索:** 設定された`memory_service`にクエリを実行する`search_memory(query)`。
- **`function_call_id`プロパティ:** このツールの実行をトリガーしたLLMからの特定の関数呼び出しを識別し、認証要求や応答を正しくリンクするために重要です。
- **`actions`プロパティ:** このステップの`EventActions`オブジェクトに直接アクセスし、ツールが状態の変更、認証要求などを通知できるようにします。
=== "Python"
```python
# 例: ToolContext を受け取るツール関数
from google.adk.tools import ToolContext
from typing import Dict, Any
# この関数がFunctionToolによってラップされていると仮定
def search_external_api(query: str, tool_context: ToolContext) -> Dict[str, Any]:
api_key = tool_context.state.get("api_key")
if not api_key:
# 必要な認証設定を定義
# auth_config = AuthConfig(...)
# tool_context.request_credential(auth_config) # 認証情報を要求
# 'actions'プロパティを使用して認証要求が行われたことを通知
# tool_context.actions.requested_auth_configs[tool_context.function_call_id] = auth_config
return {"status": "認証が必要です"}
# APIキーを使用...
print(f"APIキーを使用してクエリ '{query}' のためのツールを実行中。呼び出し: {tool_context.invocation_id}")
# オプションでメモリを検索したり、アーティファクトを一覧表示したりする
# relevant_docs = tool_context.search_memory(f"{query}に関する情報")
# available_files = tool_context.list_artifacts()
return {"result": f"{query}のデータを取得しました。"}
```
=== "TypeScript"
```typescript
// 疑似コード: Context を受け取るツール関数
import { Context } from '@google/adk';
// この関数が FunctionTool によってラップされていると仮定
function searchExternalApi(query: string, context: Context): { [key: string]: string } {
const apiKey = context.state.get('api_key') as string;
if (!apiKey) {
// 必要な認証設定を定義
// const authConfig = new AuthConfig(...);
// context.requestCredential(authConfig); // 認証情報を要求
// requestCredential により 'actions' プロパティは自動的に更新される
return { status: '認証が必要です' };
}
// API キーを使用...
console.log(`API キーを使用してクエリ '${query}' のためのツールを実行中。呼び出し: ${context.invocationId}`);
// オプションでメモリを検索したり、アーティファクトを一覧表示したりする
// TS では memory/artifacts などのサービスアクセスは通常 async なので、
// 実際に再利用する場合はこの関数を async にする必要があります。
// context.searchMemory(`info related to ${query}`).then(...)
// context.listArtifacts().then(...)
return { result: `${query} のデータを取得しました` };
}
```
=== "Go"
```go
import "google.golang.org/adk/tool"
// Pseudocode: Tool function receiving ToolContext
type searchExternalAPIArgs struct {
Query string `json:"query" jsonschema:"The query to search for."`
}
func searchExternalAPI(tc tool.Context, input searchExternalAPIArgs) (string, error) {
apiKey, err := tc.State().Get("api_key")
if err != nil || apiKey == "" {
// In a real scenario, you would define and request credentials here.
// This is a conceptual placeholder.
return "", fmt.Errorf("auth required")
}
// Use the API key...
fmt.Printf("Tool executing for query '%s' using API key. Invocation: %s\n", input.Query, tc.InvocationID())
// Optionally search memory or list artifacts
// relevantDocs, _ := tc.SearchMemory(tc, "info related to %s", input.Query))
// availableFiles, _ := tc.Artifacts().List()
return fmt.Sprintf("Data for %s fetched.", input.Query), nil
}
```
=== "Java"
```java
// 例: ToolContext を受け取るツール関数
import com.google.adk.tools.ToolContext;
import java.util.Map;
// この関数がFunctionToolによってラップされていると仮定
public Map<String, Object> searchExternalApi(String query, ToolContext toolContext) {
String apiKey = (String) toolContext.state().getOrDefault("api_key", "");
if (apiKey.isEmpty()) {
// 必要な認証設定を定義
// authConfig = AuthConfig(...);
// toolContext.requestCredential(authConfig); // 認証情報を要求
// 'actions'プロパティを使用して認証要求が行われたことを通知
return Map.of("status", "認証が必要です");
}
// APIキーを使用...
System.out.println("APIキーを使用してクエリ " + query + " のためのツールを実行中。");
// オプションでアーティファクトを一覧表示
// Single<List<String>> availableFiles = toolContext.listArtifacts();
return Map.of("result", query + " のデータを取得しました");
}
```
これらの異なるコンテキストオブジェクトと、それらをいつ使用するかを理解することは、ADKアプリケーションの状態を効果的に管理し、サービスにアクセスし、フローを制御するための鍵となります。次のセクションでは、これらのコンテキストを使用して実行できる一般的なタスクについて詳しく説明します。
コンテキストを使用した一般的なタスク¶
様々なコンテキストオブジェクトについて理解したところで、エージェントやツールを構築する際に、それらを使って一般的なタスクを実行する方法に焦点を当てましょう。
情報へのアクセス¶
コンテキスト内に保存された情報を読み取る必要が頻繁にあります。
-
セッション状態の読み取り: 以前のステップで保存されたデータや、ユーザー/アプリレベルの設定にアクセスします。
stateプロパティに対して辞書のようにアクセスします。# 例: ツール関数内 from google.adk.tools import ToolContext def my_tool(tool_context: ToolContext, **kwargs): user_pref = tool_context.state.get("user_display_preference", "default_mode") api_endpoint = tool_context.state.get("app:api_endpoint") # アプリレベルの状態を読み取り if user_pref == "dark_mode": # ... ダークモードのロジックを適用 ... pass print(f"使用中のAPIエンドポイント: {api_endpoint}") # ... 残りのツールのロジック ... # 例: コールバック関数内 from google.adk.agents.context import Context def my_callback(context: Context, **kwargs): last_tool_result = context.state.get("temp:last_api_result") # 一時的な状態を読み取り if last_tool_result: print(f"最後のツールからの一時的な結果が見つかりました: {last_tool_result}") # ... コールバックのロジック ...// 疑似コード: ツール関数内 import { Context } from '@google/adk'; async function myTool(context: Context) { const userPref = context.state.get('user_display_preference', 'default_mode'); const apiEndpoint = context.state.get('app:api_endpoint'); // アプリレベルの状態を読み取り if (userPref === 'dark_mode') { // ... ダークモードのロジックを適用 ... } console.log(`使用中の API エンドポイント: ${apiEndpoint}`); // ... 残りのツールのロジック ... } // 疑似コード: コールバック関数内 import { Context } from '@google/adk'; function myCallback(context: Context) { const lastToolResult = context.state.get('temp:last_api_result'); // 一時的な状態を読み取り if (lastToolResult) { console.log(`最後のツールからの一時的な結果が見つかりました: ${lastToolResult}`); } // ... コールバックのロジック ... }import ( "google.golang.org/adk/agent" "google.golang.org/adk/session" "google.golang.org/adk/tool" "google.golang.org/genai" ) // Pseudocode: In a Tool function type toolArgs struct { // Define tool-specific arguments here } type toolResults struct { // Define tool-specific results here } // Example tool function demonstrating state access func myTool(tc tool.Context, input toolArgs) (toolResults, error) { userPref, err := tc.State().Get("user_display_preference") if err != nil { userPref = "default_mode" } apiEndpoint, _ := tc.State().Get("app:api_endpoint") // Read app-level state if userPref == "dark_mode" { // ... apply dark mode logic ... } fmt.Printf("Using API endpoint: %v\n", apiEndpoint) // ... rest of tool logic ... return toolResults{}, nil } // Pseudocode: In a Callback function func myCallback(ctx agent.CallbackContext) (*genai.Content, error) { lastToolResult, err := ctx.State().Get("temp:last_api_result") // Read temporary state if err == nil { fmt.Printf("Found temporary result from last tool: %v\n", lastToolResult) } else { fmt.Println("No temporary result found.") } // ... callback logic ... return nil, nil }// 例: ツール関数内 import com.google.adk.tools.ToolContext; public void myTool(ToolContext toolContext) { String userPref = (String) toolContext.state().getOrDefault("user_display_preference", "default_mode"); String apiEndpoint = (String) toolContext.state().get("app:api_endpoint"); // アプリレベルの状態を読み取り if ("dark_mode".equals(userPref)) { // ... ダークモードのロジックを適用 ... } System.out.println("使用中のAPIエンドポイント: " + apiEndpoint); // ... 残りのツールのロジック ... } // 例: コールバック関数内 import com.google.adk.agents.CallbackContext; public void myCallback(CallbackContext callbackContext) { String lastToolResult = (String) callbackContext.state().get("temp:last_api_result"); // 一時的な状態を読み取り if (lastToolResult != null && !lastToolResult.isEmpty()) { System.out.println("最後のツールからの一時的な結果が見つかりました: " + lastToolResult); } // ... コールバックのロジック ... } -
現在の識別子の取得: ロギングや、現在の操作に基づいたカスタムロジックに便利です。
# 例: 任意のコンテキスト内(ToolContextの例) from google.adk.tools import ToolContext def log_tool_usage(tool_context: ToolContext, **kwargs): agent_name = tool_context.agent_name inv_id = tool_context.invocation_id func_call_id = getattr(tool_context, 'function_call_id', 'N/A') # ToolContextに固有 print(f"ログ: 呼び出し={inv_id}, エージェント={agent_name}, 関数呼び出しID={func_call_id} - ツールが実行されました。")// 疑似コード: 任意のコンテキスト内 import { Context } from '@google/adk'; function logToolUsage(context: Context) { const agentName = context.agentName; const invId = context.invocationId; const functionCallId = context.functionCallId ?? 'N/A'; // ツール実行時に利用可能 console.log(`ログ: 呼び出し=${invId}, エージェント=${agentName}, 関数呼び出しID=${functionCallId} - ツールが実行されました。`); }import "google.golang.org/adk/tool" // Pseudocode: In any context (ToolContext shown) type logToolUsageArgs struct{} type logToolUsageResult struct { Status string `json:"status"` } func logToolUsage(tc tool.Context, args logToolUsageArgs) (logToolUsageResult, error) { agentName := tc.AgentName() invID := tc.InvocationID() funcCallID := tc.FunctionCallID() fmt.Printf("Log: Invocation=%s, Agent=%s, FunctionCallID=%s - Tool Executed.\n", invID, agentName, funcCallID) return logToolUsageResult{Status: "Logged successfully"}, nil }// 例: 任意のコンテキスト内(ToolContextの例) import com.google.adk.tools.ToolContext; public void logToolUsage(ToolContext toolContext) { String agentName = toolContext.agentName(); String invId = toolContext.invocationId(); String functionCallId = toolContext.functionCallId().orElse("N/A"); // ToolContextに固有 System.out.println("ログ: 呼び出し=" + invId + ", エージェント=" + agentName + ", 関数呼び出しID=" + functionCallId + " - ツールが実行されました。"); } -
最初のユーザー入力へのアクセス: 現在の呼び出しを開始したメッセージを再参照します。
# 例: コールバック内 from google.adk.agents.context import Context def check_initial_intent(context: Context, **kwargs): initial_text = "N/A" if context.user_content and context.user_content.parts: initial_text = context.user_content.parts[0].text or "テキスト以外の入力" print(f"この呼び出しはユーザー入力で始まりました: '{initial_text}'") # 例: エージェントの _run_async_impl 内 # async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]: # if ctx.user_content and ctx.user_content.parts: # initial_text = ctx.user_content.parts[0].text # print(f"最初のクエリを記憶しているエージェントのロジック: {initial_text}") # ...// 疑似コード: コールバック内 import { Context } from '@google/adk'; function checkInitialIntent(context: Context) { let initialText = 'N/A'; const userContent = context.userContent; if (userContent?.parts?.length) { initialText = userContent.parts[0].text ?? 'テキスト以外の入力'; } console.log(`この呼び出しはユーザー入力で始まりました: '${initialText}'`); }import ( "google.golang.org/adk/agent" "google.golang.org/genai" ) // Pseudocode: In a Callback func logInitialUserInput(ctx agent.CallbackContext) (*genai.Content, error) { userContent := ctx.UserContent() if userContent != nil && len(userContent.Parts) > 0 { if text := userContent.Parts[0].Text; text != "" { fmt.Printf("User's initial input for this turn: '%s'\n", text) } } return nil, nil // No modification }// 例: コールバック内 import com.google.adk.agents.CallbackContext; import com.google.genai.types.Content; public void checkInitialIntent(CallbackContext callbackContext) { String initialText = "N/A"; if (callbackContext.userContent().isPresent() && callbackContext.userContent().get().parts() != null && !callbackContext.userContent().get().parts().get().isEmpty()) { initialText = callbackContext.userContent().get().parts().get().get(0).text().orElse("テキスト以外の入力"); ... System.out.println("この呼び出しはユーザー入力で始まりました: " + initialText); } }
状態管理¶
状態は、メモリとデータフローにとって非常に重要です。CallbackContextやToolContextを使って状態を変更すると、その変更はフレームワークによって自動的に追跡され、永続化されます。
-
仕組み:
callback_context.state['my_key'] = my_valueやtool_context.state['my_key'] = my_valueに書き込むと、この変更が現在のステップのイベントに関連付けられたEventActions.state_deltaに追加されます。そして、SessionServiceがイベントを永続化する際にこれらのデルタを適用します。 -
ツール間でのデータ受け渡し
# 例: ツール1 - ユーザーIDを取得 from google.adk.tools import ToolContext import uuid def get_user_profile(tool_context: ToolContext) -> dict: user_id = str(uuid.uuid4()) # ID取得をシミュレート # 次のツールのためにIDを状態に保存 tool_context.state["temp:current_user_id"] = user_id return {"profile_status": "IDが生成されました"} # 例: ツール2 - 状態からユーザーIDを使用 def get_user_orders(tool_context: ToolContext) -> dict: user_id = tool_context.state.get("temp:current_user_id") if not user_id: return {"error": "状態にユーザーIDが見つかりません"} print(f"ユーザーIDの注文を取得中: {user_id}") # ... user_idを使って注文を取得するロジック ... return {"orders": ["order123", "order456"]}// 疑似コード: ツール1 - ユーザーIDを取得 import { Context } from '@google/adk'; import { v4 as uuidv4 } from 'uuid'; function getUserProfile(context: Context): Record<string, string> { const userId = uuidv4(); // ID取得をシミュレート // 次のツールのためにIDを状態に保存 context.state.set('temp:current_user_id', userId); return { profile_status: 'IDが生成されました' }; } // 疑似コード: ツール2 - 状態からユーザーIDを使用 function getUserOrders(context: Context): Record<string, string | string[]> { const userId = context.state.get('temp:current_user_id'); if (!userId) { return { error: '状態にユーザーIDが見つかりません' }; } console.log(`ユーザーIDの注文を取得中: ${userId}`); // ... user_id を使って注文を取得するロジック ... return { orders: ['order123', 'order456'] }; }import "google.golang.org/adk/tool" // Pseudocode: Tool 1 - Fetches user ID type GetUserProfileArgs struct { } func getUserProfile(tc tool.Context, input GetUserProfileArgs) (string, error) { // A random user ID for demonstration purposes userID := "random_user_456" // Save the ID to state for the next tool if err := tc.State().Set("temp:current_user_id", userID); err != nil { return "", fmt.Errorf("failed to set user ID in state: %w", err) } return "ID generated", nil } // Pseudocode: Tool 2 - Uses user ID from state type GetUserOrdersArgs struct { } type getUserOrdersResult struct { Orders []string `json:"orders"` } func getUserOrders(tc tool.Context, input GetUserOrdersArgs) (*getUserOrdersResult, error) { userID, err := tc.State().Get("temp:current_user_id") if err != nil { return &getUserOrdersResult{}, fmt.Errorf("user ID not found in state") } fmt.Printf("Fetching orders for user ID: %v\n", userID) // ... logic to fetch orders using user_id ... return &getUserOrdersResult{Orders: []string{"order123", "order456"}}, nil }// 例: ツール1 - ユーザーIDを取得 import com.google.adk.tools.ToolContext; import java.util.Map; import java.util.UUID; public Map<String, String> getUserProfile(ToolContext toolContext) { String userId = UUID.randomUUID().toString(); // 次のツールのためにIDを状態に保存 toolContext.state().put("temp:current_user_id", userId); return Map.of("profile_status", "IDが生成されました"); } // 例: ツール2 - 状態からユーザーIDを使用 public Map<String, String> getUserOrders(ToolContext toolContext) { String userId = (String) toolContext.state().get("temp:current_user_id"); if (userId == null || userId.isEmpty()) { return Map.of("error", "状態にユーザーIDが見つかりません"); } System.out.println("ユーザーIDの注文を取得中: " + userId); // ... userId を使って注文を取得するロジック ... return Map.of("orders", "order123"); } -
ユーザー設定の更新:
# 例: ツールまたはコールバックが設定を識別 from google.adk.tools import ToolContext # または Context def set_user_preference(tool_context: ToolContext, preference: str, value: str) -> dict: # 永続的なSessionServiceを使用する場合、ユーザーレベルの状態には 'user:' プレフィックスを使用 state_key = f"user:{preference}" tool_context.state[state_key] = value print(f"ユーザー設定 '{preference}' を '{value}' に設定しました") return {"status": "設定が更新されました"}// 疑似コード: ツールまたはコールバックが設定を識別 import { Context } from '@google/adk'; function setUserPreference(context: Context, preference: string, value: string): Record<string, string> { // 永続的な SessionService を使用する場合、ユーザーレベルの状態には 'user:' プレフィックスを使用 const stateKey = `user:${preference}`; context.state.set(stateKey, value); console.log(`ユーザー設定 '${preference}' を '${value}' に設定しました`); return { status: '設定が更新されました' }; }import "google.golang.org/adk/tool" // Pseudocode: Tool or Callback identifies a preference type setUserPreferenceArgs struct { Preference string `json:"preference" jsonschema:"The name of the preference to set."` Value string `json:"value" jsonschema:"The value to set for the preference."` } type setUserPreferenceResult struct { Status string `json:"status"` } func setUserPreference(tc tool.Context, args setUserPreferenceArgs) (setUserPreferenceResult, error) { // Use 'user:' prefix for user-level state (if using a persistent SessionService) stateKey := fmt.Sprintf("user:%s", args.Preference) if err := tc.State().Set(stateKey, args.Value); err != nil { return setUserPreferenceResult{}, fmt.Errorf("failed to set preference in state: %w", err) } fmt.Printf("Set user preference '%s' to '%s'\n", args.Preference, args.Value) return setUserPreferenceResult{Status: "Preference updated"}, nil }// 例: ツールまたはコールバックが設定を識別 import com.google.adk.tools.ToolContext; // または CallbackContext public Map<String, String> setUserPreference(ToolContext toolContext, String preference, String value) { // 永続的なSessionServiceを使用する場合、ユーザーレベルの状態には 'user:' プレフィックスを使用 String stateKey = "user:" + preference; toolContext.state().put(stateKey, value); System.out.println("ユーザー設定 '" + preference + "' を '" + value + "' に設定しました"); return Map.of("status", "設定が更新されました"); } -
状態のプレフィックス: 基本的な状態はセッション固有ですが、
app:やuser:のようなプレフィックスは、永続的なSessionService実装(DatabaseSessionServiceやVertexAiSessionServiceなど)と共に使用して、より広いスコープ(アプリ全体またはセッションをまたいだユーザー全体)を示すことができます。temp:は、現在の呼び出し内でのみ関連するデータを示すために使用できます。
アーティファクトの操作¶
セッションに関連付けられたファイルや大きなデータのかたまりを扱うには、アーティファクトを使用します。一般的なユースケース:アップロードされたドキュメントの処理。
-
ドキュメント要約器のフロー例:
-
参照の取り込み(例:セットアップツールやコールバック内): ドキュメントのコンテンツ全体ではなく、パスやURIをアーティファクトとして保存します。
# 例: コールバックまたは初期ツール内 from google.adk.agents.context import Context # または ToolContext from google.genai import types def save_document_reference(context: Context, file_path: str) -> None: # file_pathが "gs://my-bucket/docs/report.pdf" や "/local/path/to/report.pdf" のようなものであると仮定 try: # パス/URIテキストを含むPartを作成 artifact_part = types.Part.from_text(file_path) version = context.save_artifact("document_to_summarize.txt", artifact_part) print(f"ドキュメント参照 '{file_path}' をアーティファクトバージョン {version} として保存しました") # 他のツールで必要な場合はファイル名を状態に保存 context.state["temp:doc_artifact_name"] = "document_to_summarize.txt" except ValueError as e: print(f"アーティファクトの保存エラー: {e}") # 例:アーティファクトサービスが設定されていない except Exception as e: print(f"アーティファクト参照の保存中に予期せぬエラーが発生しました: {e}") # 使用例: # save_document_reference(context, "gs://my-bucket/docs/report.pdf")// 疑似コード: コールバックまたは初期ツール内 import { Context } from '@google/adk'; import type { Part } from '@google/genai'; async function saveDocumentReference(context: Context, filePath: string) { // filePath が "gs://my-bucket/docs/report.pdf" や "/local/path/to/report.pdf" のようなものであると仮定 try { // パス/URI テキストを含む Part を作成 const artifactPart: Part = { text: filePath }; const version = await context.saveArtifact('document_to_summarize.txt', artifactPart); console.log(`ドキュメント参照 '${filePath}' をアーティファクトバージョン ${version} として保存しました`); context.state.set('temp:doc_artifact_name', 'document_to_summarize.txt'); } catch (e) { console.error(`アーティファクト参照の保存中に予期せぬエラーが発生しました: ${e}`); } } // 使用例: // saveDocumentReference(context, "gs://my-bucket/docs/report.pdf");import ( "google.golang.org/adk/tool" "google.golang.org/genai" ) // Adapt the saveDocumentReference callback into a tool for this example. type saveDocRefArgs struct { FilePath string `json:"file_path" jsonschema:"The path to the file to save."` } type saveDocRefResult struct { Status string `json:"status"` } func saveDocRef(tc tool.Context, args saveDocRefArgs) (saveDocRefResult, error) { artifactPart := genai.NewPartFromText(args.FilePath) _, err := tc.Artifacts().Save(tc, "document_to_summarize.txt", artifactPart) if err != nil { return saveDocRefResult{}, err } fmt.Printf("Saved document reference '%s' as artifact\n", args.FilePath) if err := tc.State().Set("temp:doc_artifact_name", "document_to_summarize.txt"); err != nil { return saveDocRefResult{}, fmt.Errorf("failed to set artifact name in state") } return saveDocRefResult{"Reference saved"}, nil }// 例: コールバックまたは初期ツール内 import com.google.adk.agents.CallbackContext; import com.google.genai.types.Content; import com.google.genai.types.Part; import java.util.Optional; public void saveDocumentReference(CallbackContext context, String filePath) { // file_pathが "gs://my-bucket/docs/report.pdf" や "/local/path/to/report.pdf" のようなものであると仮定 try { // パス/URIテキストを含むPartを作成 Part artifactPart = Part.fromText(filePath); Optional<Integer> version = context.saveArtifact("document_to_summarize.txt", artifactPart); System.out.println("ドキュメント参照 " + filePath + " をアーティファクトバージョン " + version.orElse(-1) + " として保存しました"); // 他のツールで必要な場合はファイル名を状態に保存 context.state().put("temp:doc_artifact_name", "document_to_summarize.txt"); } catch (Exception e) { System.out.println("アーティファクト参照の保存中に予期せぬエラーが発生しました: " + e); } } // 使用例: // saveDocumentReference(context, "gs://my-bucket/docs/report.pdf") -
要約ツール: アーティファクトをロードしてパス/URIを取得し、適切なライブラリを使用して実際のドキュメントコンテンツを読み取り、要約して結果を返します。
# 例: 要約ツールの関数内 from google.adk.tools import ToolContext from google.genai import types # google.cloud.storage や組み込みの open のようなライブラリが利用可能であると仮定 # 'summarize_text' 関数が存在すると仮定 # from my_summarizer_lib import summarize_text def summarize_document_tool(tool_context: ToolContext) -> dict: artifact_name = tool_context.state.get("temp:doc_artifact_name") if not artifact_name: return {"error": "状態にドキュメントのアーティファクト名が見つかりません。"} try: # 1. パス/URIを含むアーティファクトパートをロード artifact_part = tool_context.load_artifact(artifact_name) if not artifact_part or not artifact_part.text: return {"error": f"アーティファクトをロードできないか、アーティファクトにテキストパスがありません: {artifact_name}"} file_path = artifact_part.text print(f"ロードされたドキュメント参照: {file_path}") # 2. 実際のドキュメントコンテンツを読み取る(ADKコンテキスト外) document_content = "" if file_path.startswith("gs://"): # 例:GCSクライアントライブラリを使用してダウンロード/読み取り pass # 実際のGCS読み取りロジックに置き換える elif file_path.startswith("/"): # 例:ローカルファイルシステムを使用 with open(file_path, 'r', encoding='utf-8') as f: document_content = f.read() else: return {"error": f"サポートされていないファイルパススキーム: {file_path}"} # 3. コンテンツを要約 if not document_content: return {"error": "ドキュメントコンテンツの読み取りに失敗しました。"} # summary = summarize_text(document_content) # 要約ロジックを呼び出す summary = f"{file_path}からのコンテンツの要約" # プレースホルダー return {"summary": summary} except ValueError as e: return {"error": f"アーティファクトサービスのエラー: {e}"} except FileNotFoundError: return {"error": f"ローカルファイルが見つかりません: {file_path}"}// 疑似コード: 要約ツールの関数内 import { Context } from '@google/adk'; async function summarizeDocumentTool(context: Context): Promise<Record<string, string>> { const artifactName = context.state.get('temp:doc_artifact_name') as string; if (!artifactName) { return { error: '状態にドキュメントのアーティファクト名が見つかりません。' }; } try { // 1. パス/URI を含むアーティファクトパートをロード const artifactPart = await context.loadArtifact(artifactName); if (!artifactPart?.text) { return { error: `アーティファクトをロードできないか、アーティファクトにテキストパスがありません: ${artifactName}` }; } const filePath = artifactPart.text; console.log(`ロードされたドキュメント参照: ${filePath}`); // 2. 実際のドキュメントコンテンツを読み取る(ADK コンテキスト外) let documentContent = ''; if (filePath.startsWith('gs://')) { // 例: GCS クライアントライブラリを使用してダウンロード/読み取り // const storage = new Storage(); // const bucket = storage.bucket('my-bucket'); // const file = bucket.file(filePath.replace('gs://my-bucket/', '')); // const [contents] = await file.download(); // documentContent = contents.toString(); } else if (filePath.startsWith('/')) { // 例: ローカルファイルシステムを使用 // import { readFile } from 'fs/promises'; // documentContent = await readFile(filePath, 'utf8'); } else { return { error: `サポートされていないファイルパススキーム: ${filePath}` }; } // 3. コンテンツを要約 if (!documentContent) { return { error: 'ドキュメントコンテンツの読み取りに失敗しました。' }; } // const summary = summarizeText(documentContent); // 要約ロジックを呼び出す const summary = `${filePath}からのコンテンツの要約`; // プレースホルダー return { summary }; } catch (e) { return { error: `アーティファクト処理エラー: ${e}` }; } }import "google.golang.org/adk/tool" // Pseudocode: In the Summarizer tool function type summarizeDocumentArgs struct{} type summarizeDocumentResult struct { Summary string `json:"summary"` } func summarizeDocumentTool(tc tool.Context, input summarizeDocumentArgs) (summarizeDocumentResult, error) { artifactName, err := tc.State().Get("temp:doc_artifact_name") if err != nil { return summarizeDocumentResult{}, fmt.Errorf("No document artifact name found in state") } // 1. Load the artifact part containing the path/URI artifactPart, err := tc.Artifacts().Load(tc, artifactName.(string)) if err != nil { return summarizeDocumentResult{}, err } if artifactPart.Part.Text == "" { return summarizeDocumentResult{}, fmt.Errorf("Could not load artifact or artifact has no text path.") } filePath := artifactPart.Part.Text fmt.Printf("Loaded document reference: %s\n", filePath) // 2. Read the actual document content (outside ADK context) // In a real implementation, you would use a GCS client or local file reader. documentContent := "This is the fake content of the document at " + filePath _ = documentContent // Avoid unused variable error. // 3. Summarize the content summary := "Summary of content from " + filePath // Placeholder return summarizeDocumentResult{Summary: summary}, nil }// 例: 要約ツールの関数内 import com.google.adk.tools.ToolContext; import com.google.genai.types.Content; import com.google.genai.types.Part; import java.util.Map; import java.util.Optional; import java.io.FileNotFoundException; public Map<String, String> summarizeDocumentTool(ToolContext toolContext) { String artifactName = (String) toolContext.state().get("temp:doc_artifact_name"); if (artifactName == null || artifactName.isEmpty()) { return Map.of("error", "状態にドキュメントのアーティファクト名が見つかりません。"); } try { // 1. パス/URIを含むアーティファクトパートをロード Optional<Part> artifactPart = toolContext.loadArtifact(artifactName); if (!artifactPart.isPresent() || !artifactPart.get().text().isPresent() || artifactPart.get().text().get().isEmpty()) { return Map.of("error", "アーティファクトをロードできないか、アーティファクトにテキストパスがありません: " + artifactName); } String filePath = artifactPart.get().text().get(); System.out.println("ロードされたドキュメント参照: " + filePath); // 2. 実際のドキュメントコンテンツを読み取る(ADKコンテキスト外) String documentContent = ""; if (filePath.startsWith("gs://")) { // 例:GCSクライアントライブラリを使用してdocumentContentにダウンロード/読み取り // 実際のGCS読み取りロジックに置き換える } else if (filePath.startsWith("/")) { // 例:ローカルファイルシステムを使用してdocumentContentにダウンロード/読み取り } else { return Map.of("error", "サポートされていないファイルパススキーム: " + filePath); } // 3. コンテンツを要約 if (documentContent.isEmpty()) { return Map.of("error", "ドキュメントコンテンツの読み取りに失敗しました。"); } // summary = summarizeText(documentContent) // 要約ロジックを呼び出す String summary = filePath + "からのコンテンツの要約"; // プレースホルダー return Map.of("summary", summary); } catch (IllegalArgumentException e) { return Map.of("error", "アーティファクトサービスのエラー " + e); } catch (Exception e) { return Map.of("error", "ドキュメントの読み取りエラー " + e); } }
-
-
アーティファクトの一覧表示: 利用可能なファイルを確認します。
# 例: ツール関数内 from google.adk.tools import ToolContext def check_available_docs(tool_context: ToolContext) -> dict: try: artifact_keys = tool_context.list_artifacts() print(f"利用可能なアーティファクト: {artifact_keys}") return {"available_docs": artifact_keys} except ValueError as e: return {"error": f"アーティファクトサービスのエラー: {e}"}// 疑似コード: ツール関数内 import { Context } from '@google/adk'; async function checkAvailableDocs(context: Context): Promise<Record<string, string[] | string>> { try { const artifactKeys = await context.listArtifacts(); console.log(`利用可能なアーティファクト: ${artifactKeys}`); return { available_docs: artifactKeys }; } catch (e) { return { error: `アーティファクトサービスのエラー: ${e}` }; } }import "google.golang.org/adk/tool" // Pseudocode: In a tool function type checkAvailableDocsArgs struct{} type checkAvailableDocsResult struct { AvailableDocs []string `json:"available_docs"` } func checkAvailableDocs(tc tool.Context, args checkAvailableDocsArgs) (checkAvailableDocsResult, error) { artifactKeys, err := tc.Artifacts().List(tc) if err != nil { return checkAvailableDocsResult{}, err } fmt.Printf("Available artifacts: %v\n", artifactKeys) return checkAvailableDocsResult{AvailableDocs: artifactKeys.FileNames}, nil }// 例: ツール関数内 import com.google.adk.tools.ToolContext; import io.reactivex.rxjava3.core.Single; import java.util.List; import java.util.Map; public Map<String, Object> checkAvailableDocs(ToolContext toolContext) { try { Single<List<String>> artifactKeys = toolContext.listArtifacts(); System.out.println("利用可能なアーティファクト: " + artifactKeys.blockingGet().toString()); return Map.of("availableDocs", artifactKeys.blockingGet()); } catch (IllegalArgumentException e) { return Map.of("error", "アーティファクトサービスのエラー: " + e); } }
ツール認証の処理¶
ツールに必要なAPIキーやその他の認証情報を安全に管理します。
```python
例:認証が必要なツール¶
from google.adk.tools import ToolContext from google.adk.auth import AuthConfig # 適切なAuthConfigが定義されていると仮定
必要な認証設定を定義(例:OAuth, APIキー)¶
MY_API_AUTH_CONFIG = AuthConfig(...) AUTH_STATE_KEY = "user:my_api_credential" # 取得した認証情報を保存するキー
def call_secure_api(tool_context: ToolContext, request_data: str) -> dict: # 1. 認証情報が既に状態に存在するか確認 credential = tool_context.state.get(AUTH_STATE_KEY)
if not credential:
# 2. 存在しない場合は要求
print("認証情報が見つからないため、要求します...")
try:
tool_context.request_credential(MY_API_AUTH_CONFIG)
# フレームワークがイベントのyieldを処理します。ツールの実行はこのターンでここで停止します。
return {"status": "認証が必要です。認証情報を提供してください。"}
except ValueError as e:
return {"error": f"認証エラー: {e}"} # 例:function_call_id がない
except Exception as e:
return {"error": f"認証情報の要求に失敗しました: {e}"}
# 3. 認証情報が存在する場合(要求後の前のターンからのものである可能性)
# または外部での認証フロー完了後の後続の呼び出しの場合
try:
# オプションで、必要に応じて再検証/再取得、または直接使用
# 外部フローが完了したばかりの場合、ここで認証情報を取得できる可能性がある
auth_credential_obj = tool_context.get_auth_response(MY_API_AUTH_CONFIG)
api_key = auth_credential_obj.api_key # または access_token など
# セッション内の将来の呼び出しのために状態に保存し直す
tool_context.state[AUTH_STATE_KEY] = auth_credential_obj.model_dump() # 取得した認証情報を永続化
print(f"取得した認証情報を使用してAPIをデータで呼び出します: {request_data}")
# ... api_keyを使用して実際のAPI呼び出しを行う ...
api_result = f"{request_data} の API 結果"
return {"result": api_result}
except Exception as e:
# 認証情報の使用/取得エラーの処理
print(f"認証情報の使用エラー: {e}")
# 認証情報が無効な場合は状態キーをクリアすることも検討
# tool_context.state[AUTH_STATE_KEY] = None
return {"error": "認証情報の使用に失敗しました"}
```
```typescript // 疑似コード: 認証が必要なツール import { Context } from '@google/adk';
interface AuthConfig { credentialKey: string; authScheme: { type: string }; }
const MY_API_AUTH_CONFIG: AuthConfig = { credentialKey: 'my-api-key', authScheme: { type: 'api-key' }, }; const AUTH_STATE_KEY = 'user:my_api_credential';
async function callSecureApi(context: Context, requestData: string): Promise
if (!credential) {
console.log('認証情報が見つからないため要求します...');
try {
context.requestCredential(MY_API_AUTH_CONFIG);
return { status: '認証が必要です。認証情報を提供してください。' };
} catch (e) {
return { error: 認証または認証情報要求エラー: ${e} };
}
}
try { const authCredentialObj = context.getAuthResponse(MY_API_AUTH_CONFIG); context.state.set(AUTH_STATE_KEY, JSON.stringify(authCredentialObj));
console.log(`取得した認証情報を使って API を呼び出します: ${requestData}`);
const apiResult = `${requestData} の API 結果`;
return { result: apiResult };
} catch (e) {
console.error(認証情報の使用エラー: ${e});
return { error: '認証情報を使用できませんでした。' };
}
}
```
// 例: 認証が必要なツール
import com.google.adk.tools.ToolContext;
import java.util.Map;
// 注: AuthConfig、requestCredential、getAuthResponse は
// Java ADK の公開 API ではまだ完全には実装されていません。
// この例では、外部で認証情報をセッション状態に投入する前提です。
public class SecureApiTool {
private static final String AUTH_STATE_KEY = "user:my_api_credential";
public Map<String, String> callSecureApi(ToolContext context, String requestData) {
// 1. 認証情報が既に状態にあるか確認
Object credential = context.state().get(AUTH_STATE_KEY);
if (credential == null) {
// 2. なければ要求
System.out.println("認証情報が見つからないため要求します...");
try {
// context.requestCredential(MY_API_AUTH_CONFIG); // Java ADK では未実装
return Map.of("status", "認証が必要です。認証情報を提供してください。");
} catch (Exception e) {
return Map.of("error", "認証または認証情報要求エラー: " + e.getMessage());
}
}
// 3. 認証情報が存在する場合(要求後の前ターン由来の可能性あり)
// または外部認証フロー完了後の後続呼び出しの場合
try {
// String apiKey = context.getAuthResponse(MY_API_AUTH_CONFIG).getApiKey();
String apiKey = credential.toString(); // 例のため簡略化
// セッション内の今後の呼び出し向けに状態へ保存し直す
context.state().put(AUTH_STATE_KEY, apiKey);
System.out.println("取得した認証情報を使って API を呼び出します: " + requestData);
String apiResult = requestData + " の API 結果";
return Map.of("result", apiResult);
} catch (Exception e) {
System.err.println("認証情報の使用エラー: " + e.getMessage());
return Map.of("error", "認証情報を使用できませんでした。");
}
}
}
注意:request_credentialはツールを一時停止し、認証の必要性を通知します。ユーザー/システムが認証情報を提供すると、後続の呼び出しでget_auth_response(または状態の再確認)によってツールが続行できるようになります。 tool_context.function_call_idは、要求と応答をリンクするためにフレームワークによって暗黙的に使用されます。
メモリの活用¶
過去や外部ソースから関連情報にアクセスします。
```python
例:メモリ検索を使用するツール¶
from google.adk.tools import ToolContext
def find_related_info(tool_context: ToolContext, topic: str) -> dict: try: search_results = tool_context.search_memory(f"{topic}に関する情報") if search_results.results: print(f"'{topic}' に関するメモリ検索結果が {len(search_results.results)} 件見つかりました") # search_results.resultsを処理(これらはSearchMemoryResponseEntry) top_result_text = search_results.results[0].text return {"memory_snippet": top_result_text} else: return {"message": "関連するメモリが見つかりませんでした。"} except ValueError as e: return {"error": f"メモリサービスのエラー: {e}"} # 例:サービスが設定されていない except Exception as e: return {"error": f"メモリ検索中に予期せぬエラーが発生しました: {e}"} ```
```typescript // 疑似コード: メモリ検索を使用するツール import { Context } from '@google/adk';
async function findRelatedInfo(context: Context, topic: string): Promise${topic}に関する情報);
if (searchResults.results?.length) {
console.log('${topic}' に関するメモリ結果が ${searchResults.results.length} 件見つかりました);
const topResultText = searchResults.results[0].text;
return { memory_snippet: topResultText };
} else {
return { message: '関連するメモリは見つかりませんでした。' };
}
} catch (e) {
return { error: メモリサービスのエラー: ${e} };
}
}
```
// 例: メモリ検索を使うツール
import com.google.adk.tools.ToolContext;
import com.google.adk.memory.SearchMemoryResponse;
import io.reactivex.rxjava3.core.Single;
import java.util.Map;
public class MemorySearchTool {
public Single<Map<String, String>> findRelatedInfo(ToolContext context, String topic) {
return context.searchMemory("Information about " + topic)
.map(searchResults -> {
if (searchResults != null && searchResults.results() != null && !searchResults.results().isEmpty()) {
System.out.println("'" + topic + "' に関するメモリ結果が " + searchResults.results().size() + " 件見つかりました");
String topResultText = searchResults.results().get(0).text();
return Map.of("memory_snippet", topResultText);
} else {
return Map.of("message", "関連するメモリは見つかりませんでした。");
}
})
.onErrorReturnItem(Map.of("error", "メモリサービスのエラー"));
}
}
上級:直接的な InvocationContext の使用¶
ほとんどのインタラクションはCallbackContextやToolContextを介して行われますが、時にはエージェントのコアロジック(_run_async_impl/_run_live_impl)が直接アクセスする必要がある場合があります。
```python
例:エージェントの_run_async_impl内¶
from google.adk.agents import BaseAgent from google.adk.agents.invocation_context import InvocationContext from google.adk.events import Event from typing import AsyncGenerator
class MyControllingAgent(BaseAgent): async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]: # 例:特定のサービスが利用可能か確認 if not ctx.memory_service: print("この呼び出しではメモリサービスは利用できません。") # エージェントの振る舞いを変更する可能性
# 例:何らかの条件に基づいて早期終了
if ctx.session.state.get("critical_error_flag"):
print("重大なエラーが検出されたため、呼び出しを終了します。")
ctx.end_invocation = True # フレームワークに処理停止を通知
yield Event(author=self.name, invocation_id=ctx.invocation_id, content="重大なエラーのため停止します。")
return # このエージェントの実行を停止
# ... 通常のエージェント処理 ...
yield # ... イベント ...
```
// 例: エージェントの runAsyncImpl 内
import com.google.adk.agents.BaseAgent;
import com.google.adk.agents.InvocationContext;
import com.google.adk.events.Event;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Flowable;
import java.util.List;
public class MyControllingAgent extends BaseAgent {
@Override
protected Flowable<Event> runAsyncImpl(InvocationContext ctx) {
// 例: 特定のサービスが利用可能か確認
if (ctx.memoryService() == null) {
System.out.println("この呼び出しではメモリサービスを利用できません。");
}
// 例: 条件に基づく早期終了
Boolean criticalError = (Boolean) ctx.session().state().getOrDefault("critical_error_flag", false);
if (criticalError != null && criticalError) {
System.out.println("重大なエラーが検出されたため呼び出しを終了します。");
ctx.setEndInvocation(true); // フレームワークに処理停止を通知
Event errorEvent = Event.builder()
.author(name())
.invocationId(ctx.invocationId())
.content(Content.builder().parts(List.of(Part.builder().text("重大なエラーのため停止します。").build())).build())
.build();
return Flowable.just(errorEvent);
}
// ... 通常のエージェント処理 ...
return Flowable.empty();
}
}
ctx.end_invocation = Trueを設定することは、エージェントやそのコールバック/ツール内から(それぞれのコンテキストオブジェクトを介して基盤となるInvocationContextのフラグにアクセスし変更することで)リクエスト-レスポンスサイクル全体を正常に停止させる方法です。
要点とベストプラクティス¶
- 適切なコンテキストを使用する: 常に提供される最も具体的なコンテキストオブジェクトを使用してください(ツール/ツールコールバックでは
ToolContext、エージェント/モデルコールバックではCallbackContext、該当する場合はReadonlyContext)。完全なInvocationContext(ctx)は、必要な場合にのみ_run_async_impl/_run_live_implで直接使用してください。 - データフローのための状態:
context.stateは、呼び出し内でデータを共有し、好みを記憶し、会話メモリを管理するための主要な方法です。永続ストレージを使用する場合は、プレフィックス(app:、user:、temp:)を慎重に使用してください。 - ファイルのためのアーティファクト: ファイル参照(パスやURIなど)や大きなデータのかたまりを管理するには、
context.save_artifactとcontext.load_artifactを使用してください。参照を保存し、必要に応じてコンテンツをロードします。 - 追跡される変更: コンテキストメソッドを介して状態やアーティファクトに行われた変更は、現在のステップの
EventActionsに自動的にリンクされ、SessionServiceによって処理されます。 - シンプルに始める: まずは
stateと基本的なアーティファクトの使用に焦点を当ててください。ニーズがより複雑になるにつれて、認証、メモリ、および高度なInvocationContextのフィールド(ライブストリーミング用のものなど)を探求してください。
これらのコンテキストオブジェクトを理解し効果的に使用することで、ADKでより洗練され、ステートフルで、有能なエージェントを構築することができます。