このエントリは2024/10/30現在の情報に基づいています。将来の機能追加や変更に伴い、記載内容からの乖離が発生する可能性があります。
このエントリは何のために書いたものか
Python、C#版はすでに以下のお歴々(御大とも言う)が記載されていたので、どうせならJavaのやつも書いておこうか、と思った次第(たまには仕事に関連する内容もやっておこう、というちょっとなんともあれな動機ではない、知らんけど)。
Azure OpenAIでStructured Outputsを使う!
https://zenn.dev/microsoft/articles/azure-openai-structured-outputs
Azure OpenAIでStructured Outputsを使う! C# 版
https://zenn.dev/microsoft/articles/azure-openai-json-schema
依存関係
Azure OpenAI client library for Javaは2024/10/30現在1.0.0-beta.12。
Azure OpenAI client library for Java – version 1.0.0-beta.12
https://learn.microsoft.com/java/api/overview/azure/ai-openai-readme?view=azure-java-preview
依存関係として以下を指定する。
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-ai-openai</artifactId>
</dependency>
必要に応じて、azure-identityなどの依存関係も指定する。
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<scope>compile</scope>
</dependency>
Structured Outputsの利用を指定するには
C#の場合と類似していて、例えばChat Completions APIを呼ぶ時のオプションにStructuredOutputsResponseFormatを指定する。処理自体は以下のサンプルコードの通りではあるが、JSON Schemaについては、このサンプルコード内でゴリゴリ作成していて、どうにもわかりづらい。
Structured Outputs
https://learn.microsoft.com/en-us/java/api/overview/azure/ai-openai-readme?view=azure-java-preview#structured-outputs
ResponseFormatは、ChatCompletionsOptions#setResponseFormatメソッドを使い、ChatCompletionsJsonSchemaResponseFormatオブジェクトを設定して構成、指定している。JSON Schemaの指定は、ChatCompletionsJsonSchemaResponseFormatJsonSchemaオブジェクトに対して実施している。
もちろんJSON Schemaファイルや文字列を使ってSchemaを指定してもいいが、class(record classを含む)をベースに作成するのであれば、Jacksonを使っても問題ない。
例えばPersonというrecord classがあるとして
public record Person (
String name,
int age
) {}
これを使ってJSON Schemaを作成するとしたら、以下のような感じ。
ObjectMapper objectMapper = new ObjectMapper();
SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
// Generate JSON schema
try {
objectMapper.acceptJsonFormatVisitor(objectMapper.constructType(Person.class), visitor);
} catch (JsonMappingException e) {
throw new RuntimeException(e);
}
JsonSchema jsonSchema = visitor.finalSchema();
ここで作成したjsonSchemaを使って、以下のようにResponseFormatを設定する。
.setResponseFormat(
new ChatCompletionsJsonSchemaResponseFormat(
new ChatCompletionsJsonSchemaResponseFormatJsonSchema("Person")
.setStrict(false)
.setDescription("Fetch age and name from response.")
.setSchema(BinaryData.fromObject(jsonSchema))
)
)
もしファイルで指定するなら、6行目でBinaryData#fromFile()で指定し、文字列だとしたら、同様にBinaryData#fromString()で指定すればよい。
注意
上記コード内の4行目、setStrictメソッドの引数は、以下の方針でtrue/falseを決定する。
| 条件 | 設定 | 備考 |
|---|---|---|
(下例のように)使用するJSON SchemaでadditionalPropertiesとしてfalseを設定している場合 | setStrict(true) | |
使用するJSON SchemaでadditionalPropertiesとしてfalseを設定していない、設定できない場合 | setStrict(false) | setStrict(true) ではエラーが発生する |
{
...,
"additionalProperties" : false
}
あとは、APIバージョンやモデルのバージョンの制約に従う必要がある。これは以下のドキュメントに記載の通りで、執筆日時点では、GPT-4oのモデル2024-08-06、APIは2024-08-01-preview(もしくはそれ以後)を利用する必要がある。
構造化出力 / Structured outputs
https://learn.microsoft.com/azure/ai-services/openai/how-to/structured-outputs
作ってみる
例によって、Micronautで作ってみる。例えば、以下のようなシステムプロンプトを用意して、ユーザーメッセージを指定しておく。今回はユーザーメッセージは英語にしているが、別に日本語でもフランス語でもかまわない。
String userMessage = """
My name is John and I was born in 1970.
""";
String systemMessage = """
ユーザーの発言内容から名前と年齢を抽出して JSON 形式に整形してください。
""";
あとはOpenAIClientを作成して、ChatCompletionsオブジェクトを生成し、結果をPOJOに格納して終了。
@Get("/query1")
@Produces(MediaType.APPLICATION_JSON)
@ExecuteOn(TaskExecutors.IO)
public Person querySample() throws IOException {
Logger logger = LoggerFactory.getLogger(AOAIStructured.class);
// Initialize ObjectMapper and SchemaFactoryWrapper
ObjectMapper objectMapper = new ObjectMapper();
SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
// Generate JSON schema
try {
objectMapper.acceptJsonFormatVisitor(objectMapper.constructType(Person.class), visitor);
} catch (JsonMappingException e) {
throw new RuntimeException(e);
}
JsonSchema jsonSchema = visitor.finalSchema();
OpenAIClient client = aoaiClientFactory.getAOAIClient();
ChatCompletions chatCompletions
= client.getChatCompletions(deploymentName,
new ChatCompletionsOptions(
Arrays.asList(
new ChatRequestSystemMessage(systemMessage),
new ChatRequestUserMessage(userMessage)
)
)
.setResponseFormat(
new ChatCompletionsJsonSchemaResponseFormat(
new ChatCompletionsJsonSchemaResponseFormatJsonSchema("Person")
.setStrict(false)
.setDescription("Fetch age and name from response.")
.setSchema(BinaryData.fromObject(jsonSchema))
)
)
);
return objectMapper.readValue(chatCompletions.getChoices().getFirst().getMessage().getContent(), Person.class);
}
ここで紹介したSnippetを含む完全なコードは、以下のリポジトリにある。