Skip to content

Commit efc6596

Browse files
committed
Add JSON schema support to Azure OpenAI models
- Add supportedCapabilities() method to both AzureOpenAiChatModel and AzureOpenAiStreamingChatModel - Return RESPONSE_FORMAT_JSON_SCHEMA capability when responseFormat type is JSON - Support request-level ResponseFormat override from ChatRequest - Enables structured JSON output with schema validation for Azure OpenAI models (e.g., gpt-4o-2024-08-06) Fixes #1953
1 parent f3342e0 commit efc6596

File tree

2 files changed

+47
-3
lines changed

2 files changed

+47
-3
lines changed

model-providers/openai/azure-openai/runtime/src/main/java/io/quarkiverse/langchain4j/azure/openai/AzureOpenAiChatModel.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static dev.langchain4j.internal.RetryUtils.withRetry;
44
import static dev.langchain4j.internal.Utils.getOrDefault;
55
import static dev.langchain4j.internal.ValidationUtils.ensureNotBlank;
6+
import static dev.langchain4j.model.chat.Capability.RESPONSE_FORMAT_JSON_SCHEMA;
67
import static dev.langchain4j.model.openai.internal.OpenAiUtils.aiMessageFrom;
78
import static dev.langchain4j.model.openai.internal.OpenAiUtils.finishReasonFrom;
89
import static dev.langchain4j.model.openai.internal.OpenAiUtils.toFunctions;
@@ -13,15 +14,18 @@
1314
import java.net.Proxy;
1415
import java.time.Duration;
1516
import java.util.Collections;
17+
import java.util.HashSet;
1618
import java.util.List;
1719
import java.util.Locale;
1820
import java.util.Map;
21+
import java.util.Set;
1922
import java.util.concurrent.ConcurrentHashMap;
2023

2124
import org.jboss.logging.Logger;
2225

2326
import dev.langchain4j.agent.tool.ToolSpecification;
2427
import dev.langchain4j.data.message.ChatMessage;
28+
import dev.langchain4j.model.chat.Capability;
2529
import dev.langchain4j.model.ModelProvider;
2630
import dev.langchain4j.model.TokenCountEstimator;
2731
import dev.langchain4j.model.chat.ChatModel;
@@ -74,6 +78,7 @@ public class AzureOpenAiChatModel implements ChatModel {
7478
private final TokenCountEstimator tokenizer;
7579
private final ResponseFormat responseFormat;
7680
private final List<ChatModelListener> listeners;
81+
private final Set<Capability> supportedCapabilities;
7782

7883
public AzureOpenAiChatModel(String endpoint,
7984
String apiVersion,
@@ -128,10 +133,22 @@ public AzureOpenAiChatModel(String endpoint,
128133
: ResponseFormat.builder()
129134
.type(ResponseFormatType.valueOf(responseFormat.toUpperCase(Locale.ROOT)))
130135
.build();
136+
137+
// Azure OpenAI supports JSON schema for models like gpt-4o-2024-08-06+
138+
this.supportedCapabilities = new HashSet<>();
139+
if (this.responseFormat != null && ResponseFormatType.JSON.equals(this.responseFormat.type())) {
140+
this.supportedCapabilities.add(RESPONSE_FORMAT_JSON_SCHEMA);
141+
}
131142
}
132143

133-
@Override
134144
public ChatResponse doChat(ChatRequest chatRequest) {
145+
ResponseFormat requestResponseFormat = this.responseFormat;
146+
147+
// Handle ChatRequest-level ResponseFormat if provided
148+
if (chatRequest.responseFormat() != null) {
149+
requestResponseFormat = chatRequest.responseFormat();
150+
}
151+
135152
List<ChatMessage> messages = chatRequest.messages();
136153
List<ToolSpecification> toolSpecifications = chatRequest.toolSpecifications();
137154

@@ -143,7 +160,7 @@ public ChatResponse doChat(ChatRequest chatRequest) {
143160
.maxTokens(maxTokens)
144161
.presencePenalty(presencePenalty)
145162
.frequencyPenalty(frequencyPenalty)
146-
.responseFormat(responseFormat);
163+
.responseFormat(requestResponseFormat);
147164

148165
if (toolSpecifications != null && !toolSpecifications.isEmpty()) {
149166
requestBuilder.functions(toFunctions(toolSpecifications));
@@ -239,6 +256,11 @@ private ChatResponse createModelListenerResponse(String responseId,
239256
.build();
240257
}
241258

259+
@Override
260+
public Set<Capability> supportedCapabilities() {
261+
return supportedCapabilities;
262+
}
263+
242264
public static Builder builder() {
243265
return new Builder();
244266
}

model-providers/openai/azure-openai/runtime/src/main/java/io/quarkiverse/langchain4j/azure/openai/AzureOpenAiStreamingChatModel.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static dev.langchain4j.internal.Utils.isNullOrBlank;
55
import static dev.langchain4j.internal.Utils.isNullOrEmpty;
66
import static dev.langchain4j.internal.ValidationUtils.ensureNotBlank;
7+
import static dev.langchain4j.model.chat.Capability.RESPONSE_FORMAT_JSON_SCHEMA;
78
import static dev.langchain4j.model.openai.internal.OpenAiUtils.toFunctions;
89
import static dev.langchain4j.model.openai.internal.OpenAiUtils.toOpenAiMessages;
910
import static io.quarkiverse.langchain4j.azure.openai.Consts.DEFAULT_USER_AGENT;
@@ -12,16 +13,19 @@
1213
import java.net.Proxy;
1314
import java.time.Duration;
1415
import java.util.Collections;
16+
import java.util.HashSet;
1517
import java.util.List;
1618
import java.util.Locale;
1719
import java.util.Map;
20+
import java.util.Set;
1821
import java.util.concurrent.ConcurrentHashMap;
1922
import java.util.concurrent.atomic.AtomicReference;
2023

2124
import org.jboss.logging.Logger;
2225

2326
import dev.langchain4j.agent.tool.ToolSpecification;
2427
import dev.langchain4j.data.message.ChatMessage;
28+
import dev.langchain4j.model.chat.Capability;
2529
import dev.langchain4j.model.ModelProvider;
2630
import dev.langchain4j.model.StreamingResponseHandler;
2731
import dev.langchain4j.model.TokenCountEstimator;
@@ -78,6 +82,7 @@ public class AzureOpenAiStreamingChatModel implements StreamingChatModel {
7882
private final TokenCountEstimator tokenizer;
7983
private final ResponseFormat responseFormat;
8084
private final List<ChatModelListener> listeners;
85+
private final Set<Capability> supportedCapabilities;
8186

8287
public AzureOpenAiStreamingChatModel(String endpoint,
8388
String apiVersion,
@@ -124,10 +129,22 @@ public AzureOpenAiStreamingChatModel(String endpoint,
124129
: ResponseFormat.builder()
125130
.type(ResponseFormatType.valueOf(responseFormat.toUpperCase(Locale.ROOT)))
126131
.build();
132+
133+
// Azure OpenAI supports JSON schema for models like gpt-4o-2024-08-06+
134+
this.supportedCapabilities = new HashSet<>();
135+
if (this.responseFormat != null && ResponseFormatType.JSON.equals(this.responseFormat.type())) {
136+
this.supportedCapabilities.add(RESPONSE_FORMAT_JSON_SCHEMA);
137+
}
127138
}
128139

129140
@Override
130141
public void doChat(ChatRequest chatRequest, StreamingChatResponseHandler handler) {
142+
ResponseFormat requestResponseFormat = this.responseFormat;
143+
144+
// Handle ChatRequest-level ResponseFormat if provided
145+
if (chatRequest.responseFormat() != null) {
146+
requestResponseFormat = chatRequest.responseFormat();
147+
}
131148

132149
List<ChatMessage> messages = chatRequest.messages();
133150
List<ToolSpecification> toolSpecifications = chatRequest.toolSpecifications();
@@ -140,7 +157,7 @@ public void doChat(ChatRequest chatRequest, StreamingChatResponseHandler handler
140157
.maxTokens(maxTokens)
141158
.presencePenalty(presencePenalty)
142159
.frequencyPenalty(frequencyPenalty)
143-
.responseFormat(responseFormat);
160+
.responseFormat(requestResponseFormat);
144161

145162
Integer inputTokenCount = tokenizer == null ? null : tokenizer.estimateTokenCountInMessages(messages);
146163

@@ -272,6 +289,11 @@ private ChatResponse createModelListenerResponse(String responseId,
272289
.build();
273290
}
274291

292+
@Override
293+
public Set<Capability> supportedCapabilities() {
294+
return supportedCapabilities;
295+
}
296+
275297
public static Builder builder() {
276298
return new Builder();
277299
}

0 commit comments

Comments
 (0)