Skip to content

Commit 3bf1fe7

Browse files
committed
code formatting
1 parent 03648e9 commit 3bf1fe7

15 files changed

+800
-6
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.8.17] - 2024-09-12
9+
10+
### Fixed
11+
12+
- Ollama host overriding
13+
814
## [0.8.16] - 2024-09-05
915

1016
### Fixed
@@ -216,7 +222,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
216222

217223
- Upgrade OpenAI chat models: **gpt-4-0125-preview**, **gpt-3.5-turbo-0125**
218224

219-
[0.8.16]: https://github.com/carlrobertoh/llm-client/compare/d714854331915387da583c9a5b24877cc06286e...HEAD
225+
[0.8.17]: https://github.com/carlrobertoh/llm-client/compare/6b7e26477b8e3454e78c8c639e97c8803fa5a301...HEAD
226+
[0.8.16]: https://github.com/carlrobertoh/llm-client/compare/d714854331915387da583c9a5b24877cc06286e...6b7e26477b8e3454e78c8c639e97c8803fa5a301
220227
[0.8.15]: https://github.com/carlrobertoh/llm-client/compare/fa0539e06d6cd8d21a4d0fa3336c747c2cb68fcc...d714854331915387da583c9a5b24877cc06286e
221228
[0.8.14]: https://github.com/carlrobertoh/llm-client/compare/6461c8458325e7b2a33670fc09493b3357eb094c...fa0539e06d6cd8d21a4d0fa3336c747c2cb68fcc
222229
[0.8.13]: https://github.com/carlrobertoh/llm-client/compare/a55fe7dcefbe6b911d5b99950d402dd06a66ec1e...6461c8458325e7b2a33670fc09493b3357eb094c

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ To use the package, you need to use following Maven dependency:
1212
<dependency>
1313
<groupId>ee.carlrobert</groupId>
1414
<artifactId>llm-client</artifactId>
15-
<version>0.8.16</version>
15+
<version>0.8.17</version>
1616
</dependency>
1717
```
1818
Gradle dependency:
1919
```kts
2020
dependencies {
21-
implementation("ee.carlrobert:llm-client:0.8.16")
21+
implementation("ee.carlrobert:llm-client:0.8.17")
2222
}
2323
```
2424

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ plugins {
77
}
88

99
group = "ee.carlrobert"
10-
version = "0.8.16"
10+
version = "0.8.17"
1111

1212
repositories {
1313
mavenCentral()

src/main/java/ee/carlrobert/llm/client/ollama/OllamaClient.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,8 @@ private void processStreamRequest(
244244
private HttpRequest buildPostHttpRequest(
245245
Object request,
246246
String path) throws JsonProcessingException {
247-
var requestBuilder = HttpRequest.newBuilder(URI.create(BASE_URL + path))
247+
var baseHost = port == null ? BASE_URL : format("http://localhost:%d", port);
248+
var requestBuilder = HttpRequest.newBuilder(URI.create((host == null ? baseHost : host) + path))
248249
.POST(HttpRequest.BodyPublishers.ofString(new ObjectMapper().writeValueAsString(request)))
249250
.header("Content-Type", "application/x-ndjson");
250251

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package ee.carlrobert.llm.client.watsonx;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
6+
@JsonIgnoreProperties(ignoreUnknown = true)
7+
public class IBMAuthBearerToken {
8+
9+
@JsonProperty("access_token")
10+
String accessToken;
11+
@JsonProperty("expiration")
12+
int expiration;
13+
14+
String getAccessToken() {
15+
return this.accessToken;
16+
}
17+
18+
public void setAccessToken(String accessToken) {
19+
this.accessToken = accessToken;
20+
}
21+
22+
int getExpiration() {
23+
return this.expiration;
24+
}
25+
26+
public void setExpiration(int expiration) {
27+
this.expiration = expiration;
28+
}
29+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package ee.carlrobert.llm.client.watsonx;
2+
3+
import static ee.carlrobert.llm.client.DeserializationUtil.OBJECT_MAPPER;
4+
5+
import com.fasterxml.jackson.core.JsonProcessingException;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import java.io.IOException;
8+
import java.util.Base64;
9+
import java.util.Date;
10+
import java.util.LinkedHashMap;
11+
import java.util.Map;
12+
import okhttp3.MediaType;
13+
import okhttp3.OkHttpClient;
14+
import okhttp3.Request;
15+
import okhttp3.RequestBody;
16+
import okhttp3.Response;
17+
18+
public class WatsonxAuthenticator {
19+
20+
IBMAuthBearerToken bearerToken;
21+
OkHttpClient client;
22+
Request request;
23+
Boolean isZenApiKey = false;
24+
25+
// On Cloud
26+
public WatsonxAuthenticator(String apiKey) {
27+
this.client = new OkHttpClient().newBuilder()
28+
.build();
29+
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
30+
RequestBody body = RequestBody.create(mediaType,
31+
"grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey=" + apiKey);
32+
this.request = new Request.Builder()
33+
.url("https://iam.cloud.ibm.com/identity/token")
34+
.method("POST", body)
35+
.addHeader("Content-Type", "application/x-www-form-urlencoded")
36+
.build();
37+
try {
38+
Response response = client.newCall(request).execute();
39+
this.bearerToken = OBJECT_MAPPER.readValue(response.body().string(),
40+
IBMAuthBearerToken.class);
41+
} catch (IOException e) {
42+
System.out.println(e);
43+
}
44+
}
45+
46+
// Zen API Key
47+
public WatsonxAuthenticator(String username, String zenApiKey) {
48+
IBMAuthBearerToken token = new IBMAuthBearerToken();
49+
String tokenStr = Base64.getEncoder().encode((username + ":" + zenApiKey).getBytes())
50+
.toString();
51+
token.setAccessToken(tokenStr);
52+
this.bearerToken = token;
53+
this.isZenApiKey = true;
54+
}
55+
56+
// Watsonx API Key
57+
public WatsonxAuthenticator(String username, String apiKey,
58+
String host) {//TODO add support for password
59+
this.client = new OkHttpClient().newBuilder()
60+
.build();
61+
ObjectMapper mapper = new ObjectMapper();
62+
Map<String, String> authParams = new LinkedHashMap<>();
63+
authParams.put("username", username);
64+
authParams.put("api_key", apiKey);
65+
66+
String authParamsStr = "";
67+
try {
68+
authParamsStr = mapper.writeValueAsString(authParams);
69+
} catch (JsonProcessingException e) {
70+
throw new RuntimeException(e);
71+
}
72+
73+
MediaType mediaType = MediaType.parse("application/json");
74+
RequestBody body = RequestBody.create(mediaType, authParamsStr);
75+
this.request = new Request.Builder()
76+
.url(host
77+
+ "/icp4d-api/v1/authorize") // TODO add support for IAM endpoint v1/auth/identitytoken
78+
.method("POST", body)
79+
.addHeader("Content-Type", "application/json")
80+
.build();
81+
try {
82+
Response response = client.newCall(request).execute();
83+
this.bearerToken = OBJECT_MAPPER.readValue(response.body().string(),
84+
IBMAuthBearerToken.class);
85+
} catch (IOException e) {
86+
System.out.println(e);
87+
}
88+
}
89+
90+
private void generateNewBearerToken() {
91+
try {
92+
Response response = client.newCall(request).execute();
93+
this.bearerToken = OBJECT_MAPPER.readValue(response.body().string(),
94+
IBMAuthBearerToken.class);
95+
} catch (IOException e) {
96+
System.out.println(e);
97+
}
98+
}
99+
100+
public String getBearerTokenValue() {
101+
if (!isZenApiKey && (this.bearerToken == null || (this.bearerToken.getExpiration() * 1000)
102+
< (new Date().getTime() + 60000))) {
103+
generateNewBearerToken();
104+
}
105+
return this.bearerToken.getAccessToken();
106+
}
107+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package ee.carlrobert.llm.client.watsonx;
2+
3+
import static ee.carlrobert.llm.client.DeserializationUtil.OBJECT_MAPPER;
4+
5+
import com.fasterxml.jackson.core.JsonProcessingException;
6+
import ee.carlrobert.llm.PropertiesLoader;
7+
import ee.carlrobert.llm.client.DeserializationUtil;
8+
import ee.carlrobert.llm.client.openai.completion.ErrorDetails;
9+
import ee.carlrobert.llm.client.watsonx.completion.WatsonxCompletionRequest;
10+
import ee.carlrobert.llm.client.watsonx.completion.WatsonxCompletionResponse;
11+
import ee.carlrobert.llm.client.watsonx.completion.WatsonxCompletionResponseError;
12+
import ee.carlrobert.llm.completion.CompletionEventListener;
13+
import ee.carlrobert.llm.completion.CompletionEventSourceListener;
14+
import java.io.IOException;
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
import okhttp3.Headers;
18+
import okhttp3.MediaType;
19+
import okhttp3.OkHttpClient;
20+
import okhttp3.Request;
21+
import okhttp3.RequestBody;
22+
import okhttp3.sse.EventSource;
23+
import okhttp3.sse.EventSources;
24+
25+
public class WatsonxClient {
26+
27+
private static final MediaType APPLICATION_JSON = MediaType.parse("application/json");
28+
private final OkHttpClient httpClient;
29+
private final String host;
30+
private final String apiVersion;
31+
private final WatsonxAuthenticator authenticator;
32+
33+
private WatsonxClient(Builder builder, OkHttpClient.Builder httpClientBuilder) {
34+
this.httpClient = httpClientBuilder.build();
35+
this.apiVersion = builder.apiVersion;
36+
this.host = builder.host;
37+
if (builder.isOnPrem) {
38+
if (builder.isZenApiKey) {
39+
this.authenticator = new WatsonxAuthenticator(builder.username, builder.apiKey);
40+
} else {
41+
this.authenticator = new WatsonxAuthenticator(builder.username, builder.apiKey,
42+
builder.host);
43+
}
44+
} else {
45+
this.authenticator = new WatsonxAuthenticator(builder.apiKey);
46+
}
47+
}
48+
49+
public EventSource getCompletionAsync(
50+
WatsonxCompletionRequest request,
51+
CompletionEventListener<String> eventListener) {
52+
return EventSources.createFactory(httpClient).newEventSource(
53+
buildCompletionRequest(request),
54+
getCompletionEventSourceListener(eventListener));
55+
}
56+
57+
public WatsonxCompletionResponse getCompletion(WatsonxCompletionRequest request) {
58+
try (var response = httpClient.newCall(buildCompletionRequest(request)).execute()) {
59+
return DeserializationUtil.mapResponse(response, WatsonxCompletionResponse.class);
60+
} catch (IOException e) {
61+
throw new RuntimeException(e);
62+
}
63+
}
64+
65+
protected Request buildCompletionRequest(WatsonxCompletionRequest request) {
66+
var headers = new HashMap<>(getRequiredHeaders());
67+
if (request.getStream()) {
68+
headers.put("Accept", "text/event-stream");
69+
}
70+
try {
71+
String deployment = request.getDeploymentId().isEmpty() ? ""
72+
: "deployments/" + request.getDeploymentId() + "/";
73+
String generation = request.getStream() ? "generation_stream" : "generation";
74+
return new Request.Builder()
75+
.url(host + "/ml/v1/text/" + deployment + generation + "?version=" + apiVersion)
76+
.headers(Headers.of(headers))
77+
.post(RequestBody.create(OBJECT_MAPPER.writeValueAsString(request), APPLICATION_JSON))
78+
.build();
79+
} catch (JsonProcessingException e) {
80+
throw new RuntimeException("Unable to process request", e);
81+
}
82+
}
83+
84+
private Map<String, String> getRequiredHeaders() {
85+
return new HashMap<>(Map.of("Authorization",
86+
(this.authenticator.isZenApiKey ? "ZenApiKey " : "Bearer ")
87+
+ authenticator.getBearerTokenValue()));
88+
}
89+
90+
private CompletionEventSourceListener<String> getCompletionEventSourceListener(
91+
CompletionEventListener<String> eventListener) {
92+
return new CompletionEventSourceListener<>(eventListener) {
93+
@Override
94+
protected String getMessage(String data) {
95+
try {
96+
return OBJECT_MAPPER.readValue(data, WatsonxCompletionResponse.class)
97+
.getResults().get(0).getGeneratedText();
98+
} catch (Exception e) {
99+
try {
100+
System.out.println(data);
101+
String message = OBJECT_MAPPER.readValue(data, WatsonxCompletionResponseError.class)
102+
.getError()
103+
.getMessage();
104+
if (message == null) return "";
105+
return message;
106+
} catch (Exception ex) {
107+
System.out.println(ex.toString());
108+
return "";
109+
}
110+
}
111+
}
112+
113+
@Override
114+
protected ErrorDetails getErrorDetails(String error) {
115+
try {
116+
return OBJECT_MAPPER.readValue(error, WatsonxCompletionResponseError.class).getError();
117+
} catch (JsonProcessingException e) {
118+
throw new RuntimeException(e);
119+
}
120+
}
121+
};
122+
}
123+
124+
public static class Builder {
125+
126+
private final String apiKey;
127+
private String host = PropertiesLoader.getValue("watsonx.baseUrl");
128+
private String apiVersion = "2024-03-14";
129+
private Boolean isOnPrem;
130+
private Boolean isZenApiKey;
131+
private String username;
132+
133+
public Builder(String apiKey) {
134+
this.apiKey = apiKey;
135+
}
136+
137+
public Builder setApiVersion(String apiVersion) {
138+
this.apiVersion = apiVersion;
139+
return this;
140+
}
141+
142+
public Builder setHost(String host) {
143+
this.host = host;
144+
return this;
145+
}
146+
147+
public Builder setIsZenApiKey(Boolean isZenApiKey) {
148+
this.isZenApiKey = isZenApiKey;
149+
return this;
150+
}
151+
152+
public Builder setIsOnPrem(Boolean isOnPrem) {
153+
this.isOnPrem = isOnPrem;
154+
return this;
155+
}
156+
157+
public Builder setUsername(String username) {
158+
this.username = username;
159+
return this;
160+
}
161+
162+
public WatsonxClient build(OkHttpClient.Builder builder) {
163+
return new WatsonxClient(this, builder);
164+
}
165+
166+
public WatsonxClient build() {
167+
return build(new OkHttpClient.Builder());
168+
}
169+
}
170+
}
171+
172+
173+
174+
175+
176+

0 commit comments

Comments
 (0)