跳到主要内容

ツール(関数呼び出し)

一部のLLMは、テキスト生成に加えて、アクションをトリガーすることもできます。

备注

ツールをサポートするすべてのLLMはこちらで確認できます(「ツール」列を参照)。

备注

すべてのLLMが同じようにツールをサポートしているわけではありません。 ツールを理解し、選択し、正しく使用する能力は、特定のモデルとその機能に大きく依存します。 一部のモデルはツールをまったくサポートしていない場合もあれば、慎重なプロンプトエンジニアリングや 追加のシステム指示が必要な場合もあります。

「ツール」または「関数呼び出し」と呼ばれる概念があります。 これにより、LLMは必要に応じて、通常は開発者によって定義された1つまたは複数の利用可能なツールを呼び出すことができます。 ツールは何でも構いません:ウェブ検索、外部APIの呼び出し、特定のコードの実行など。 LLMは実際にツール自体を呼び出すことはできません。代わりに、(プレーンテキストで応答する代わりに) 特定のツールを呼び出す意図を応答で表現します。 開発者である私たちは、提供された引数でこのツールを実行し、ツール実行の結果を報告する必要があります。

例えば、LLM自体は数学が得意ではないことが知られています。 ユースケースに時々数学計算が含まれる場合、LLMに「数学ツール」を提供したいかもしれません。 LLMへのリクエストで1つまたは複数のツールを宣言することで、 適切だと判断した場合、それらのうちの1つを呼び出すことを決定できます。 数学の質問と一連の数学ツールが与えられた場合、LLMは質問に適切に答えるために、 まず提供された数学ツールの1つを呼び出すべきだと判断するかもしれません。

これが実際にどのように機能するか見てみましょう(ツールありとなし):

ツールなしのメッセージ交換の例:

リクエスト:
- メッセージ:
- UserMessage:
- テキスト:475695037565の平方根は何ですか?

レスポンス:
- AiMessage:
- テキスト:475695037565の平方根は約689710です。

近いですが、正確ではありません。

以下のツールを使用したメッセージ交換の例:

@Tool("2つの数値を合計します")
double sum(double a, double b) {
return a + b;
}

@Tool("与えられた数値の平方根を返します")
double squareRoot(double x) {
return Math.sqrt(x);
}
リクエスト1:
- メッセージ:
- UserMessage:
- テキスト:475695037565の平方根は何ですか?
- ツール:
- sum(double a, double b):2つの数値を合計します
- squareRoot(double x):与えられた数値の平方根を返します

レスポンス1:
- AiMessage:
- toolExecutionRequests:
- squareRoot(475695037565)


... ここで「475695037565」引数でsquareRootメソッドを実行し、結果として「689706.486532」を取得します ...


リクエスト2:
- メッセージ:
- UserMessage:
- テキスト:475695037565の平方根は何ですか?
- AiMessage:
- toolExecutionRequests:
- squareRoot(475695037565)
- ToolExecutionResultMessage:
- テキスト:689706.486532

レスポンス2:
- AiMessage:
- テキスト:475695037565の平方根は689706.486532です。

ご覧のように、LLMがツールにアクセスできる場合、適切なときにそれらの1つを呼び出すことを決定できます。

これは非常に強力な機能です。 この単純な例では、LLMに基本的な数学ツールを与えましたが、 例えば、googleSearchsendEmailツールを与え、 「友人がAI分野の最新ニュースを知りたがっています。friend@email.comに短い要約を送ってください」というクエリを与えた場合、 googleSearchツールを使用して最新ニュースを見つけ、 それを要約してsendEmailツールを使用してメールで要約を送信することができます。

备注

LLMが正しいツールを正しい引数で呼び出す可能性を高めるために、 明確で曖昧さのない以下の情報を提供する必要があります:

  • ツールの名前
  • ツールの機能と使用すべき状況の説明
  • 各ツールパラメータの説明

良い経験則:人間がツールの目的と使用方法を理解できれば、 LLMも理解できる可能性が高いです。

LLMはツールを呼び出すタイミングと方法を検出するために特別に微調整されています。 一部のモデルは複数のツールを一度に呼び出すこともできます。例えば、 OpenAI

备注

すべてのモデルがツールをサポートしているわけではないことに注意してください。 どのモデルがツールをサポートしているかを確認するには、こちらのページの「ツール」列を参照してください。

备注

ツール/関数呼び出しはJSONモードと同じではないことに注意してください。

2つの抽象化レベル

LangChain4jはツールを使用するための2つの抽象化レベルを提供しています:

  • 低レベル:ChatModelToolSpecification APIを使用
  • 高レベル:AIサービス@Toolアノテーション付きJavaメソッドを使用

低レベルツールAPI

低レベルでは、ChatModelchat(ChatRequest)メソッドを使用できます。 同様のメソッドはStreamingChatModelにも存在します。

ChatRequestを作成する際に1つ以上のToolSpecificationを指定できます。

ToolSpecificationはツールに関するすべての情報を含むオブジェクトです:

  • ツールのname
  • ツールのdescription
  • ツールのparametersとその説明

ツールについてできるだけ多くの情報を提供することをお勧めします: 明確な名前、包括的な説明、各パラメータの説明など。

ToolSpecificationを作成するには2つの方法があります:

  1. 手動で
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("getWeather")
.description("指定された都市の天気予報を返します")
.parameters(JsonObjectSchema.builder()
.addStringProperty("city", "天気予報を取得する都市")
.addEnumProperty("temperatureUnit", List.of("CELSIUS", "FAHRENHEIT"))
.required("city") // 必須プロパティは明示的に指定する必要があります
.build())
.build();

JsonObjectSchemaの詳細についてはこちらをご覧ください。

  1. ヘルパーメソッドを使用:
  • ToolSpecifications.toolSpecificationsFrom(Class)
  • ToolSpecifications.toolSpecificationsFrom(Object)
  • ToolSpecifications.toolSpecificationFrom(Method)
class WeatherTools { 

@Tool("指定された都市の天気予報を返します")
String getWeather(
@P("天気予報を取得する都市") String city,
TemperatureUnit temperatureUnit
) {
...
}
}

List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(WeatherTools.class);

List<ToolSpecification>を取得したら、モデルを呼び出すことができます:

ChatRequest request = ChatRequest.builder()
.messages(UserMessage.from("明日のロンドンの天気はどうなりますか?"))
.toolSpecifications(toolSpecifications)
.build();
ChatResponse response = model.chat(request);
AiMessage aiMessage = response.aiMessage();

LLMがツールを呼び出すことを決定した場合、返されたAiMessagetoolExecutionRequestsフィールドにデータを含みます。 この場合、AiMessage.hasToolExecutionRequests()trueを返します。 LLMによっては、1つまたは複数のToolExecutionRequestオブジェクトを含むことがあります (一部のLLMは複数のツールを並列で呼び出すことをサポートしています)。

ToolExecutionRequestには以下が含まれるはずです:

  • ツール呼び出しのid(一部のLLMはこれを提供しません)
  • 呼び出すツールのname、例:getWeather
  • arguments、例:{ "city": "London", "temperatureUnit": "CELSIUS" }

ToolExecutionRequestからの情報を使用して、ツールを手動で実行する必要があります。

ツール実行の結果をLLMに送り返したい場合は、 ToolExecutionResultMessageを作成し(各ToolExecutionRequestに対して1つ)、 以前のすべてのメッセージと一緒に送信する必要があります:


String result = "明日はロンドンで雨が予想されます。";
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from(toolExecutionRequest, result);
ChatRequest request2 = ChatRequest.builder()
.messages(List.of(userMessage, aiMessage, toolExecutionResultMessage))
.toolSpecifications(toolSpecifications)
.build();
ChatResponse response2 = model.chat(request2);

高レベルツールAPI

高レベルの抽象化では、任意のJavaメソッドに@Toolアノテーションを付け、 AIサービスを作成する際に指定できます。

AIサービスは自動的にそのようなメソッドをToolSpecificationに変換し、 LLMとの各対話のリクエストに含めます。 LLMがツールを呼び出すことを決定すると、AIサービスは自動的に適切なメソッドを実行し、 メソッドの戻り値(もしあれば)がLLMに送り返されます。 実装の詳細はDefaultToolExecutorで確認できます。

いくつかのツールの例:

@Tool("関連URLをGoogleで検索します")
public List<String> searchGoogle(@P("検索クエリ") String query) {
return googleSearchService.search(query);
}

@Tool("指定されたURLのウェブページの内容を返します")
public String getWebPageContent(@P("ページのURL") String url) {
Document jsoupDocument = Jsoup.connect(url).get();
return jsoupDocument.body().text();
}

ツールメソッドの制限

@Toolアノテーションが付いたメソッド:

  • staticまたは非staticのいずれでも可能
  • 任意の可視性(public、privateなど)を持つことができます。

ツールメソッドのパラメータ

@Toolアノテーションが付いたメソッドは、様々な型の任意の数のパラメータを受け入れることができます:

  • プリミティブ型:intdoubleなど
  • オブジェクト型:StringIntegerDoubleなど
  • カスタムPOJO(ネストされたPOJOを含むことができます)
  • enum
  • List<T>/Set<T>Tは上記の型のいずれか)
  • Map<K,V>KVの型を@Pでパラメータ説明に手動で指定する必要があります)

パラメータのないメソッドもサポートされています。

必須とオプション

デフォルトでは、すべてのツールメソッドパラメータは必須と見なされます。 これは、LLMがそのようなパラメータの値を生成する必要があることを意味します。 パラメータは@P(required = false)でアノテーションを付けることでオプションにできます:

@Tool
void getTemperature(String location, @P(required = false) Unit unit) {
...
}

複雑なパラメータのフィールドとサブフィールドもデフォルトでは必須と見なされます。 @JsonProperty(required = false)でアノテーションを付けることでフィールドをオプションにできます:

record User(String name, @JsonProperty(required = false) String email) {}

@Tool
void add(User user) {
...
}
备注

構造化出力で使用する場合、すべてのフィールドとサブフィールドはデフォルトでオプションと見なされることに注意してください。

再帰的なパラメータ(例:PersonクラスがSet<Person> childrenフィールドを持つ場合) は現在OpenAIでのみサポートされています。

ツールメソッドの戻り値の型

@Toolアノテーションが付いたメソッドは、voidを含む任意の型を返すことができます。 メソッドの戻り値の型がvoidの場合、メソッドが正常に戻ると「Success」という文字列がLLMに送信されます。

メソッドの戻り値の型がStringの場合、返された値は変換なしにそのままLLMに送信されます。

その他の戻り値の型については、返された値はLLMに送信される前にJSON文字列に変換されます。

例外処理

@Toolアノテーションが付いたメソッドがExceptionをスローする場合、 Exceptionのメッセージ(e.getMessage())がツール実行の結果としてLLMに送信されます。 これにより、LLMは必要と判断した場合、ミスを修正して再試行することができます。

@Tool

@Toolでアノテーションが付けられた任意のJavaメソッドで、 AIサービスのビルド時に_明示的に_指定されたものはLLMによって実行できます:

interface MathGenius {

String ask(String question);
}

class Calculator {

@Tool
double add(int a, int b) {
return a + b;
}

@Tool
double squareRoot(double x) {
return Math.sqrt(x);
}
}

MathGenius mathGenius = AiServices.builder(MathGenius.class)
.chatModel(model)
.tools(new Calculator())
.build();

String answer = mathGenius.ask("475695037565の平方根は何ですか?");

System.out.println(answer); // 475695037565の平方根は689706.486532です。

askメソッドが呼び出されると、前のセクションで説明したように、LLMとの2つの対話が発生します。 それらの対話の間に、squareRootメソッドが自動的に呼び出されます。

@Toolアノテーションには2つのオプションフィールドがあります:

  • name:ツールの名前。これが提供されない場合、メソッドの名前がツールの名前として使用されます。
  • value:ツールの説明。

ツールによっては、LLMは説明がなくてもそれをよく理解するかもしれません (例えば、add(a, b)は明白です)、 しかし通常は明確で意味のある名前と説明を提供する方が良いです。 このようにして、LLMは与えられたツールを呼び出すかどうか、そしてどのように呼び出すかを決定するためのより多くの情報を持ちます。

@P

メソッドパラメータはオプションで@Pでアノテーションを付けることができます。

@Pアノテーションには2つのフィールドがあります

  • value:パラメータの説明。必須フィールド。
  • required:パラメータが必須かどうか、デフォルトはtrue。オプションフィールド。

@Description

クラスとフィールドの説明は@Descriptionアノテーションを使用して指定できます:

@Description("実行するクエリ")
class Query {

@Description("選択するフィールド")
private List<String> select;

@Description("フィルタリングする条件")
private List<Condition> where;
}

@Tool
Result executeQuery(Query query) {
...
}
备注

enum値に配置された@Description効果がなく、生成されたJSONスキーマに含まれないことに注意してください:

enum Priority {

@Description("決済ゲートウェイの障害やセキュリティ侵害などの重大な問題。") // これは無視されます
CRITICAL,

@Description("主要な機能の不具合や広範囲にわたる停止などの高優先度の問題。") // これは無視されます
HIGH,

@Description("軽微なバグや見た目の問題などの低優先度の問題。") // これは無視されます
LOW
}

@ToolMemoryId

AIサービスメソッドに@MemoryIdでアノテーションが付いたパラメータがある場合、 @Toolメソッドのパラメータにも@ToolMemoryIdでアノテーションを付けることができます。 AIサービスメソッドに提供された値は自動的に@Toolメソッドに渡されます。 この機能は、複数のユーザーや1ユーザーあたり複数のチャット/メモリがあり、 @Toolメソッド内でそれらを区別したい場合に役立ちます。

実行されたツールへのアクセス

AIサービスの呼び出し中に実行されたツールにアクセスしたい場合、 戻り値の型をResultクラスでラップすることで簡単に行えます:

interface Assistant {

Result<String> chat(String userMessage);
}

Result<String> result = assistant.chat("予約123-456をキャンセルしてください");

String answer = result.content();
List<ToolExecution> toolExecutions = result.toolExecutions();

ストリーミングモードでは、onToolExecutedコールバックを指定することでアクセスできます:

interface Assistant {

TokenStream chat(String message);
}

TokenStream tokenStream = assistant.chat("予約をキャンセルしてください");

tokenStream
.onToolExecuted((ToolExecution toolExecution) -> System.out.println(toolExecution))
.onPartialResponse(...)
.onCompleteResponse(...)
.onError(...)
.start();

プログラムによるツールの指定

AIサービスを使用する場合、ツールをプログラムで指定することもできます。 このアプローチは、ツールをデータベースや設定ファイルなどの外部ソースから ロードできるため、多くの柔軟性を提供します。

ツール名、説明、パラメータ名、説明はすべてToolSpecificationを使用して設定できます:

ToolSpecification toolSpecification = ToolSpecification.builder()
.name("get_booking_details")
.description("予約の詳細を返します")
.parameters(JsonObjectSchema.builder()
.properties(Map.of(
"bookingNumber", JsonStringSchema.builder()
.description("B-12345形式の予約番号")
.build()
))
.build())
.build();

ToolSpecificationに対して、LLMによって生成されたツール実行リクエストを 処理するToolExecutor実装を提供する必要があります:

ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
Map<String, Object> arguments = fromJson(toolExecutionRequest.arguments());
String bookingNumber = arguments.get("bookingNumber").toString();
Booking booking = getBooking(bookingNumber);
return booking.toString();
};

1つまたは複数の(ToolSpecificationToolExecutor)ペアを取得したら、 AIサービスを作成する際にそれらを指定できます:

Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.tools(Map.of(toolSpecification, toolExecutor))
.build();

動的なツールの指定

AIサービスを使用する場合、ツールを各呼び出しごとに動的に指定することもできます。 AIサービスが呼び出されるたびに呼び出され、現在のLLMへのリクエストに含めるべきツールを 提供するToolProviderを設定できます。 ToolProviderUserMessageとチャットメモリIDを含むToolProviderRequestを受け取り、 ToolSpecificationからToolExecutorへのマップ形式のツールを含むToolProviderResultを返します。

ユーザーのメッセージに「booking」という単語が含まれる場合にのみget_booking_detailsツールを追加する例:

ToolProvider toolProvider = (toolProviderRequest) -> {
if (toolProviderRequest.userMessage().singleText().contains("booking")) {
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("get_booking_details")
.description("予約の詳細を返します")
.parameters(JsonObjectSchema.builder()
.addStringProperty("bookingNumber")
.build())
.build();
return ToolProviderResult.builder()
.add(toolSpecification, toolExecutor)
.build();
} else {
return null;
}
};

Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(model)
.toolProvider(toolProvider)
.build();

AIサービスは同じ呼び出しでプログラムによって指定されたツールと動的に指定されたツールの両方を使用することが可能です。

ツール幻覚戦略

LLMがツール呼び出しで幻覚を起こす、つまり存在しない名前のツールを使用するよう要求することがあるかもしれません。この場合、デフォルトではLangChain4jは問題を報告する例外をスローしますが、この状況で使用する戦略を提供することで異なる動作を設定することが可能です。

この戦略は、利用できないツールを呼び出すリクエストを含むToolExecutionRequestに対して生成すべきToolExecutionResultMessageを定義するFunction<ToolExecutionRequest, ToolExecutionResultMessage>の実装です。例えば、以前に要求されたツールが存在しないことを知り、異なるツール呼び出しを再試行するようLLMを促す応答を返す戦略でAIサービスを設定することが可能です:

AssistantHallucinatedTool assistant = AiServices.builder(AssistantHallucinatedTool.class)
.chatModel(chatModel)
.tools(new HelloWorld())
.hallucinatedToolNameStrategy(toolExecutionRequest -> ToolExecutionResultMessage.from(
toolExecutionRequest, "エラー:" + toolExecutionRequest.name() + "というツールはありません"))
.build();

モデルコンテキストプロトコル(MCP)

MCPサーバーからツールをインポートすることもできます。 これに関する詳細はこちらで確認できます。

関連チュートリアル