Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.2] - 2026-03-30

### Added
- Test set execution support: `execute()`, `rescore()`, and `lastRun()` on `TestSetClient`, matching the Python SDK.
- `ExecutionMode` enum (`PARALLEL`, `SEQUENTIAL`) and `ExecutionRequest` model for controlling test run execution.
- Typed stats models for test run analytics (`TestRunStats`, `TestRunOverallSummary`, `StatusDistribution`, `ResultDistribution`, `TestRunTimelineData`, etc.).
- Typed stats models for test result analytics (`TestResultStats`, `MetricStats`, `OverallStats`, `TimelineData`, `TestRunSummary`, etc.).
- `TestRunStatsMode` and `TestResultStatsMode` enums for controlling which stat sections are returned.
- `TestRunClient.stats()` with overloads for mode, run ID filtering, and arbitrary filter params.
- `TestResultClient.stats()` with mode and 18 filter parameters matching the Python SDK.
- Test set metric management: `getMetrics()`, `addMetric()`, `removeMetric()` on `TestSetClient`.
- Test association management: `addTests()`, `removeTests()` on `TestSetClient`.
- Integration tests for test run lifecycle, execution, and stats (`TestRunIntegrationTest`).
- WireMock unit tests for all new client methods.
- Five new runnable examples: `ExecuteTestSetExample`, `TestRunWorkflowExample`, `TestSetMetricsExample`, `TestRunStatsExample`, `TestResultStatsExample`.
- Examples README with setup instructions and quick reference.

### Changed
- `TestRun.status` changed from `Object` to `String` with `NameStringDeserializer` to handle both plain string and nested object responses from the API.
- `TestRun` entity now includes `createdAt`, `nanoId`, `testCount`, and `passRate` fields.
- Updated project README with new features, all nine examples, and link to examples README.

## [0.1.1] - 2026-03-20

### Added
Expand Down
41 changes: 30 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ The Rhesis Java SDK empowers developers to programmatically access curated test
- [Quick Start](#-quick-start)
- [Generating Custom Test Sets](#generating-custom-test-sets-%EF%B8%8F)
- [Examples](#examples-)
- [Test Execution](#test-execution)
- [Analytics & Stats](#analytics--stats)
- [About Rhesis AI](#-about-rhesis-ai)
- [Community](#-community-)
- [Hugging Face](#-hugging-face)
Expand All @@ -62,6 +64,9 @@ The Rhesis Java SDK provides programmatic access to the Rhesis testing platform:

- **Access Test Sets**: Browse and load curated test sets across multiple domains and use cases
- **Generate Test Scenarios**: Create custom test sets from prompts, requirements, or domain knowledge natively using local LLM models
- **Execute Test Runs**: Trigger test set runs against your endpoints in parallel or sequential mode
- **Analytics & Stats**: Typed stats for test runs and test results — pass rates by metric, behavior, category, topic, and timeline trends
- **Manage Metrics & Tests**: Add/remove metrics on test sets, associate/disassociate tests, rescore previous runs
- **Seamless Integration**: Integrate testing into your Java CI/CD pipeline and development workflow
- **Comprehensive Coverage**: Scale your testing from dozens to thousands of scenarios
- **Open Source**: MIT-licensed with full transparency and community-driven development
Expand Down Expand Up @@ -258,23 +263,37 @@ If none of the existing test sets fit your needs, you can generate your own. You

### Examples 💡

Looking for more detailed examples? Check out our runnable examples in the [`src/test/java/ai/rhesis/sdk/examples/`](src/test/java/ai/rhesis/sdk/examples/) directory. These include:
- [Creating an Endpoint](src/test/java/ai/rhesis/sdk/examples/CreateEndpointExample.java)
- [Generating a Test Set](src/test/java/ai/rhesis/sdk/examples/GenerateTestSetExample.java)
Looking for more detailed examples? Check out the full [Examples README](src/test/java/ai/rhesis/sdk/examples/README.md) and runnable examples in [`src/test/java/ai/rhesis/sdk/examples/`](src/test/java/ai/rhesis/sdk/examples/):

You can run these examples directly from the command line using Maven. Make sure your `RHESIS_API_KEY` is set in your environment (it will be automatically picked up from a `.env` file at the root of the project if one exists):
**Endpoints & Projects**
- [Creating an Endpoint](src/test/java/ai/rhesis/sdk/examples/CreateEndpointExample.java) — Look up a project and create a REST endpoint with request/response mappings

**Test Generation**
- [Generating a Test Set](src/test/java/ai/rhesis/sdk/examples/GenerateTestSetExample.java) — Configure a synthesizer, generate tests, and push to Rhesis
- [Generate Test Set with Files](src/test/java/ai/rhesis/sdk/examples/GenerateTestSetWithFilesExample.java) — Generate tests and attach files to each one

**File Management**
- [File Support](src/test/java/ai/rhesis/sdk/examples/FileSupportExample.java) — Create tests with file attachments, download content, and clean up

**Test Execution**
- [Execute a Test Set](src/test/java/ai/rhesis/sdk/examples/ExecuteTestSetExample.java) — Trigger runs in parallel or sequential mode, with custom metrics
- [Test Run Workflow](src/test/java/ai/rhesis/sdk/examples/TestRunWorkflowExample.java) — Full lifecycle: list runs, inspect results, get last run, rescore

**Test Set Management**
- [Test Set Metrics](src/test/java/ai/rhesis/sdk/examples/TestSetMetricsExample.java) — List, add, and remove metrics; associate and disassociate tests

**Analytics & Stats**
- [Test Run Stats](src/test/java/ai/rhesis/sdk/examples/TestRunStatsExample.java) — Overall summary, status distribution, most-run test sets, timeline, filtered queries
- [Test Result Stats](src/test/java/ai/rhesis/sdk/examples/TestResultStatsExample.java) — Pass rates by metric, behavior, category, and topic; per-run summaries and timeline trends

You can run any example from the command line using Maven. Make sure your `RHESIS_API_KEY` is set in your environment (it will be automatically picked up from a `.env` file at the root of the project if one exists):

```bash
# Ensure the examples are compiled
mvn clean test-compile

# Run the Create Endpoint example
mvn exec:java -Dexec.classpathScope=test -Dexec.mainClass="ai.rhesis.sdk.examples.CreateEndpointExample"
```

To run the multi-turn synthesizer example:
```bash
mvn exec:java -Dexec.classpathScope=test -Dexec.mainClass="ai.rhesis.sdk.examples.GenerateTestSetExample"
# Run any example
mvn exec:java -Dexec.classpathScope=test -Dexec.mainClass="ai.rhesis.sdk.examples.ExecuteTestSetExample"
```

## 🧪 About Rhesis AI
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>ai.rhesis</groupId>
<artifactId>rhesis-java</artifactId>
<version>0.1.1</version>
<version>0.1.2</version>

<properties>
<maven.compiler.source>21</maven.compiler.source>
Expand Down
83 changes: 83 additions & 0 deletions src/main/java/ai/rhesis/sdk/clients/TestResultClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import ai.rhesis.sdk.entities.File;
import ai.rhesis.sdk.entities.TestResult;
import ai.rhesis.sdk.entities.stats.TestResultStats;
import ai.rhesis.sdk.enums.TestResultStatsMode;
import ai.rhesis.sdk.http.InternalHttpClient;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
import java.util.Map;

public class TestResultClient {
private final InternalHttpClient httpClient;
Expand All @@ -25,4 +28,84 @@ public List<File> getFiles(String testResultId) {
return httpClient.get(
"/test_results/" + testResultId + "/files", new TypeReference<List<File>>() {});
}

/**
* Get aggregated test result statistics with all sections.
*
* @return typed TestResultStats with all sections populated
*/
public TestResultStats stats() {
return stats(TestResultStatsMode.ALL, null);
}

/**
* Get aggregated test result statistics with the given mode.
*
* @param mode controls which sections the backend populates
* @return typed TestResultStats
*/
public TestResultStats stats(TestResultStatsMode mode) {
return stats(mode, null);
}

/**
* Get aggregated test result statistics with full control over mode and filters.
*
* <p>Supported filter keys:
*
* <ul>
* <li>{@code months} — number of months of historical data (default 6)
* <li>{@code test_run_id} — filter by a single test run ID
* <li>{@code test_run_ids} — filter by multiple test run IDs (List)
* <li>{@code test_set_ids} — filter by test set IDs (List)
* <li>{@code behavior_ids} — filter by behavior IDs (List)
* <li>{@code category_ids} — filter by category IDs (List)
* <li>{@code topic_ids} — filter by topic IDs (List)
* <li>{@code status_ids} — filter by test status IDs (List)
* <li>{@code test_ids} — filter by specific test IDs (List)
* <li>{@code test_type_ids} — filter by test type IDs (List)
* <li>{@code user_ids} — filter by test creator user IDs (List)
* <li>{@code assignee_ids} — filter by assignee user IDs (List)
* <li>{@code owner_ids} — filter by test owner user IDs (List)
* <li>{@code prompt_ids} — filter by prompt IDs (List)
* <li>{@code priority_min} — minimum priority (inclusive)
* <li>{@code priority_max} — maximum priority (inclusive)
* <li>{@code tags} — filter by tags (List)
* <li>{@code start_date} — start date (ISO format), overrides months
* <li>{@code end_date} — end date (ISO format), overrides months
* </ul>
*
* @param mode controls which sections the backend populates
* @param params optional filter parameters (may be null)
* @return typed TestResultStats
*/
public TestResultStats stats(TestResultStatsMode mode, Map<String, Object> params) {
StringBuilder path = new StringBuilder("/test_results/stats?mode=");
path.append(encode(mode.getValue()));

if (params != null) {
for (Map.Entry<String, Object> entry : params.entrySet()) {
Object value = entry.getValue();
if (value instanceof List<?> listVal) {
for (Object item : listVal) {
path.append("&")
.append(encode(entry.getKey()))
.append("=")
.append(encode(item.toString()));
}
} else {
path.append("&")
.append(encode(entry.getKey()))
.append("=")
.append(encode(value.toString()));
}
}
}

return httpClient.get(path.toString(), TestResultStats.class);
}

private static String encode(String value) {
return java.net.URLEncoder.encode(value, java.nio.charset.StandardCharsets.UTF_8);
}
}
76 changes: 76 additions & 0 deletions src/main/java/ai/rhesis/sdk/clients/TestRunClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

import ai.rhesis.sdk.entities.TestResult;
import ai.rhesis.sdk.entities.TestRun;
import ai.rhesis.sdk.entities.stats.TestRunStats;
import ai.rhesis.sdk.enums.TestRunStatsMode;
import ai.rhesis.sdk.http.InternalHttpClient;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class TestRunClient {
private final InternalHttpClient httpClient;
Expand All @@ -28,4 +32,76 @@ public List<TestResult> getTestResults(String testRunId) {
String filter = "?$filter=test_run_id%20eq%20'" + testRunId + "'";
return httpClient.get("/test_results/" + filter, new TypeReference<List<TestResult>>() {});
}

/**
* Get aggregated test run statistics with all sections.
*
* @return typed TestRunStats with all sections populated
*/
public TestRunStats stats() {
return stats(TestRunStatsMode.ALL, null);
}

/**
* Get aggregated test run statistics with the given mode.
*
* @param mode controls which sections the backend populates
* @return typed TestRunStats
*/
public TestRunStats stats(TestRunStatsMode mode) {
return stats(mode, null);
}

/**
* Get statistics scoped to specific test run IDs.
*
* @param testRunIds list of test run IDs to filter by
* @return typed TestRunStats
*/
public TestRunStats stats(List<String> testRunIds) {
Map<String, Object> params = new LinkedHashMap<>();
if (testRunIds != null && !testRunIds.isEmpty()) {
params.put("test_run_ids", testRunIds);
}
return stats(TestRunStatsMode.ALL, params);
}

/**
* Get aggregated test run statistics with full control over mode and filters.
*
* @param mode controls which sections the backend populates
* @param params optional filter parameters. Supported keys: "months", "top", "test_run_ids"
* (List), "user_ids" (List), "endpoint_ids" (List), "test_set_ids" (List), "status_list"
* (List), "start_date", "end_date"
* @return typed TestRunStats
*/
public TestRunStats stats(TestRunStatsMode mode, Map<String, Object> params) {
StringBuilder path = new StringBuilder("/test_runs/stats?mode=");
path.append(encode(mode.getValue()));

if (params != null) {
for (Map.Entry<String, Object> entry : params.entrySet()) {
Object value = entry.getValue();
if (value instanceof List<?> listVal) {
for (Object item : listVal) {
path.append("&")
.append(encode(entry.getKey()))
.append("=")
.append(encode(item.toString()));
}
} else {
path.append("&")
.append(encode(entry.getKey()))
.append("=")
.append(encode(value.toString()));
}
}
}

return httpClient.get(path.toString(), TestRunStats.class);
}

private static String encode(String value) {
return java.net.URLEncoder.encode(value, java.nio.charset.StandardCharsets.UTF_8);
}
}
Loading
Loading