Skip to content

Commit 4acc5bf

Browse files
authored
Fix error messages (#102)
1 parent 4c1197c commit 4acc5bf

10 files changed

Lines changed: 84 additions & 8 deletions

File tree

langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/BadRequestException.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
1010

1111
class BadRequestException
1212
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
13-
LangChainServiceException("400: $body", cause) {
13+
LangChainServiceException(formatMessage(400, body), cause) {
1414

1515
override fun statusCode(): Int = 400
1616

langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/InternalServerException.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ private constructor(
1414
private val headers: Headers,
1515
private val body: JsonValue,
1616
cause: Throwable?,
17-
) : LangChainServiceException("$statusCode: $body", cause) {
17+
) : LangChainServiceException(formatMessage(statusCode, body), cause) {
1818

1919
override fun statusCode(): Int = statusCode
2020

langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/LangChainServiceException.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
package com.langchain.smith.errors
44

5+
import com.fasterxml.jackson.databind.ObjectMapper
56
import com.langchain.smith.core.JsonValue
67
import com.langchain.smith.core.http.Headers
78

@@ -14,4 +15,19 @@ protected constructor(message: String, cause: Throwable? = null) :
1415
abstract fun headers(): Headers
1516

1617
abstract fun body(): JsonValue
18+
19+
companion object {
20+
21+
/** Formats a status code and [JsonValue] error body into an error message. */
22+
@JvmStatic
23+
fun formatMessage(statusCode: Int, body: JsonValue): String {
24+
val bodyStr =
25+
try {
26+
ObjectMapper().writeValueAsString(body)
27+
} catch (_: Exception) {
28+
body.toString()
29+
}
30+
return "$statusCode: $bodyStr"
31+
}
32+
}
1733
}

langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/NotFoundException.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
1010

1111
class NotFoundException
1212
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
13-
LangChainServiceException("404: $body", cause) {
13+
LangChainServiceException(formatMessage(404, body), cause) {
1414

1515
override fun statusCode(): Int = 404
1616

langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/PermissionDeniedException.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
1010

1111
class PermissionDeniedException
1212
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
13-
LangChainServiceException("403: $body", cause) {
13+
LangChainServiceException(formatMessage(403, body), cause) {
1414

1515
override fun statusCode(): Int = 403
1616

langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/RateLimitException.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
1010

1111
class RateLimitException
1212
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
13-
LangChainServiceException("429: $body", cause) {
13+
LangChainServiceException(formatMessage(429, body), cause) {
1414

1515
override fun statusCode(): Int = 429
1616

langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/UnauthorizedException.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
1010

1111
class UnauthorizedException
1212
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
13-
LangChainServiceException("401: $body", cause) {
13+
LangChainServiceException(formatMessage(401, body), cause) {
1414

1515
override fun statusCode(): Int = 401
1616

langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/UnexpectedStatusCodeException.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ private constructor(
1414
private val headers: Headers,
1515
private val body: JsonValue,
1616
cause: Throwable?,
17-
) : LangChainServiceException("$statusCode: $body", cause) {
17+
) : LangChainServiceException(formatMessage(statusCode, body), cause) {
1818

1919
override fun statusCode(): Int = statusCode
2020

langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/UnprocessableEntityException.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
1010

1111
class UnprocessableEntityException
1212
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
13-
LangChainServiceException("422: $body", cause) {
13+
LangChainServiceException(formatMessage(422, body), cause) {
1414

1515
override fun statusCode(): Int = 422
1616

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.langchain.smith.errors
2+
3+
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient
4+
import com.langchain.smith.models.runs.Run
5+
import com.langchain.smith.models.runs.RunIngestBatchParams
6+
import org.assertj.core.api.Assertions.assertThat
7+
import org.assertj.core.api.Assertions.assertThatThrownBy
8+
import org.junit.jupiter.api.Assumptions.assumeTrue
9+
import org.junit.jupiter.api.Test
10+
11+
/**
12+
* Verifies that service exception error messages contain the actual API error body rather than
13+
* opaque strings like `[object Object]`.
14+
*
15+
* Run with:
16+
* ```bash
17+
* ./gradlew :langsmith-java-core:test --tests "com.langchain.smith.errors.ServiceExceptionErrorMessageTest"
18+
* ```
19+
*/
20+
internal class ServiceExceptionErrorMessageTest {
21+
22+
@Test
23+
fun forbiddenErrorContainsDetail() {
24+
val client = LangsmithOkHttpClient.builder().apiKey("ls-invalid-key").build()
25+
26+
assertThatThrownBy { client.runs().ingestBatch() }
27+
.isInstanceOf(PermissionDeniedException::class.java)
28+
.satisfies({ e ->
29+
val message = e.message ?: ""
30+
assertThat(message).startsWith("403:")
31+
assertThat(message).doesNotContain("[object Object]")
32+
assertThat(message).contains("Forbidden")
33+
})
34+
}
35+
36+
@Test
37+
fun badRequestErrorContainsDetail() {
38+
assumeTrue(
39+
!System.getenv("LANGSMITH_API_KEY").isNullOrBlank(),
40+
"Skipping: LANGSMITH_API_KEY must be set",
41+
)
42+
val client = LangsmithOkHttpClient.fromEnv()
43+
44+
val params =
45+
RunIngestBatchParams.builder()
46+
.addPost(
47+
Run.builder().id("not-a-uuid").name("test").runType(Run.RunType.CHAIN).build()
48+
)
49+
.build()
50+
51+
assertThatThrownBy { client.runs().ingestBatch(params) }
52+
.isInstanceOf(UnprocessableEntityException::class.java)
53+
.satisfies({ e ->
54+
val message = e.message ?: ""
55+
assertThat(message).startsWith("422:")
56+
assertThat(message).doesNotContain("[object Object]")
57+
assertThat(message).contains("error")
58+
})
59+
}
60+
}

0 commit comments

Comments
 (0)