ツール(関数呼び出し)
テキスト生成に加えて、一部のLLMは操作をトリガーすることもできます。
ツールをサポートするすべてのLLMはこちらで確認できます(「Tools」列を参照)。
すべてのLLMがツールを同等にサポートしているわけではありません。 ツールを理解、選択、正しく使用する能力は、特定のモデルとその能力に大きく依存します。 一部のモデルはツールをまったくサポートしない場合があり、他のモデルは慎重なプロンプトエンジニアリングや 追加のシステム指示が必要な場合があります。
「ツール」または「関数呼び出し」と呼ばれる概念があります。 これは、LLMが必要に応じて1つ以上の利用可能なツール(通常は開発者によって定義されます)を呼び出すことを可能にします。 ツールは、ウェブ検索、外部APIの呼び出し、特定のコードスニペットの実行など、何でも構いません。 LLMは実際にはツールを自分で呼び出すことはできません。代わりに、応答の中で 特定のツールを呼び出す意図を表現します(プレーンテキストで返信する代わりに)。 開発者である私たちは、その後、提供された引数でそのツールを実行し、 ツールの実行結果をLLMに返す必要があります。
例えば、LLM自体は数学が得意ではないことが知られています。 ユースケースで数学計算が時折必要になる場合、LLMに「数学ツール」を提供するとよいでしょう。 LLMに送信するリクエストで1つ以上のツールを宣言することで、 LLMは適切と判断した場合にそれらの1つを呼び出すことを決定できます。 数学の問題と一連の数学ツールが与えられた場合、LLMは正しく回答するために、 提供された数学ツールの1つを先に呼び出すべきだと判断するかもしれません。
実際にどのように機能するかを見てみましょう(ツールありの場合となしの場合)。
ツールなしのメッセージ交換の例:
Request:
- messages:
- UserMessage:
- text: What is the square root of 475695037565?
Response:
- AiMessage:
- text: The square root of 475695037565 is approximately 689710.
近いですが、正しくありません。
以下のツールを使用したメッセージ交換の例:
@Tool("Sums 2 given numbers")
double sum(double a, double b) {
return a + b;
}
@Tool("Returns a square root of a given number")
double squareRoot(double x) {
return Math.sqrt(x);
}
申し訳ありませんが、翻訳するための中国語のMarkdown断片が提供されていません。翻訳対象のテキストを入力してください。
Request 1:
- messages:
- UserMessage:
- text: What is the square root of 475695037565?
- tools:
- sum(double a, double b): Sums 2 given numbers
- squareRoot(double x): Returns a square root of a given number
Response 1:
- AiMessage:
- toolExecutionRequests:
- squareRoot(475695037565)
... here we are executing the squareRoot method with the "475695037565" argument and getting "689706.486532" as a result ...
Request 2:
- messages:
- UserMessage:
- text: What is the square root of 475695037565?
- AiMessage:
- toolExecutionRequests:
- squareRoot(475695037565)
- ToolExecutionResultMessage:
- text: 689706.486532
Response 2:
- AiMessage:
- text: The square root of 475695037565 is 689706.486532.
ご覧のとおり、LLMがツールにアクセスできる場合、適切なタイミングでそのうちの1つを呼び出すことを決定できます。
これは非常に強力な機能です。
この簡単な例では、LLMに基本的な数学ツールを提供しましたが、
例えば googleSearch や sendEmail ツールを提供し、
「私の友人がAI分野の最新ニュースを知りたがっています。簡単な要約をfriend@email.comに送信してください」のようなクエリを与えた場合を想像してみてくだ さい。
LLMは googleSearch ツールを使用して最新ニュースを検索し、
その後 sendEmail ツールを使用して要約を送信することができます。
LLMが正しいパラメータで正しいツールを呼び出す可能性を高めるために、 明確で曖昧さのないものを提供する必要があります:
- ツール名
- ツールが何をするか、いつ使用すべきかの説明
- 各ツールパラメータの説明
経験則:人間がツールの目的と使用方法を理解できるなら、 LLMもおそらく理解できるでしょう。
LLMは、いつツールを呼び出すか、どのように呼び出すかを検出するために特別に微調整されています。 一部のモデルは一度に複数のツールを呼び出すこともできます。例えば OpenAI などです。
すべてのモデルがツールをサポートしているわけではないことに注意してください。
ツールをサポートするモデルを確認するには、このページの「Tools」列を参照してください。
ツール/関数呼び出しは JSON モード とは異なることに注意してください。
2つの抽象化レベル
LangChain4j はツールを使 用するための2つの抽象化レベルを提供します:
- 低レベル:
ChatModelとToolSpecificationAPI を使用 - 高レベル:AI Services と
@Toolアノテーションが付いた Java メソッドを使用
低レベルツール API
低レベルでは、ChatModel の chat(ChatRequest) メソッドを使用できます。
StreamingChatModel にも同様のメソッドがあります。
ChatRequest を作成する際に、1つ以上の ToolSpecification を指定できます。
ToolSpecification はツールに関するすべての情報を含むオブジェクトです:
- ツールの
name - ツールの
description - ツールの
parametersとその説明 - ツールの
metadata。 デフォルトでは LLM プロバイダーに送信されず、ChatModelを作成する際にどのメタデータキーを送信するかを明示的に指定する必要があります。 現在、ツールメタデータはlangchain4j-anthropicモジュールのみでサポートされています。 ツールが McpToolProvider によって提供される場合、metadataには MCP 固有のエントリを含めることができます。
ツールに関する詳細情報を提供することをお勧めします: 明確な名前、包括的な説明、各パラメータの説明など。
ツール 仕様の作成
ToolSpecification を作成するには2つの方法があります:
- 手動で作成
ToolSpecification toolSpecification = ToolSpecification.builder()
.name("getWeather")
.description("Returns the weather forecast for a given city")
.parameters(JsonObjectSchema.builder()
.addStringProperty("city", "The city for which the weather forecast should be returned")
.addEnumProperty("temperatureUnit", List.of("CELSIUS", "FAHRENHEIT"))
.required("city") // the required properties should be specified explicitly
.build())
.build();
JsonObjectSchema の詳細については、こちら を参照してください。
- ヘルパーメソッドを使用する:
ToolSpecifications.toolSpecificationsFrom(Class)ToolSpecifications.toolSpecificationsFrom(Object)ToolSpecifications.toolSpecificationFrom(Method)
class WeatherTools {
@Tool("Returns the weather forecast for a given city")
String getWeather(
@P("The city for which the weather forecast should be returned") String city,
TemperatureUnit temperatureUnit
) {
...
}
}
List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(WeatherTools.class);
JSON シリアライゼーション
ToolSpecification は、toJson() メソッドと fromJson() メソッドを使用して JSON にシリアライズし、元に戻すことができます。
これは、例えばツール仕様をデータベースに保存したり、ネットワーク経由で転送したりする場合に便利です。
String json = toolSpecification.toJson();
ToolSpecification deserialized = ToolSpecification.fromJson(json);
デフォルトでは、専用のJackson ObjectMapperがJSON変換に使用されます。
ToolSpecificationJsonCodecFactoryを実装し、
META-INF/services/dev.langchain4j.spi.agent.tool.ToolSpecificationJsonCodecFactoryに登録することで、
SPIを介して独自のToolSpecificationJsonCodec実装を提供できます。
ChatModel の使用
List<ToolSpecification>を取得したら、モデルを呼び出すことができます:
ChatRequest request = ChatRequest.builder()
.messages(UserMessage.from("What will the weather be like in London tomorrow?"))
.toolSpecifications(toolSpecifications)
.build();
ChatResponse response = model.chat(request);
AiMessage aiMessage = response.aiMessage();
LLM がツール呼び出しを決定した場合、返される AiMessage には
toolExecutionRequests フィールドにデータが含まれます。
このとき、AiMessage.hasToolExecutionRequests() は true を返します。
LLM によっては、1 つまたは複数の ToolExecutionRequest オブジェクトが含まれることがあります
(一部の LLM は複数のツールの並列呼び出しをサポートしています)。
各 ToolExecutionRequest には以下が含まれる必要があります:
- ツール呼び出しの
id。一部の LLM プロバイダー(例:Google、Ollama)ではこの ID が省略される場合があることに注意してください。 - 呼び出すツールの
name(例:getWeather) arguments(例:{ "city": "London", "temperatureUnit": "CELSIUS" })
ToolExecutionRequest の情報を使用して、ツールを手動で実行する必要があります。
ツールの実行結果を LLM に送り返すには、
ToolExecutionResultMessage(各 ToolExecutionRequest につき 1 つ)を作成し、
それを以前のすべてのメッセージと一緒に送信する必要があります:
String result = "It is expected to rain in London tomorrow.";
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.from(toolExecutionRequest, result);
ChatRequest request2 = ChatRequest.builder()
.messages(List.of(userMessage, aiMessage, toolExecutionResultMessage))
.toolSpecifications(toolSpecifications)
.build();
ChatResponse response2 = model.chat(request2);
マルチモーダルツール結果
ToolExecutionResultMessage は、画像などの非テキストコンテンツも保持できます。
text() を使用するだけでなく、contents() を備えたビルダーも使用できます:
ToolExecutionResultMessage toolExecutionResultMessage = ToolExecutionResultMessage.builder()
.id(toolExecutionRequest.id())
.toolName(toolExecutionRequest.name())
.contents(
TextContent.from("Here is the photo"),
ImageContent.from(Image.builder()
.base64Data(base64Data)
.mimeType("image/png")
.build())
)
.build();
すべてのLLMプロバイダーがマルチモーダルツール結果をサポートしているわけではありません。 プロバイダーのサポート状況の詳細については、画像とマルチモーダルコンテンツの返却を参照してください。
StreamingChatModel の使用
List<ToolSpecification> を取得したら、モデルを呼び出すことができます:
ChatRequest request = ChatRequest.builder()
.messages(UserMessage.from("What will the weather be like in London tomorrow?"))
.toolSpecifications(toolSpecifications)
.build();
model.chat(request, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
System.out.println("onPartialResponse: " + partialResponse);
}
@Override
public void onPartialToolCall(PartialToolCall partialToolCall) {
System.out.println("onPartialToolCall: " + partialToolCall);
}
@Override
public void onCompleteToolCall(CompleteToolCall completeToolCall) {
System.out.println("onCompleteToolCall: " + completeToolCall);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
System.out.println("onCompleteResponse: " + completeResponse);
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
}
});
LLM がツール呼び出しを決定した場合、onPartialToolCall(PartialToolCall) コールバックは、
通常、最終的に onCompleteToolCall(CompleteToolCall) が呼び出される前に複数回呼び出されます。
後者は、そのツール呼び出しのストリーミング出力が完了したことを示します。
すべての LLM プロバイダーが部分的なツール呼び出しをストリーミングするわけではないことに注意してください。
一部のプロバイダー(例:Bedrock、Google、Mistral、Ollama)は完全なツール呼び出しのみを返します。
これらの場合、onPartialToolCall コールバックは呼び出されず、onCompleteToolCall のみが呼び出されます。
単一のツール呼び出しのストリーミング出力は次のようになります:
onPartialToolCall(index = 0, id = "call_abc", name = "get_weather", partialArguments = "{\"")
onPartialToolCall(index = 0, id = "call_abc", name = "get_weather", partialArguments = "city")
onPartialToolCall(index = 0, id = "call_abc", name = "get_weather", partialArguments = ""\":\"")
onPartialToolCall(index = 0, id = "call_abc", name = "get_weather", partialArguments = "London")
onPartialToolCall(index = 0, id = "call_abc", name = "get_weather", partialArguments = "\"}")
onCompleteToolCall(index = 0, id = "call_abc", name = "get_weather", arguments = "{\"city\":\"London\"}")
LLMが複数のツール呼び出しを開始した場合、indexは増加し、異なるPartialToolCall同士や、最終的なCompleteToolCallとの対応付けが可能になります。
完全なレスポンスのストリーミングが終了し、onCompleteResponse(ChatResponse)が呼び出されると、
ChatResponse内のAiMessageには、ストリーミング中に発生したすべてのツール呼び出しが含まれます。
高レベルツールAPI
高レベルの抽象化では、任意のJava メソッドに@Toolアノテーションを付与し、
AIサービスを作成する際に指定できます。
AIサービスは、そのようなメソッドを自動的にToolSpecificationに変換し、
LLMとの各やり取りのリクエストに含めます。
LLMがツールを呼び出すことを決定した場合、AIサービスは自動的に対応するメソッドを実行し、
メソッドの戻り値(存在する場合)はLLMに送り返されます。
実装の詳細はDefaultToolExecutorにあります。
ツールの例をいくつか示します。
@Tool("Searches Google for relevant URLs, given the query")
public List<String> searchGoogle(@P("search query") String query) {
return googleSearchService.search(query);
}
@Tool("Returns the content of a web page, given the URL")
public String getWebPageContent(@P("URL of the page") String url) {
Document jsoupDocument = Jsoup.connect(url).get();
return jsoupDocument.body().text();
}
ツールメソッドの制限
@Tool で注釈されたメソッドは:
- 静的メソッドまたは非静的メソッドのいずれかを使用できます
- 任意の可視性(public、private など)を持つことができます