Skip to content

Commit 644c655

Browse files
committed
GeminiException
1 parent 64590dc commit 644c655

File tree

2 files changed

+92
-7
lines changed

2 files changed

+92
-7
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package swiss.ameri.gemini.api;
2+
3+
import java.util.OptionalInt;
4+
5+
/**
6+
* Thrown if we get an unexpected body or response code from Gemini API.
7+
*/
8+
public class GeminiException extends RuntimeException {
9+
10+
private final Integer code;
11+
12+
13+
/**
14+
* Create a new exception.
15+
*
16+
* @param message of the exception
17+
*/
18+
public GeminiException(String message) {
19+
this(message, null, null);
20+
}
21+
22+
/**
23+
* Create a new exception.
24+
*
25+
* @param message of the exception
26+
* @param code optional http response code
27+
*/
28+
public GeminiException(String message, int code) {
29+
this(message, code, null);
30+
}
31+
32+
/**
33+
* Create a new exception.
34+
*
35+
* @param message of the exception
36+
* @param cause the cause of the exception
37+
*/
38+
public GeminiException(String message, Throwable cause) {
39+
this(message, null, cause);
40+
}
41+
42+
/**
43+
* Create a new exception.
44+
*
45+
* @param message of the exception
46+
* @param code optional http response code
47+
* @param cause the cause of the exception
48+
*/
49+
public GeminiException(String message, Integer code, Throwable cause) {
50+
super(message, cause);
51+
this.code = code;
52+
}
53+
54+
/**
55+
* Get the optional http response code.
56+
*
57+
* @return the response code, if present
58+
*/
59+
public OptionalInt getCode() {
60+
return code == null ? OptionalInt.empty() : OptionalInt.of(code);
61+
}
62+
63+
}

gemini-api/src/main/java/swiss/ameri/gemini/api/GenAi.java

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.UUID;
1515
import java.util.concurrent.CompletableFuture;
1616
import java.util.concurrent.ConcurrentHashMap;
17+
import java.util.stream.Collectors;
1718
import java.util.stream.Stream;
1819

1920
import static java.util.Collections.emptyList;
@@ -173,11 +174,11 @@ public CompletableFuture<Long> countTokens(GenerativeModel model) {
173174
try {
174175
var ctr = jsonParser.fromJson(body, CountTokenResponse.class);
175176
if (ctr.totalTokens() == null) {
176-
throw new RuntimeException("No token field in response");
177+
throw new GeminiException("No token field in response");
177178
}
178179
return ctr.totalTokens();
179180
} catch (Exception e) {
180-
throw new RuntimeException("Unexpected body:\n" + body, e);
181+
throw new GeminiException("Unexpected body:\n" + body, e);
181182
}
182183
});
183184
});
@@ -208,6 +209,27 @@ public Stream<GeneratedContent> generateContentStream(GenerativeModel model) {
208209
request,
209210
HttpResponse.BodyHandlers.ofLines()
210211
);
212+
// e.g. Response code: 503 (Service Unavailable); Time: 5813ms (5 s 813 ms)
213+
//
214+
//{
215+
// "error": {
216+
// "code": 503,
217+
// "message": "The model is overloaded. Please try again later.",
218+
// "status": "UNAVAILABLE"
219+
// }
220+
//}
221+
222+
if (response.statusCode() != 200) {
223+
// in case of an error, we don't stream, but block and give the whole response, because
224+
// we don't want to parse it and potentially cause more errors
225+
String error = response.body()
226+
.collect(Collectors.joining("\n"));
227+
throw new GeminiException(
228+
"Unexpected stream response:\n%s".formatted(error),
229+
response.statusCode()
230+
);
231+
}
232+
211233
return response.body()
212234
.filter(l -> l.length() > STREAM_LINE_PREFIX_LENGTH)
213235
.map(line -> parse(line.substring(STREAM_LINE_PREFIX_LENGTH), uuid));
@@ -295,12 +317,12 @@ public CompletableFuture<List<ContentEmbedding>> embedContents(
295317
try {
296318
BatchEmbedContentResponse becr = jsonParser.fromJson(body, BatchEmbedContentResponse.class);
297319
if (becr.embeddings() == null) {
298-
throw new RuntimeException();
320+
throw new GeminiException("No embeddings field in response:\n" + body);
299321
}
300322
return becr
301323
.embeddings();
302324
} catch (Exception e) {
303-
throw new RuntimeException("Unexpected body:\n" + body, e);
325+
throw new GeminiException("Unexpected body:\n" + body, e);
304326
}
305327
});
306328

@@ -371,7 +393,7 @@ private static List<GenerationContent> convertGenerationContents(GenerativeModel
371393
).toList()
372394
);
373395
} else {
374-
throw new RuntimeException("Unexpected content:\n" + content);
396+
throw new GeminiException("Unexpected content:\n" + content);
375397
}
376398
})
377399
.toList();
@@ -384,7 +406,7 @@ private <T> T execute(ThrowingSupplier<T> supplier) {
384406
throw new UncheckedIOException(e);
385407
} catch (InterruptedException e) {
386408
Thread.currentThread().interrupt();
387-
throw new RuntimeException(e);
409+
throw new GeminiException("Thread was interrupted.", e);
388410
}
389411
}
390412

@@ -537,7 +559,7 @@ private GeneratedContent parse(String body, UUID uuid) {
537559
}
538560
return new GeneratedContent(uuid, candidate.content().parts().get(0).text(), candidate.finishReason());
539561
} catch (Exception e) {
540-
throw new RuntimeException("Unexpected body:\n" + body, e);
562+
throw new GeminiException("Unexpected body:\n" + body, e);
541563
}
542564
}
543565

0 commit comments

Comments
 (0)