Skip to content

Commit 001f4e9

Browse files
authored
feat: add Azure OpenAi LLM module (#134)
1 parent 7215fbd commit 001f4e9

5 files changed

Lines changed: 272 additions & 0 deletions

File tree

dat-cli/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@
125125
<artifactId>dat-llm-xinference</artifactId>
126126
<version>${project.version}</version>
127127
</dependency>
128+
<dependency>
129+
<groupId>cn.datask</groupId>
130+
<artifactId>dat-llm-azure-openai</artifactId>
131+
<version>${project.version}</version>
132+
</dependency>
128133

129134
<!-- DAT Database Adapter -->
130135
<dependency>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>cn.datask</groupId>
7+
<artifactId>dat-llms</artifactId>
8+
<version>${revision}</version>
9+
</parent>
10+
11+
<artifactId>dat-llm-azure-openai</artifactId>
12+
<name>DAT : LLMs : Azure OpenAi</name>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>cn.datask</groupId>
17+
<artifactId>dat-core</artifactId>
18+
<version>${project.version}</version>
19+
</dependency>
20+
<dependency>
21+
<groupId>dev.langchain4j</groupId>
22+
<artifactId>langchain4j-azure-open-ai</artifactId>
23+
<version>${langchain4j.vserion}</version>
24+
</dependency>
25+
</dependencies>
26+
27+
</project>
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
package ai.dat.llm.azure;
2+
3+
import ai.dat.core.configuration.ConfigOption;
4+
import ai.dat.core.configuration.ConfigOptions;
5+
import ai.dat.core.configuration.ReadableConfig;
6+
import ai.dat.core.factories.ChatModelFactory;
7+
import ai.dat.core.utils.FactoryUtil;
8+
import com.google.common.base.Preconditions;
9+
import dev.langchain4j.model.azure.AzureOpenAiChatModel;
10+
import dev.langchain4j.model.azure.AzureOpenAiStreamingChatModel;
11+
import dev.langchain4j.model.chat.ChatModel;
12+
import dev.langchain4j.model.chat.StreamingChatModel;
13+
import dev.langchain4j.model.chat.request.ResponseFormat;
14+
15+
import java.time.Duration;
16+
import java.util.LinkedHashSet;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.Set;
20+
21+
/**
22+
*
23+
*/
24+
public class AzureOpenAiChatModelFactory implements ChatModelFactory {
25+
26+
public static final String IDENTIFIER = "azure-openai";
27+
28+
public static final ConfigOption<String> ENDPOINT =
29+
ConfigOptions.key("endpoint")
30+
.stringType()
31+
.noDefaultValue()
32+
.withDescription("""
33+
Supported Azure OpenAI endpoints
34+
(protocol and hostname, for example: https://aoairesource.openai.azure.com. \
35+
Replace "aoairesource" with your Azure OpenAI resource name).
36+
https://{your-resource-name}.openai.azure.com
37+
""");
38+
39+
public static final ConfigOption<String> DEPLOYMENT_ID =
40+
ConfigOptions.key("deployment-id")
41+
.stringType()
42+
.noDefaultValue()
43+
.withDescription("Deployment ID of the model which was deployed.");
44+
45+
public static final ConfigOption<String> API_VERSION =
46+
ConfigOptions.key("api-version")
47+
.stringType()
48+
.noDefaultValue()
49+
.withDescription("The API version of Azure OpenAI.");
50+
51+
public static final ConfigOption<String> API_KEY =
52+
ConfigOptions.key("api-key")
53+
.stringType()
54+
.noDefaultValue()
55+
.withDescription("Provide Azure OpenAI API key here.");
56+
57+
public static final ConfigOption<Double> TEMPERATURE =
58+
ConfigOptions.key("temperature")
59+
.doubleType()
60+
.noDefaultValue()
61+
.withDescription("Controls the randomness of the generated responses. " +
62+
"Higher values (e.g., 1.0) result in more diverse output, " +
63+
"while lower values (e.g., 0.2) produce more deterministic responses.");
64+
65+
public static final ConfigOption<Double> TOP_P =
66+
ConfigOptions.key("top-p")
67+
.doubleType()
68+
.noDefaultValue()
69+
.withDescription("Controls the diversity of the generated responses by setting a threshold " +
70+
"for the cumulative probability of top tokens.");
71+
72+
public static final ConfigOption<Duration> TIMEOUT =
73+
ConfigOptions.key("timeout")
74+
.durationType()
75+
.noDefaultValue()
76+
.withDescription("The maximum time allowed for the API call to complete.");
77+
78+
public static final ConfigOption<Integer> MAX_RETRIES =
79+
ConfigOptions.key("max-retries")
80+
.intType()
81+
.defaultValue(2)
82+
.withDescription("The maximum number of retries in case of API call failure.");
83+
84+
public static final ConfigOption<Integer> MAX_TOKENS =
85+
ConfigOptions.key("max-tokens")
86+
.intType()
87+
.defaultValue(4096)
88+
.withDescription("Azure OpenAI LLM model maximum tokens");
89+
90+
public static final ConfigOption<Long> SEED =
91+
ConfigOptions.key("seed")
92+
.longType()
93+
.noDefaultValue()
94+
.withDescription("Azure OpenAI LLM model seed");
95+
96+
public static final ConfigOption<String> USER =
97+
ConfigOptions.key("user")
98+
.stringType()
99+
.noDefaultValue()
100+
.withDescription("Azure OpenAI user");
101+
102+
public static final ConfigOption<List<String>> STOP =
103+
ConfigOptions.key("stop")
104+
.stringType()
105+
.asList()
106+
.noDefaultValue()
107+
.withDescription("A list of strings that, if generated, will mark the end of the response.");
108+
109+
public static final ConfigOption<Boolean> LOG_REQUESTS_AND_RESPONSES =
110+
ConfigOptions.key("log-requests-and-responses")
111+
.booleanType()
112+
.defaultValue(false)
113+
.withDescription("Whether to print the LLM requests and responses log");
114+
115+
public static final ConfigOption<String> RESPONSE_FORMAT =
116+
ConfigOptions.key("response-format")
117+
.stringType()
118+
.noDefaultValue()
119+
.withDescription("OpenAI LLM response format. Supported: text, json");
120+
121+
public static final ConfigOption<Boolean> STRICT_JSON_SCHEMA =
122+
ConfigOptions.key("strict-json-schema")
123+
.booleanType()
124+
.defaultValue(false)
125+
.withDescription("Whether to output strict json schema");
126+
127+
public static final ConfigOption<Map<String, String>> CUSTOM_HEADERS =
128+
ConfigOptions.key("custom-headers")
129+
.mapType()
130+
.noDefaultValue()
131+
.withDescription("Custom HTTP headers. " +
132+
"For example: {\"content-type\": \"application/json\", " +
133+
"\"accept\": \"application/json, text/event-stream\"}");
134+
135+
@Override
136+
public String factoryIdentifier() {
137+
return IDENTIFIER;
138+
}
139+
140+
@Override
141+
public Set<ConfigOption<?>> requiredOptions() {
142+
return new LinkedHashSet<>(List.of(ENDPOINT, DEPLOYMENT_ID, API_VERSION, API_KEY));
143+
}
144+
145+
@Override
146+
public Set<ConfigOption<?>> optionalOptions() {
147+
return new LinkedHashSet<>(List.of(TEMPERATURE, TOP_P, TIMEOUT,
148+
MAX_RETRIES, MAX_TOKENS, LOG_REQUESTS_AND_RESPONSES,
149+
RESPONSE_FORMAT, STRICT_JSON_SCHEMA, STOP, SEED, USER, CUSTOM_HEADERS));
150+
}
151+
152+
@Override
153+
public ChatModel create(ReadableConfig config) {
154+
FactoryUtil.validateFactoryOptions(this, config);
155+
validateConfigOptions(config);
156+
157+
String endpoint = config.get(ENDPOINT);
158+
String deploymentId = config.get(DEPLOYMENT_ID);
159+
String apiVersion = config.get(API_VERSION);
160+
String apiKey = config.get(API_KEY);
161+
162+
AzureOpenAiChatModel.Builder builder = AzureOpenAiChatModel.builder()
163+
.endpoint(endpoint)
164+
.deploymentName(deploymentId)
165+
.serviceVersion(apiVersion)
166+
.apiKey(apiKey);
167+
168+
config.getOptional(TEMPERATURE).ifPresent(builder::temperature);
169+
config.getOptional(TOP_P).ifPresent(builder::topP);
170+
config.getOptional(TIMEOUT).ifPresent(builder::timeout);
171+
config.getOptional(MAX_RETRIES).ifPresent(builder::maxRetries);
172+
config.getOptional(MAX_TOKENS).ifPresent(builder::maxTokens);
173+
config.getOptional(RESPONSE_FORMAT).ifPresent(format -> {
174+
ResponseFormat responseFormat = format.equalsIgnoreCase("JSON")
175+
? ResponseFormat.JSON : ResponseFormat.TEXT;
176+
builder.responseFormat(responseFormat);
177+
});
178+
config.getOptional(LOG_REQUESTS_AND_RESPONSES).ifPresent(builder::logRequestsAndResponses);
179+
config.getOptional(STRICT_JSON_SCHEMA).ifPresent(builder::strictJsonSchema);
180+
config.getOptional(STOP).ifPresent(builder::stop);
181+
config.getOptional(SEED).ifPresent(builder::seed);
182+
config.getOptional(USER).ifPresent(builder::user);
183+
config.getOptional(CUSTOM_HEADERS).ifPresent(builder::customHeaders);
184+
185+
return builder.build();
186+
}
187+
188+
@Override
189+
public StreamingChatModel createStream(ReadableConfig config) {
190+
FactoryUtil.validateFactoryOptions(this, config);
191+
validateConfigOptions(config);
192+
193+
String endpoint = config.get(ENDPOINT);
194+
String deploymentId = config.get(DEPLOYMENT_ID);
195+
String apiVersion = config.get(API_VERSION);
196+
String apiKey = config.get(API_KEY);
197+
198+
AzureOpenAiStreamingChatModel.Builder builder = AzureOpenAiStreamingChatModel.builder()
199+
.endpoint(endpoint)
200+
.deploymentName(deploymentId)
201+
.serviceVersion(apiVersion)
202+
.apiKey(apiKey);
203+
204+
config.getOptional(TEMPERATURE).ifPresent(builder::temperature);
205+
config.getOptional(TOP_P).ifPresent(builder::topP);
206+
config.getOptional(TIMEOUT).ifPresent(builder::timeout);
207+
config.getOptional(MAX_RETRIES).ifPresent(builder::maxRetries);
208+
config.getOptional(MAX_TOKENS).ifPresent(builder::maxTokens);
209+
config.getOptional(RESPONSE_FORMAT).ifPresent(format -> {
210+
ResponseFormat responseFormat = format.equalsIgnoreCase("JSON")
211+
? ResponseFormat.JSON : ResponseFormat.TEXT;
212+
builder.responseFormat(responseFormat);
213+
});
214+
config.getOptional(LOG_REQUESTS_AND_RESPONSES).ifPresent(builder::logRequestsAndResponses);
215+
config.getOptional(STRICT_JSON_SCHEMA).ifPresent(builder::strictJsonSchema);
216+
config.getOptional(STOP).ifPresent(builder::stop);
217+
config.getOptional(SEED).ifPresent(builder::seed);
218+
config.getOptional(USER).ifPresent(builder::user);
219+
config.getOptional(CUSTOM_HEADERS).ifPresent(builder::customHeaders);
220+
221+
return builder.build();
222+
}
223+
224+
private void validateConfigOptions(ReadableConfig config) {
225+
config.getOptional(TEMPERATURE)
226+
.ifPresent(t -> Preconditions.checkArgument(t >= 0.0 && t <= 2.0,
227+
"'" + TEMPERATURE.key() + "' value must be between 0.0 and 2.0"));
228+
config.getOptional(TOP_P)
229+
.ifPresent(v -> Preconditions.checkArgument(v > 0.0 && v <= 1.0,
230+
"'" + TOP_P.key() + "' value must > 0.0 and <= 1.0"));
231+
Integer maxRetries = config.get(MAX_RETRIES);
232+
Preconditions.checkArgument(maxRetries >= 0,
233+
"'" + MAX_RETRIES.key() + "' value must be greater than or equal to 0");
234+
Integer maxTokens = config.get(MAX_TOKENS);
235+
Preconditions.checkArgument(maxTokens > 0,
236+
"'" + MAX_TOKENS.key() + "' value must be greater than 0");
237+
}
238+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ai.dat.llm.azure.AzureOpenAiChatModelFactory

dat-llms/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<module>dat-llm-ollama</module>
1919
<module>dat-llm-gemini</module>
2020
<module>dat-llm-xinference</module>
21+
<module>dat-llm-azure-openai</module>
2122
</modules>
2223

2324
</project>

0 commit comments

Comments
 (0)