diff --git a/CHANGELOG.md b/CHANGELOG.md index 22642bb..59895b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- `ScoreAnswer.legend()` is now `Map`, since levels are sent as any JSON value and echoed back as sent. A `Score` with an object or array level previously failed the whole response, including its other answers and usage, with `Cannot deserialize value of type java.lang.String` (#11). + ## 0.4.0 - 2026-09-22 - Logging through slf4j on the `io.github.premocloud.typesafe` logger: `DEBUG` for one line per request with status and elapsed, plus retries and connection failures; `TRACE` for the wire in both directions. Nothing at `INFO` or above. Credential headers are masked. Adds `org.slf4j:slf4j-api` (#6, #7). diff --git a/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/ScoreAnswer.java b/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/ScoreAnswer.java index 64a031c..00983ef 100644 --- a/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/ScoreAnswer.java +++ b/typesafe-sdk/src/main/java/io/github/premocloud/typesafe/ScoreAnswer.java @@ -10,14 +10,14 @@ * @param score probability-weighted position from 0 to the top level index * @param probabilities probability per level index (keys are the index as a string) * @param confidence 0 to 1, how concentrated the distribution is - * @param legend level index back to the description that was sent + * @param legend level index back to the description that was sent: a String, or the Map or List it was given as */ @JsonIgnoreProperties(ignoreUnknown = true) public record ScoreAnswer( double score, Map probabilities, double confidence, - Map legend + Map legend ) implements TypeSafeAnswer { /** Jackson entry point: a score answer missing its score, probabilities, or confidence is malformed. */ @@ -26,7 +26,7 @@ public record ScoreAnswer( @JsonProperty("score") Double score, @JsonProperty("probabilities") Map probabilities, @JsonProperty("confidence") Double confidence, - @JsonProperty("legend") Map legend, + @JsonProperty("legend") Map legend, @JsonProperty("type") String ignoredType ) { this(TypeSafeAnswer.required(score, "score", "score").doubleValue(), diff --git a/typesafe-sdk/src/test/java/io/github/premocloud/typesafe/TypeSafeClientTest.java b/typesafe-sdk/src/test/java/io/github/premocloud/typesafe/TypeSafeClientTest.java index b706c4b..9fef716 100644 --- a/typesafe-sdk/src/test/java/io/github/premocloud/typesafe/TypeSafeClientTest.java +++ b/typesafe-sdk/src/test/java/io/github/premocloud/typesafe/TypeSafeClientTest.java @@ -334,6 +334,28 @@ void systemOneRejectsScoreAnswerMissingItsScore() { assertTrue(exception.getMessage().contains("score"), exception.getMessage()); } + @Test + void systemOneReadsAScoreLegendWithObjectAndArrayLevels() { + // Levels are sent as any JSON value and echoed back as sent, so the legend holds whatever was asked (#11). + server.reply(200, """ + {"model": "jev-1.13.0", "answers": { + "is_phishing": {"type": "noul", "noul": 0.93}, + "spam_category": {"type": "choice", "choice": "PHISHING", "probabilities": {"PHISHING": 1.0}, "confidence": 1.0}, + "urgency": {"type": "score", "score": 1.0, "probabilities": {"0": 0.2, "1": 0.5, "2": 0.3}, "confidence": 0.5, + "legend": {"0": "none", "1": {"what": "Threatens loss within hours", "examples": ["final notice"]}, "2": ["a", "b"]}}}, + "usage": {"input_tokens": 1, "output_tokens": 1}} + """); + + TypeSafeResponse response = client.systemOne(spamRequest()); + + Map legend = response.score("urgency").legend(); + assertEquals("none", legend.get("0")); + assertEquals(Map.of("what", "Threatens loss within hours", "examples", List.of("final notice")), legend.get("1")); + assertEquals(List.of("a", "b"), legend.get("2")); + assertEquals(0.93, response.noul("is_phishing")); + assertEquals(1, response.usage().inputTokens()); + } + @Test void systemOneRejectsAResponseMissingAnAnswerForAQuestionThatWasAsked() { server.reply(200, """