mirror of
https://github.com/langchain-ai/langsmith-java.git
synced 2026-07-25 21:25:45 -04:00
Compare commits
270 Commits
v0.1.0-alpha.23
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2775de9d25 | |||
| 2ab482c2d3 | |||
| b1649d975e | |||
| aece72d035 | |||
| aa4be05c11 | |||
| 8aded40800 | |||
| 6165434d21 | |||
| 95da1e9b94 | |||
| eace8c3f99 | |||
| 056dbd3673 | |||
| e7fe407f74 | |||
| f6608288d4 | |||
| b072b2e2fc | |||
| 391b75ca3b | |||
| 42326ef486 | |||
| b6d079b991 | |||
| 804022edf1 | |||
| feb140e343 | |||
| 0d0a8f5ffb | |||
| 57da309643 | |||
| 388984c005 | |||
| 283c4c2bcf | |||
| b8f1caebe1 | |||
| b64cfc2ca8 | |||
| 101472f4ce | |||
| df8f95af97 | |||
| 6720c1f0e4 | |||
| 2f1ca870dd | |||
| 7ba0bad56f | |||
| 68707629d2 | |||
| 3880cb324a | |||
| 37d650a362 | |||
| cff262179d | |||
| a19f9aae0b | |||
| 1409dd89d8 | |||
| 839107858c | |||
| 8eb067064e | |||
| 306a079479 | |||
| 8b1dff9a1e | |||
| 50525423cc | |||
| d53da42ffa | |||
| cbc00807ee | |||
| 2cf0ff6e08 | |||
| 8e2a0583bf | |||
| 63ca829e51 | |||
| d35ada22c7 | |||
| 2c9704f3c0 | |||
| b2ed882d0e | |||
| 99fe7dbe33 | |||
| c3764a059c | |||
| 3f086d2302 | |||
| ca210b2c66 | |||
| e639046090 | |||
| 1893f83cf2 | |||
| 63bde3c6e4 | |||
| 9cfd26bef9 | |||
| 17551decf7 | |||
| 0a29041897 | |||
| 12185beb4c | |||
| 18d7867206 | |||
| a80e55d510 | |||
| b8f033e3d9 | |||
| 38646cb1fa | |||
| 59d3e33e71 | |||
| c35c92289b | |||
| c22f69aac3 | |||
| 57f86d2e60 | |||
| 141e3e5603 | |||
| 5d5112753c | |||
| 13b36e3f11 | |||
| fcc7ab733b | |||
| a07babf48b | |||
| fdba8e6123 | |||
| bf2443da84 | |||
| 3bc6173328 | |||
| d71cff9f59 | |||
| e2fc8a84dd | |||
| 8e17124b68 | |||
| 6ab6fa281f | |||
| 585b12dd26 | |||
| 4513b3ffb0 | |||
| b00f843bc8 | |||
| 5b3f2edfce | |||
| f6ea8673f6 | |||
| 539f2893ea | |||
| e3b478437e | |||
| c01f895e57 | |||
| 6b52583c7c | |||
| 384c001825 | |||
| a00b1f18ca | |||
| 48b3b08984 | |||
| 7a1922239b | |||
| 8b752b109b | |||
| 636b67ce06 | |||
| 2d4900b16b | |||
| 4d45358e0e | |||
| 10a1f9bcbb | |||
| 2f0ce40b86 | |||
| bbd15dab2c | |||
| 2c08fe0ecb | |||
| 7d9a2fafd4 | |||
| ab6c153520 | |||
| 34f1c52fc1 | |||
| d1420efd7a | |||
| cdbd36936f | |||
| 49bf68d747 | |||
| 387526fd3a | |||
| e88560861c | |||
| cfed196979 | |||
| 70edc4514c | |||
| 6f1ae304fa | |||
| 84e02f96b4 | |||
| 65b5b9ae40 | |||
| a82b5c88fc | |||
| e097147ff3 | |||
| ba419de13e | |||
| 17bb73a006 | |||
| 48c283c7a7 | |||
| 3c36fa9f58 | |||
| 49039f2770 | |||
| 54d105ef91 | |||
| b963cab6c6 | |||
| 734a8285cd | |||
| 152169f4e1 | |||
| ac8da0f77c | |||
| 7f24878418 | |||
| 3c218e98bc | |||
| 2ddc687610 | |||
| f00fef565e | |||
| 87e3acbb7b | |||
| 280a37489d | |||
| 77f12e3a6e | |||
| b86e854687 | |||
| fd2980ce69 | |||
| e480cd4104 | |||
| 3f3a0c9c30 | |||
| 77d19d3241 | |||
| 5a6bddd241 | |||
| 10f1d65063 | |||
| 79647d1579 | |||
| f3ce273a02 | |||
| bbbcf7bf40 | |||
| f859585592 | |||
| 370b940a1c | |||
| ad6daecac9 | |||
| 6b25ea38a8 | |||
| 8b85e16472 | |||
| 7cb70fc3e3 | |||
| cb519f93d4 | |||
| 6bbe522f0a | |||
| bb9f973f41 | |||
| da0fc7b507 | |||
| 9c85b0cfd1 | |||
| 25b9aacfda | |||
| 3bd596cefa | |||
| 334fe4ef56 | |||
| 744987978d | |||
| c904c2d278 | |||
| 1540e83048 | |||
| 513d207b80 | |||
| 48a0841016 | |||
| 090c06bbaa | |||
| ea5f34e155 | |||
| 14634be733 | |||
| 3220bc56d5 | |||
| 9ccfc6a350 | |||
| 9f90d52312 | |||
| 4e86f4ee9e | |||
| f3d8fdeccf | |||
| 4ba75e7776 | |||
| 35bbb77c96 | |||
| ad80d1105b | |||
| 12a841eeb4 | |||
| 19c9b420ea | |||
| 53f2092dcd | |||
| 81f7359cac | |||
| 5c4f4acb92 | |||
| 80dceff65a | |||
| b5b42f69a3 | |||
| c794cc215d | |||
| d07b7f3f40 | |||
| 305cae3bef | |||
| dd6d82830b | |||
| 9491540375 | |||
| 22ed526472 | |||
| f7f2672957 | |||
| eaf31118a5 | |||
| da3535ded5 | |||
| 2796973131 | |||
| d977e55ba6 | |||
| 7b9a6f2b09 | |||
| 219e966da6 | |||
| d8873a4cce | |||
| 2ce327828b | |||
| b0f3b5aab6 | |||
| a54a91735b | |||
| 959d619ee0 | |||
| 960ee71498 | |||
| 26b3f9cb2f | |||
| d6e265a7df | |||
| 315a4bd074 | |||
| bd1b0e8b59 | |||
| 57116f2104 | |||
| 554e497084 | |||
| c8873ff34e | |||
| 12e572bdd1 | |||
| a91eece7d1 | |||
| d3a153ef43 | |||
| 8705818e81 | |||
| d272832132 | |||
| 0cf53d62a4 | |||
| 8336c6349a | |||
| 382da9a537 | |||
| ad75c9789b | |||
| f6d0b66f60 | |||
| 6666c85031 | |||
| 8f1fc7bf69 | |||
| f09f2c48b3 | |||
| 3517baad85 | |||
| a42751fe92 | |||
| bca5f1858b | |||
| 43023605f0 | |||
| f247ab1bf3 | |||
| 4461ffbf99 | |||
| bb060d0897 | |||
| 3e731df52b | |||
| b9ce5af2d4 | |||
| 3eed0e974f | |||
| c616f1a7af | |||
| c6e950f613 | |||
| 2d81794530 | |||
| 33152dc37b | |||
| a1533b5161 | |||
| c9238e7d8a | |||
| 8c1f5f27ad | |||
| 06771e7b9e | |||
| 9b3cd93e74 | |||
| 0ff2f3175f | |||
| f06475795a | |||
| 81ce4fe1f3 | |||
| 6c35351e46 | |||
| 7256a6ca4a | |||
| feb7baba86 | |||
| 4baa783b9b | |||
| 97f40ac895 | |||
| 7d80a1978c | |||
| 92ce263397 | |||
| 4466c613fc | |||
| 848c0a5751 | |||
| 1d2e391160 | |||
| dab7b73dc9 | |||
| 59ea961218 | |||
| 873396aceb | |||
| f12b234a80 | |||
| a2520117a5 | |||
| 10946e186a | |||
| 9bd1c8c310 | |||
| 44a2308df3 | |||
| 765bad3142 | |||
| 41fcd7cec9 | |||
| f102c9929d | |||
| 6866c4fdfe | |||
| 45dd9a2727 | |||
| 7512302dd4 | |||
| 98e16a283e | |||
| 8cdc61ab86 | |||
| 3a1a72f559 | |||
| 76da93f2c0 | |||
| cf4696d51a | |||
| 3c79eea5dd |
@@ -1,5 +0,0 @@
|
||||
#
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
#
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
@@ -0,0 +1,58 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gradle
|
||||
directory: /
|
||||
target-branch: "next"
|
||||
schedule:
|
||||
interval: monthly
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
major:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- major
|
||||
open-pull-requests-limit: 5
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: /.devcontainer
|
||||
target-branch: "next"
|
||||
schedule:
|
||||
interval: monthly
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
major:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- major
|
||||
open-pull-requests-limit: 5
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
target-branch: "next"
|
||||
schedule:
|
||||
interval: monthly
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- minor
|
||||
- patch
|
||||
major:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- major
|
||||
open-pull-requests-limit: 5
|
||||
@@ -0,0 +1,72 @@
|
||||
name: Check Stainless File Modification
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check-stainless-files:
|
||||
name: Check for Stainless-generated file modifications
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'langchain-ai/langsmith-java-staging'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for modifications to Stainless-generated files
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
SKIP_LABEL="skip-stainless-generated-file-modification-check"
|
||||
|
||||
# Check if the skip label is present
|
||||
LABELS=$(gh api repos/"$REPO"/pulls/"$PR_NUMBER" --jq '[.labels[].name] | join(",")')
|
||||
if echo "$LABELS" | grep -qF "$SKIP_LABEL"; then
|
||||
echo "Skip label '$SKIP_LABEL' is present — skipping check."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get the list of files changed in this PR
|
||||
CHANGED_FILES=$(git diff --name-only "origin/${{ github.base_ref }}"...HEAD)
|
||||
|
||||
STAINLESS_FILES=""
|
||||
while IFS= read -r FILE; do
|
||||
[ -z "$FILE" ] && continue
|
||||
[ ! -f "$FILE" ] && continue
|
||||
if grep -qF "File generated from our OpenAPI spec by Stainless." "$FILE"; then
|
||||
STAINLESS_FILES="$STAINLESS_FILES\n - $FILE"
|
||||
fi
|
||||
done <<< "$CHANGED_FILES"
|
||||
|
||||
if [ -n "$STAINLESS_FILES" ]; then
|
||||
echo "::error::This PR modifies Stainless-generated files, which should not be edited directly."
|
||||
printf "Stainless-generated files modified:%b\n" "$STAINLESS_FILES"
|
||||
echo ""
|
||||
echo "Please choose one of the following options:"
|
||||
echo ""
|
||||
echo " 1. Refactor your change to avoid modifying the generated file directly."
|
||||
echo ""
|
||||
echo " 2. Use Stainless configuration instead:"
|
||||
echo " - OpenAPI extensions: https://www.stainless.com/docs/openapi/extensions/"
|
||||
echo " - Transforms: https://www.stainless.com/docs/openapi/transforms/reference/"
|
||||
echo ""
|
||||
echo " 3. If neither option above is feasible, add the label '$SKIP_LABEL' to this PR to bypass this check."
|
||||
echo " Use this only as a last resort and document why it is necessary."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "No Stainless-generated files were modified."
|
||||
+22
-49
@@ -1,29 +1,34 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'generated'
|
||||
- 'codegen/**'
|
||||
- 'integrated/**'
|
||||
- 'stl-preview-head/**'
|
||||
- 'stl-preview-base/**'
|
||||
branches:
|
||||
- '**'
|
||||
- '!integrated/**'
|
||||
- '!stl-preview-head/**'
|
||||
- '!stl-preview-base/**'
|
||||
- '!generated'
|
||||
- '!codegen/**'
|
||||
- 'codegen/stl/**'
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- 'stl-preview-head/**'
|
||||
- 'stl-preview-base/**'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
timeout-minutes: 15
|
||||
name: lint
|
||||
runs-on: ${{ github.repository == 'stainless-sdks/langsmith-api-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
|
||||
runs-on: ubuntu-latest
|
||||
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: |
|
||||
@@ -32,7 +37,7 @@ jobs:
|
||||
cache: gradle
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
|
||||
|
||||
- name: Run lints
|
||||
run: ./scripts/lint
|
||||
@@ -43,14 +48,14 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
runs-on: ${{ github.repository == 'stainless-sdks/langsmith-api-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
|
||||
runs-on: ubuntu-latest
|
||||
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: |
|
||||
@@ -59,45 +64,13 @@ jobs:
|
||||
cache: gradle
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
|
||||
|
||||
- name: Build SDK
|
||||
run: ./scripts/build
|
||||
|
||||
- name: Get GitHub OIDC Token
|
||||
if: github.repository == 'stainless-sdks/langsmith-api-java'
|
||||
id: github-oidc
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: core.setOutput('github_token', await core.getIDToken());
|
||||
|
||||
- name: Build and upload Maven artifacts
|
||||
if: github.repository == 'stainless-sdks/langsmith-api-java'
|
||||
env:
|
||||
URL: https://pkg.stainless.com/s
|
||||
AUTH: ${{ steps.github-oidc.outputs.github_token }}
|
||||
SHA: ${{ github.sha }}
|
||||
PROJECT: langsmith-api-java
|
||||
run: ./scripts/upload-artifacts
|
||||
test:
|
||||
timeout-minutes: 15
|
||||
name: test
|
||||
runs-on: ${{ github.repository == 'stainless-sdks/langsmith-api-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: |
|
||||
8
|
||||
21
|
||||
cache: gradle
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
uses: gradle/gradle-build-action@ac2d340dc04d9e1113182899e983b5400c17cda1 # v3
|
||||
|
||||
- name: Run tests
|
||||
run: ./scripts/test
|
||||
|
||||
@@ -7,6 +7,10 @@ on:
|
||||
branches: [main, next]
|
||||
schedule:
|
||||
- cron: "0 0 * * 0" # Weekly scan on Sunday at midnight
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
@@ -18,17 +22,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: |
|
||||
8
|
||||
21
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
name: Count number of methods in the SDK
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- 'generated'
|
||||
- 'codegen/**'
|
||||
- 'integrated/**'
|
||||
- 'stl-preview-head/**'
|
||||
- 'stl-preview-base/**'
|
||||
|
||||
jobs:
|
||||
count-methods:
|
||||
name: count methods
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Read method count from .stats.yml
|
||||
id: read_method_count
|
||||
run: |
|
||||
if [ ! -f .stats.yml ]; then
|
||||
echo "Error: .stats.yml file not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
METHOD_COUNT=$(grep -E '^configured_endpoints:\s*[0-9]+' .stats.yml | awk '{print $2}' || echo "")
|
||||
|
||||
if [ -z "$METHOD_COUNT" ]; then
|
||||
echo "Error: Could not find configured_endpoints in .stats.yml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Method count from .stats.yml: $METHOD_COUNT"
|
||||
echo "method_count=$METHOD_COUNT" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Fail if method count exceeds threshold
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const methodCount = parseInt('${{ steps.read_method_count.outputs.method_count }}');
|
||||
const threshold = 100;
|
||||
|
||||
if (methodCount > threshold) {
|
||||
core.setFailed(
|
||||
` Method count check failed!\n` +
|
||||
` Current method count: ${methodCount}\n` +
|
||||
` Maximum allowed: ${threshold}\n` +
|
||||
` Excess: ${methodCount - threshold} methods\n\n` +
|
||||
`This check ensures the SDK doesn't exceed the configured endpoint limit.`
|
||||
);
|
||||
} else {
|
||||
console.log(`Currently at ${methodCount} methods`);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
# This workflow is triggered when a GitHub release is created.
|
||||
# It can also be run manually to re-publish to Sonatype in case it failed for some reason.
|
||||
# You can run this workflow by navigating to https://www.github.com/langchain-ai/langsmith-java/actions/workflows/publish-sonatype.yml
|
||||
# workflow for re-running publishing to Sonatype in case it fails for some reason
|
||||
# you can run this workflow by navigating to https://www.github.com/langchain-ai/langsmith-java/actions/workflows/publish-sonatype.yml
|
||||
name: Publish Sonatype
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -8,16 +7,19 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: publish
|
||||
runs-on: ubuntu-latest-l-oss
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v5
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: |
|
||||
@@ -26,14 +28,18 @@ jobs:
|
||||
cache: gradle
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
uses: gradle/gradle-build-action@ac2d340dc04d9e1113182899e983b5400c17cda1 # v3
|
||||
|
||||
- name: Publish to Sonatype
|
||||
run: |-
|
||||
export -- GPG_SIGNING_KEY_ID
|
||||
printenv -- GPG_SIGNING_KEY | gpg --batch --passphrase-fd 3 --import 3<<< "$GPG_SIGNING_PASSWORD"
|
||||
GPG_SIGNING_KEY_ID="$(gpg --with-colons --list-keys | awk -F : -- '/^pub:/ { getline; print "0x" substr($10, length($10) - 7) }')"
|
||||
./gradlew publishAndReleaseToMavenCentral --stacktrace -PmavenCentralUsername="$SONATYPE_USERNAME" -PmavenCentralPassword="$SONATYPE_PASSWORD" --no-configuration-cache
|
||||
export ORG_GRADLE_PROJECT_signingInMemoryKey="$GPG_SIGNING_KEY"
|
||||
export ORG_GRADLE_PROJECT_signingInMemoryKeyId="$GPG_SIGNING_KEY_ID"
|
||||
export ORG_GRADLE_PROJECT_signingInMemoryKeyPassword="$GPG_SIGNING_PASSWORD"
|
||||
# Keep publish execution serial to avoid Gradle/Dokka task graph race conditions.
|
||||
./gradlew publishAndReleaseToMavenCentral --stacktrace -PmavenCentralUsername="$SONATYPE_USERNAME" -PmavenCentralPassword="$SONATYPE_PASSWORD" --no-configuration-cache --no-parallel
|
||||
env:
|
||||
SONATYPE_USERNAME: ${{ secrets.LANGCHAIN_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }}
|
||||
SONATYPE_PASSWORD: ${{ secrets.LANGCHAIN_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
name: Release Doctor
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
release_doctor:
|
||||
name: release doctor
|
||||
@@ -12,12 +15,13 @@ jobs:
|
||||
if: github.repository == 'langchain-ai/langsmith-java' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Check release environment
|
||||
run: |
|
||||
bash ./bin/check-release-environment
|
||||
env:
|
||||
RELEASE_PLEASE_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
||||
SONATYPE_USERNAME: ${{ secrets.LANGCHAIN_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }}
|
||||
SONATYPE_PASSWORD: ${{ secrets.LANGCHAIN_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }}
|
||||
GPG_SIGNING_KEY: ${{ secrets.LANGCHAIN_SONATYPE_GPG_SIGNING_KEY || secrets.GPG_SIGNING_KEY }}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Release Please
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
release-please:
|
||||
if: github.repository == 'langchain-ai/langsmith-java'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1
|
||||
id: release
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Trigger publish
|
||||
if: ${{ steps.release.outputs.release_created }}
|
||||
run: gh workflow run publish-sonatype.yml --ref ${{ steps.release.outputs.tag_name }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
@@ -0,0 +1,47 @@
|
||||
name: "stlc: trigger promote on stlc/from-prod → main approval"
|
||||
|
||||
# Dispatches the ff-only merge workflow in langchainplus whenever the stlc/from-prod → main
|
||||
# PR in this repo receives an approving review.
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
trigger-promote:
|
||||
if: |
|
||||
github.repository == 'langchain-ai/langsmith-java-staging' &&
|
||||
github.event.review.state == 'approved' &&
|
||||
github.event.pull_request.base.ref == 'main' &&
|
||||
github.event.pull_request.head.ref == 'stlc/from-prod'
|
||||
runs-on: self-hosted-ubuntu-small-amd64
|
||||
steps:
|
||||
- name: Load GitHub App key from 1Password
|
||||
id: load-gh-app-secrets
|
||||
uses: 1password/load-secrets-action@92467eb28f72e8255933372f1e0707c567ce2259 # v4.0.0
|
||||
with:
|
||||
export-env: false
|
||||
env:
|
||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN_INTERNAL }}
|
||||
OP_CONNECT_HOST: ${{ secrets.OP_CONNECT_HOST }}
|
||||
LANGCHAINPLUS_GITHUB_APP_PRIVATE_KEY: op://${{ secrets.INTERNAL_INFRA_OP_VAULT }}/langchain-actions-pr-bot/private_key
|
||||
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: "Iv23liMFOFBcPTKFWi3S"
|
||||
private-key: ${{ steps.load-gh-app-secrets.outputs.LANGCHAINPLUS_GITHUB_APP_PRIVATE_KEY }}
|
||||
repositories: langchainplus
|
||||
|
||||
- name: Dispatch ff-only merge workflow in langchainplus
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
gh workflow run git-ff-only-merge.yml \
|
||||
--repo langchain-ai/langchainplus \
|
||||
--field repo=langsmith-java-staging \
|
||||
--field src_branch=stlc/from-prod \
|
||||
--field target_branch=main
|
||||
@@ -0,0 +1,87 @@
|
||||
name: 'stlc: promote to production'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
promote:
|
||||
if: github.repository == 'langchain-ai/langsmith-java-staging'
|
||||
runs-on: self-hosted-ubuntu-small-amd64
|
||||
env:
|
||||
PRODUCTION_REPO: langchain-ai/langsmith-java
|
||||
steps:
|
||||
- name: Check out staging
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Load GitHub App key from 1Password
|
||||
id: load-gh-app-secrets
|
||||
uses: 1password/load-secrets-action@581a835fb51b8e7ec56b71cf2ffddd7e68bb25e0 # v2
|
||||
with:
|
||||
export-env: false
|
||||
env:
|
||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN_INTERNAL }}
|
||||
OP_CONNECT_HOST: ${{ secrets.OP_CONNECT_HOST }}
|
||||
LANGCHAINPLUS_GITHUB_APP_PRIVATE_KEY: op://${{ secrets.INTERNAL_INFRA_OP_VAULT }}/langchain-actions-pr-bot/private_key
|
||||
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: "Iv23liMFOFBcPTKFWi3S"
|
||||
private-key: ${{ steps.load-gh-app-secrets.outputs.LANGCHAINPLUS_GITHUB_APP_PRIVATE_KEY }}
|
||||
repositories: langsmith-java-staging,langsmith-java
|
||||
|
||||
- name: Fetch production main
|
||||
run: |
|
||||
git remote add production \
|
||||
"https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${PRODUCTION_REPO}.git"
|
||||
git fetch production main
|
||||
|
||||
- name: Check if production is already in sync
|
||||
id: diff
|
||||
run: |
|
||||
STAGING_SHA=$(git rev-parse origin/main)
|
||||
PRODUCTION_SHA=$(git rev-parse production/main)
|
||||
if [ "$STAGING_SHA" = "$PRODUCTION_SHA" ]; then
|
||||
echo "Production is already at $STAGING_SHA. Nothing to release."
|
||||
echo "synced=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "synced=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Push staging main to the release branch on production
|
||||
if: steps.diff.outputs.synced == 'false'
|
||||
run: |
|
||||
git push production origin/main:refs/heads/stainless/release --force
|
||||
|
||||
- name: Open or update the release PR on production
|
||||
if: steps.diff.outputs.synced == 'false'
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
EXISTING_PR=$(gh pr list \
|
||||
--repo "${PRODUCTION_REPO}" \
|
||||
--head stainless/release \
|
||||
--state open \
|
||||
--json number \
|
||||
--jq '.[0].number')
|
||||
if [ -z "${EXISTING_PR}" ]; then
|
||||
gh pr create \
|
||||
--repo "${PRODUCTION_REPO}" \
|
||||
--base main \
|
||||
--head stainless/release \
|
||||
--title "Release SDK updates" \
|
||||
--body "$(git log --reverse --format="%s%n" production/main..origin/main)"
|
||||
else
|
||||
gh pr edit "${EXISTING_PR}" \
|
||||
--repo "${PRODUCTION_REPO}" \
|
||||
--body "$(git log --reverse --format="%s%n" production/main..origin/main)"
|
||||
echo "Release PR #${EXISTING_PR} already exists. Updated its description."
|
||||
fi
|
||||
@@ -0,0 +1,47 @@
|
||||
name: "stlc: seal custom code"
|
||||
|
||||
# Triggered on every push to the `main` branch of langsmith-java-staging.
|
||||
# Delegates to the stlc_seal_custom_code workflow in langchainplus (where
|
||||
# the stainless workspace and reusable actions live) by dispatching it via
|
||||
# the GitHub API, scoped to the java target only.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
seal:
|
||||
if: github.repository == 'langchain-ai/langsmith-java-staging'
|
||||
runs-on: self-hosted-ubuntu-small-amd64
|
||||
steps:
|
||||
- name: Load GitHub App key from 1Password
|
||||
id: load-gh-app-secrets
|
||||
uses: 1password/load-secrets-action@581a835fb51b8e7ec56b71cf2ffddd7e68bb25e0 # v2
|
||||
with:
|
||||
export-env: false
|
||||
env:
|
||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN_INTERNAL }}
|
||||
OP_CONNECT_HOST: ${{ secrets.OP_CONNECT_HOST }}
|
||||
LANGCHAINPLUS_GITHUB_APP_PRIVATE_KEY: op://${{ secrets.INTERNAL_INFRA_OP_VAULT }}/langchain-actions-pr-bot/private_key
|
||||
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: "Iv23liMFOFBcPTKFWi3S"
|
||||
private-key: ${{ steps.load-gh-app-secrets.outputs.LANGCHAINPLUS_GITHUB_APP_PRIVATE_KEY }}
|
||||
repositories: langsmith-java-staging,langchainplus
|
||||
|
||||
- name: Trigger stlc seal workflow in langchainplus
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
gh workflow run stlc_seal_custom_code.yml \
|
||||
--repo langchain-ai/langchainplus \
|
||||
--field repositories=langsmith-java-staging
|
||||
@@ -0,0 +1,86 @@
|
||||
name: "stlc: sync from production"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 */6 * * *'
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: stlc-sync-from-production
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: self-hosted-ubuntu-small-amd64
|
||||
if: github.repository == 'langchain-ai/langsmith-java-staging'
|
||||
env:
|
||||
PRODUCTION_REPO: langchain-ai/langsmith-java
|
||||
steps:
|
||||
- name: Load GitHub App key from 1Password
|
||||
id: load-gh-app-secrets
|
||||
uses: 1password/load-secrets-action@581a835fb51b8e7ec56b71cf2ffddd7e68bb25e0 # v2
|
||||
with:
|
||||
export-env: false
|
||||
env:
|
||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN_INTERNAL }}
|
||||
OP_CONNECT_HOST: ${{ secrets.OP_CONNECT_HOST }}
|
||||
LANGCHAINPLUS_GITHUB_APP_PRIVATE_KEY: op://${{ secrets.INTERNAL_INFRA_OP_VAULT }}/langchain-actions-pr-bot/private_key
|
||||
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: "Iv23liMFOFBcPTKFWi3S"
|
||||
private-key: ${{ steps.load-gh-app-secrets.outputs.LANGCHAINPLUS_GITHUB_APP_PRIVATE_KEY }}
|
||||
repositories: langsmith-java-staging,langsmith-java,stlc,stlc-java,langchainplus
|
||||
|
||||
- name: Check out staging
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Fetch production main
|
||||
env:
|
||||
APP_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
git remote add production \
|
||||
"https://x-access-token:${APP_TOKEN}@github.com/${PRODUCTION_REPO}.git"
|
||||
git fetch production main
|
||||
|
||||
- name: Check whether production has content staging lacks
|
||||
id: diff
|
||||
run: |
|
||||
MERGED=$(git merge-tree --write-tree origin/main production/main) || MERGED=conflict
|
||||
STAGING_TREE=$(git rev-parse 'origin/main^{tree}')
|
||||
if [ "$MERGED" = "$STAGING_TREE" ]; then
|
||||
echo "Staging already has production's content. Nothing to pull back."
|
||||
echo "behind=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "behind=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Push production main to the back-sync branch on staging
|
||||
if: steps.diff.outputs.behind == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: git push origin production/main:refs/heads/stlc/from-prod --force
|
||||
|
||||
- name: Open or update the back-sync PR on staging
|
||||
if: steps.diff.outputs.behind == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
EXISTING_PR=$(gh pr list --head stlc/from-prod --base main --state open --json number --jq '.[0].number')
|
||||
if [ -z "${EXISTING_PR}" ]; then
|
||||
gh pr create \
|
||||
--base main \
|
||||
--head stlc/from-prod \
|
||||
--title "Sync release changes from production" \
|
||||
--body "Pulls production's release-please commits (and any merged contributions) back onto staging \`main\` so the next \`stlc build\` reseals against the released state. Bot-authored; safe to auto-merge."
|
||||
fi
|
||||
+13
@@ -1,7 +1,20 @@
|
||||
.prism.log
|
||||
.stdy.log
|
||||
.env
|
||||
.env.*
|
||||
*.pem
|
||||
*.key
|
||||
*.crt
|
||||
credentials.json
|
||||
node_modules/
|
||||
__pycache__/
|
||||
.venv/
|
||||
.DS_Store
|
||||
.gradle
|
||||
.idea
|
||||
.kotlin
|
||||
**/bin/main/
|
||||
**/bin/test/
|
||||
build/
|
||||
codegen.log
|
||||
kls_database.db
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.1.0-alpha.23"
|
||||
".": "0.1.0-beta.19"
|
||||
}
|
||||
+1
-4
@@ -1,4 +1 @@
|
||||
configured_endpoints: 100
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/langsmith%2Flangsmith-api-9301fa88ebc17384444c65b726e0a222d018834fa33dedcc2d32f31f460337cb.yml
|
||||
openapi_spec_hash: 3e08d32477a470030a45ceb0f15aabf2
|
||||
config_hash: dbf6574c76c5c4ae18ffb6d212de7a7c
|
||||
configured_endpoints: 149
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
# Agent Guidelines
|
||||
|
||||
Code conventions and patterns for this project, learned from review feedback.
|
||||
|
||||
## Code structure
|
||||
|
||||
### Break up complex functions with helpers
|
||||
|
||||
When a function has deeply nested logic or multiple concerns, extract helpers. Use `flatMap` + small named functions instead of imperative loops with nested `when`/`if`:
|
||||
|
||||
```kotlin
|
||||
// Good
|
||||
fun format(variables: Map<String, Any>): PromptMessages {
|
||||
val formatted = messages.flatMap { msg ->
|
||||
if (msg.isPlaceholder()) expandPlaceholder(msg, variables)
|
||||
else listOf(PromptMessage.withTemplate(msg, msg.format(variables)))
|
||||
}
|
||||
return PromptMessages(formatted, inputVariables, outputSchema)
|
||||
}
|
||||
|
||||
private fun expandPlaceholder(msg: PromptMessage, variables: Map<String, Any>): List<PromptMessage> {
|
||||
val items = variables[msg.template] as? List<*> ?: return emptyList()
|
||||
return items.mapNotNull(::toPromptMessage)
|
||||
}
|
||||
|
||||
// Bad — deeply nested imperative loop
|
||||
fun format(variables: Map<String, Any>): PromptMessages {
|
||||
val formatted = mutableListOf<PromptMessage>()
|
||||
for (msg in messages) {
|
||||
if (msg.isPlaceholder()) {
|
||||
val value = variables[msg.template]
|
||||
if (value is List<*>) {
|
||||
for (item in value) {
|
||||
when (item) {
|
||||
is PromptMessage -> formatted.add(item)
|
||||
is Map<*, *> -> { /* 15 more lines */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Kotlin idioms
|
||||
|
||||
### Prefer immutable collection transformations
|
||||
|
||||
Avoid mutable accumulators when `map`, `filter`, `partition`, `associate`, `buildMap`, or `buildList` express the same logic clearly. Use mutation only when it materially improves readability, performance, or is required by an API.
|
||||
|
||||
### Use `buildMap` / `buildList` instead of mutable + convert
|
||||
|
||||
```kotlin
|
||||
// Good
|
||||
val messages = items.map { msg ->
|
||||
buildMap<String, String> {
|
||||
put("role", msg.role)
|
||||
put("content", msg.content)
|
||||
msg.toolCallId?.let { put("tool_call_id", it) }
|
||||
}
|
||||
}
|
||||
|
||||
// Bad — unnecessary mutable/immutable conversion
|
||||
val messages = items.map { msg ->
|
||||
val base = mutableMapOf<String, String>(
|
||||
"role" to msg.role,
|
||||
"content" to msg.content,
|
||||
)
|
||||
if (msg.toolCallId != null) {
|
||||
base["tool_call_id"] = msg.toolCallId
|
||||
}
|
||||
base.toMap()
|
||||
}
|
||||
```
|
||||
|
||||
### Use `buildList` for conditional `toString()` parts
|
||||
|
||||
```kotlin
|
||||
// Good
|
||||
override fun toString(): String {
|
||||
val parts = buildList {
|
||||
add("messages=[${messages.joinToString(", ")}]")
|
||||
if (inputVariables.isNotEmpty()) add("inputVariables=$inputVariables")
|
||||
commitHash?.let { add("commitHash=$it") }
|
||||
outputSchema?.let { add("outputSchema=${it["title"] ?: "..."}") }
|
||||
}
|
||||
return "Prompt{${parts.joinToString(", ")}}"
|
||||
}
|
||||
|
||||
// Bad — chained ternary string concatenation
|
||||
override fun toString(): String =
|
||||
"Prompt{messages=[...]" +
|
||||
(if (commitHash != null) ", commitHash=$commitHash" else "") +
|
||||
(if (hasOutputSchema()) ", outputSchema=..." else "") +
|
||||
"}"
|
||||
```
|
||||
|
||||
### Prefer extension functions over type casts
|
||||
|
||||
When adding behavior to a type you don't own, use a `private` extension function instead of casting to an implementation type:
|
||||
|
||||
```kotlin
|
||||
// Good — extension function, no cast needed
|
||||
private fun Stream<*>.withErrorTracking(
|
||||
errorRef: AtomicReference<Throwable>,
|
||||
exhaustedRef: AtomicBoolean,
|
||||
): Stream<Any?> { ... }
|
||||
|
||||
val instrumented = result.withErrorTracking(iterationError, streamExhausted)
|
||||
|
||||
// Bad — casting to implementation type
|
||||
val instrumented = wrapStreamWithErrorCapture(result, iterationError, streamExhausted)
|
||||
// or worse:
|
||||
(runs as? RunServiceImpl)?.flush()
|
||||
```
|
||||
|
||||
### Use `partition` instead of double `filter`
|
||||
|
||||
```kotlin
|
||||
// Good — single pass
|
||||
val (systemMessages, nonSystemMessages) =
|
||||
messages.partition { it.role == Role.SYSTEM }
|
||||
|
||||
// Bad — iterates the list twice
|
||||
val systemMessages = messages.filter { it.role == Role.SYSTEM }
|
||||
val nonSystemMessages = messages.filter { it.role != Role.SYSTEM }
|
||||
```
|
||||
|
||||
### Use parameterized tests for table-driven cases
|
||||
|
||||
When multiple tests share the exact same structure (input → assert same fields), use `@ParameterizedTest` with `@MethodSource`:
|
||||
|
||||
```kotlin
|
||||
data class Case(val input: String, val expected: String)
|
||||
|
||||
@ParameterizedTest(name = "{index}: \"{0}\"")
|
||||
@MethodSource("cases")
|
||||
fun myTest(case: Case) {
|
||||
assertThat(transform(case.input)).isEqualTo(case.expected)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun cases(): Stream<Case> = Stream.of(
|
||||
Case("input1", "expected1"),
|
||||
Case("input2", "expected2"),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Only do this when every test has the same assertion shape. If tests have different setup or assertions, keep them as individual `@Test` methods.
|
||||
|
||||
### Extract test assertion helpers to reduce repetition
|
||||
|
||||
When the same assertion pattern appears across many tests, extract a helper:
|
||||
|
||||
```kotlin
|
||||
// Good — readable, DRY
|
||||
private fun assertMessage(msg: Map<String, String>, role: String, content: String) {
|
||||
assertThat(msg["role"]).isEqualTo(role)
|
||||
assertThat(msg["content"]).isEqualTo(content)
|
||||
}
|
||||
|
||||
assertMessage(result.messages[0], "system", "You are helpful.")
|
||||
assertMessage(result.messages[1], "user", "Hello")
|
||||
|
||||
// Bad — verbose, repetitive
|
||||
assertThat(result.messages[0]).isEqualTo(mapOf("role" to "system", "content" to "You are helpful."))
|
||||
assertThat(result.messages[1]).isEqualTo(mapOf("role" to "user", "content" to "Hello"))
|
||||
```
|
||||
|
||||
## Formatting and linting
|
||||
|
||||
```bash
|
||||
./gradlew :langsmith-java-core:formatKotlin
|
||||
./gradlew lintKotlin
|
||||
```
|
||||
|
||||
The project uses ktfmt with `--kotlinlang-style`.
|
||||
|
||||
## Testing
|
||||
|
||||
### Don't add comments that restate the test name
|
||||
|
||||
Test function names should be descriptive enough on their own. Don't add comments that repeat what the name already says:
|
||||
|
||||
```kotlin
|
||||
// Good — name is self-documenting
|
||||
@Test
|
||||
fun parseLegacyPromptTemplateWithTemplateFormat() {
|
||||
val manifest = ...
|
||||
}
|
||||
|
||||
// Bad — comment restates the function name
|
||||
@Test
|
||||
fun parseLegacyPromptTemplateWithTemplateFormat() {
|
||||
// Legacy PromptTemplate format with template_format field
|
||||
val manifest = ...
|
||||
}
|
||||
```
|
||||
|
||||
### Running tests
|
||||
|
||||
Try to run targeted tests matching the code you changed:
|
||||
|
||||
```bash
|
||||
# Run tests for the package you changed
|
||||
./gradlew :langsmith-java-core:test --tests "com.langchain.smith.prompts.*" --rerun
|
||||
|
||||
# Run a single test class
|
||||
./gradlew :langsmith-java-core:test --tests "com.langchain.smith.prompts.ManifestParserTest" --rerun
|
||||
|
||||
# Run a single test method
|
||||
./gradlew :langsmith-java-core:test --tests "com.langchain.smith.prompts.ManifestParserTest.parsePromptTemplate" --rerun
|
||||
|
||||
# See println output
|
||||
./gradlew :langsmith-java-core:test --tests "..." --rerun --info
|
||||
```
|
||||
|
||||
Only run the full suite (`./gradlew :langsmith-java-core:test --rerun`) before finalizing a PR or after large cross-cutting changes.
|
||||
|
||||
### Integration tests
|
||||
|
||||
Integration tests require environment variables:
|
||||
|
||||
```bash
|
||||
export LANGSMITH_API_KEY="lsv2_pt_..."
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
```
|
||||
|
||||
Tests skip gracefully via `assumeTrue` if keys are missing.
|
||||
|
||||
## Code style
|
||||
|
||||
- For cross-method concurrency coordination, prefer an explicit named `ReentrantLock` over `synchronized` when review clarity matters. Use Kotlin's `withLock { ... }` extension instead of manual `lock()` / `try` / `finally { unlock() }` unless explicit lock management is required. Keep the locked section minimal and do slow/blocking work outside the lock.
|
||||
- Choose the simplest concurrency primitive that fits the state being protected:
|
||||
- Use atomic types (`AtomicBoolean`, `AtomicInteger`, etc.) for simple flags, counters, and compare-and-set state.
|
||||
- Use `synchronized` only for small, local critical sections where a named lock would not improve clarity.
|
||||
- Use `ReentrantReadWriteLock` when reads are frequent, writes are infrequent, and concurrent reads materially help.
|
||||
- Use coroutine `Mutex.withLock { ... }` for coroutine-based concurrency instead of blocking thread locks.
|
||||
- `toString()` should be single-line, following the `ClassName{field=value, field=value}` convention used by the rest of the SDK.
|
||||
- Avoid `@Suppress("UNCHECKED_CAST")` — restructure code to use safe patterns (`as? String`, `is Map<*, *>` with `entries.associate`, etc). When unavoidable (e.g. generic type erasure after an `is` check), add a comment explaining why the cast is safe.
|
||||
- Use named arguments for constructor/function calls with 2+ parameters, especially when types could be confused:
|
||||
```kotlin
|
||||
// Good
|
||||
PromptMessage(
|
||||
role = PromptMessage.Role.HUMAN,
|
||||
template = template,
|
||||
templateFormat = templateFormat,
|
||||
)
|
||||
|
||||
// Bad — positional args are ambiguous
|
||||
PromptMessage(PromptMessage.Role.HUMAN, template, templateFormat = templateFormat)
|
||||
```
|
||||
- Name functions from the caller's perspective — describe what the caller gets, not what the function does internally. Prefer `stream.withErrorTracking()` over `wrapStreamWithErrorCapture(stream)`.
|
||||
- When an `Optional` has a fallback default, use `orElse(default)` directly instead of `orElse(null) ?: default`:
|
||||
```kotlin
|
||||
// Good — default goes straight into orElse
|
||||
kwargs["template_format"]?.asString()?.orElse("f-string") ?: "f-string"
|
||||
|
||||
// Bad — creates unnecessary null intermediary
|
||||
kwargs["template_format"]?.asString()?.orElse(null) ?: "f-string"
|
||||
```
|
||||
The `?: "f-string"` is still needed to handle the case where the key is missing from the map (`null` from `kwargs["template_format"]`), but `orElse` should carry the default for when the key exists but isn't a string.
|
||||
- Anthropic SDK is a `compileOnly` dependency — users must add it themselves. Methods that use Anthropic types should catch `NoClassDefFoundError` and throw `IllegalStateException` with a clear message.
|
||||
+523
@@ -1,5 +1,528 @@
|
||||
# Changelog
|
||||
|
||||
## [0.1.0-beta.19](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.18...v0.1.0-beta.19) (2026-07-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **backend:** Allow storing grid layout to custom dashboards ([8aded40](https://github.com/langchain-ai/langsmith-java/commit/8aded40800f6096880f43d2ef51a07963dec65e5))
|
||||
* **backend:** update CRUD endpoint to allow retrieving / storing chart series metadata ([aa4be05](https://github.com/langchain-ai/langsmith-java/commit/aa4be05c111d702aa801cba4acbda69e0a5da7a6))
|
||||
* **host:** p50/p99 run queue wait-time chart in project monitoring [LSD-1547] ([f660828](https://github.com/langchain-ai/langsmith-java/commit/f6608288d483e0041c6e0fe643f6881f3c1a3a68))
|
||||
* **sandboxes:** free-form labels on sandboxes and snapshots ([6165434](https://github.com/langchain-ai/langsmith-java/commit/6165434d21025dfaeef22b4a63e31a0f0e4977a1))
|
||||
* **sandboxes:** let proxy rules contribute sandbox env vars ([eace8c3](https://github.com/langchain-ai/langsmith-java/commit/eace8c3f9903e180fc7e9cfbaf0a13a7a91a5635))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **evaluators:** persist and wire through playground_settings_id for llm-as-judge oauth models [part of ENT-1402] ([b072b2e](https://github.com/langchain-ai/langsmith-java/commit/b072b2e2fcf6dafd32cb0213e02127a82ab0659a))
|
||||
* **runs:** require session in POST /runs/stats ([056dbd3](https://github.com/langchain-ai/langsmith-java/commit/056dbd3673294fcd1fbc564d4332ba38c151ec6b))
|
||||
* support last_queued_at in v2 runs query ([e7fe407](https://github.com/langchain-ai/langsmith-java/commit/e7fe407f74819ac775fcc092289d9dcfaafc036d))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* **runs:** require session in POST /runs/stats ([95da1e9](https://github.com/langchain-ai/langsmith-java/commit/95da1e9b944058f30b5274c882f4669933b8bbfd))
|
||||
|
||||
|
||||
### Refactors
|
||||
|
||||
* **runs:** remove SmithDB v2 endpoint flag [LSO-3391] ([aece72d](https://github.com/langchain-ai/langsmith-java/commit/aece72d035ca24eb8ee94236d301f7610b18eefc))
|
||||
|
||||
## [0.1.0-beta.18](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.17...v0.1.0-beta.18) (2026-07-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **runs:** add GET /v2/runs/{run_id}/url endpoint and SDK runs.get_url method ([57da309](https://github.com/langchain-ai/langsmith-java/commit/57da3096437fe210865bab3a7560eff50d20feef))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* build, lint, and test failures (feat(gateway): rate limit policy CRUD, validation, and materialization [ENT-1138]) ([388984c](https://github.com/langchain-ai/langsmith-java/commit/388984c00503fe2dd39aacb93fe4053ae2030d43))
|
||||
|
||||
## [0.1.0-beta.17](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.16...v0.1.0-beta.17) (2026-07-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **runs/v2:** make start_time optional in GET /v2/runs/:id ([101472f](https://github.com/langchain-ai/langsmith-java/commit/101472f4ce1210028bb37a577a3992a49511af40))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* remove stlc-promote-to-main workflow ([#45](https://github.com/langchain-ai/langsmith-java/issues/45)) ([df8f95a](https://github.com/langchain-ai/langsmith-java/commit/df8f95af97e784a035da210551d5aa17c63608c3))
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* minor rewording of List Commits endpoint description ([2f1ca87](https://github.com/langchain-ai/langsmith-java/commit/2f1ca870dd9a60016d2c0bad66b1ece2826d41da))
|
||||
|
||||
## [0.1.0-beta.16](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.15...v0.1.0-beta.16) (2026-07-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add run rule evaluator tracing flag [LSO-3323] ([d53da42](https://github.com/langchain-ai/langsmith-java/commit/d53da42ffaa0d5072409f45d4398a03741013c6a))
|
||||
* **engine:** issue lifecycle backend + webhook status filter (2/8) ([cbc0080](https://github.com/langchain-ai/langsmith-java/commit/cbc00807ee3991638b76430095c521243bcf5675))
|
||||
* **feedback:** support GET /feedback for thread AQs ([5052542](https://github.com/langchain-ai/langsmith-java/commit/50525423cca7d2024be967a074267a97068c33ed))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **datasets:** serve public dataset sessions via Go, fixing SmithDB-only 501 ([8eb0670](https://github.com/langchain-ai/langsmith-java/commit/8eb067064eedc528a7e3381e9a84c9bceff0886f))
|
||||
* patch 6 high-severity security alerts ([#42](https://github.com/langchain-ai/langsmith-java/issues/42)) ([8b1dff9](https://github.com/langchain-ai/langsmith-java/commit/8b1dff9a1ed8a7982278f393ca2859a3d96a97a0))
|
||||
* remove July 2026 premier plan ([1409dd8](https://github.com/langchain-ai/langsmith-java/commit/1409dd89d8da75b5650ca15509c6e73d545364df))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* new unshare endpoint that doesn't require lookup ([8391078](https://github.com/langchain-ai/langsmith-java/commit/839107858ca208c7238ef83e4fbd9379e31bf220))
|
||||
* Stainless SDK methods for V2 public-run APIs ([306a079](https://github.com/langchain-ai/langsmith-java/commit/306a079479b914fc67a0d7627f29c8c95b6b5c4c))
|
||||
|
||||
## [0.1.0-beta.15](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.14...v0.1.0-beta.15) (2026-07-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add July 2026 startup and premier plan tiers ([b2ed882](https://github.com/langchain-ai/langsmith-java/commit/b2ed882d0edd038733fcdeda5448519a5f4dfe9b))
|
||||
* **context-hub:** fire commit webhook and deliver async ([2c9704f](https://github.com/langchain-ai/langsmith-java/commit/2c9704f3c0151bd51ff57ecc54edbcfb527e61d7))
|
||||
* **sandboxes:** add preserve_memory_on_stop create attribute ([99fe7db](https://github.com/langchain-ai/langsmith-java/commit/99fe7dbe33f3a0bb8709577bcd08a3f24113d5e4))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* update smithdb proto generated code ([d35ada2](https://github.com/langchain-ai/langsmith-java/commit/d35ada22c7e73d60b6fc44185d7a65a99a4255e1))
|
||||
|
||||
## [0.1.0-beta.14](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.13...v0.1.0-beta.14) (2026-07-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **dashboards:** convert prebuilts to v2 charts on clone [LSO-3306, LSO-3322] ([0a29041](https://github.com/langchain-ai/langsmith-java/commit/0a2904189719cead7198fcc1765e2c44ab36dec9))
|
||||
* **evaluators:** expose is_managed on Go evaluator reads [LSE-2532] ([17551de](https://github.com/langchain-ai/langsmith-java/commit/17551decf77e4e2141473fe9cdbf33ea4b4648b6))
|
||||
* forward filter param to thread stats API ([12185be](https://github.com/langchain-ai/langsmith-java/commit/12185beb4cf348a4252af15257ae2b0133027a42))
|
||||
* **sdk:** wire annotation-queue runs/by-key into stainless [LSAQ-141] ([18d7867](https://github.com/langchain-ai/langsmith-java/commit/18d7867206e7b66a91a3d4452811636aa173e55c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* patch remaining Java security alerts ([#38](https://github.com/langchain-ai/langsmith-java/issues/38)) ([e639046](https://github.com/langchain-ai/langsmith-java/commit/e639046090177450d005f9fa511d4c03a565a504))
|
||||
* **smith-go:** default time bounds for POST /v2/threads/query ([63bde3c](https://github.com/langchain-ai/langsmith-java/commit/63bde3c6e4ef1c470a82a7814dd5f6083f98c110))
|
||||
* **smith-sdks:** name thread stats response model ThreadStats ([1893f83](https://github.com/langchain-ai/langsmith-java/commit/1893f83cf27ed269a62fba637c829e31c1a94bf8))
|
||||
|
||||
|
||||
### Refactors
|
||||
|
||||
* **sandboxes:** make context hub sync a generic mount ([9cfd26b](https://github.com/langchain-ai/langsmith-java/commit/9cfd26bef9d4efc3d66d25f6a54f2224393d935b))
|
||||
|
||||
## [0.1.0-beta.13](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.12...v0.1.0-beta.13) (2026-07-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Adds attachment support to Java multipart requests ([#33](https://github.com/langchain-ai/langsmith-java/issues/33)) ([38646cb](https://github.com/langchain-ai/langsmith-java/commit/38646cb1fabc1155b36ea8db43c69f2dd38dede9))
|
||||
* **backend:** extend_trace_retention field for annotation queue adds ([38646cb](https://github.com/langchain-ai/langsmith-java/commit/38646cb1fabc1155b36ea8db43c69f2dd38dede9))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* re-emit commits missed by release-please due to timestamp ordering ([#181](https://github.com/langchain-ai/langsmith-java/issues/181)) ([38646cb](https://github.com/langchain-ai/langsmith-java/commit/38646cb1fabc1155b36ea8db43c69f2dd38dede9))
|
||||
|
||||
|
||||
### Refactors
|
||||
|
||||
* **sandboxes:** resolve default sandbox via snapshot tag instead of blueprint ([38646cb](https://github.com/langchain-ai/langsmith-java/commit/38646cb1fabc1155b36ea8db43c69f2dd38dede9))
|
||||
|
||||
## [0.1.0-beta.12](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.11...v0.1.0-beta.12) (2026-07-08)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **stainless:** skip sessions resource for python and typescript
|
||||
|
||||
### Features
|
||||
|
||||
* **managed-eval:** expose is_managed_evaluator on run-rule read/write paths ([8e17124](https://github.com/langchain-ai/langsmith-java/commit/8e17124b681e504789b3bcb7b03653e327339d1a))
|
||||
* **smith-sdks:** expose threads and traces v2 endpoints in public SDK ([3bc6173](https://github.com/langchain-ai/langsmith-java/commit/3bc61733286b179f5567439dd65cf94e6faff571))
|
||||
* **stainless:** skip sessions resource for python and typescript ([d71cff9](https://github.com/langchain-ai/langsmith-java/commit/d71cff9f592c797a6b7383755ebfde6feea7e7c6))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove duplicate threads/traces declarations in client impls ([#35](https://github.com/langchain-ai/langsmith-java/issues/35)) ([fdba8e6](https://github.com/langchain-ai/langsmith-java/commit/fdba8e6123bda467312acd260ef7cf20e21dba9b))
|
||||
* **smith-sdks:** remove dead retrieve_thread_preview SDK mapping ([585b12d](https://github.com/langchain-ai/langsmith-java/commit/585b12dd2672808df97c6221c783342e17dd6755))
|
||||
* **smith-sdks:** rename dataset runs POST methods from create to query ([4513b3f](https://github.com/langchain-ai/langsmith-java/commit/4513b3ffb05b51ec5be3e5f394d312726de1dd13))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* **stainless:** skip all datasets endpoints in Python/TS SDKs except experiment-runs ([6ab6fa2](https://github.com/langchain-ai/langsmith-java/commit/6ab6fa281f3f9db790121a94d20a32323e56dc80))
|
||||
|
||||
## [0.1.0-beta.11](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.10...v0.1.0-beta.11) (2026-07-02)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* disallow run_count session sort
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* restore title/additionalProperties on v2 RunResponse JSON fields for Stainless ([e3b4784](https://github.com/langchain-ai/langsmith-java/commit/e3b478437ef307d6895f0256b4945e68f5e32a23))
|
||||
|
||||
|
||||
### Refactors
|
||||
|
||||
* disallow run_count session sort ([a00b1f1](https://github.com/langchain-ai/langsmith-java/commit/a00b1f18ca2c9b6048f4789df0da60704852a79a))
|
||||
|
||||
## [0.1.0-beta.10](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.9...v0.1.0-beta.10) (2026-07-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **evaluation:** add core evaluate() param parity ([#17](https://github.com/langchain-ai/langsmith-java/issues/17)) ([7d9a2fa](https://github.com/langchain-ai/langsmith-java/commit/7d9a2fafd45c60dbe11852c1c4c74565c4517595))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ci:** add GH_REPO to release-please trigger publish step ([#24](https://github.com/langchain-ai/langsmith-java/issues/24)) ([10a1f9b](https://github.com/langchain-ai/langsmith-java/commit/10a1f9bcbbf662e048b4e4f0a1bb4e9a29f18bc7))
|
||||
* update hand-written code to use RunIngest after stainless rename ([#25](https://github.com/langchain-ai/langsmith-java/issues/25)) ([4d45358](https://github.com/langchain-ai/langsmith-java/commit/4d45358e0ef8c0df8905cc3f927a495af2bcbeca))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* **examples:** dual-write source run lookup coordinates in Python ([2d4900b](https://github.com/langchain-ai/langsmith-java/commit/2d4900b16bdda9512fa71368dd03f4729f55620a))
|
||||
* **stainless:** rename run models in runs resource ([2f0ce40](https://github.com/langchain-ai/langsmith-java/commit/2f0ce40b862dc33d52ff8cb0fb8d50fde790ef73))
|
||||
|
||||
## [0.1.0-beta.9](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.8...v0.1.0-beta.9) (2026-06-29)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **api:** document v2 experiment-runs and de-publicize legacy dataset endpoints
|
||||
* **backend:** accept float value for percentile metric [LS-000]
|
||||
|
||||
### Features
|
||||
|
||||
* add GET endpoint to retrieve single workspace by id [closes ([34f1c52](https://github.com/langchain-ai/langsmith-java/commit/34f1c52fc1088310910de4f21670e2a4d41d196b))
|
||||
* add GET endpoint to retrieve single workspace by id [closes ENT-508] ([cdbd369](https://github.com/langchain-ai/langsmith-java/commit/cdbd36936fde80c7c3dea773deefd2789d0feecf))
|
||||
* **api:** document v2 experiment-runs and de-publicize legacy dataset endpoints ([387526f](https://github.com/langchain-ai/langsmith-java/commit/387526fd3ac9e892de0786be5b538c53d2f40049))
|
||||
* **evaluation:** add Java evaluate orchestrator ([#8](https://github.com/langchain-ai/langsmith-java/issues/8)) ([34f1c52](https://github.com/langchain-ai/langsmith-java/commit/34f1c52fc1088310910de4f21670e2a4d41d196b))
|
||||
* **evaluation:** add Java evaluate orchestrator ([#8](https://github.com/langchain-ai/langsmith-java/issues/8)) ([d1420ef](https://github.com/langchain-ai/langsmith-java/commit/d1420efd7a9c307d8b99d64ac57e3f0f26b1580b))
|
||||
* **sandbox-host:** per-VM cgroups for CPU bursting (2x), fractional ([34f1c52](https://github.com/langchain-ai/langsmith-java/commit/34f1c52fc1088310910de4f21670e2a4d41d196b))
|
||||
* **sandbox-host:** per-VM cgroups for CPU bursting (2x), fractional vCPU, and sustained-burst limiting ([49bf68d](https://github.com/langchain-ai/langsmith-java/commit/49bf68d74763138fa7894d0e04eb1b73e72e82b3))
|
||||
* **sandboxes:** expose registry CRUD and generate Python/TypeScript SDK ([34f1c52](https://github.com/langchain-ai/langsmith-java/commit/34f1c52fc1088310910de4f21670e2a4d41d196b))
|
||||
* **sandboxes:** expose registry CRUD and generate Python/TypeScript SDK clients ([cfed196](https://github.com/langchain-ai/langsmith-java/commit/cfed196979dce85b747a4171cb1dcacb38f88641))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** accept float value for percentile metric [LS-000] ([e885608](https://github.com/langchain-ai/langsmith-java/commit/e88560861c9f3de50e7448b97d2cde1cfdfbca46))
|
||||
* trigger publish-sonatype workflow after release-please creates a ([34f1c52](https://github.com/langchain-ai/langsmith-java/commit/34f1c52fc1088310910de4f21670e2a4d41d196b))
|
||||
* trigger publish-sonatype workflow after release-please creates a release ([#13](https://github.com/langchain-ai/langsmith-java/issues/13)) ([70edc45](https://github.com/langchain-ai/langsmith-java/commit/70edc4514cfc54a98fbf5605795a386b8149300d))
|
||||
|
||||
## [0.1.0-beta.8](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.7...v0.1.0-beta.8) (2026-06-25)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* **sandboxes:** default S3 mount endpoint
|
||||
* **sandboxes:** add ArtifactFS git mounts
|
||||
* **sandboxes:** add gcs bucket mounts
|
||||
|
||||
### Features
|
||||
|
||||
* **abac:** add tag_value_ids to dataset creation endpoints ([280a374](https://github.com/langchain-ai/langsmith-java/commit/280a37489da492957d07773692e5ae62140d98d0))
|
||||
* **abac:** add tag_value_ids to prompt creation endpoints [ENT-1176] ([f3ce273](https://github.com/langchain-ai/langsmith-java/commit/f3ce273a028c0de24171835d854edb4c69268d91))
|
||||
* **abac:** add tag_value_ids to tracer session creation ([f859585](https://github.com/langchain-ai/langsmith-java/commit/f859585592c616310f02f91d8f0f6822ab9be17d))
|
||||
* add online evaluator endpoints to stainless config ([77d19d3](https://github.com/langchain-ai/langsmith-java/commit/77d19d324195ca6a93b51ea74aaa4484fb7a2cef))
|
||||
* adding time to first token for experiment metrics ([5a6bddd](https://github.com/langchain-ai/langsmith-java/commit/5a6bddd241bb95b0d176d70ae36c810a8157177d))
|
||||
* **backend:** allow retrieving and storing V2 charts in database [LSO-2799] ([b86e854](https://github.com/langchain-ai/langsmith-java/commit/b86e854687089b26205de336c8c99d994fd57369))
|
||||
* end-to-end OAuth bearer for Playground [part of ENT-760] ([77f12e3](https://github.com/langchain-ai/langsmith-java/commit/77f12e3a6e6d627a0e2309ecd35396abb01448ac))
|
||||
* **evaluation:** Java evaluate() primitives ([#7](https://github.com/langchain-ai/langsmith-java/issues/7)) ([a82b5c8](https://github.com/langchain-ai/langsmith-java/commit/a82b5c88fc08de041e78657d368c2b0359196248))
|
||||
* **fleet:** frontend passes typed fields (closes AB-000) ([bbbcf7b](https://github.com/langchain-ai/langsmith-java/commit/bbbcf7bf4027f95b13f2a0bffe18564da3fcfbe9))
|
||||
* **group-stats:** back thread stats with SmithDB ([7f24878](https://github.com/langchain-ai/langsmith-java/commit/7f24878418347b187fbc2c7064c52725de775c8b))
|
||||
* **hub:** add include_owners to repos list for Fleet [closes AB-2537] ([10f1d65](https://github.com/langchain-ai/langsmith-java/commit/10f1d6506332402672366470117774db374350a6))
|
||||
* make online evaluator retention opt-in (backend) ([e480cd4](https://github.com/langchain-ai/langsmith-java/commit/e480cd4104a22b71607aca0f209b416320fe62e0))
|
||||
* **run-rules:** per-action trace-retention control for automations [LSO-2749] ([3c218e9](https://github.com/langchain-ai/langsmith-java/commit/3c218e98bc255847a60b75499d9230372468adb6))
|
||||
* **runs/v2:** expose runs v2 endpoints publicly with stainless config ([48c283c](https://github.com/langchain-ai/langsmith-java/commit/48c283c7a7448b03b52185053a578027005bbdbd))
|
||||
* **sandboxes:** add ArtifactFS git mounts ([2ddc687](https://github.com/langchain-ai/langsmith-java/commit/2ddc687610df41bf15429b2492fd85cbcff86472))
|
||||
* **sandboxes:** add GCP proxy auth flow ([fd2980c](https://github.com/langchain-ai/langsmith-java/commit/fd2980ce69f3be63a0fd44922f08f72a0a6d9ac2))
|
||||
* **sandboxes:** add gcs bucket mounts ([87e3acb](https://github.com/langchain-ai/langsmith-java/commit/87e3acbb7b1d871a44e82472ff7fbce8669395de))
|
||||
* **sandboxes:** add sandbox env var support ([da0fc7b](https://github.com/langchain-ai/langsmith-java/commit/da0fc7b507828609f0264e90a50f6f8910ce9567))
|
||||
* **sandboxes:** filter lists by creator [INF-1492] ([bb9f973](https://github.com/langchain-ai/langsmith-java/commit/bb9f973f41a930ed23d33ee42deeaaaba4d07c35))
|
||||
* **sandboxes:** move mounts under mount_config ([734a828](https://github.com/langchain-ai/langsmith-java/commit/734a8285cd37d10e9676e67ae8fa98b6420514b0))
|
||||
* **sandboxes:** proxy runtime routes through platform backend [INF-0000] ([7cb70fc](https://github.com/langchain-ai/langsmith-java/commit/7cb70fc3e3dc71788c9ad66360dbcf4d577a098d))
|
||||
* **sandboxes:** resolve AWS proxy rule secrets ([cb519f9](https://github.com/langchain-ai/langsmith-java/commit/cb519f93d44d0a34633cae88c0fa79a4c3db182b))
|
||||
* **sandboxes:** set AWS proxy compatibility env ([6bbe522](https://github.com/langchain-ai/langsmith-java/commit/6bbe522f0a167e6671c5528eec2fa95dde0b680e))
|
||||
* **sandboxes:** snapshot memory from stopped boxes + honor restore_memory in v2 [INF-0000] ([79647d1](https://github.com/langchain-ai/langsmith-java/commit/79647d1579da9cdc6027f7922075ddb489e9601a))
|
||||
* **stats:** add include_details param to /runs/stats for backwards compat ([49039f2](https://github.com/langchain-ai/langsmith-java/commit/49039f277018a60f46af85108a30815609c8482c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **evaluators:** show code evaluator trace counts [LSE-2359] ([3f3a0c9](https://github.com/langchain-ai/langsmith-java/commit/3f3a0c9c30fc61198a19b27c7b3c6b8e5aa444c6))
|
||||
* **sandboxes:** default S3 mount endpoint ([54d105e](https://github.com/langchain-ai/langsmith-java/commit/54d105ef91e0c65de0f2a77ccc7e794570da15cb))
|
||||
* **sandboxes:** use built-in gcp proxy host matching ([ac8da0f](https://github.com/langchain-ai/langsmith-java/commit/ac8da0f77c706c7fc072842d5ec4e95b4f82a2d7))
|
||||
* **tests:** remove duplicate retrieve() method in RunServiceTest ([#10](https://github.com/langchain-ai/langsmith-java/issues/10)) ([ba419de](https://github.com/langchain-ai/langsmith-java/commit/ba419de13e4749f8427a98b1572bc1764404dd20))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* add release-please workflow ([#11](https://github.com/langchain-ai/langsmith-java/issues/11)) ([65b5b9a](https://github.com/langchain-ai/langsmith-java/commit/65b5b9ae402be8b84253d5ca7c255bd188c0e415))
|
||||
* add stlc sync and promotion workflows ([#5](https://github.com/langchain-ai/langsmith-java/issues/5)) ([f00fef5](https://github.com/langchain-ai/langsmith-java/commit/f00fef565e6c5c5edfa651223790e8e9cf90c317))
|
||||
* **fleet:** add param to list threads [closes AB-2522] ([8b85e16](https://github.com/langchain-ai/langsmith-java/commit/8b85e16472982a2821e82a6677c843d0565d17f3))
|
||||
* **smith-sdks:** update Java SDK stainless custom-code tracking file ([17bb73a](https://github.com/langchain-ai/langsmith-java/commit/17bb73a0061627a86d2ed1dba19316dcdfaf9c6d))
|
||||
|
||||
|
||||
### Refactors
|
||||
|
||||
* **playground:** remove legacy experiment endpoints [LSO-2230] ([370b940](https://github.com/langchain-ai/langsmith-java/commit/370b940a1ca252f0198b49d315749dc5d9282714))
|
||||
* rename field to match prev trace retention fields ([b963cab](https://github.com/langchain-ai/langsmith-java/commit/b963cab6c63f10bed9a7a1237b6df12c111be9d4))
|
||||
|
||||
## 0.1.0-beta.7 (2026-05-27)
|
||||
|
||||
Full Changelog: [v0.1.0-beta.6...v0.1.0-beta.7](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.6...v0.1.0-beta.7)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([cad4993](https://github.com/langchain-ai/langsmith-java/commit/cad499356e3a38d660814b7ceed92eafbcf2989d))
|
||||
* **api:** api update ([84e287c](https://github.com/langchain-ai/langsmith-java/commit/84e287ca9be34e45b80fd0d33c3ebc1dd1ca0f32))
|
||||
* **api:** api update ([ce44c3b](https://github.com/langchain-ai/langsmith-java/commit/ce44c3b540624598c9152b8d1d469bb28ec5c67e))
|
||||
* **api:** api update ([81f7b39](https://github.com/langchain-ai/langsmith-java/commit/81f7b3937992f117bc573e968e9d5988c5bb5399))
|
||||
* **api:** api update ([3d0c992](https://github.com/langchain-ai/langsmith-java/commit/3d0c992f4c4772d5a3e294ebeba2b28a641b21ed))
|
||||
* **api:** api update ([b991c00](https://github.com/langchain-ai/langsmith-java/commit/b991c00c14ee68cffd2df17c6d6bdbc6c780cfd1))
|
||||
* **api:** api update ([104d3c6](https://github.com/langchain-ai/langsmith-java/commit/104d3c65623d66f24f90589df3ee04586fa8d913))
|
||||
* **api:** api update ([e01a424](https://github.com/langchain-ai/langsmith-java/commit/e01a42416bef9d17a897900e70cbef70e13ad552))
|
||||
* **api:** api update ([e1443b6](https://github.com/langchain-ai/langsmith-java/commit/e1443b69ab955cbdcee618449f11bc3cdd41add4))
|
||||
* **api:** api update ([9deafad](https://github.com/langchain-ai/langsmith-java/commit/9deafade612c0e7c163bf13fae4c2d27751122be))
|
||||
* **api:** api update ([7e7c479](https://github.com/langchain-ai/langsmith-java/commit/7e7c479912229f27ae96878f2e939971b5fff829))
|
||||
* **api:** api update ([b5a3200](https://github.com/langchain-ai/langsmith-java/commit/b5a32009fc34eb5a04d70205c19bf973414d800f))
|
||||
* **api:** api update ([3ab0c6c](https://github.com/langchain-ai/langsmith-java/commit/3ab0c6c5482c2378fc1d2a2b8657248ec218a501))
|
||||
* **client:** support proxy authentication ([d9e3ab5](https://github.com/langchain-ai/langsmith-java/commit/d9e3ab5b34a8fc2321fb1f9f86910517ec30dbdb))
|
||||
* **sdk:** add hub directories endpoints to Stainless config ([b3dbb81](https://github.com/langchain-ai/langsmith-java/commit/b3dbb81bd63f8b21fce99d345cf9c73345626451))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **go:** avoid panic when http.DefaultTransport is wrapped ([aba9578](https://github.com/langchain-ai/langsmith-java/commit/aba9578496866a723d85540da2d9f9bc6418027a))
|
||||
* **go:** avoid panic when http.DefaultTransport is wrapped ([9ea7653](https://github.com/langchain-ai/langsmith-java/commit/9ea76536a054185cbfdad5ddad7036b2e5d46137))
|
||||
* restore dokka-javadoc plugin application to subprojects ([#157](https://github.com/langchain-ai/langsmith-java/issues/157)) ([1df1c4a](https://github.com/langchain-ai/langsmith-java/commit/1df1c4aacd6c3cc9477576403a1da641bd20302d))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **client:** create one json mapper ([e68229b](https://github.com/langchain-ai/langsmith-java/commit/e68229b7009315bf60149df92e4af5f0b083c012))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* **internal:** codegen related update ([5e551de](https://github.com/langchain-ai/langsmith-java/commit/5e551de99e03fb34a737f3fcac3ee6a08fab8863))
|
||||
* **internal:** renormalize gradlew.bat line endings ([4738606](https://github.com/langchain-ai/langsmith-java/commit/4738606db159ecc8ad5088714e3100c0e7972f52))
|
||||
* remove duplicated dokka setup ([b1af8d8](https://github.com/langchain-ai/langsmith-java/commit/b1af8d842d8aa3b1a47cb00ffa22d71134b0d883))
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* clarify forwards compat behavior ([d4b4164](https://github.com/langchain-ai/langsmith-java/commit/d4b41649cb18523851c353c6a47b83b492e0c8e5))
|
||||
|
||||
## 0.1.0-beta.6 (2026-05-20)
|
||||
|
||||
Full Changelog: [v0.1.0-beta.5...v0.1.0-beta.6](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.5...v0.1.0-beta.6)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* patch security alerts — OTel, Tomcat, Jetty, Logback, Spring, SnakeYAML ([#153](https://github.com/langchain-ai/langsmith-java/issues/153)) ([dfa5469](https://github.com/langchain-ai/langsmith-java/commit/dfa54697292c14a987320ba776b580ee25c58f94))
|
||||
## 0.1.0-beta.5 (2026-05-13)
|
||||
|
||||
Full Changelog: [v0.1.0-beta.4...v0.1.0-beta.5](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.4...v0.1.0-beta.5)
|
||||
|
||||
### Features
|
||||
|
||||
* **sandbox:** make snapshot optional ([#152](https://github.com/langchain-ai/langsmith-java/issues/152)) ([5849b16](https://github.com/langchain-ai/langsmith-java/commit/5849b1695ec4504460205c80cabf8d443fd8a5f0))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* **deps:** bump actions/github-script from 8 to 9 ([#144](https://github.com/langchain-ai/langsmith-java/issues/144)) ([c3f8fbd](https://github.com/langchain-ai/langsmith-java/commit/c3f8fbd5449fb5f52529166fd9b2db5eb8dbe24a))
|
||||
* **deps:** bump gradle/actions ([#143](https://github.com/langchain-ai/langsmith-java/issues/143)) ([fd8a8ae](https://github.com/langchain-ai/langsmith-java/commit/fd8a8aef399ba3784c6d459c758e7a06f6ee5e98))
|
||||
* **deps:** bump the minor-and-patch group across 1 directory with 3 updates ([#145](https://github.com/langchain-ai/langsmith-java/issues/145)) ([c4a883d](https://github.com/langchain-ai/langsmith-java/commit/c4a883dfd2e89373180f88c7149c13ef426996ee))
|
||||
|
||||
## 0.1.0-beta.4 (2026-05-04)
|
||||
|
||||
Full Changelog: [v0.1.0-beta.3...v0.1.0-beta.4](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.3...v0.1.0-beta.4)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([4dc8a79](https://github.com/langchain-ai/langsmith-java/commit/4dc8a7912f8b1b449ad65c24bdf74b667dde30f4))
|
||||
|
||||
## 0.1.0-beta.3 (2026-05-01)
|
||||
|
||||
Full Changelog: [v0.1.0-beta.2...v0.1.0-beta.3](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.2...v0.1.0-beta.3)
|
||||
|
||||
## 0.1.0-beta.2 (2026-04-30)
|
||||
|
||||
Full Changelog: [v0.1.0-beta.1...v0.1.0-beta.2](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.1...v0.1.0-beta.2)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix another build issue ([#141](https://github.com/langchain-ai/langsmith-java/issues/141)) ([46baa8a](https://github.com/langchain-ai/langsmith-java/commit/46baa8a73e2770e190966d237996135b7fb6b0a3))
|
||||
|
||||
## 0.1.0-beta.1 (2026-04-30)
|
||||
|
||||
Full Changelog: [v0.1.0-beta.0...v0.1.0-beta.1](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.0...v0.1.0-beta.1)
|
||||
|
||||
### Chores
|
||||
|
||||
* Fix build ([#139](https://github.com/langchain-ai/langsmith-java/issues/139)) ([cbaa348](https://github.com/langchain-ai/langsmith-java/commit/cbaa3480cd21c2f90a0056a575e977af1bf906b6))
|
||||
|
||||
## 0.1.0-beta.0 (2026-04-30)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.28...v0.1.0-beta.0](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.28...v0.1.0-beta.0)
|
||||
|
||||
### Features
|
||||
|
||||
* Add autobatch tracing mode ([#134](https://github.com/langchain-ai/langsmith-java/issues/134)) ([230959e](https://github.com/langchain-ai/langsmith-java/commit/230959e124ec705648c4cd1e48da915d945154b8))
|
||||
* Add multipart batching ([#138](https://github.com/langchain-ai/langsmith-java/issues/138)) ([288221c](https://github.com/langchain-ai/langsmith-java/commit/288221c44835d472eb9f0ff59d1f22d0ec150ccb))
|
||||
* Add zstd compression to runs service ([#136](https://github.com/langchain-ai/langsmith-java/issues/136)) ([e412191](https://github.com/langchain-ai/langsmith-java/commit/e4121913a5d5a8a6de2a16e272925c947694e7c0))
|
||||
* **api:** add workspaces ([cc88cdf](https://github.com/langchain-ai/langsmith-java/commit/cc88cdf706b9f2ff7a1e66e00273af027c60f203))
|
||||
* **api:** api update ([c3252b8](https://github.com/langchain-ai/langsmith-java/commit/c3252b8becd6ee89c95dc6ae39bf701d76629e18))
|
||||
* **api:** api update ([af1ba52](https://github.com/langchain-ai/langsmith-java/commit/af1ba5242ec894847e0dc007674681858cb37a42))
|
||||
* **api:** api update ([e1cca2b](https://github.com/langchain-ai/langsmith-java/commit/e1cca2b004858451074865be8b9fb4e532f2f5f5))
|
||||
* **api:** api update ([fd1fc58](https://github.com/langchain-ai/langsmith-java/commit/fd1fc5830a739a88c221e682d1520612c0e45e66))
|
||||
* **api:** api update ([b487509](https://github.com/langchain-ai/langsmith-java/commit/b4875093d10d752fcc8c3757e33d6c2bbe334c15))
|
||||
* **api:** api update ([0758583](https://github.com/langchain-ai/langsmith-java/commit/0758583e5cc42b465c662a2897fff92a6fd61394))
|
||||
* **api:** api update ([1310303](https://github.com/langchain-ai/langsmith-java/commit/131030331c0bab2568498a2bff96112f0fe46fee))
|
||||
* **api:** manual updates ([0be4215](https://github.com/langchain-ai/langsmith-java/commit/0be4215e63ce63ee562ce1c48b001a6848cce704))
|
||||
* **api:** manual updates ([f35b312](https://github.com/langchain-ai/langsmith-java/commit/f35b31249d59265e74d6d33f4e3298fe416757ae))
|
||||
* **api:** sandbox apis ([bd21a1d](https://github.com/langchain-ai/langsmith-java/commit/bd21a1df96b708ac04712aaed7ef1a95505cf1f1))
|
||||
* **evaluators:** add list evaluators (GET /api/v1/runs/rules) ([be32489](https://github.com/langchain-ai/langsmith-java/commit/be324893c09e4ab6e2176dda1d2f6a6cf15413e6))
|
||||
* Make autobatch queue denote batches by payload size, add aggregation delay ([#137](https://github.com/langchain-ai/langsmith-java/issues/137)) ([2fb72ae](https://github.com/langchain-ai/langsmith-java/commit/2fb72ae9681964dd2e798642d88ded506c8189b5))
|
||||
* Merge enqueued posts and patches to optimize batching ([#135](https://github.com/langchain-ai/langsmith-java/issues/135)) ([d65e301](https://github.com/langchain-ai/langsmith-java/commit/d65e3019910bb077ea59056aa37c9d0ef30bfb1a))
|
||||
* support setting headers via env ([567af0c](https://github.com/langchain-ai/langsmith-java/commit/567af0c1eba8bf2dc6648529c11260f36e586bd3))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* **deps:** bump the minor-and-patch group across 1 directory with 5 updates ([#123](https://github.com/langchain-ai/langsmith-java/issues/123)) ([1f88790](https://github.com/langchain-ai/langsmith-java/commit/1f887908d8d851dc641905b7beb8cf4f7cf935b1))
|
||||
|
||||
## 0.1.0-alpha.28 (2026-04-14)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.27...v0.1.0-alpha.28](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.27...v0.1.0-alpha.28)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([35484cf](https://github.com/langchain-ai/langsmith-java/commit/35484cf9b1f1ed6642ef857d3fb4aefdb37ae369))
|
||||
|
||||
## 0.1.0-alpha.27 (2026-04-14)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.26...v0.1.0-alpha.27](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.26...v0.1.0-alpha.27)
|
||||
|
||||
## 0.1.0-alpha.26 (2026-04-13)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.25...v0.1.0-alpha.26](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.25...v0.1.0-alpha.26)
|
||||
|
||||
### Features
|
||||
|
||||
* Allow manually specifying parent run tree in traceable config ([#126](https://github.com/langchain-ai/langsmith-java/issues/126)) ([a1409fb](https://github.com/langchain-ai/langsmith-java/commit/a1409fb0d7c0319d4129daea5300578e4371a858))
|
||||
* **api:** api update ([8198ce7](https://github.com/langchain-ai/langsmith-java/commit/8198ce7001d0656bcb4807331d385b0eb8b81a75))
|
||||
* **api:** api update ([286a62f](https://github.com/langchain-ai/langsmith-java/commit/286a62fba8b5bac8105c71dc52b4a36a37012eae))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* **deps:** bump gradle-wrapper from 8.12 to 9.4.1 ([#124](https://github.com/langchain-ai/langsmith-java/issues/124)) ([78b7db0](https://github.com/langchain-ai/langsmith-java/commit/78b7db0e1f08c6de8c12af504dd098cf7c958faa))
|
||||
* **deps:** bump gradle/actions ([#121](https://github.com/langchain-ai/langsmith-java/issues/121)) ([e3ca5d7](https://github.com/langchain-ai/langsmith-java/commit/e3ca5d7b712f92ce72586bfd7be208c73cec332f))
|
||||
* **deps:** bump gradle/gradle-build-action ([#122](https://github.com/langchain-ai/langsmith-java/issues/122)) ([d91305f](https://github.com/langchain-ai/langsmith-java/commit/d91305f9157b8d73dfaf8a925c7b2b0e5743f4b2))
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* improve examples ([1556365](https://github.com/langchain-ai/langsmith-java/commit/1556365dd6c04f3290b9a836b3c28c09056be5ad))
|
||||
|
||||
## 0.1.0-alpha.25 (2026-03-31)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.24...v0.1.0-alpha.25](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.24...v0.1.0-alpha.25)
|
||||
|
||||
### Features
|
||||
|
||||
* Add `traceable` function wrapper for LangSmith tracing ([#101](https://github.com/langchain-ai/langsmith-java/issues/101)) ([f3bf340](https://github.com/langchain-ai/langsmith-java/commit/f3bf3404fe56be9801806ea44f283eb586d96ce8))
|
||||
* Adds package version resource to build ([#100](https://github.com/langchain-ai/langsmith-java/issues/100)) ([31cc003](https://github.com/langchain-ai/langsmith-java/commit/31cc0037be79a336cd86f6d53c7093f6c3fb7034))
|
||||
* Adds processInputs and processOutputs to traceable ([#113](https://github.com/langchain-ai/langsmith-java/issues/113)) ([ee2991f](https://github.com/langchain-ai/langsmith-java/commit/ee2991f830703f29b3d0eddfd621109362a465e5))
|
||||
* Adds streaming support for wrapOpenAI ([#118](https://github.com/langchain-ai/langsmith-java/issues/118)) ([aae9d2c](https://github.com/langchain-ai/langsmith-java/commit/aae9d2c0dc7f7dae40960113154d9ddff3bd0e06))
|
||||
* Adds support for tracing streams with traceable ([#117](https://github.com/langchain-ai/langsmith-java/issues/117)) ([0217521](https://github.com/langchain-ai/langsmith-java/commit/0217521b852c70dba4795b145b3eb7b16fd2d10f))
|
||||
* **api:** api update ([9182ef8](https://github.com/langchain-ai/langsmith-java/commit/9182ef84e304c602899c73b6865473d3ade4953f))
|
||||
* **api:** api update ([0c91488](https://github.com/langchain-ai/langsmith-java/commit/0c914884473e1de2877708cd92db30fdc8fc10aa))
|
||||
* **api:** api update ([efa16fc](https://github.com/langchain-ai/langsmith-java/commit/efa16fcb5974fbd5500714936cb52a6e930a6181))
|
||||
* **api:** api update ([979e8af](https://github.com/langchain-ai/langsmith-java/commit/979e8af2b6f01444bcea33e1e09d1acbc986a6be))
|
||||
* **api:** api update ([cd9f555](https://github.com/langchain-ai/langsmith-java/commit/cd9f555e525172d5ba0bcb0cd033dc045a9026bd))
|
||||
* **api:** api update ([af93e09](https://github.com/langchain-ai/langsmith-java/commit/af93e09dc9dd8597454b2a02b1ed37de4e71fde4))
|
||||
* **api:** manual updates ([493d5e9](https://github.com/langchain-ai/langsmith-java/commit/493d5e9b2844ff160598b87602c9f91733722fe4))
|
||||
* Rework wrapOpenAI ([#114](https://github.com/langchain-ai/langsmith-java/issues/114)) ([63e0bdc](https://github.com/langchain-ai/langsmith-java/commit/63e0bdc5c893185399065c2c602ed0d5c46bbc68))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix format detection when pulling legacy prompts ([#115](https://github.com/langchain-ai/langsmith-java/issues/115)) ([ea6c3d2](https://github.com/langchain-ai/langsmith-java/commit/ea6c3d29d7c726b7b8455e6e93ec3322330ea5e7))
|
||||
* patch security alerts — bump Jackson and constrain vulnerable transitive deps ([#119](https://github.com/langchain-ai/langsmith-java/issues/119)) ([dfe092c](https://github.com/langchain-ai/langsmith-java/commit/dfe092c51ab27c3131b81785321664a75d584764))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* **deps:** bump gradle/actions from 5 to 6 in the all-actions group ([#99](https://github.com/langchain-ai/langsmith-java/issues/99)) ([4ba75e7](https://github.com/langchain-ai/langsmith-java/commit/4ba75e7776499833eddb1959b194b71eb847840f))
|
||||
* fix dependabot.yml posture issues ([#105](https://github.com/langchain-ai/langsmith-java/issues/105)) ([a0e5441](https://github.com/langchain-ai/langsmith-java/commit/a0e544170d0c62e340a6250c32b6b521f818eaa4))
|
||||
|
||||
## 0.1.0-alpha.24 (2026-03-25)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.23...v0.1.0-alpha.24](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.23...v0.1.0-alpha.24)
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** api update ([8e94d9d](https://github.com/langchain-ai/langsmith-java/commit/8e94d9dc65dfacd3f238e88631623a1338edec2e))
|
||||
* **api:** api update ([3288052](https://github.com/langchain-ai/langsmith-java/commit/32880529fe17d465d20ee922894a9e1af3351edd))
|
||||
* **api:** api update ([a4e3e5c](https://github.com/langchain-ai/langsmith-java/commit/a4e3e5cb858e66a9409ac162f04660c514cc24df))
|
||||
* **api:** api update ([ff16250](https://github.com/langchain-ai/langsmith-java/commit/ff162506f81ad1616567c9119ded36c1a344c774))
|
||||
* **api:** api update ([ef7c7a7](https://github.com/langchain-ai/langsmith-java/commit/ef7c7a798786b073e403d994ee2a9367d4db59ee))
|
||||
* **api:** api update ([643c7b1](https://github.com/langchain-ai/langsmith-java/commit/643c7b121acd874a72c36e80fbd6d8d4aa772319))
|
||||
* **api:** api update ([4b9c97d](https://github.com/langchain-ai/langsmith-java/commit/4b9c97d873492f919885fc1e394d1819fbedd09a))
|
||||
* **api:** api update ([ea4e843](https://github.com/langchain-ai/langsmith-java/commit/ea4e843eea1709113fd259659c2cc85fd6317fa5))
|
||||
* **api:** api update ([74d2131](https://github.com/langchain-ai/langsmith-java/commit/74d213181b7a2398fa8852235de5a1123ff0a7fe))
|
||||
* **api:** api update ([2708533](https://github.com/langchain-ai/langsmith-java/commit/27085335b36f0ecb249a3bd079f96d8d9fad6c1f))
|
||||
* **api:** api update ([8f48677](https://github.com/langchain-ai/langsmith-java/commit/8f48677e5ca0dfd9c9e9bc248c204863f233542a))
|
||||
* **api:** api update ([c11821f](https://github.com/langchain-ai/langsmith-java/commit/c11821f56ba08939568332468a1e9376c78e6372))
|
||||
* **api:** api update ([44fe8b3](https://github.com/langchain-ai/langsmith-java/commit/44fe8b31986af7ae7ac489c0a59335afb8021f9f))
|
||||
* **api:** api update ([d11c759](https://github.com/langchain-ai/langsmith-java/commit/d11c759b1cf0d360f07aa0807f554c36f3262b14))
|
||||
* **api:** manual updates ([09f1302](https://github.com/langchain-ai/langsmith-java/commit/09f1302329d0fcd9082bc52b2381271aabd71bab))
|
||||
* **api:** manual updates ([dbed70a](https://github.com/langchain-ai/langsmith-java/commit/dbed70af760f0e807626650a2a7d6ecb8d30f0ad))
|
||||
* **api:** manual updates ([48e2ca2](https://github.com/langchain-ai/langsmith-java/commit/48e2ca294bd412398290777db3ef14655ed47b1c))
|
||||
* **api:** manual updates ([d8ac870](https://github.com/langchain-ai/langsmith-java/commit/d8ac87089db42004a53e880f419dcf6749c60817))
|
||||
* **api:** manual updates ([bdb35f0](https://github.com/langchain-ai/langsmith-java/commit/bdb35f01f6374860fe6fa3c83f6f102f22590b23))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **client:** allow updating header/query affecting fields in `toBuilder()` ([5069881](https://github.com/langchain-ai/langsmith-java/commit/5069881aebcffc8ea2058de667a7af33efc15ec3))
|
||||
* **client:** incorrect `Retry-After` parsing ([b607264](https://github.com/langchain-ai/langsmith-java/commit/b607264e2c7e10374f5d79d922c6850208b6f839))
|
||||
* fix streaming in java sdk ([#95](https://github.com/langchain-ai/langsmith-java/issues/95)) ([76da93f](https://github.com/langchain-ai/langsmith-java/commit/76da93f2c0c3a0466feee196243a214e8642b303))
|
||||
|
||||
|
||||
### Chores
|
||||
|
||||
* align user agent format ([#96](https://github.com/langchain-ai/langsmith-java/issues/96)) ([5cf06d6](https://github.com/langchain-ai/langsmith-java/commit/5cf06d612963f01b49d6f62117226d948e004046))
|
||||
* **ci:** skip lint on metadata-only changes ([4c1197c](https://github.com/langchain-ai/langsmith-java/commit/4c1197ce483f317defed85f81d3ec3f5053f68d3))
|
||||
* **ci:** skip uploading artifacts on stainless-internal branches ([c994122](https://github.com/langchain-ai/langsmith-java/commit/c994122d8c4eea78bb8a942b0ed1db58e6338e1e))
|
||||
* **deps:** bump the all-actions group with 5 updates ([#92](https://github.com/langchain-ai/langsmith-java/issues/92)) ([3a1a72f](https://github.com/langchain-ai/langsmith-java/commit/3a1a72f5595393a437a8ddf1acc69bb44529b0e8))
|
||||
* drop apache dependency ([75ca9d7](https://github.com/langchain-ai/langsmith-java/commit/75ca9d71b49bad02bdbd66141f693204b296a9ec))
|
||||
* **internal:** bump ktfmt ([d9a4a39](https://github.com/langchain-ai/langsmith-java/commit/d9a4a396ad92c9f33c5922dabc00a8ba2451199b))
|
||||
* **internal:** bump palantir-java-format ([576ebd2](https://github.com/langchain-ai/langsmith-java/commit/576ebd2a02613c632dd1e00ba9c606df99de8f37))
|
||||
* **internal:** codegen related update ([29d55b0](https://github.com/langchain-ai/langsmith-java/commit/29d55b0ee9f40c2d327f1c08a714ccd9cad1384b))
|
||||
* **internal:** expand imports ([4c7542f](https://github.com/langchain-ai/langsmith-java/commit/4c7542f4525b43d8fc2adcd7d084a080581b5c48))
|
||||
* **internal:** tweak CI branches ([b7cecc1](https://github.com/langchain-ai/langsmith-java/commit/b7cecc1caa774426df72acd5734a2c29a96a3e60))
|
||||
* **internal:** update gitignore ([d0df3fa](https://github.com/langchain-ai/langsmith-java/commit/d0df3fa13a923514d9d3c66d5d96b4fa1fea4a22))
|
||||
* **internal:** update retry delay tests ([a9c7999](https://github.com/langchain-ai/langsmith-java/commit/a9c7999f55bc12c2d1bac268ce3a2ca5bbd58dc5))
|
||||
* make `Properties` more resilient to `null` ([47c70f9](https://github.com/langchain-ai/langsmith-java/commit/47c70f91d5ec941fafc08a81baabf946602076c9))
|
||||
* remove old test ([#97](https://github.com/langchain-ai/langsmith-java/issues/97)) ([79d708b](https://github.com/langchain-ai/langsmith-java/commit/79d708b935ba234c50d10980a7fef8ad12c58f61))
|
||||
* update placeholder string ([be1baa7](https://github.com/langchain-ai/langsmith-java/commit/be1baa704ad8ebe899fbf8e4003f706fbc3d3f73))
|
||||
|
||||
## 0.1.0-alpha.23 (2026-02-20)
|
||||
|
||||
Full Changelog: [v0.1.0-alpha.22...v0.1.0-alpha.23](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.22...v0.1.0-alpha.23)
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
# Contributing
|
||||
|
||||
Thanks for your interest in contributing to the LangSmith Java SDK!
|
||||
|
||||
## Overview
|
||||
|
||||
This SDK is **auto-generated by [Stainless](https://www.stainless.com/)** from our OpenAPI spec. Custom code (prompts, OTel wrappers, etc.) lives alongside generated code — Stainless is configured to [preserve custom code](https://www.stainless.com/docs/sdks/configure/custom-code/) and never overwrite it.
|
||||
|
||||
> **Important:** Most files in the SDK are generated. If you need to change generated code, talk to @edongashi — there may be weird errors in the Stainless config, but they're generally fixable.
|
||||
|
||||
## Branching and workflow
|
||||
|
||||
| Branch | Purpose |
|
||||
| ----------------------------------------------- | ----------------------------------------------------- |
|
||||
| `next` | **Development branch.** All PRs should target `next`. |
|
||||
| `main` | Release branch. Only updated via release-please. |
|
||||
| `release-please--branches--main--changes--next` | Auto-generated release PR. Merges `next` → `main`. |
|
||||
|
||||
**Always work off `next`.** Do not merge directly to `main` — Dependabot or other bots occasionally violate this, which can cause headaches.
|
||||
|
||||
### Release process
|
||||
|
||||
1. Merge your PR into `next`.
|
||||
2. A release-please PR is automatically created/updated (`release-please--branches--main--changes--next`).
|
||||
3. Verify your change appears in the release PR diff and description.
|
||||
4. Approve the release PR — it auto-merges on approval.
|
||||
5. A GitHub Action publishes to Maven Central via `./gradlew publishAndReleaseToMavenCentral`.
|
||||
6. Maven Central can be slow to index — verify on [repo1.maven.org](https://repo1.maven.org/maven2/com/langchain/smith/langsmith-java/) rather than [mvnrepository.com](https://mvnrepository.com/artifact/com.langchain.smith/langsmith-java), which lags behind.
|
||||
|
||||
> **Note:** If lint fixes or other changes are pushed to the release-please branch after a version bump, the publish action can fail. Always ensure CI is green before approving.
|
||||
|
||||
## Development setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Java 8+ (tested against 8 and 21)
|
||||
- Gradle (uses the included `gradlew` wrapper)
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
./scripts/build
|
||||
```
|
||||
|
||||
### Formatting
|
||||
|
||||
The project uses [ktfmt](https://github.com/facebook/ktfmt) (`--kotlinlang-style`) for Kotlin and [Palantir Java Format](https://github.com/palantir/palantir-java-format) for Java.
|
||||
|
||||
```bash
|
||||
# Format all files
|
||||
./scripts/format
|
||||
|
||||
# Format only Kotlin
|
||||
./gradlew formatKotlin
|
||||
|
||||
# Format a single module
|
||||
./gradlew :langsmith-java-core:formatKotlin
|
||||
```
|
||||
|
||||
### Linting
|
||||
|
||||
```bash
|
||||
# Lint all files
|
||||
./scripts/lint
|
||||
|
||||
# Lint only Kotlin
|
||||
./gradlew lintKotlin
|
||||
```
|
||||
|
||||
CI blocks on lint failures. Always run `./scripts/format` before pushing.
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
./scripts/test
|
||||
|
||||
# Run specific tests
|
||||
./gradlew :langsmith-java-core:test --tests "com.langchain.smith.prompts.*"
|
||||
|
||||
# Force re-run (skip Gradle cache)
|
||||
./gradlew :langsmith-java-core:test --tests "..." --rerun
|
||||
|
||||
# See stdout output
|
||||
./gradlew :langsmith-java-core:test --tests "..." --rerun --info
|
||||
```
|
||||
|
||||
#### Integration tests
|
||||
|
||||
Integration tests require API keys and skip gracefully via `assumeTrue` if they're missing:
|
||||
|
||||
```bash
|
||||
export LANGSMITH_API_KEY="lsv2_pt_..."
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
```
|
||||
|
||||
## CI
|
||||
|
||||
CI runs three jobs on every push and PR:
|
||||
|
||||
1. **lint** — `./scripts/lint`
|
||||
2. **build** — `./scripts/build`
|
||||
3. **test** — `./scripts/test`
|
||||
|
||||
All three must pass before merging.
|
||||
|
||||
## Code style
|
||||
|
||||
See [AGENTS.md](AGENTS.md) for detailed Kotlin conventions, including:
|
||||
|
||||
- Prefer `buildMap`/`buildList` over mutable collections
|
||||
- Use `partition` instead of double `filter`
|
||||
- Use parameterized tests for table-driven cases
|
||||
- Avoid `@Suppress("UNCHECKED_CAST")` — restructure with safe casts
|
||||
- `toString()` should follow the `ClassName{field=value}` convention
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Anthropic SDK** is a `compileOnly` dependency — users must add it themselves. Methods using Anthropic types should catch `NoClassDefFoundError` and throw `IllegalStateException` with a clear message.
|
||||
- **SLF4J** (`slf4j-api`) is an `api` dependency for logging. Tests use `slf4j-simple`.
|
||||
- **Jackson** 2.22.0 is the default, but the SDK is compatible with 2.13.4+.
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
[](https://central.sonatype.com/artifact/com.langchain.smith/langsmith-java/0.1.0-alpha.23)
|
||||
[](https://javadoc.io/doc/com.langchain.smith/langsmith-java/0.1.0-alpha.23)
|
||||
[](https://central.sonatype.com/artifact/com.langchain.smith/langsmith-java/0.1.0-beta.19)
|
||||
[](https://javadoc.io/doc/com.langchain.smith/langsmith-java/0.1.0-beta.18)
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
||||
@@ -13,7 +13,7 @@ To learn more about LangSmith, check out the [docs](https://docs.smith.langchain
|
||||
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
The REST API documentation can be found on [docs.smith.langchain.com](https://docs.smith.langchain.com/). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.langchain.smith/langsmith-java/0.1.0-alpha.23).
|
||||
The REST API documentation can be found on [docs.smith.langchain.com](https://docs.smith.langchain.com/). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.langchain.smith/langsmith-java/0.1.0-beta.19).
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
||||
@@ -24,7 +24,7 @@ The REST API documentation can be found on [docs.smith.langchain.com](https://do
|
||||
### Gradle
|
||||
|
||||
```kotlin
|
||||
implementation("com.langchain.smith:langsmith-java:0.1.0-alpha.23")
|
||||
implementation("com.langchain.smith:langsmith-java:0.1.0-beta.19")
|
||||
```
|
||||
|
||||
### Maven
|
||||
@@ -33,7 +33,7 @@ implementation("com.langchain.smith:langsmith-java:0.1.0-alpha.23")
|
||||
<dependency>
|
||||
<groupId>com.langchain.smith</groupId>
|
||||
<artifactId>langsmith-java</artifactId>
|
||||
<version>0.1.0-alpha.23</version>
|
||||
<version>0.1.0-beta.19</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
@@ -48,25 +48,17 @@ This library requires Java 8 or later.
|
||||
```java
|
||||
import com.langchain.smith.client.LangsmithClient;
|
||||
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
|
||||
import com.langchain.smith.models.runs.RunQueryParams;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Page;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Params;
|
||||
|
||||
// Configures using the `langchain.langsmithApiKey`, `langchain.langsmithTenantId`, `langchain.langsmithBearerToken`, `langchain.langsmithOrganizationId` and `langchain.baseUrl` system properties
|
||||
// Or configures using the `LANGSMITH_API_KEY`, `LANGSMITH_TENANT_ID`, `LANGSMITH_BEARER_TOKEN`, `LANGSMITH_ORGANIZATION_ID` and `LANGSMITH_ENDPOINT` environment variables
|
||||
// Configures using the `langchain.langsmithApiKey`, `langchain.langsmithTenantId` and `langchain.baseUrl` system properties
|
||||
// Or configures using the `LANGSMITH_API_KEY`, `LANGSMITH_TENANT_ID` and `LANGSMITH_ENDPOINT` environment variables
|
||||
LangsmithClient client = LangsmithOkHttpClient.fromEnv();
|
||||
|
||||
RunQueryParams params = RunQueryParams.builder()
|
||||
.addSession("1ffaeba7-541e-469f-bae7-df3208ea3d45")
|
||||
.limit(10L)
|
||||
RunQueryV2Params params = RunQueryV2Params.builder()
|
||||
.addProjectId("00000000-0000-0000-0000-000000000000")
|
||||
.build();
|
||||
var response = client.runs().query(params);
|
||||
|
||||
// Print runs
|
||||
System.out.println("Found " + response.runs().size() + " runs:");
|
||||
for (var run : response.runs()) {
|
||||
System.out.println("Run ID: " + run.id());
|
||||
System.out.println("Run Name: " + run.name());
|
||||
System.out.println("---");
|
||||
}
|
||||
RunQueryV2Page page = client.runs().queryV2(params);
|
||||
```
|
||||
|
||||
## Examples
|
||||
@@ -103,8 +95,8 @@ Configure the client using system properties or environment variables:
|
||||
import com.langchain.smith.client.LangsmithClient;
|
||||
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
|
||||
|
||||
// Configures using the `langchain.langsmithApiKey`, `langchain.langsmithTenantId`, `langchain.langsmithBearerToken`, `langchain.langsmithOrganizationId` and `langchain.baseUrl` system properties
|
||||
// Or configures using the `LANGSMITH_API_KEY`, `LANGSMITH_TENANT_ID`, `LANGSMITH_BEARER_TOKEN`, `LANGSMITH_ORGANIZATION_ID` and `LANGSMITH_ENDPOINT` environment variables
|
||||
// Configures using the `langchain.langsmithApiKey`, `langchain.langsmithTenantId` and `langchain.baseUrl` system properties
|
||||
// Or configures using the `LANGSMITH_API_KEY`, `LANGSMITH_TENANT_ID` and `LANGSMITH_ENDPOINT` environment variables
|
||||
LangsmithClient client = LangsmithOkHttpClient.fromEnv();
|
||||
```
|
||||
|
||||
@@ -117,7 +109,6 @@ import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
|
||||
LangsmithClient client = LangsmithOkHttpClient.builder()
|
||||
.apiKey("My API Key")
|
||||
.tenantId("My Tenant ID")
|
||||
.organizationId("My Organization ID")
|
||||
.build();
|
||||
```
|
||||
|
||||
@@ -128,8 +119,8 @@ import com.langchain.smith.client.LangsmithClient;
|
||||
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
|
||||
|
||||
LangsmithClient client = LangsmithOkHttpClient.builder()
|
||||
// Configures using the `langchain.langsmithApiKey`, `langchain.langsmithTenantId`, `langchain.langsmithBearerToken`, `langchain.langsmithOrganizationId` and `langchain.baseUrl` system properties
|
||||
// Or configures using the `LANGSMITH_API_KEY`, `LANGSMITH_TENANT_ID`, `LANGSMITH_BEARER_TOKEN`, `LANGSMITH_ORGANIZATION_ID` and `LANGSMITH_ENDPOINT` environment variables
|
||||
// Configures using the `langchain.langsmithApiKey`, `langchain.langsmithTenantId` and `langchain.baseUrl` system properties
|
||||
// Or configures using the `LANGSMITH_API_KEY`, `LANGSMITH_TENANT_ID` and `LANGSMITH_ENDPOINT` environment variables
|
||||
.fromEnv()
|
||||
.apiKey("My API Key")
|
||||
.build();
|
||||
@@ -137,13 +128,11 @@ LangsmithClient client = LangsmithOkHttpClient.builder()
|
||||
|
||||
See this table for the available options:
|
||||
|
||||
| Setter | System property | Environment variable | Required | Default value |
|
||||
| ---------------- | ----------------------------------- | --------------------------- | -------- | ------------------------------------ |
|
||||
| `apiKey` | `langchain.langsmithApiKey` | `LANGSMITH_API_KEY` | false | - |
|
||||
| `tenantId` | `langchain.langsmithTenantId` | `LANGSMITH_TENANT_ID` | false | - |
|
||||
| `bearerToken` | `langchain.langsmithBearerToken` | `LANGSMITH_BEARER_TOKEN` | false | - |
|
||||
| `organizationId` | `langchain.langsmithOrganizationId` | `LANGSMITH_ORGANIZATION_ID` | false | - |
|
||||
| `baseUrl` | `langchain.baseUrl` | `LANGSMITH_ENDPOINT` | true | `"https://api.smith.langchain.com/"` |
|
||||
| Setter | System property | Environment variable | Required | Default value |
|
||||
| ---------- | ----------------------------- | --------------------- | -------- | ------------------------------------ |
|
||||
| `apiKey` | `langchain.langsmithApiKey` | `LANGSMITH_API_KEY` | false | - |
|
||||
| `tenantId` | `langchain.langsmithTenantId` | `LANGSMITH_TENANT_ID` | false | - |
|
||||
| `baseUrl` | `langchain.baseUrl` | `LANGSMITH_ENDPOINT` | true | `"https://api.smith.langchain.com/"` |
|
||||
|
||||
System properties take precedence over environment variables.
|
||||
|
||||
@@ -170,7 +159,7 @@ The `withOptions()` method does not affect the original client or service.
|
||||
|
||||
To send a request to the LangChain API, build an instance of some `Params` class and pass it to the corresponding client method. When the response is received, it will be deserialized into an instance of a Java class.
|
||||
|
||||
For example, `client.sessions().dashboard(...)` should be called with an instance of `SessionDashboardParams`, and it will return an instance of `CustomChartsSection`.
|
||||
For example, `client.runs().queryV2(...)` should be called with an instance of `RunQueryV2Params`, and it will return an instance of `RunQueryV2Page`.
|
||||
|
||||
## Immutability
|
||||
|
||||
@@ -187,20 +176,18 @@ The default client is synchronous. To switch to asynchronous execution, call the
|
||||
```java
|
||||
import com.langchain.smith.client.LangsmithClient;
|
||||
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
|
||||
import com.langchain.smith.models.sessions.CustomChartsSection;
|
||||
import com.langchain.smith.models.sessions.CustomChartsSectionRequest;
|
||||
import com.langchain.smith.models.sessions.SessionDashboardParams;
|
||||
import com.langchain.smith.models.runs.RunQueryV2PageAsync;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Params;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
// Configures using the `langchain.langsmithApiKey`, `langchain.langsmithTenantId`, `langchain.langsmithBearerToken`, `langchain.langsmithOrganizationId` and `langchain.baseUrl` system properties
|
||||
// Or configures using the `LANGSMITH_API_KEY`, `LANGSMITH_TENANT_ID`, `LANGSMITH_BEARER_TOKEN`, `LANGSMITH_ORGANIZATION_ID` and `LANGSMITH_ENDPOINT` environment variables
|
||||
// Configures using the `langchain.langsmithApiKey`, `langchain.langsmithTenantId` and `langchain.baseUrl` system properties
|
||||
// Or configures using the `LANGSMITH_API_KEY`, `LANGSMITH_TENANT_ID` and `LANGSMITH_ENDPOINT` environment variables
|
||||
LangsmithClient client = LangsmithOkHttpClient.fromEnv();
|
||||
|
||||
SessionDashboardParams params = SessionDashboardParams.builder()
|
||||
.sessionId("1ffaeba7-541e-469f-bae7-df3208ea3d45")
|
||||
.customChartsSectionRequest(CustomChartsSectionRequest.builder().build())
|
||||
RunQueryV2Params params = RunQueryV2Params.builder()
|
||||
.addProjectId("00000000-0000-0000-0000-000000000000")
|
||||
.build();
|
||||
CompletableFuture<CustomChartsSection> customChartsSection = client.async().sessions().dashboard(params);
|
||||
CompletableFuture<RunQueryV2PageAsync> page = client.async().runs().queryV2(params);
|
||||
```
|
||||
|
||||
Or create an asynchronous client from the beginning:
|
||||
@@ -208,20 +195,18 @@ Or create an asynchronous client from the beginning:
|
||||
```java
|
||||
import com.langchain.smith.client.LangsmithClientAsync;
|
||||
import com.langchain.smith.client.okhttp.LangsmithOkHttpClientAsync;
|
||||
import com.langchain.smith.models.sessions.CustomChartsSection;
|
||||
import com.langchain.smith.models.sessions.CustomChartsSectionRequest;
|
||||
import com.langchain.smith.models.sessions.SessionDashboardParams;
|
||||
import com.langchain.smith.models.runs.RunQueryV2PageAsync;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Params;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
// Configures using the `langchain.langsmithApiKey`, `langchain.langsmithTenantId`, `langchain.langsmithBearerToken`, `langchain.langsmithOrganizationId` and `langchain.baseUrl` system properties
|
||||
// Or configures using the `LANGSMITH_API_KEY`, `LANGSMITH_TENANT_ID`, `LANGSMITH_BEARER_TOKEN`, `LANGSMITH_ORGANIZATION_ID` and `LANGSMITH_ENDPOINT` environment variables
|
||||
// Configures using the `langchain.langsmithApiKey`, `langchain.langsmithTenantId` and `langchain.baseUrl` system properties
|
||||
// Or configures using the `LANGSMITH_API_KEY`, `LANGSMITH_TENANT_ID` and `LANGSMITH_ENDPOINT` environment variables
|
||||
LangsmithClientAsync client = LangsmithOkHttpClientAsync.fromEnv();
|
||||
|
||||
SessionDashboardParams params = SessionDashboardParams.builder()
|
||||
.sessionId("1ffaeba7-541e-469f-bae7-df3208ea3d45")
|
||||
.customChartsSectionRequest(CustomChartsSectionRequest.builder().build())
|
||||
RunQueryV2Params params = RunQueryV2Params.builder()
|
||||
.addProjectId("00000000-0000-0000-0000-000000000000")
|
||||
.build();
|
||||
CompletableFuture<CustomChartsSection> customChartsSection = client.sessions().dashboard(params);
|
||||
CompletableFuture<RunQueryV2PageAsync> page = client.runs().queryV2(params);
|
||||
```
|
||||
|
||||
The asynchronous client supports the same options as the synchronous one, except most methods return `CompletableFuture`s.
|
||||
@@ -303,26 +288,24 @@ To access this data, prefix any HTTP method call on a client or service with `wi
|
||||
```java
|
||||
import com.langchain.smith.core.http.Headers;
|
||||
import com.langchain.smith.core.http.HttpResponseFor;
|
||||
import com.langchain.smith.models.sessions.CustomChartsSection;
|
||||
import com.langchain.smith.models.sessions.CustomChartsSectionRequest;
|
||||
import com.langchain.smith.models.sessions.SessionDashboardParams;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Page;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Params;
|
||||
|
||||
SessionDashboardParams params = SessionDashboardParams.builder()
|
||||
.sessionId("1ffaeba7-541e-469f-bae7-df3208ea3d45")
|
||||
.customChartsSectionRequest(CustomChartsSectionRequest.builder().build())
|
||||
RunQueryV2Params params = RunQueryV2Params.builder()
|
||||
.addProjectId("00000000-0000-0000-0000-000000000000")
|
||||
.build();
|
||||
HttpResponseFor<CustomChartsSection> customChartsSection = client.sessions().withRawResponse().dashboard(params);
|
||||
HttpResponseFor<RunQueryV2Page> page = client.runs().withRawResponse().queryV2(params);
|
||||
|
||||
int statusCode = customChartsSection.statusCode();
|
||||
Headers headers = customChartsSection.headers();
|
||||
int statusCode = page.statusCode();
|
||||
Headers headers = page.headers();
|
||||
```
|
||||
|
||||
You can still deserialize the response into an instance of a Java class if needed:
|
||||
|
||||
```java
|
||||
import com.langchain.smith.models.sessions.CustomChartsSection;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Page;
|
||||
|
||||
CustomChartsSection parsedCustomChartsSection = customChartsSection.parse();
|
||||
RunQueryV2Page parsedPage = page.parse();
|
||||
```
|
||||
|
||||
## Error handling
|
||||
@@ -361,43 +344,43 @@ To iterate through all results across all pages, use the `autoPager()` method, w
|
||||
When using the synchronous client, the method returns an [`Iterable`](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html)
|
||||
|
||||
```java
|
||||
import com.langchain.smith.models.datasets.Dataset;
|
||||
import com.langchain.smith.models.datasets.DatasetListPage;
|
||||
import com.langchain.smith.models.runs.Run;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Page;
|
||||
|
||||
DatasetListPage page = client.datasets().list();
|
||||
RunQueryV2Page page = client.runs().queryV2();
|
||||
|
||||
// Process as an Iterable
|
||||
for (Dataset dataset : page.autoPager()) {
|
||||
System.out.println(dataset);
|
||||
for (Run run : page.autoPager()) {
|
||||
System.out.println(run);
|
||||
}
|
||||
|
||||
// Process as a Stream
|
||||
page.autoPager()
|
||||
.stream()
|
||||
.limit(50)
|
||||
.forEach(dataset -> System.out.println(dataset));
|
||||
.forEach(run -> System.out.println(run));
|
||||
```
|
||||
|
||||
When using the asynchronous client, the method returns an [`AsyncStreamResponse`](langsmith-java-core/src/main/kotlin/com/langchain/smith/core/http/AsyncStreamResponse.kt):
|
||||
|
||||
```java
|
||||
import com.langchain.smith.core.http.AsyncStreamResponse;
|
||||
import com.langchain.smith.models.datasets.Dataset;
|
||||
import com.langchain.smith.models.datasets.DatasetListPageAsync;
|
||||
import com.langchain.smith.models.runs.Run;
|
||||
import com.langchain.smith.models.runs.RunQueryV2PageAsync;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
CompletableFuture<DatasetListPageAsync> pageFuture = client.async().datasets().list();
|
||||
CompletableFuture<RunQueryV2PageAsync> pageFuture = client.async().runs().queryV2();
|
||||
|
||||
pageFuture.thenRun(page -> page.autoPager().subscribe(dataset -> {
|
||||
System.out.println(dataset);
|
||||
pageFuture.thenRun(page -> page.autoPager().subscribe(run -> {
|
||||
System.out.println(run);
|
||||
}));
|
||||
|
||||
// If you need to handle errors or completion of the stream
|
||||
pageFuture.thenRun(page -> page.autoPager().subscribe(new AsyncStreamResponse.Handler<>() {
|
||||
@Override
|
||||
public void onNext(Dataset dataset) {
|
||||
System.out.println(dataset);
|
||||
public void onNext(Run run) {
|
||||
System.out.println(run);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -413,8 +396,8 @@ pageFuture.thenRun(page -> page.autoPager().subscribe(new AsyncStreamResponse.Ha
|
||||
|
||||
// Or use futures
|
||||
pageFuture.thenRun(page -> page.autoPager()
|
||||
.subscribe(dataset -> {
|
||||
System.out.println(dataset);
|
||||
.subscribe(run -> {
|
||||
System.out.println(run);
|
||||
})
|
||||
.onCompleteFuture()
|
||||
.whenComplete((unused, error) -> {
|
||||
@@ -433,13 +416,13 @@ To access individual page items and manually request the next page, use the `ite
|
||||
`hasNextPage()`, and `nextPage()` methods:
|
||||
|
||||
```java
|
||||
import com.langchain.smith.models.datasets.Dataset;
|
||||
import com.langchain.smith.models.datasets.DatasetListPage;
|
||||
import com.langchain.smith.models.runs.Run;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Page;
|
||||
|
||||
DatasetListPage page = client.datasets().list();
|
||||
RunQueryV2Page page = client.runs().queryV2();
|
||||
while (true) {
|
||||
for (Dataset dataset : page.items()) {
|
||||
System.out.println(dataset);
|
||||
for (Run run : page.items()) {
|
||||
System.out.println(run);
|
||||
}
|
||||
|
||||
if (!page.hasNextPage()) {
|
||||
@@ -452,8 +435,6 @@ while (true) {
|
||||
|
||||
## Logging
|
||||
|
||||
The SDK uses the standard [OkHttp logging interceptor](https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor).
|
||||
|
||||
Enable logging by setting the `LANGCHAIN_LOG` environment variable to `info`:
|
||||
|
||||
```sh
|
||||
@@ -466,6 +447,19 @@ Or to `debug` for more verbose logging:
|
||||
export LANGCHAIN_LOG=debug
|
||||
```
|
||||
|
||||
Or configure the client manually using the `logLevel` method:
|
||||
|
||||
```java
|
||||
import com.langchain.smith.client.LangsmithClient;
|
||||
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
|
||||
import com.langchain.smith.core.LogLevel;
|
||||
|
||||
LangsmithClient client = LangsmithOkHttpClient.builder()
|
||||
.fromEnv()
|
||||
.logLevel(LogLevel.INFO)
|
||||
.build();
|
||||
```
|
||||
|
||||
## ProGuard and R8
|
||||
|
||||
Although the SDK uses reflection, it is still usable with [ProGuard](https://github.com/Guardsquare/proguard) and [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization) because `langsmith-java-core` is published with a [configuration file](langsmith-java-core/src/main/resources/META-INF/proguard/langsmith-java-core.pro) containing [keep rules](https://www.guardsquare.com/manual/configuration/usage).
|
||||
@@ -474,7 +468,7 @@ ProGuard and R8 should automatically detect and use the published rules, but you
|
||||
|
||||
## Jackson
|
||||
|
||||
The SDK depends on [Jackson](https://github.com/FasterXML/jackson) for JSON serialization/deserialization. It is compatible with version 2.13.4 or higher, but depends on version 2.18.2 by default.
|
||||
The SDK depends on [Jackson](https://github.com/FasterXML/jackson) for JSON serialization/deserialization. It is compatible with version 2.13.4 or higher, but depends on version 2.22.0 by default.
|
||||
|
||||
The SDK throws an exception if it detects an incompatible Jackson version at runtime (e.g. if the default version was overridden in your Maven or Gradle config).
|
||||
|
||||
@@ -520,11 +514,9 @@ Requests time out after 90 seconds by default.
|
||||
To set a custom timeout, configure the method call using the `timeout` method:
|
||||
|
||||
```java
|
||||
import com.langchain.smith.models.sessions.CustomChartsSection;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Page;
|
||||
|
||||
CustomChartsSection customChartsSection = client.sessions().dashboard(
|
||||
params, RequestOptions.builder().timeout(Duration.ofSeconds(30)).build()
|
||||
);
|
||||
RunQueryV2Page page = client.runs().queryV2(RequestOptions.builder().timeout(Duration.ofSeconds(30)).build());
|
||||
```
|
||||
|
||||
Or configure the default for all method calls at the client level:
|
||||
@@ -560,6 +552,21 @@ LangsmithClient client = LangsmithOkHttpClient.builder()
|
||||
.build();
|
||||
```
|
||||
|
||||
If the proxy responds with `407 Proxy Authentication Required`, supply credentials by also configuring `proxyAuthenticator`:
|
||||
|
||||
```java
|
||||
import com.langchain.smith.client.LangsmithClient;
|
||||
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
|
||||
import com.langchain.smith.core.http.ProxyAuthenticator;
|
||||
|
||||
LangsmithClient client = LangsmithOkHttpClient.builder()
|
||||
.fromEnv()
|
||||
.proxy(...)
|
||||
// Or a custom implementation of `ProxyAuthenticator`.
|
||||
.proxyAuthenticator(ProxyAuthenticator.basic("username", "password"))
|
||||
.build();
|
||||
```
|
||||
|
||||
### Connection pooling
|
||||
|
||||
To customize the underlying OkHttp connection pool, configure the client using the `maxIdleConnections` and `keepAliveDuration` methods:
|
||||
@@ -646,9 +653,9 @@ To set undocumented parameters, call the `putAdditionalHeader`, `putAdditionalQu
|
||||
|
||||
```java
|
||||
import com.langchain.smith.core.JsonValue;
|
||||
import com.langchain.smith.models.sessions.SessionDashboardParams;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Params;
|
||||
|
||||
SessionDashboardParams params = SessionDashboardParams.builder()
|
||||
RunQueryV2Params params = RunQueryV2Params.builder()
|
||||
.putAdditionalHeader("Secret-Header", "42")
|
||||
.putAdditionalQueryParam("secret_query_param", "42")
|
||||
.putAdditionalBodyProperty("secretProperty", JsonValue.from("42"))
|
||||
@@ -660,11 +667,11 @@ These can be accessed on the built object later using the `_additionalHeaders()`
|
||||
To set a documented parameter or property to an undocumented or not yet supported _value_, pass a [`JsonValue`](langsmith-java-core/src/main/kotlin/com/langchain/smith/core/Values.kt) object to its setter:
|
||||
|
||||
```java
|
||||
import com.langchain.smith.models.sessions.CustomChartsSectionRequest;
|
||||
import com.langchain.smith.models.sessions.SessionDashboardParams;
|
||||
import com.langchain.smith.core.JsonValue;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Params;
|
||||
|
||||
SessionDashboardParams params = SessionDashboardParams.builder()
|
||||
.customChartsSectionRequest(CustomChartsSectionRequest.builder().build())
|
||||
RunQueryV2Params params = RunQueryV2Params.builder()
|
||||
.projectIds(JsonValue.from(42))
|
||||
.build();
|
||||
```
|
||||
|
||||
@@ -713,11 +720,10 @@ To forcibly omit a required parameter or property, pass [`JsonMissing`](langsmit
|
||||
|
||||
```java
|
||||
import com.langchain.smith.core.JsonMissing;
|
||||
import com.langchain.smith.models.sessions.CustomChartsSectionRequest;
|
||||
import com.langchain.smith.models.sessions.SessionDashboardParams;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Params;
|
||||
import com.langchain.smith.models.sessions.SessionRetrieveParams;
|
||||
|
||||
SessionDashboardParams params = SessionDashboardParams.builder()
|
||||
.customChartsSectionRequest(CustomChartsSectionRequest.builder().build())
|
||||
RunQueryV2Params params = SessionRetrieveParams.builder()
|
||||
.sessionId(JsonMissing.of())
|
||||
.build();
|
||||
```
|
||||
@@ -730,7 +736,7 @@ To access undocumented response properties, call the `_additionalProperties()` m
|
||||
import com.langchain.smith.core.JsonValue;
|
||||
import java.util.Map;
|
||||
|
||||
Map<String, JsonValue> additionalProperties = client.sessions().dashboard(params)._additionalProperties();
|
||||
Map<String, JsonValue> additionalProperties = client.sessions().create(params)._additionalProperties();
|
||||
JsonValue secretPropertyValue = additionalProperties.get("secretProperty");
|
||||
|
||||
String result = secretPropertyValue.accept(new JsonValue.Visitor<>() {
|
||||
@@ -760,19 +766,19 @@ To access a property's raw JSON value, which may be undocumented, call its `_` p
|
||||
import com.langchain.smith.core.JsonField;
|
||||
import java.util.Optional;
|
||||
|
||||
JsonField<Object> field = client.sessions().dashboard(params)._field();
|
||||
JsonField<String> id = client.sessions().create(params)._id();
|
||||
|
||||
if (field.isMissing()) {
|
||||
if (id.isMissing()) {
|
||||
// The property is absent from the JSON response
|
||||
} else if (field.isNull()) {
|
||||
} else if (id.isNull()) {
|
||||
// The property was set to literal null
|
||||
} else {
|
||||
// Check if value was provided as a string
|
||||
// Other methods include `asNumber()`, `asBoolean()`, etc.
|
||||
Optional<String> jsonString = field.asString();
|
||||
Optional<String> jsonString = id.asString();
|
||||
|
||||
// Try to deserialize into a custom type
|
||||
MyClass myObject = field.asUnknown().orElseThrow().convert(MyClass.class);
|
||||
MyClass myObject = id.asUnknown().orElseThrow().convert(MyClass.class);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -782,22 +788,22 @@ In rare cases, the API may return a response that doesn't match the expected typ
|
||||
|
||||
By default, the SDK will not throw an exception in this case. It will throw [`LangChainInvalidDataException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/LangChainInvalidDataException.kt) only if you directly access the property.
|
||||
|
||||
If you would prefer to check that the response is completely well-typed upfront, then either call `validate()`:
|
||||
Validating the response is _not_ forwards compatible with new types from the API for existing fields.
|
||||
|
||||
If you would still prefer to check that the response is completely well-typed upfront, then either call `validate()`:
|
||||
|
||||
```java
|
||||
import com.langchain.smith.models.sessions.CustomChartsSection;
|
||||
import com.langchain.smith.models.sessions.TracerSessionWithoutVirtualFields;
|
||||
|
||||
CustomChartsSection customChartsSection = client.sessions().dashboard(params).validate();
|
||||
TracerSessionWithoutVirtualFields tracerSessionWithoutVirtualFields = client.sessions().create(params).validate();
|
||||
```
|
||||
|
||||
Or configure the method call to validate the response using the `responseValidation` method:
|
||||
|
||||
```java
|
||||
import com.langchain.smith.models.sessions.CustomChartsSection;
|
||||
import com.langchain.smith.models.runs.RunQueryV2Page;
|
||||
|
||||
CustomChartsSection customChartsSection = client.sessions().dashboard(
|
||||
params, RequestOptions.builder().responseValidation(true).build()
|
||||
);
|
||||
RunQueryV2Page page = client.runs().queryV2(RequestOptions.builder().responseValidation(true).build());
|
||||
```
|
||||
|
||||
Or configure the default for all method calls at the client level:
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# Security Fix Plan - langsmith-java
|
||||
|
||||
Validated 2026-07-02 against the open Dependabot alerts for `langsmith-java-staging`.
|
||||
|
||||
## Status
|
||||
|
||||
| Item | Scope | Status |
|
||||
|------|-------|--------|
|
||||
| Jackson 2.13.4/2.14.0/2.18.6 -> 2.22.0 | Published SDK + examples + tests | Done |
|
||||
| Spring Boot 2.7.18 -> 3.5.16 | Example module only, non-published | Done |
|
||||
| Spring Framework 5.x pins removed | Example module only, non-published | Done |
|
||||
| Tomcat 9.x pins removed | Example module only, non-published | Done |
|
||||
| SnakeYAML 1.x pin removed | Example module only, non-published | Done |
|
||||
| WireMock JRE8 2.35.2 -> org.wiremock:wiremock 4.0.0-beta.37 | Test scope only | Done |
|
||||
| Jetty test constraints -> 12.1.10 | Test scope only | Done |
|
||||
| Bouncy Castle test constraints -> 1.84 | Test scope only | Done |
|
||||
| Log4j Core test constraint -> 2.25.4 | Test scope only | Done |
|
||||
| OpenNLP test constraint -> 2.5.9 | Test scope only | Done |
|
||||
| Handlebars test constraint -> 4.5.2 | Test scope only | Done |
|
||||
| Plexus Utils test constraint -> 4.0.3 | Test scope only | Done |
|
||||
| Commons/Guava/json-path/XMLUnit/json-smart test constraints bumped | Test scope only | Done |
|
||||
|
||||
## Notes
|
||||
|
||||
The Spring Boot upgrade is intentionally limited to the example module, which is not published as part
|
||||
of the SDK artifacts. It removes the old Spring Boot 2/Spring Framework 5 workaround pins that kept
|
||||
several alerts open.
|
||||
|
||||
WireMock 4 is currently beta-only, but it is test scoped and moves the test server dependency graph to
|
||||
Jetty 12. Keeping WireMock 3 would still leave Jetty 11 in at least one advisory range.
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
errors=()
|
||||
|
||||
if [ -z "${RELEASE_PLEASE_TOKEN}" ]; then
|
||||
errors+=("The RELEASE_PLEASE_TOKEN secret has not been set. Create a fine-grained GitHub PAT and add it as a repository secret.")
|
||||
fi
|
||||
|
||||
if [ -z "${SONATYPE_USERNAME}" ]; then
|
||||
errors+=("The SONATYPE_USERNAME secret has not been set. Please set it in either this repository's secrets or your organization secrets")
|
||||
fi
|
||||
|
||||
+28
-3
@@ -1,5 +1,16 @@
|
||||
buildscript {
|
||||
configurations.classpath {
|
||||
resolutionStrategy {
|
||||
// CVE-2026-54512, CVE-2026-54513, and CVE-2026-54515: Dokka 2.2.0 depends on a vulnerable
|
||||
// jackson-databind version. Remove once Dokka resolves jackson-databind >= 2.18.9.
|
||||
force("com.fasterxml.jackson.core:jackson-databind:2.18.9")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("org.jetbrains.dokka") version "2.0.0"
|
||||
id("org.jetbrains.dokka") version "2.2.0"
|
||||
id("org.jetbrains.dokka-javadoc") version "2.2.0"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -8,7 +19,21 @@ repositories {
|
||||
|
||||
allprojects {
|
||||
group = "com.langchain.smith"
|
||||
version = "0.1.0-alpha.23" // x-release-please-version
|
||||
version = "0.1.0-beta.19" // x-release-please-version
|
||||
|
||||
configurations.matching { it.name.startsWith("dokka") }.configureEach {
|
||||
// Keep Dokka worker classpaths on the same patched version as the buildscript classpath.
|
||||
resolutionStrategy.force("com.fasterxml.jackson.core:jackson-databind:2.18.9")
|
||||
}
|
||||
|
||||
configurations.matching { it.name == "kotlinBouncyCastleConfiguration" }.configureEach {
|
||||
// CVE-2026-3505: remove once Kotlin's publishing validation selects Bouncy Castle >= 1.84.
|
||||
resolutionStrategy.force(
|
||||
"org.bouncycastle:bcpg-jdk18on:1.84",
|
||||
"org.bouncycastle:bcpkix-jdk18on:1.84",
|
||||
"org.bouncycastle:bcprov-jdk18on:1.84",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
@@ -21,11 +46,11 @@ subprojects {
|
||||
group = "Verification"
|
||||
description = "Verifies all source files are formatted."
|
||||
}
|
||||
apply(plugin = "org.jetbrains.dokka")
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply(plugin = "org.jetbrains.dokka")
|
||||
apply(plugin = "org.jetbrains.dokka-javadoc")
|
||||
}
|
||||
|
||||
// Avoid race conditions between `dokkaJavadocCollector` and `dokkaJavadocJar` tasks
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
kotlin("jvm") version "2.0.21"
|
||||
id("com.vanniktech.maven.publish") version "0.28.0"
|
||||
kotlin("jvm") version "2.3.21"
|
||||
id("com.vanniktech.maven.publish") version "0.36.0"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -16,6 +16,6 @@ java {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
|
||||
implementation("com.vanniktech:gradle-maven-publish-plugin:0.28.0")
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.21")
|
||||
implementation("com.vanniktech:gradle-maven-publish-plugin:0.36.0")
|
||||
}
|
||||
|
||||
@@ -44,9 +44,14 @@ tasks.withType<Test>().configureEach {
|
||||
}
|
||||
}
|
||||
|
||||
val palantir by configurations.creating
|
||||
val palantir by configurations.creating {
|
||||
resolutionStrategy {
|
||||
// CVE-2026-54515: remove once Palantir resolves jackson-databind >= 2.21.5.
|
||||
force("com.fasterxml.jackson.core:jackson-databind:2.21.5")
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
palantir("com.palantir.javaformat:palantir-java-format:2.73.0")
|
||||
palantir("com.palantir.javaformat:palantir-java-format:2.93.0")
|
||||
}
|
||||
|
||||
fun registerPalantir(
|
||||
|
||||
@@ -24,8 +24,8 @@ kotlin {
|
||||
"-nowarn",
|
||||
)
|
||||
jvmTarget.set(JvmTarget.JVM_1_8)
|
||||
languageVersion.set(KotlinVersion.KOTLIN_2_0)
|
||||
apiVersion.set(KotlinVersion.KOTLIN_2_0)
|
||||
languageVersion.set(KotlinVersion.KOTLIN_2_1)
|
||||
apiVersion.set(KotlinVersion.KOTLIN_2_1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,17 @@ tasks.withType<Test>().configureEach {
|
||||
systemProperty("junit.jupiter.execution.parallel.enabled", true)
|
||||
systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent")
|
||||
|
||||
// Suppress JVM warnings about dynamically loaded agents (e.g. byte-buddy used by Mockito).
|
||||
// Without this, agent warnings are written to stderr and break tests that capture stderr output.
|
||||
jvmArgs("-XX:+EnableDynamicAgentLoading")
|
||||
|
||||
// `SKIP_MOCK_TESTS` affects which tests run so it must be added as input for proper cache invalidation.
|
||||
inputs.property("skipMockTests", System.getenv("SKIP_MOCK_TESTS")).optional(true)
|
||||
}
|
||||
|
||||
val ktfmt by configurations.creating
|
||||
dependencies {
|
||||
ktfmt("com.facebook:ktfmt:0.56")
|
||||
ktfmt("com.facebook:ktfmt:0.61")
|
||||
}
|
||||
|
||||
fun registerKtfmt(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import com.vanniktech.maven.publish.JavadocJar
|
||||
import com.vanniktech.maven.publish.KotlinJvm
|
||||
import com.vanniktech.maven.publish.MavenPublishBaseExtension
|
||||
import com.vanniktech.maven.publish.SonatypeHost
|
||||
import com.vanniktech.maven.publish.SourcesJar
|
||||
|
||||
plugins {
|
||||
id("com.vanniktech.maven.publish")
|
||||
@@ -23,27 +23,23 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
extra["signingInMemoryKey"] = System.getenv("GPG_SIGNING_KEY")
|
||||
extra["signingInMemoryKeyId"] = System.getenv("GPG_SIGNING_KEY_ID")
|
||||
extra["signingInMemoryKeyPassword"] = System.getenv("GPG_SIGNING_PASSWORD")
|
||||
|
||||
configure<MavenPublishBaseExtension> {
|
||||
if (!project.hasProperty("publishLocal")) {
|
||||
signAllPublications()
|
||||
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
|
||||
publishToMavenCentral()
|
||||
}
|
||||
|
||||
coordinates(project.group.toString(), project.name, project.version.toString())
|
||||
configure(
|
||||
KotlinJvm(
|
||||
javadocJar = JavadocJar.Dokka("dokkaJavadoc"),
|
||||
sourcesJar = true,
|
||||
javadocJar = JavadocJar.Dokka("dokkaGeneratePublicationJavadoc"),
|
||||
sourcesJar = SourcesJar.Sources(),
|
||||
)
|
||||
)
|
||||
|
||||
pom {
|
||||
name.set("LangSmith")
|
||||
description.set("The LangSmith API is used to programmatically create and manage LangSmith\nresources.\n\n## Host\n\nhttps://dev.api.smith.langchain.com\n\n## Authentication\n\nTo authenticate with the LangSmith API, set the `X-Api-Key` header to a valid\n[LangSmith API key](https://docs.langchain.com/langsmith/create-account-api-key#create-an-api-key).")
|
||||
description.set("The LangSmith API is used to programmatically create and manage LangSmith\nresources.\n\n## Host\n\nhttp://localhost:1984\n\n## Authentication\n\nTo authenticate with the LangSmith API, set the `X-Api-Key` header to a valid\n[LangSmith API key](https://docs.langchain.com/langsmith/create-account-api-key#create-an-api-key).")
|
||||
url.set("https://www.github.com/langchain-ai/langsmith-java")
|
||||
|
||||
licenses {
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+3
-1
@@ -1,7 +1,9 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -57,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -114,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -172,7 +171,6 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -205,15 +203,14 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+82
-94
@@ -1,94 +1,82 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||
setlocal EnableExtensions
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
"%COMSPEC%" /c exit 1
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
"%COMSPEC%" /c exit 1
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||
@rem which allows us to clear the local environment before executing the java command
|
||||
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||
|
||||
:exitWithErrorLevel
|
||||
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
File generated from our OpenAPI spec by Stainless.
|
||||
|
||||
This directory can be used to store custom files to expand the SDK.
|
||||
It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.
|
||||
@@ -3,13 +3,35 @@ plugins {
|
||||
id("langchain.publish")
|
||||
}
|
||||
|
||||
configurations.matching { it.name in setOf("testCompileClasspath", "testRuntimeClasspath") }.configureEach {
|
||||
// Test-only WireMock 4 requires Java 17+ dependencies. Published artifacts still target Java 8.
|
||||
attributes.attribute(org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":langsmith-java-core"))
|
||||
|
||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
|
||||
|
||||
constraints {
|
||||
testImplementation("org.eclipse.jetty:jetty-server") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty:jetty-http") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty:jetty-servlets") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty:jetty-xml") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty.http2:http2-common") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty.http2:http2-hpack") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty.http2:http2-server") { version { require("12.1.10") } }
|
||||
testImplementation("com.github.jknack:handlebars") { version { require("4.5.2") } }
|
||||
testImplementation("commons-fileupload:commons-fileupload") { version { require("1.6.0") } }
|
||||
testImplementation("commons-io:commons-io") { version { require("2.22.0") } }
|
||||
testImplementation("com.google.guava:guava") { version { require("33.6.0-jre") } }
|
||||
testImplementation("com.jayway.jsonpath:json-path") { version { require("2.10.0") } }
|
||||
testImplementation("org.xmlunit:xmlunit-core") { version { require("2.12.0") } }
|
||||
testImplementation("net.minidev:json-smart") { version { require("2.6.0") } }
|
||||
}
|
||||
|
||||
testImplementation(kotlin("test"))
|
||||
testImplementation("org.assertj:assertj-core:3.27.7")
|
||||
testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2")
|
||||
testImplementation("org.wiremock:wiremock:4.0.0-beta.37")
|
||||
testImplementation("org.wiremock:wiremock-junit5:4.0.0-beta.37")
|
||||
}
|
||||
|
||||
+40
-17
@@ -6,11 +6,13 @@ import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.langchain.smith.client.LangsmithClient
|
||||
import com.langchain.smith.client.LangsmithClientImpl
|
||||
import com.langchain.smith.core.ClientOptions
|
||||
import com.langchain.smith.core.LogLevel
|
||||
import com.langchain.smith.core.Sleeper
|
||||
import com.langchain.smith.core.Timeout
|
||||
import com.langchain.smith.core.http.AsyncStreamResponse
|
||||
import com.langchain.smith.core.http.Headers
|
||||
import com.langchain.smith.core.http.HttpClient
|
||||
import com.langchain.smith.core.http.ProxyAuthenticator
|
||||
import com.langchain.smith.core.http.QueryParams
|
||||
import com.langchain.smith.core.jsonMapper
|
||||
import java.net.Proxy
|
||||
@@ -49,6 +51,7 @@ class LangsmithOkHttpClient private constructor() {
|
||||
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
|
||||
private var dispatcherExecutorService: ExecutorService? = null
|
||||
private var proxy: Proxy? = null
|
||||
private var proxyAuthenticator: ProxyAuthenticator? = null
|
||||
private var maxIdleConnections: Int? = null
|
||||
private var keepAliveDuration: Duration? = null
|
||||
private var sslSocketFactory: SSLSocketFactory? = null
|
||||
@@ -79,6 +82,20 @@ class LangsmithOkHttpClient private constructor() {
|
||||
/** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */
|
||||
fun proxy(proxy: Optional<Proxy>) = proxy(proxy.getOrNull())
|
||||
|
||||
/**
|
||||
* Provides credentials when an HTTP proxy responds with `407 Proxy Authentication
|
||||
* Required`.
|
||||
*/
|
||||
fun proxyAuthenticator(proxyAuthenticator: ProxyAuthenticator?) = apply {
|
||||
this.proxyAuthenticator = proxyAuthenticator
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for calling [Builder.proxyAuthenticator] with `proxyAuthenticator.orElse(null)`.
|
||||
*/
|
||||
fun proxyAuthenticator(proxyAuthenticator: Optional<ProxyAuthenticator>) =
|
||||
proxyAuthenticator(proxyAuthenticator.getOrNull())
|
||||
|
||||
/**
|
||||
* The maximum number of idle connections kept by the underlying OkHttp connection pool.
|
||||
*
|
||||
@@ -230,6 +247,9 @@ class LangsmithOkHttpClient private constructor() {
|
||||
/**
|
||||
* Whether to call `validate` on every response before returning it.
|
||||
*
|
||||
* Setting this to `true` is _not_ forwards compatible with new types from the API for
|
||||
* existing fields.
|
||||
*
|
||||
* Defaults to false, which means the shape of the response will not be validated upfront.
|
||||
* Instead, validation will only occur for the parts of the response that are accessed.
|
||||
*/
|
||||
@@ -271,6 +291,25 @@ class LangsmithOkHttpClient private constructor() {
|
||||
*/
|
||||
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
|
||||
|
||||
/**
|
||||
* Whether run create/update calls should be automatically batched for tracing.
|
||||
*
|
||||
* Defaults to true. Set to false to send run create/update calls synchronously through the
|
||||
* single-run endpoints.
|
||||
*/
|
||||
fun autoBatchTracing(autoBatchTracing: Boolean) = apply {
|
||||
clientOptions.autoBatchTracing(autoBatchTracing)
|
||||
}
|
||||
|
||||
/**
|
||||
* The level at which to log request and response information.
|
||||
*
|
||||
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
|
||||
*
|
||||
* Defaults to [LogLevel.fromEnv].
|
||||
*/
|
||||
fun logLevel(logLevel: LogLevel) = apply { clientOptions.logLevel(logLevel) }
|
||||
|
||||
fun apiKey(apiKey: String?) = apply { clientOptions.apiKey(apiKey) }
|
||||
|
||||
/** Alias for calling [Builder.apiKey] with `apiKey.orElse(null)`. */
|
||||
@@ -281,23 +320,6 @@ class LangsmithOkHttpClient private constructor() {
|
||||
/** Alias for calling [Builder.tenantId] with `tenantId.orElse(null)`. */
|
||||
fun tenantId(tenantId: Optional<String>) = tenantId(tenantId.getOrNull())
|
||||
|
||||
/**
|
||||
* Bearer tokens are used to authenticate from the UI. Must also specify x-tenant-id or
|
||||
* x-organization-id (for org scoped apis).
|
||||
*/
|
||||
fun bearerToken(bearerToken: String?) = apply { clientOptions.bearerToken(bearerToken) }
|
||||
|
||||
/** Alias for calling [Builder.bearerToken] with `bearerToken.orElse(null)`. */
|
||||
fun bearerToken(bearerToken: Optional<String>) = bearerToken(bearerToken.getOrNull())
|
||||
|
||||
fun organizationId(organizationId: String?) = apply {
|
||||
clientOptions.organizationId(organizationId)
|
||||
}
|
||||
|
||||
/** Alias for calling [Builder.organizationId] with `organizationId.orElse(null)`. */
|
||||
fun organizationId(organizationId: Optional<String>) =
|
||||
organizationId(organizationId.getOrNull())
|
||||
|
||||
fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
|
||||
|
||||
fun headers(headers: Map<String, Iterable<String>>) = apply {
|
||||
@@ -397,6 +419,7 @@ class LangsmithOkHttpClient private constructor() {
|
||||
OkHttpClient.builder()
|
||||
.timeout(clientOptions.timeout())
|
||||
.proxy(proxy)
|
||||
.proxyAuthenticator(proxyAuthenticator)
|
||||
.maxIdleConnections(maxIdleConnections)
|
||||
.keepAliveDuration(keepAliveDuration)
|
||||
.dispatcherExecutorService(dispatcherExecutorService)
|
||||
|
||||
+40
-17
@@ -6,11 +6,13 @@ import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.langchain.smith.client.LangsmithClientAsync
|
||||
import com.langchain.smith.client.LangsmithClientAsyncImpl
|
||||
import com.langchain.smith.core.ClientOptions
|
||||
import com.langchain.smith.core.LogLevel
|
||||
import com.langchain.smith.core.Sleeper
|
||||
import com.langchain.smith.core.Timeout
|
||||
import com.langchain.smith.core.http.AsyncStreamResponse
|
||||
import com.langchain.smith.core.http.Headers
|
||||
import com.langchain.smith.core.http.HttpClient
|
||||
import com.langchain.smith.core.http.ProxyAuthenticator
|
||||
import com.langchain.smith.core.http.QueryParams
|
||||
import com.langchain.smith.core.jsonMapper
|
||||
import java.net.Proxy
|
||||
@@ -49,6 +51,7 @@ class LangsmithOkHttpClientAsync private constructor() {
|
||||
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
|
||||
private var dispatcherExecutorService: ExecutorService? = null
|
||||
private var proxy: Proxy? = null
|
||||
private var proxyAuthenticator: ProxyAuthenticator? = null
|
||||
private var maxIdleConnections: Int? = null
|
||||
private var keepAliveDuration: Duration? = null
|
||||
private var sslSocketFactory: SSLSocketFactory? = null
|
||||
@@ -79,6 +82,20 @@ class LangsmithOkHttpClientAsync private constructor() {
|
||||
/** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */
|
||||
fun proxy(proxy: Optional<Proxy>) = proxy(proxy.getOrNull())
|
||||
|
||||
/**
|
||||
* Provides credentials when an HTTP proxy responds with `407 Proxy Authentication
|
||||
* Required`.
|
||||
*/
|
||||
fun proxyAuthenticator(proxyAuthenticator: ProxyAuthenticator?) = apply {
|
||||
this.proxyAuthenticator = proxyAuthenticator
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for calling [Builder.proxyAuthenticator] with `proxyAuthenticator.orElse(null)`.
|
||||
*/
|
||||
fun proxyAuthenticator(proxyAuthenticator: Optional<ProxyAuthenticator>) =
|
||||
proxyAuthenticator(proxyAuthenticator.getOrNull())
|
||||
|
||||
/**
|
||||
* The maximum number of idle connections kept by the underlying OkHttp connection pool.
|
||||
*
|
||||
@@ -230,6 +247,9 @@ class LangsmithOkHttpClientAsync private constructor() {
|
||||
/**
|
||||
* Whether to call `validate` on every response before returning it.
|
||||
*
|
||||
* Setting this to `true` is _not_ forwards compatible with new types from the API for
|
||||
* existing fields.
|
||||
*
|
||||
* Defaults to false, which means the shape of the response will not be validated upfront.
|
||||
* Instead, validation will only occur for the parts of the response that are accessed.
|
||||
*/
|
||||
@@ -271,6 +291,25 @@ class LangsmithOkHttpClientAsync private constructor() {
|
||||
*/
|
||||
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
|
||||
|
||||
/**
|
||||
* Whether run create/update calls should be automatically batched for tracing.
|
||||
*
|
||||
* Defaults to true. Set to false to send run create/update calls through the single-run
|
||||
* endpoints.
|
||||
*/
|
||||
fun autoBatchTracing(autoBatchTracing: Boolean) = apply {
|
||||
clientOptions.autoBatchTracing(autoBatchTracing)
|
||||
}
|
||||
|
||||
/**
|
||||
* The level at which to log request and response information.
|
||||
*
|
||||
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
|
||||
*
|
||||
* Defaults to [LogLevel.fromEnv].
|
||||
*/
|
||||
fun logLevel(logLevel: LogLevel) = apply { clientOptions.logLevel(logLevel) }
|
||||
|
||||
fun apiKey(apiKey: String?) = apply { clientOptions.apiKey(apiKey) }
|
||||
|
||||
/** Alias for calling [Builder.apiKey] with `apiKey.orElse(null)`. */
|
||||
@@ -281,23 +320,6 @@ class LangsmithOkHttpClientAsync private constructor() {
|
||||
/** Alias for calling [Builder.tenantId] with `tenantId.orElse(null)`. */
|
||||
fun tenantId(tenantId: Optional<String>) = tenantId(tenantId.getOrNull())
|
||||
|
||||
/**
|
||||
* Bearer tokens are used to authenticate from the UI. Must also specify x-tenant-id or
|
||||
* x-organization-id (for org scoped apis).
|
||||
*/
|
||||
fun bearerToken(bearerToken: String?) = apply { clientOptions.bearerToken(bearerToken) }
|
||||
|
||||
/** Alias for calling [Builder.bearerToken] with `bearerToken.orElse(null)`. */
|
||||
fun bearerToken(bearerToken: Optional<String>) = bearerToken(bearerToken.getOrNull())
|
||||
|
||||
fun organizationId(organizationId: String?) = apply {
|
||||
clientOptions.organizationId(organizationId)
|
||||
}
|
||||
|
||||
/** Alias for calling [Builder.organizationId] with `organizationId.orElse(null)`. */
|
||||
fun organizationId(organizationId: Optional<String>) =
|
||||
organizationId(organizationId.getOrNull())
|
||||
|
||||
fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
|
||||
|
||||
fun headers(headers: Map<String, Iterable<String>>) = apply {
|
||||
@@ -397,6 +419,7 @@ class LangsmithOkHttpClientAsync private constructor() {
|
||||
OkHttpClient.builder()
|
||||
.timeout(clientOptions.timeout())
|
||||
.proxy(proxy)
|
||||
.proxyAuthenticator(proxyAuthenticator)
|
||||
.maxIdleConnections(maxIdleConnections)
|
||||
.keepAliveDuration(keepAliveDuration)
|
||||
.dispatcherExecutorService(dispatcherExecutorService)
|
||||
|
||||
+149
-103
@@ -8,9 +8,11 @@ import com.langchain.smith.core.http.HttpMethod
|
||||
import com.langchain.smith.core.http.HttpRequest
|
||||
import com.langchain.smith.core.http.HttpRequestBody
|
||||
import com.langchain.smith.core.http.HttpResponse
|
||||
import com.langchain.smith.core.http.ProxyAuthenticator
|
||||
import com.langchain.smith.errors.LangChainIoException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.net.Proxy
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.CancellationException
|
||||
@@ -20,10 +22,12 @@ import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.HostnameVerifier
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.ConnectionPool
|
||||
import okhttp3.Dispatcher
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
@@ -31,8 +35,9 @@ import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import okio.BufferedSink
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
|
||||
class OkHttpClient
|
||||
internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClient) : HttpClient {
|
||||
@@ -41,7 +46,7 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie
|
||||
val call = newCall(request, requestOptions)
|
||||
|
||||
return try {
|
||||
call.execute().toResponse()
|
||||
call.execute().toHttpResponse()
|
||||
} catch (e: IOException) {
|
||||
throw LangChainIoException("Request failed", e)
|
||||
} finally {
|
||||
@@ -59,7 +64,7 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie
|
||||
call.enqueue(
|
||||
object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
future.complete(response.toResponse())
|
||||
future.complete(response.toHttpResponse())
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
@@ -87,23 +92,6 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie
|
||||
private fun newCall(request: HttpRequest, requestOptions: RequestOptions): Call {
|
||||
val clientBuilder = okHttpClient.newBuilder()
|
||||
|
||||
val logLevel =
|
||||
when (System.getenv("LANGCHAIN_LOG")?.lowercase()) {
|
||||
"info" -> HttpLoggingInterceptor.Level.BASIC
|
||||
"debug" -> HttpLoggingInterceptor.Level.BODY
|
||||
else -> null
|
||||
}
|
||||
if (logLevel != null) {
|
||||
clientBuilder.addNetworkInterceptor(
|
||||
HttpLoggingInterceptor().setLevel(logLevel).apply {
|
||||
redactHeader("X-API-Key")
|
||||
redactHeader("X-Organization-Id")
|
||||
redactHeader("Authorization")
|
||||
redactHeader("X-Tenant-Id")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
requestOptions.timeout?.let {
|
||||
clientBuilder
|
||||
.connectTimeout(it.connect())
|
||||
@@ -116,89 +104,6 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie
|
||||
return client.newCall(request.toRequest(client))
|
||||
}
|
||||
|
||||
private fun HttpRequest.toRequest(client: okhttp3.OkHttpClient): Request {
|
||||
var body: RequestBody? = body?.toRequestBody()
|
||||
if (body == null && requiresBody(method)) {
|
||||
body = "".toRequestBody()
|
||||
}
|
||||
|
||||
val builder = Request.Builder().url(toUrl()).method(method.name, body)
|
||||
headers.names().forEach { name ->
|
||||
headers.values(name).forEach { builder.addHeader(name, it) }
|
||||
}
|
||||
|
||||
if (
|
||||
!headers.names().contains("X-Stainless-Read-Timeout") && client.readTimeoutMillis != 0
|
||||
) {
|
||||
builder.addHeader(
|
||||
"X-Stainless-Read-Timeout",
|
||||
Duration.ofMillis(client.readTimeoutMillis.toLong()).seconds.toString(),
|
||||
)
|
||||
}
|
||||
if (!headers.names().contains("X-Stainless-Timeout") && client.callTimeoutMillis != 0) {
|
||||
builder.addHeader(
|
||||
"X-Stainless-Timeout",
|
||||
Duration.ofMillis(client.callTimeoutMillis.toLong()).seconds.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
/** `OkHttpClient` always requires a request body for some methods. */
|
||||
private fun requiresBody(method: HttpMethod): Boolean =
|
||||
when (method) {
|
||||
HttpMethod.POST,
|
||||
HttpMethod.PUT,
|
||||
HttpMethod.PATCH -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun HttpRequest.toUrl(): String {
|
||||
val builder = baseUrl.toHttpUrl().newBuilder()
|
||||
pathSegments.forEach(builder::addPathSegment)
|
||||
queryParams.keys().forEach { key ->
|
||||
queryParams.values(key).forEach { builder.addQueryParameter(key, it) }
|
||||
}
|
||||
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
private fun HttpRequestBody.toRequestBody(): RequestBody {
|
||||
val mediaType = contentType()?.toMediaType()
|
||||
val length = contentLength()
|
||||
|
||||
return object : RequestBody() {
|
||||
override fun contentType(): MediaType? = mediaType
|
||||
|
||||
override fun contentLength(): Long = length
|
||||
|
||||
override fun isOneShot(): Boolean = !repeatable()
|
||||
|
||||
override fun writeTo(sink: BufferedSink) = writeTo(sink.outputStream())
|
||||
}
|
||||
}
|
||||
|
||||
private fun Response.toResponse(): HttpResponse {
|
||||
val headers = headers.toHeaders()
|
||||
|
||||
return object : HttpResponse {
|
||||
override fun statusCode(): Int = code
|
||||
|
||||
override fun headers(): Headers = headers
|
||||
|
||||
override fun body(): InputStream = body!!.byteStream()
|
||||
|
||||
override fun close() = body!!.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun okhttp3.Headers.toHeaders(): Headers {
|
||||
val headersBuilder = Headers.builder()
|
||||
forEach { (name, value) -> headersBuilder.put(name, value) }
|
||||
return headersBuilder.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
@@ -207,6 +112,7 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie
|
||||
|
||||
private var timeout: Timeout = Timeout.default()
|
||||
private var proxy: Proxy? = null
|
||||
private var proxyAuthenticator: ProxyAuthenticator? = null
|
||||
private var maxIdleConnections: Int? = null
|
||||
private var keepAliveDuration: Duration? = null
|
||||
private var dispatcherExecutorService: ExecutorService? = null
|
||||
@@ -220,6 +126,10 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie
|
||||
|
||||
fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
|
||||
|
||||
fun proxyAuthenticator(proxyAuthenticator: ProxyAuthenticator?) = apply {
|
||||
this.proxyAuthenticator = proxyAuthenticator
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of idle connections kept by the underlying [ConnectionPool].
|
||||
*
|
||||
@@ -269,6 +179,19 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie
|
||||
.callTimeout(timeout.request())
|
||||
.proxy(proxy)
|
||||
.apply {
|
||||
proxyAuthenticator?.let { auth ->
|
||||
proxyAuthenticator { route, response ->
|
||||
auth
|
||||
.authenticate(
|
||||
route?.proxy ?: Proxy.NO_PROXY,
|
||||
response.request.toHttpRequest(),
|
||||
response.toHttpResponse(),
|
||||
)
|
||||
.getOrNull()
|
||||
?.toRequest(client = null)
|
||||
}
|
||||
}
|
||||
|
||||
dispatcherExecutorService?.let { dispatcher(Dispatcher(it)) }
|
||||
|
||||
val maxIdleConnections = maxIdleConnections
|
||||
@@ -308,3 +231,126 @@ internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClie
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun HttpRequest.toRequest(client: okhttp3.OkHttpClient?): Request {
|
||||
var body: RequestBody? = body?.toRequestBody()
|
||||
if (body == null && requiresBody(method)) {
|
||||
body = "".toRequestBody()
|
||||
}
|
||||
|
||||
val builder = Request.Builder().url(toUrl()).method(method.name, body)
|
||||
headers.names().forEach { name -> headers.values(name).forEach { builder.addHeader(name, it) } }
|
||||
|
||||
if (client != null) {
|
||||
if (
|
||||
!headers.names().contains("X-Stainless-Read-Timeout") && client.readTimeoutMillis != 0
|
||||
) {
|
||||
builder.addHeader(
|
||||
"X-Stainless-Read-Timeout",
|
||||
Duration.ofMillis(client.readTimeoutMillis.toLong()).seconds.toString(),
|
||||
)
|
||||
}
|
||||
if (!headers.names().contains("X-Stainless-Timeout") && client.callTimeoutMillis != 0) {
|
||||
builder.addHeader(
|
||||
"X-Stainless-Timeout",
|
||||
Duration.ofMillis(client.callTimeoutMillis.toLong()).seconds.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
/** `OkHttpClient` always requires a request body for some methods. */
|
||||
private fun requiresBody(method: HttpMethod): Boolean =
|
||||
when (method) {
|
||||
HttpMethod.POST,
|
||||
HttpMethod.PUT,
|
||||
HttpMethod.PATCH -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun HttpRequest.toUrl(): String {
|
||||
val builder = baseUrl.toHttpUrl().newBuilder()
|
||||
pathSegments.forEach(builder::addPathSegment)
|
||||
queryParams.keys().forEach { key ->
|
||||
queryParams.values(key).forEach { builder.addQueryParameter(key, it) }
|
||||
}
|
||||
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
private fun HttpRequestBody.toRequestBody(): RequestBody {
|
||||
val mediaType = contentType()?.toMediaType()
|
||||
val length = contentLength()
|
||||
|
||||
return object : RequestBody() {
|
||||
override fun contentType(): MediaType? = mediaType
|
||||
|
||||
override fun contentLength(): Long = length
|
||||
|
||||
override fun isOneShot(): Boolean = !repeatable()
|
||||
|
||||
override fun writeTo(sink: BufferedSink) = writeTo(sink.outputStream())
|
||||
}
|
||||
}
|
||||
|
||||
private fun Request.toHttpRequest(): HttpRequest {
|
||||
val builder = HttpRequest.builder().method(HttpMethod.valueOf(method)).baseUrl(url.toBaseUrl())
|
||||
url.pathSegments.forEach(builder::addPathSegment)
|
||||
url.queryParameterNames.forEach { name ->
|
||||
url.queryParameterValues(name).filterNotNull().forEach { builder.putQueryParam(name, it) }
|
||||
}
|
||||
headers.forEach { (name, value) -> builder.putHeader(name, value) }
|
||||
body?.let { builder.body(it.toHttpRequestBody()) }
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun HttpUrl.toBaseUrl(): String = buildString {
|
||||
append(scheme).append("://").append(host)
|
||||
if (port != HttpUrl.defaultPort(scheme)) {
|
||||
append(":").append(port)
|
||||
}
|
||||
}
|
||||
|
||||
private fun RequestBody.toHttpRequestBody(): HttpRequestBody {
|
||||
val mediaType = contentType()?.toString()
|
||||
val length = contentLength()
|
||||
val isOneShot = isOneShot()
|
||||
val source = this
|
||||
return object : HttpRequestBody {
|
||||
override fun contentType(): String? = mediaType
|
||||
|
||||
override fun contentLength(): Long = length
|
||||
|
||||
override fun repeatable(): Boolean = !isOneShot
|
||||
|
||||
override fun writeTo(outputStream: OutputStream) {
|
||||
val sink = outputStream.sink().buffer()
|
||||
source.writeTo(sink)
|
||||
sink.flush()
|
||||
}
|
||||
|
||||
override fun close() {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Response.toHttpResponse(): HttpResponse {
|
||||
val headers = headers.toHeaders()
|
||||
|
||||
return object : HttpResponse {
|
||||
override fun statusCode(): Int = code
|
||||
|
||||
override fun headers(): Headers = headers
|
||||
|
||||
override fun body(): InputStream = body!!.byteStream()
|
||||
|
||||
override fun close() = body!!.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun okhttp3.Headers.toHeaders(): Headers {
|
||||
val headersBuilder = Headers.builder()
|
||||
forEach { (name, value) -> headersBuilder.put(name, value) }
|
||||
return headersBuilder.build()
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.langchain.smith.client.okhttp
|
||||
|
||||
import com.langchain.smith.client.LangsmithClient
|
||||
import com.langchain.smith.tracing.LangsmithClientProvider
|
||||
|
||||
/** [LangsmithClientProvider] backed by [LangsmithOkHttpClient]. */
|
||||
class OkHttpLangsmithClientProvider : LangsmithClientProvider {
|
||||
override fun createClient(): LangsmithClient = LangsmithOkHttpClient.fromEnv()
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
com.langchain.smith.client.okhttp.OkHttpLangsmithClientProvider
|
||||
@@ -3,50 +3,109 @@ plugins {
|
||||
id("langchain.publish")
|
||||
}
|
||||
|
||||
val generateVersionProperties by tasks.registering {
|
||||
val outputDir = layout.buildDirectory.dir("generated/resources/version")
|
||||
val projectVersion = provider { project.version.toString() }
|
||||
outputs.dir(outputDir)
|
||||
inputs.property("version", projectVersion)
|
||||
doLast {
|
||||
val dir = outputDir.get().asFile.resolve("com/langchain/smith")
|
||||
dir.mkdirs()
|
||||
dir.resolve("version.properties").writeText("version=${projectVersion.get()}\n")
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
resources.srcDir(generateVersionProperties)
|
||||
}
|
||||
|
||||
val jacksonVersion = "2.22.1"
|
||||
val jacksonAnnotationsVersion = "2.22"
|
||||
|
||||
configurations.matching { it.name in setOf("testCompileClasspath", "testRuntimeClasspath") }.configureEach {
|
||||
// Test-only WireMock 4 requires Java 17+ dependencies. Published artifacts still target Java 8.
|
||||
attributes.attribute(org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
// Compile and test against a lower Jackson version to ensure we're compatible with it. Note that
|
||||
// we generally support 2.13.4, but test against 2.14.0 because 2.13.4 has some annoying (but
|
||||
// niche) bugs (users should upgrade if they encounter them). We publish with a higher version
|
||||
// (see below) to ensure users depend on a secure version by default.
|
||||
force("com.fasterxml.jackson.core:jackson-core:2.14.0")
|
||||
force("com.fasterxml.jackson.core:jackson-databind:2.14.0")
|
||||
force("com.fasterxml.jackson.core:jackson-annotations:2.14.0")
|
||||
force("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.0")
|
||||
force("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.0")
|
||||
force("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0")
|
||||
// Keep all Jackson modules aligned so dependency resolution cannot select a vulnerable
|
||||
// transitive version from SDK or test dependencies.
|
||||
force("com.fasterxml.jackson.core:jackson-core:$jacksonVersion")
|
||||
force("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
|
||||
force("com.fasterxml.jackson.core:jackson-annotations:$jacksonAnnotationsVersion")
|
||||
force("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:$jacksonVersion")
|
||||
force("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion")
|
||||
force("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api("com.fasterxml.jackson.core:jackson-core:2.18.2")
|
||||
api("com.fasterxml.jackson.core:jackson-databind:2.18.2")
|
||||
api("com.fasterxml.jackson.core:jackson-core:$jacksonVersion")
|
||||
api("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
|
||||
api("com.google.errorprone:error_prone_annotations:2.33.0")
|
||||
|
||||
implementation("com.fasterxml.jackson.core:jackson-annotations:2.18.2")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.2")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.2")
|
||||
implementation("com.fasterxml.jackson.core:jackson-annotations:$jacksonAnnotationsVersion")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:$jacksonVersion")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
||||
implementation("com.github.luben:zstd-jni:1.5.7-7")
|
||||
implementation("org.apache.httpcomponents.core5:httpcore5:5.2.4")
|
||||
implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1")
|
||||
|
||||
|
||||
// OpenTelemetry dependencies
|
||||
api("io.opentelemetry:opentelemetry-api:1.32.0")
|
||||
api("io.opentelemetry:opentelemetry-sdk:1.32.0")
|
||||
api("io.opentelemetry:opentelemetry-exporter-otlp:1.32.0")
|
||||
api("io.opentelemetry.semconv:opentelemetry-semconv:1.23.1-alpha")
|
||||
|
||||
// OpenAI SDK (for OpenTelemetry wrappers)
|
||||
api("com.openai:openai-java:4.6.1")
|
||||
api("io.opentelemetry:opentelemetry-api:1.62.0")
|
||||
api("io.opentelemetry:opentelemetry-sdk:1.62.0")
|
||||
api("io.opentelemetry:opentelemetry-exporter-otlp:1.62.0")
|
||||
api("io.opentelemetry.semconv:opentelemetry-semconv:1.41.1")
|
||||
|
||||
// OpenAI SDK (for wrapOpenAI tracing wrapper)
|
||||
api("com.openai:openai-java:4.30.0")
|
||||
|
||||
// Mustache template engine (for prompt template formatting)
|
||||
implementation("com.samskivert:jmustache:1.16")
|
||||
|
||||
// SLF4J for logging (API only - consumers choose implementation)
|
||||
api("org.slf4j:slf4j-api:2.0.17")
|
||||
|
||||
// Anthropic SDK — optional peer dependency for prompt conversion.
|
||||
// Users who call AnthropicPayload.toAnthropicParams() must add this to their own dependencies.
|
||||
compileOnly("com.anthropic:anthropic-java:2.18.0")
|
||||
testImplementation("com.anthropic:anthropic-java:2.18.0")
|
||||
|
||||
// Security: constrain vulnerable transitive test dependencies.
|
||||
// These constraints apply to the test scope only and do not affect published artifacts.
|
||||
constraints {
|
||||
testImplementation("org.eclipse.jetty:jetty-server") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty:jetty-http") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty:jetty-servlets") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty:jetty-xml") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty.http2:http2-common") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty.http2:http2-hpack") { version { require("12.1.10") } }
|
||||
testImplementation("org.eclipse.jetty.http2:http2-server") { version { require("12.1.10") } }
|
||||
testImplementation("org.bouncycastle:bcpg-jdk18on:1.84")
|
||||
testImplementation("org.bouncycastle:bcpkix-jdk18on:1.84")
|
||||
testImplementation("org.bouncycastle:bcprov-jdk18on:1.84")
|
||||
testImplementation("org.apache.logging.log4j:log4j-core:2.25.4")
|
||||
testImplementation("org.apache.opennlp:opennlp-tools") { version { require("2.5.9") } }
|
||||
testImplementation("com.github.jknack:handlebars") { version { require("4.5.2") } }
|
||||
testImplementation("org.codehaus.plexus:plexus-utils:4.0.3")
|
||||
testImplementation("org.apache.commons:commons-lang3:3.18.0")
|
||||
testImplementation("org.apache.commons:commons-compress") { version { require("1.28.0") } }
|
||||
testImplementation("commons-fileupload:commons-fileupload") { version { require("1.6.0") } }
|
||||
testImplementation("commons-io:commons-io") { version { require("2.22.0") } }
|
||||
testImplementation("com.google.guava:guava") { version { require("33.6.0-jre") } }
|
||||
testImplementation("com.jayway.jsonpath:json-path") { version { require("2.10.0") } }
|
||||
testImplementation("org.xmlunit:xmlunit-core") { version { require("2.12.0") } }
|
||||
testImplementation("net.minidev:json-smart") { version { require("2.6.0") } }
|
||||
}
|
||||
|
||||
testImplementation(kotlin("test"))
|
||||
// Simple logging for tests only
|
||||
testImplementation("org.slf4j:slf4j-simple:2.0.17")
|
||||
testImplementation(project(":langsmith-java-client-okhttp"))
|
||||
testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2")
|
||||
testImplementation("org.wiremock:wiremock:4.0.0-beta.37")
|
||||
testImplementation("org.wiremock:wiremock-junit5:4.0.0-beta.37")
|
||||
testImplementation("org.assertj:assertj-core:3.27.7")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.3")
|
||||
@@ -54,4 +113,8 @@ dependencies {
|
||||
testImplementation("org.mockito:mockito-core:5.14.2")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter:5.14.2")
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0")
|
||||
|
||||
// LangChain4j for testing parallel tool execution context propagation
|
||||
testImplementation("dev.langchain4j:langchain4j:1.12.2")
|
||||
testImplementation("dev.langchain4j:langchain4j-core:1.12.2")
|
||||
}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.langchain.smith.client
|
||||
|
||||
import com.langchain.smith.models.info.InfoListResponse.BatchIngestConfig
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
internal data class AutoBatchIngestLimits(
|
||||
val batchSizeLimit: Int = AutoBatchQueue.DEFAULT_BATCH_SIZE_LIMIT,
|
||||
val batchSizeLimitBytes: Int = AutoBatchQueue.DEFAULT_BATCH_SIZE_LIMIT_BYTES,
|
||||
val useMultipartEndpoint: Boolean = true,
|
||||
)
|
||||
|
||||
internal fun BatchIngestConfig?.toAutoBatchIngestLimits(): AutoBatchIngestLimits =
|
||||
AutoBatchIngestLimits(
|
||||
batchSizeLimit =
|
||||
this?.sizeLimit()?.getOrNull()?.takeIf { it > 0 && it <= Int.MAX_VALUE }?.toInt()
|
||||
?: AutoBatchQueue.DEFAULT_BATCH_SIZE_LIMIT,
|
||||
batchSizeLimitBytes =
|
||||
this?.sizeLimitBytes()?.getOrNull()?.takeIf { it > 0 && it <= Int.MAX_VALUE }?.toInt()
|
||||
?: AutoBatchQueue.DEFAULT_BATCH_SIZE_LIMIT_BYTES,
|
||||
useMultipartEndpoint = this?.useMultipartEndpoint()?.getOrNull() ?: true,
|
||||
)
|
||||
@@ -0,0 +1,519 @@
|
||||
package com.langchain.smith.client
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.langchain.smith.core.RequestOptions
|
||||
import com.langchain.smith.core.Timeout
|
||||
import com.langchain.smith.core.http.Headers
|
||||
import com.langchain.smith.core.http.QueryParams
|
||||
import com.langchain.smith.core.jsonMapper
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
import com.langchain.smith.models.runs.RunIngestBatchParams
|
||||
import java.util.concurrent.CompletionException
|
||||
import java.util.concurrent.CompletionStage
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Phaser
|
||||
import java.util.concurrent.RejectedExecutionException
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
* Batches run create/update operations and sends them to LangSmith in a single `ingestBatch`
|
||||
* request, reducing HTTP overhead.
|
||||
*
|
||||
* Operations are buffered and flushed either:
|
||||
* - When the buffer reaches [batchSizeLimit] operations
|
||||
* - After [aggregationDelayMs] milliseconds of inactivity (timer-based drain)
|
||||
* - When [flush] is called explicitly
|
||||
*
|
||||
* @param sendBatch sends a batch and completes when the send has finished
|
||||
* @param batchSizeLimit max operations before auto-flush (default 100)
|
||||
* @param aggregationDelayMs inactivity delay before timer-based flush (default 250ms, matching JS
|
||||
* auto-batch aggregation and Python's initial queue wait)
|
||||
* @param sendParallelism max number of batch requests to send concurrently (default 4)
|
||||
* @param maxAggregationDelayMs max delay before timer-based flush after first item in a burst
|
||||
* (default 500ms, matching Python's background flush interval)
|
||||
* @param batchSizeLimitBytes max serialized JSON body size per batch (default 20 MiB)
|
||||
*/
|
||||
class AutoBatchQueue(
|
||||
private val sendBatch: (RunIngestBatchParams, RequestOptions) -> CompletionStage<Void?>,
|
||||
private val batchSizeLimit: Int = DEFAULT_BATCH_SIZE_LIMIT,
|
||||
private val aggregationDelayMs: Long = DEFAULT_AGGREGATION_DELAY_MS,
|
||||
private val sendParallelism: Int = DEFAULT_SEND_PARALLELISM,
|
||||
private val maxAggregationDelayMs: Long = DEFAULT_MAX_AGGREGATION_DELAY_MS,
|
||||
private val batchSizeLimitBytes: Int,
|
||||
) {
|
||||
private val items = ConcurrentLinkedQueue<BatchItem>()
|
||||
private val queuedCount = AtomicInteger(0)
|
||||
private val shutdown = AtomicBoolean(false)
|
||||
private val enqueueShutdownLock = ReentrantLock()
|
||||
private val delayedFlushLock = ReentrantLock()
|
||||
private var delayedFlushFuture: ScheduledFuture<*>? = null
|
||||
private var firstQueuedAtNanos: Long? = null
|
||||
private val activeSends =
|
||||
object : Phaser(0) {
|
||||
override fun onAdvance(phase: Int, registeredParties: Int): Boolean = false
|
||||
}
|
||||
|
||||
private val coordinator: ScheduledExecutorService =
|
||||
Executors.newSingleThreadScheduledExecutor { r ->
|
||||
Thread(r, "langsmith-batch-coordinator").apply { isDaemon = true }
|
||||
}
|
||||
|
||||
private val sendExecutor: ExecutorService =
|
||||
Executors.newFixedThreadPool(sendParallelism) { r ->
|
||||
Thread(r, "langsmith-batch-sender").apply { isDaemon = true }
|
||||
}
|
||||
|
||||
/** Enqueues a run create operation. */
|
||||
fun post(
|
||||
run: RunIngest,
|
||||
headers: Headers = Headers.builder().build(),
|
||||
queryParams: QueryParams = QueryParams.builder().build(),
|
||||
requestOptions: RequestOptions = RequestOptions.none(),
|
||||
) {
|
||||
enqueue(BatchOp.Post, run, headers, queryParams, requestOptions)
|
||||
}
|
||||
|
||||
/** Enqueues a run update (patch) operation. */
|
||||
fun patch(
|
||||
run: RunIngest,
|
||||
headers: Headers = Headers.builder().build(),
|
||||
queryParams: QueryParams = QueryParams.builder().build(),
|
||||
requestOptions: RequestOptions = RequestOptions.none(),
|
||||
) {
|
||||
enqueue(BatchOp.Patch, run, headers, queryParams, requestOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all queued operations immediately, blocking until batch requests that were queued or
|
||||
* already in-flight have completed.
|
||||
*
|
||||
* Safe to call from any thread. No-op if the queue is empty.
|
||||
*/
|
||||
fun flush() {
|
||||
cancelDelayedFlush()
|
||||
while (true) {
|
||||
if (queuedCount.get() > 0 && !drainOnCoordinator()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!waitForActiveSends()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (queuedCount.get() == 0 && !hasActiveSends()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes remaining operations and shuts down the background executors.
|
||||
*
|
||||
* After calling this, the queue will no longer accept new operations.
|
||||
*/
|
||||
fun shutdown() {
|
||||
val startedShutdown =
|
||||
enqueueShutdownLock.withLock {
|
||||
// Serialize with enqueue's check-and-add so flush cannot miss an item that observed
|
||||
// shutdown=false but has not yet been queued.
|
||||
shutdown.compareAndSet(false, true)
|
||||
}
|
||||
if (!startedShutdown) return
|
||||
|
||||
flush()
|
||||
coordinator.shutdown()
|
||||
sendExecutor.shutdown()
|
||||
|
||||
try {
|
||||
if (!coordinator.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
coordinator.shutdownNow()
|
||||
}
|
||||
if (!sendExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
sendExecutor.shutdownNow()
|
||||
}
|
||||
} catch (_: InterruptedException) {
|
||||
coordinator.shutdownNow()
|
||||
sendExecutor.shutdownNow()
|
||||
Thread.currentThread().interrupt()
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
sendBatch: (RunIngestBatchParams, RequestOptions) -> CompletionStage<Void?>,
|
||||
batchSizeLimit: Int = DEFAULT_BATCH_SIZE_LIMIT,
|
||||
aggregationDelayMs: Long = DEFAULT_AGGREGATION_DELAY_MS,
|
||||
sendParallelism: Int = DEFAULT_SEND_PARALLELISM,
|
||||
) : this(
|
||||
sendBatch = sendBatch,
|
||||
batchSizeLimit = batchSizeLimit,
|
||||
aggregationDelayMs = aggregationDelayMs,
|
||||
sendParallelism = sendParallelism,
|
||||
maxAggregationDelayMs = DEFAULT_MAX_AGGREGATION_DELAY_MS,
|
||||
batchSizeLimitBytes = DEFAULT_BATCH_SIZE_LIMIT_BYTES,
|
||||
)
|
||||
|
||||
/** Returns the number of queued operations (for testing). */
|
||||
internal fun size(): Int = queuedCount.get()
|
||||
|
||||
private fun enqueue(
|
||||
op: BatchOp,
|
||||
run: RunIngest,
|
||||
headers: Headers,
|
||||
queryParams: QueryParams,
|
||||
requestOptions: RequestOptions,
|
||||
) {
|
||||
val count =
|
||||
enqueueShutdownLock.withLock {
|
||||
check(!shutdown.get()) { "AutoBatchQueue is shut down" }
|
||||
items.add(
|
||||
BatchItem(
|
||||
op = op,
|
||||
run = run,
|
||||
headers = headers,
|
||||
queryParams = queryParams,
|
||||
requestOptions = requestOptions,
|
||||
)
|
||||
)
|
||||
queuedCount.incrementAndGet()
|
||||
}
|
||||
|
||||
afterEnqueue(count)
|
||||
}
|
||||
|
||||
private fun afterEnqueue(count: Int) {
|
||||
if (count >= batchSizeLimit) {
|
||||
triggerFlush()
|
||||
} else {
|
||||
scheduleFlush()
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleFlush() {
|
||||
delayedFlushLock.withLock {
|
||||
try {
|
||||
val now = System.nanoTime()
|
||||
val firstQueuedAt = firstQueuedAtNanos ?: now.also { firstQueuedAtNanos = it }
|
||||
val elapsedMs = TimeUnit.NANOSECONDS.toMillis(now - firstQueuedAt)
|
||||
val remainingMaxDelayMs = (maxAggregationDelayMs - elapsedMs).coerceAtLeast(0)
|
||||
val delayMs = minOf(aggregationDelayMs, remainingMaxDelayMs)
|
||||
|
||||
delayedFlushFuture?.cancel(false)
|
||||
delayedFlushFuture =
|
||||
coordinator.schedule(
|
||||
{
|
||||
clearDelayedFlushState()
|
||||
drainAndSubmitSends()
|
||||
},
|
||||
delayMs,
|
||||
TimeUnit.MILLISECONDS,
|
||||
)
|
||||
} catch (e: RejectedExecutionException) {
|
||||
delayedFlushFuture = null
|
||||
firstQueuedAtNanos = null
|
||||
logger.warn("Batch queue coordinator rejected delayed flush", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearDelayedFlushState() {
|
||||
delayedFlushLock.withLock {
|
||||
delayedFlushFuture = null
|
||||
firstQueuedAtNanos = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun triggerFlush() {
|
||||
cancelDelayedFlush()
|
||||
try {
|
||||
coordinator.execute { drainAndSubmitSends() }
|
||||
} catch (e: RejectedExecutionException) {
|
||||
logger.warn("Batch queue coordinator rejected flush", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelDelayedFlush() {
|
||||
delayedFlushLock.withLock {
|
||||
delayedFlushFuture?.cancel(false)
|
||||
delayedFlushFuture = null
|
||||
firstQueuedAtNanos = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun drainOnCoordinator(): Boolean {
|
||||
val drainFuture =
|
||||
try {
|
||||
coordinator.submit { drainAndSubmitSends() }
|
||||
} catch (e: RejectedExecutionException) {
|
||||
throw IllegalStateException("Batch queue coordinator rejected flush", e)
|
||||
}
|
||||
|
||||
try {
|
||||
drainFuture.get()
|
||||
return true
|
||||
} catch (_: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
return false
|
||||
} catch (e: ExecutionException) {
|
||||
throw RuntimeException("Failed to flush batch queue", e.cause)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drainAndSubmitSends() {
|
||||
var remainingToDrain = queuedCount.get()
|
||||
while (remainingToDrain > 0) {
|
||||
val drainResult = drainUpTo(minOf(batchSizeLimit, remainingToDrain))
|
||||
if (drainResult.itemCount == 0) break
|
||||
|
||||
drainResult.batches.forEach(::submitBatch)
|
||||
remainingToDrain -= drainResult.itemCount
|
||||
}
|
||||
|
||||
when {
|
||||
queuedCount.get() >= batchSizeLimit -> triggerFlush()
|
||||
queuedCount.get() > 0 && !shutdown.get() -> scheduleFlush()
|
||||
queuedCount.get() > 0 && shutdown.get() -> triggerFlush()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drains up to [maxItems] queued operations and returns batch params grouped by request
|
||||
* options.
|
||||
*
|
||||
* Multipart ingest compression is applied at send time; legacy `/runs/batch` remains
|
||||
* uncompressed.
|
||||
*
|
||||
* TODO: Support attachment parts in multipart ingest.
|
||||
*/
|
||||
private fun drainUpTo(maxItems: Int): DrainResult {
|
||||
val openGroups = linkedMapOf<RequestOptionsKey, BatchGroup>()
|
||||
var drained = 0
|
||||
|
||||
val batches = buildList {
|
||||
while (drained < maxItems) {
|
||||
val item = items.poll() ?: break
|
||||
queuedCount.decrementAndGet()
|
||||
drained++
|
||||
|
||||
val key = item.requestOptions.key()
|
||||
val itemSerializedSize = objectMapper.writeValueAsBytes(item.run).size
|
||||
var group = openGroups.getOrPut(key) { BatchGroup(item.requestOptions) }
|
||||
if (
|
||||
group.isNotEmpty() &&
|
||||
group.serializedSizeWith(item, itemSerializedSize) > batchSizeLimitBytes
|
||||
) {
|
||||
add(group.toBatch())
|
||||
group = BatchGroup(item.requestOptions)
|
||||
openGroups[key] = group
|
||||
}
|
||||
group.add(item, itemSerializedSize)
|
||||
}
|
||||
|
||||
addAll(openGroups.values.map { it.toBatch() })
|
||||
}
|
||||
return DrainResult(batches = batches, itemCount = drained)
|
||||
}
|
||||
|
||||
private fun submitBatch(batch: Batch) {
|
||||
activeSends.register()
|
||||
try {
|
||||
sendExecutor.execute {
|
||||
try {
|
||||
sendBatch(batch.params, batch.requestOptions).toCompletableFuture().join()
|
||||
} catch (e: CompletionException) {
|
||||
logger.warn("Failed to send batch of runs", e.cause ?: e)
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Failed to send batch of runs", e)
|
||||
} finally {
|
||||
activeSends.arriveAndDeregister()
|
||||
}
|
||||
}
|
||||
} catch (e: RejectedExecutionException) {
|
||||
activeSends.arriveAndDeregister()
|
||||
logger.warn(
|
||||
"Batch queue sender rejected a batch; dropping {} run operations",
|
||||
operationCount(batch.params),
|
||||
e,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun waitForActiveSends(): Boolean {
|
||||
while (hasActiveSends()) {
|
||||
val phase = activeSends.phase
|
||||
try {
|
||||
activeSends.awaitAdvanceInterruptibly(phase)
|
||||
} catch (_: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun hasActiveSends(): Boolean = activeSends.registeredParties > 0
|
||||
|
||||
private fun operationCount(params: RunIngestBatchParams): Int =
|
||||
params.post().orElse(emptyList()).size + params.patch().orElse(emptyList()).size
|
||||
|
||||
private enum class BatchOp {
|
||||
Post,
|
||||
Patch,
|
||||
}
|
||||
|
||||
private data class DrainResult(val batches: List<Batch>, val itemCount: Int)
|
||||
|
||||
private data class Batch(val params: RunIngestBatchParams, val requestOptions: RequestOptions)
|
||||
|
||||
private class BatchGroup(
|
||||
val requestOptions: RequestOptions,
|
||||
val posts: MutableList<RunIngest> = mutableListOf(),
|
||||
val patches: MutableList<RunIngest> = mutableListOf(),
|
||||
val headers: Headers.Builder = Headers.builder(),
|
||||
val queryParams: QueryParams.Builder = QueryParams.builder(),
|
||||
) {
|
||||
private var postSerializedSizeBytes = 0
|
||||
private var patchSerializedSizeBytes = 0
|
||||
|
||||
fun add(item: BatchItem, serializedSizeBytes: Int) {
|
||||
when (item.op) {
|
||||
BatchOp.Post -> {
|
||||
posts.add(item.run)
|
||||
postSerializedSizeBytes += serializedSizeBytes
|
||||
}
|
||||
BatchOp.Patch -> {
|
||||
patches.add(item.run)
|
||||
patchSerializedSizeBytes += serializedSizeBytes
|
||||
}
|
||||
}
|
||||
headers.putAll(item.headers)
|
||||
queryParams.putAll(item.queryParams)
|
||||
}
|
||||
|
||||
fun isNotEmpty(): Boolean = posts.isNotEmpty() || patches.isNotEmpty()
|
||||
|
||||
fun serializedSizeWith(item: BatchItem, itemSerializedSizeBytes: Int): Int =
|
||||
estimatedBodySizeBytes(
|
||||
postCount = posts.size + if (item.op == BatchOp.Post) 1 else 0,
|
||||
postItemsSizeBytes =
|
||||
postSerializedSizeBytes +
|
||||
if (item.op == BatchOp.Post) itemSerializedSizeBytes else 0,
|
||||
patchCount = patches.size + if (item.op == BatchOp.Patch) 1 else 0,
|
||||
patchItemsSizeBytes =
|
||||
patchSerializedSizeBytes +
|
||||
if (item.op == BatchOp.Patch) itemSerializedSizeBytes else 0,
|
||||
)
|
||||
|
||||
private fun estimatedBodySizeBytes(
|
||||
postCount: Int,
|
||||
postItemsSizeBytes: Int,
|
||||
patchCount: Int,
|
||||
patchItemsSizeBytes: Int,
|
||||
): Int {
|
||||
var size = 2 // Opening and closing braces.
|
||||
var fieldCount = 0
|
||||
if (patchCount > 0) {
|
||||
size += fieldSizeBytes("patch", patchCount, patchItemsSizeBytes)
|
||||
fieldCount++
|
||||
}
|
||||
if (postCount > 0) {
|
||||
if (fieldCount > 0) size++ // Comma between top-level fields.
|
||||
size += fieldSizeBytes("post", postCount, postItemsSizeBytes)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
private fun fieldSizeBytes(name: String, count: Int, itemsSizeBytes: Int): Int =
|
||||
name.length + 5 + itemsSizeBytes + (count - 1) // `"name":[]` + item commas.
|
||||
|
||||
fun toBatch(): Batch {
|
||||
val mergeResult = mergePostsAndPatches()
|
||||
val builder = RunIngestBatchParams.builder()
|
||||
if (mergeResult.posts.isNotEmpty()) builder.post(mergeResult.posts)
|
||||
if (mergeResult.patches.isNotEmpty()) builder.patch(mergeResult.patches)
|
||||
builder.additionalHeaders(headers.build())
|
||||
builder.additionalQueryParams(queryParams.build())
|
||||
return Batch(params = builder.build(), requestOptions = requestOptions)
|
||||
}
|
||||
|
||||
private fun mergePostsAndPatches(): MergeResult {
|
||||
if (posts.isEmpty() || patches.isEmpty()) {
|
||||
return MergeResult(posts = posts, patches = patches)
|
||||
}
|
||||
|
||||
val postsById =
|
||||
posts.mapNotNull { post -> post.id().getOrNull()?.let { it to post } }.toMap()
|
||||
val postsWithoutId = posts.filter { it.id().getOrNull() == null }
|
||||
val patchesByPostId =
|
||||
patches.mapNotNull { patch ->
|
||||
patch.id().getOrNull()?.takeIf(postsById::containsKey)?.let { it to patch }
|
||||
}
|
||||
val patchesByPostIdMap = patchesByPostId.toMap()
|
||||
val standalonePatches =
|
||||
patches.filter { patch ->
|
||||
patch.id().getOrNull()?.let(postsById::containsKey) != true
|
||||
}
|
||||
|
||||
return MergeResult(
|
||||
posts =
|
||||
postsWithoutId +
|
||||
postsById.map { (id, post) ->
|
||||
patchesByPostIdMap[id]?.let { mergePostAndPatch(post, it) } ?: post
|
||||
},
|
||||
patches = standalonePatches,
|
||||
mergedRunIds = patchesByPostId.map { it.first },
|
||||
)
|
||||
}
|
||||
|
||||
private fun mergePostAndPatch(post: RunIngest, patch: RunIngest): RunIngest {
|
||||
val merged = objectMapper.valueToTree<ObjectNode>(post)
|
||||
val patchFields = objectMapper.valueToTree<ObjectNode>(patch)
|
||||
patchFields.fields().forEach { (field, value) -> merged.set<ObjectNode>(field, value) }
|
||||
val mergedRun = objectMapper.treeToValue(merged, RunIngest::class.java)
|
||||
return mergedRun
|
||||
.toBuilder()
|
||||
.attachments(post.attachments() + patch.attachments())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private data class MergeResult(
|
||||
val posts: List<RunIngest>,
|
||||
val patches: List<RunIngest>,
|
||||
val mergedRunIds: List<String> = emptyList(),
|
||||
)
|
||||
|
||||
private data class BatchItem(
|
||||
val op: BatchOp,
|
||||
val run: RunIngest,
|
||||
val headers: Headers,
|
||||
val queryParams: QueryParams,
|
||||
val requestOptions: RequestOptions,
|
||||
)
|
||||
|
||||
private data class RequestOptionsKey(val responseValidation: Boolean?, val timeout: Timeout?)
|
||||
|
||||
private fun RequestOptions.key(): RequestOptionsKey =
|
||||
RequestOptionsKey(responseValidation = responseValidation, timeout = timeout)
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(AutoBatchQueue::class.java)
|
||||
private val objectMapper = jsonMapper()
|
||||
|
||||
const val DEFAULT_BATCH_SIZE_LIMIT = 100
|
||||
const val DEFAULT_BATCH_SIZE_LIMIT_BYTES = 20_971_520
|
||||
const val DEFAULT_AGGREGATION_DELAY_MS = 250L
|
||||
const val DEFAULT_MAX_AGGREGATION_DELAY_MS = 500L
|
||||
const val DEFAULT_SEND_PARALLELISM = 4
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,21 @@ import com.langchain.smith.core.ClientOptions
|
||||
import com.langchain.smith.services.blocking.AnnotationQueueService
|
||||
import com.langchain.smith.services.blocking.CommitService
|
||||
import com.langchain.smith.services.blocking.DatasetService
|
||||
import com.langchain.smith.services.blocking.EvaluatorService
|
||||
import com.langchain.smith.services.blocking.ExampleService
|
||||
import com.langchain.smith.services.blocking.FeedbackService
|
||||
import com.langchain.smith.services.blocking.InfoService
|
||||
import com.langchain.smith.services.blocking.IssueService
|
||||
import com.langchain.smith.services.blocking.OnlineEvaluatorService
|
||||
import com.langchain.smith.services.blocking.PublicService
|
||||
import com.langchain.smith.services.blocking.RepoService
|
||||
import com.langchain.smith.services.blocking.RunService
|
||||
import com.langchain.smith.services.blocking.SandboxService
|
||||
import com.langchain.smith.services.blocking.SessionService
|
||||
import com.langchain.smith.services.blocking.SettingService
|
||||
import com.langchain.smith.services.blocking.ThreadService
|
||||
import com.langchain.smith.services.blocking.TraceService
|
||||
import com.langchain.smith.services.blocking.WorkspaceService
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
@@ -59,18 +67,34 @@ interface LangsmithClient {
|
||||
|
||||
fun runs(): RunService
|
||||
|
||||
fun threads(): ThreadService
|
||||
|
||||
fun traces(): TraceService
|
||||
|
||||
fun evaluators(): EvaluatorService
|
||||
|
||||
fun onlineEvaluators(): OnlineEvaluatorService
|
||||
|
||||
fun feedback(): FeedbackService
|
||||
|
||||
fun public_(): PublicService
|
||||
|
||||
fun annotationQueues(): AnnotationQueueService
|
||||
|
||||
fun info(): InfoService
|
||||
|
||||
fun workspaces(): WorkspaceService
|
||||
|
||||
fun repos(): RepoService
|
||||
|
||||
fun commits(): CommitService
|
||||
|
||||
fun settings(): SettingService
|
||||
|
||||
fun issues(): IssueService
|
||||
|
||||
fun sandboxes(): SandboxService
|
||||
|
||||
/**
|
||||
* Closes this client, relinquishing any underlying resources.
|
||||
*
|
||||
@@ -102,16 +126,32 @@ interface LangsmithClient {
|
||||
|
||||
fun runs(): RunService.WithRawResponse
|
||||
|
||||
fun threads(): ThreadService.WithRawResponse
|
||||
|
||||
fun traces(): TraceService.WithRawResponse
|
||||
|
||||
fun evaluators(): EvaluatorService.WithRawResponse
|
||||
|
||||
fun onlineEvaluators(): OnlineEvaluatorService.WithRawResponse
|
||||
|
||||
fun feedback(): FeedbackService.WithRawResponse
|
||||
|
||||
fun public_(): PublicService.WithRawResponse
|
||||
|
||||
fun annotationQueues(): AnnotationQueueService.WithRawResponse
|
||||
|
||||
fun info(): InfoService.WithRawResponse
|
||||
|
||||
fun workspaces(): WorkspaceService.WithRawResponse
|
||||
|
||||
fun repos(): RepoService.WithRawResponse
|
||||
|
||||
fun commits(): CommitService.WithRawResponse
|
||||
|
||||
fun settings(): SettingService.WithRawResponse
|
||||
|
||||
fun issues(): IssueService.WithRawResponse
|
||||
|
||||
fun sandboxes(): SandboxService.WithRawResponse
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -6,13 +6,21 @@ import com.langchain.smith.core.ClientOptions
|
||||
import com.langchain.smith.services.async.AnnotationQueueServiceAsync
|
||||
import com.langchain.smith.services.async.CommitServiceAsync
|
||||
import com.langchain.smith.services.async.DatasetServiceAsync
|
||||
import com.langchain.smith.services.async.EvaluatorServiceAsync
|
||||
import com.langchain.smith.services.async.ExampleServiceAsync
|
||||
import com.langchain.smith.services.async.FeedbackServiceAsync
|
||||
import com.langchain.smith.services.async.InfoServiceAsync
|
||||
import com.langchain.smith.services.async.IssueServiceAsync
|
||||
import com.langchain.smith.services.async.OnlineEvaluatorServiceAsync
|
||||
import com.langchain.smith.services.async.PublicServiceAsync
|
||||
import com.langchain.smith.services.async.RepoServiceAsync
|
||||
import com.langchain.smith.services.async.RunServiceAsync
|
||||
import com.langchain.smith.services.async.SandboxServiceAsync
|
||||
import com.langchain.smith.services.async.SessionServiceAsync
|
||||
import com.langchain.smith.services.async.SettingServiceAsync
|
||||
import com.langchain.smith.services.async.ThreadServiceAsync
|
||||
import com.langchain.smith.services.async.TraceServiceAsync
|
||||
import com.langchain.smith.services.async.WorkspaceServiceAsync
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
@@ -59,18 +67,34 @@ interface LangsmithClientAsync {
|
||||
|
||||
fun runs(): RunServiceAsync
|
||||
|
||||
fun threads(): ThreadServiceAsync
|
||||
|
||||
fun traces(): TraceServiceAsync
|
||||
|
||||
fun evaluators(): EvaluatorServiceAsync
|
||||
|
||||
fun onlineEvaluators(): OnlineEvaluatorServiceAsync
|
||||
|
||||
fun feedback(): FeedbackServiceAsync
|
||||
|
||||
fun public_(): PublicServiceAsync
|
||||
|
||||
fun annotationQueues(): AnnotationQueueServiceAsync
|
||||
|
||||
fun info(): InfoServiceAsync
|
||||
|
||||
fun workspaces(): WorkspaceServiceAsync
|
||||
|
||||
fun repos(): RepoServiceAsync
|
||||
|
||||
fun commits(): CommitServiceAsync
|
||||
|
||||
fun settings(): SettingServiceAsync
|
||||
|
||||
fun issues(): IssueServiceAsync
|
||||
|
||||
fun sandboxes(): SandboxServiceAsync
|
||||
|
||||
/**
|
||||
* Closes this client, relinquishing any underlying resources.
|
||||
*
|
||||
@@ -106,16 +130,32 @@ interface LangsmithClientAsync {
|
||||
|
||||
fun runs(): RunServiceAsync.WithRawResponse
|
||||
|
||||
fun threads(): ThreadServiceAsync.WithRawResponse
|
||||
|
||||
fun traces(): TraceServiceAsync.WithRawResponse
|
||||
|
||||
fun evaluators(): EvaluatorServiceAsync.WithRawResponse
|
||||
|
||||
fun onlineEvaluators(): OnlineEvaluatorServiceAsync.WithRawResponse
|
||||
|
||||
fun feedback(): FeedbackServiceAsync.WithRawResponse
|
||||
|
||||
fun public_(): PublicServiceAsync.WithRawResponse
|
||||
|
||||
fun annotationQueues(): AnnotationQueueServiceAsync.WithRawResponse
|
||||
|
||||
fun info(): InfoServiceAsync.WithRawResponse
|
||||
|
||||
fun workspaces(): WorkspaceServiceAsync.WithRawResponse
|
||||
|
||||
fun repos(): RepoServiceAsync.WithRawResponse
|
||||
|
||||
fun commits(): CommitServiceAsync.WithRawResponse
|
||||
|
||||
fun settings(): SettingServiceAsync.WithRawResponse
|
||||
|
||||
fun issues(): IssueServiceAsync.WithRawResponse
|
||||
|
||||
fun sandboxes(): SandboxServiceAsync.WithRawResponse
|
||||
}
|
||||
}
|
||||
|
||||
+126
-6
@@ -10,20 +10,36 @@ import com.langchain.smith.services.async.CommitServiceAsync
|
||||
import com.langchain.smith.services.async.CommitServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.DatasetServiceAsync
|
||||
import com.langchain.smith.services.async.DatasetServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.EvaluatorServiceAsync
|
||||
import com.langchain.smith.services.async.EvaluatorServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.ExampleServiceAsync
|
||||
import com.langchain.smith.services.async.ExampleServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.FeedbackServiceAsync
|
||||
import com.langchain.smith.services.async.FeedbackServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.InfoServiceAsync
|
||||
import com.langchain.smith.services.async.InfoServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.IssueServiceAsync
|
||||
import com.langchain.smith.services.async.IssueServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.OnlineEvaluatorServiceAsync
|
||||
import com.langchain.smith.services.async.OnlineEvaluatorServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.PublicServiceAsync
|
||||
import com.langchain.smith.services.async.PublicServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.RepoServiceAsync
|
||||
import com.langchain.smith.services.async.RepoServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.RunServiceAsync
|
||||
import com.langchain.smith.services.async.RunServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.SandboxServiceAsync
|
||||
import com.langchain.smith.services.async.SandboxServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.SessionServiceAsync
|
||||
import com.langchain.smith.services.async.SessionServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.SettingServiceAsync
|
||||
import com.langchain.smith.services.async.SettingServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.ThreadServiceAsync
|
||||
import com.langchain.smith.services.async.ThreadServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.TraceServiceAsync
|
||||
import com.langchain.smith.services.async.TraceServiceAsyncImpl
|
||||
import com.langchain.smith.services.async.WorkspaceServiceAsync
|
||||
import com.langchain.smith.services.async.WorkspaceServiceAsyncImpl
|
||||
import java.util.function.Consumer
|
||||
|
||||
class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : LangsmithClientAsync {
|
||||
@@ -33,11 +49,11 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
else
|
||||
clientOptions
|
||||
.toBuilder()
|
||||
.putHeader("User-Agent", "${javaClass.simpleName}/Java ${getPackageVersion()}")
|
||||
.putHeader("User-Agent", "langsmith-java/${getPackageVersion()}")
|
||||
.build()
|
||||
|
||||
// Pass the original clientOptions so that this client sets its own User-Agent.
|
||||
private val sync: LangsmithClient by lazy { LangsmithClientImpl(clientOptions) }
|
||||
private val sync = lazy { LangsmithClientImpl(clientOptions) }
|
||||
|
||||
private val withRawResponse: LangsmithClientAsync.WithRawResponse by lazy {
|
||||
WithRawResponseImpl(clientOptions)
|
||||
@@ -55,7 +71,23 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
DatasetServiceAsyncImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val runs: RunServiceAsync by lazy { RunServiceAsyncImpl(clientOptionsWithUserAgent) }
|
||||
private val runs = lazy { RunServiceAsyncImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val threads: ThreadServiceAsync by lazy {
|
||||
ThreadServiceAsyncImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val traces: TraceServiceAsync by lazy {
|
||||
TraceServiceAsyncImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val evaluators: EvaluatorServiceAsync by lazy {
|
||||
EvaluatorServiceAsyncImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val onlineEvaluators: OnlineEvaluatorServiceAsync by lazy {
|
||||
OnlineEvaluatorServiceAsyncImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val feedback: FeedbackServiceAsync by lazy {
|
||||
FeedbackServiceAsyncImpl(clientOptionsWithUserAgent)
|
||||
@@ -69,6 +101,12 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
AnnotationQueueServiceAsyncImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val info: InfoServiceAsync by lazy { InfoServiceAsyncImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val workspaces: WorkspaceServiceAsync by lazy {
|
||||
WorkspaceServiceAsyncImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val repos: RepoServiceAsync by lazy { RepoServiceAsyncImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val commits: CommitServiceAsync by lazy {
|
||||
@@ -79,7 +117,15 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
SettingServiceAsyncImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
override fun sync(): LangsmithClient = sync
|
||||
private val issues: IssueServiceAsync by lazy {
|
||||
IssueServiceAsyncImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val sandboxes: SandboxServiceAsync by lazy {
|
||||
SandboxServiceAsyncImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
override fun sync(): LangsmithClient = sync.value
|
||||
|
||||
override fun withRawResponse(): LangsmithClientAsync.WithRawResponse = withRawResponse
|
||||
|
||||
@@ -92,7 +138,15 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
|
||||
override fun datasets(): DatasetServiceAsync = datasets
|
||||
|
||||
override fun runs(): RunServiceAsync = runs
|
||||
override fun runs(): RunServiceAsync = runs.value
|
||||
|
||||
override fun threads(): ThreadServiceAsync = threads
|
||||
|
||||
override fun traces(): TraceServiceAsync = traces
|
||||
|
||||
override fun evaluators(): EvaluatorServiceAsync = evaluators
|
||||
|
||||
override fun onlineEvaluators(): OnlineEvaluatorServiceAsync = onlineEvaluators
|
||||
|
||||
override fun feedback(): FeedbackServiceAsync = feedback
|
||||
|
||||
@@ -100,13 +154,30 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
|
||||
override fun annotationQueues(): AnnotationQueueServiceAsync = annotationQueues
|
||||
|
||||
override fun info(): InfoServiceAsync = info
|
||||
|
||||
override fun workspaces(): WorkspaceServiceAsync = workspaces
|
||||
|
||||
override fun repos(): RepoServiceAsync = repos
|
||||
|
||||
override fun commits(): CommitServiceAsync = commits
|
||||
|
||||
override fun settings(): SettingServiceAsync = settings
|
||||
|
||||
override fun close() = clientOptions.close()
|
||||
override fun issues(): IssueServiceAsync = issues
|
||||
|
||||
override fun sandboxes(): SandboxServiceAsync = sandboxes
|
||||
|
||||
override fun close() {
|
||||
if (runs.isInitialized()) {
|
||||
runs.value.shutdown()
|
||||
}
|
||||
if (sync.isInitialized()) {
|
||||
sync.value.close()
|
||||
} else {
|
||||
clientOptions.close()
|
||||
}
|
||||
}
|
||||
|
||||
class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
|
||||
LangsmithClientAsync.WithRawResponse {
|
||||
@@ -127,6 +198,22 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
RunServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val threads: ThreadServiceAsync.WithRawResponse by lazy {
|
||||
ThreadServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val traces: TraceServiceAsync.WithRawResponse by lazy {
|
||||
TraceServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val evaluators: EvaluatorServiceAsync.WithRawResponse by lazy {
|
||||
EvaluatorServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val onlineEvaluators: OnlineEvaluatorServiceAsync.WithRawResponse by lazy {
|
||||
OnlineEvaluatorServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val feedback: FeedbackServiceAsync.WithRawResponse by lazy {
|
||||
FeedbackServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
@@ -139,6 +226,14 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
AnnotationQueueServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val info: InfoServiceAsync.WithRawResponse by lazy {
|
||||
InfoServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val workspaces: WorkspaceServiceAsync.WithRawResponse by lazy {
|
||||
WorkspaceServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val repos: RepoServiceAsync.WithRawResponse by lazy {
|
||||
RepoServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
@@ -151,6 +246,14 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
SettingServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val issues: IssueServiceAsync.WithRawResponse by lazy {
|
||||
IssueServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val sandboxes: SandboxServiceAsync.WithRawResponse by lazy {
|
||||
SandboxServiceAsyncImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
override fun withOptions(
|
||||
modifier: Consumer<ClientOptions.Builder>
|
||||
): LangsmithClientAsync.WithRawResponse =
|
||||
@@ -166,6 +269,15 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
|
||||
override fun runs(): RunServiceAsync.WithRawResponse = runs
|
||||
|
||||
override fun threads(): ThreadServiceAsync.WithRawResponse = threads
|
||||
|
||||
override fun traces(): TraceServiceAsync.WithRawResponse = traces
|
||||
|
||||
override fun evaluators(): EvaluatorServiceAsync.WithRawResponse = evaluators
|
||||
|
||||
override fun onlineEvaluators(): OnlineEvaluatorServiceAsync.WithRawResponse =
|
||||
onlineEvaluators
|
||||
|
||||
override fun feedback(): FeedbackServiceAsync.WithRawResponse = feedback
|
||||
|
||||
override fun public_(): PublicServiceAsync.WithRawResponse = public_
|
||||
@@ -173,10 +285,18 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
override fun annotationQueues(): AnnotationQueueServiceAsync.WithRawResponse =
|
||||
annotationQueues
|
||||
|
||||
override fun info(): InfoServiceAsync.WithRawResponse = info
|
||||
|
||||
override fun workspaces(): WorkspaceServiceAsync.WithRawResponse = workspaces
|
||||
|
||||
override fun repos(): RepoServiceAsync.WithRawResponse = repos
|
||||
|
||||
override fun commits(): CommitServiceAsync.WithRawResponse = commits
|
||||
|
||||
override fun settings(): SettingServiceAsync.WithRawResponse = settings
|
||||
|
||||
override fun issues(): IssueServiceAsync.WithRawResponse = issues
|
||||
|
||||
override fun sandboxes(): SandboxServiceAsync.WithRawResponse = sandboxes
|
||||
}
|
||||
}
|
||||
|
||||
+111
-4
@@ -10,20 +10,36 @@ import com.langchain.smith.services.blocking.CommitService
|
||||
import com.langchain.smith.services.blocking.CommitServiceImpl
|
||||
import com.langchain.smith.services.blocking.DatasetService
|
||||
import com.langchain.smith.services.blocking.DatasetServiceImpl
|
||||
import com.langchain.smith.services.blocking.EvaluatorService
|
||||
import com.langchain.smith.services.blocking.EvaluatorServiceImpl
|
||||
import com.langchain.smith.services.blocking.ExampleService
|
||||
import com.langchain.smith.services.blocking.ExampleServiceImpl
|
||||
import com.langchain.smith.services.blocking.FeedbackService
|
||||
import com.langchain.smith.services.blocking.FeedbackServiceImpl
|
||||
import com.langchain.smith.services.blocking.InfoService
|
||||
import com.langchain.smith.services.blocking.InfoServiceImpl
|
||||
import com.langchain.smith.services.blocking.IssueService
|
||||
import com.langchain.smith.services.blocking.IssueServiceImpl
|
||||
import com.langchain.smith.services.blocking.OnlineEvaluatorService
|
||||
import com.langchain.smith.services.blocking.OnlineEvaluatorServiceImpl
|
||||
import com.langchain.smith.services.blocking.PublicService
|
||||
import com.langchain.smith.services.blocking.PublicServiceImpl
|
||||
import com.langchain.smith.services.blocking.RepoService
|
||||
import com.langchain.smith.services.blocking.RepoServiceImpl
|
||||
import com.langchain.smith.services.blocking.RunService
|
||||
import com.langchain.smith.services.blocking.RunServiceImpl
|
||||
import com.langchain.smith.services.blocking.SandboxService
|
||||
import com.langchain.smith.services.blocking.SandboxServiceImpl
|
||||
import com.langchain.smith.services.blocking.SessionService
|
||||
import com.langchain.smith.services.blocking.SessionServiceImpl
|
||||
import com.langchain.smith.services.blocking.SettingService
|
||||
import com.langchain.smith.services.blocking.SettingServiceImpl
|
||||
import com.langchain.smith.services.blocking.ThreadService
|
||||
import com.langchain.smith.services.blocking.ThreadServiceImpl
|
||||
import com.langchain.smith.services.blocking.TraceService
|
||||
import com.langchain.smith.services.blocking.TraceServiceImpl
|
||||
import com.langchain.smith.services.blocking.WorkspaceService
|
||||
import com.langchain.smith.services.blocking.WorkspaceServiceImpl
|
||||
import java.util.function.Consumer
|
||||
|
||||
class LangsmithClientImpl(private val clientOptions: ClientOptions) : LangsmithClient {
|
||||
@@ -33,7 +49,7 @@ class LangsmithClientImpl(private val clientOptions: ClientOptions) : LangsmithC
|
||||
else
|
||||
clientOptions
|
||||
.toBuilder()
|
||||
.putHeader("User-Agent", "${javaClass.simpleName}/Java ${getPackageVersion()}")
|
||||
.putHeader("User-Agent", "langsmith-java/${getPackageVersion()}")
|
||||
.build()
|
||||
|
||||
// Pass the original clientOptions so that this client sets its own User-Agent.
|
||||
@@ -49,7 +65,19 @@ class LangsmithClientImpl(private val clientOptions: ClientOptions) : LangsmithC
|
||||
|
||||
private val datasets: DatasetService by lazy { DatasetServiceImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val runs: RunService by lazy { RunServiceImpl(clientOptionsWithUserAgent) }
|
||||
private val runs = lazy { RunServiceImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val threads: ThreadService by lazy { ThreadServiceImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val traces: TraceService by lazy { TraceServiceImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val evaluators: EvaluatorService by lazy {
|
||||
EvaluatorServiceImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val onlineEvaluators: OnlineEvaluatorService by lazy {
|
||||
OnlineEvaluatorServiceImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val feedback: FeedbackService by lazy {
|
||||
FeedbackServiceImpl(clientOptionsWithUserAgent)
|
||||
@@ -61,12 +89,22 @@ class LangsmithClientImpl(private val clientOptions: ClientOptions) : LangsmithC
|
||||
AnnotationQueueServiceImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val info: InfoService by lazy { InfoServiceImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val workspaces: WorkspaceService by lazy {
|
||||
WorkspaceServiceImpl(clientOptionsWithUserAgent)
|
||||
}
|
||||
|
||||
private val repos: RepoService by lazy { RepoServiceImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val commits: CommitService by lazy { CommitServiceImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val settings: SettingService by lazy { SettingServiceImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val issues: IssueService by lazy { IssueServiceImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
private val sandboxes: SandboxService by lazy { SandboxServiceImpl(clientOptionsWithUserAgent) }
|
||||
|
||||
override fun async(): LangsmithClientAsync = async
|
||||
|
||||
override fun withRawResponse(): LangsmithClient.WithRawResponse = withRawResponse
|
||||
@@ -80,7 +118,15 @@ class LangsmithClientImpl(private val clientOptions: ClientOptions) : LangsmithC
|
||||
|
||||
override fun datasets(): DatasetService = datasets
|
||||
|
||||
override fun runs(): RunService = runs
|
||||
override fun runs(): RunService = runs.value
|
||||
|
||||
override fun threads(): ThreadService = threads
|
||||
|
||||
override fun traces(): TraceService = traces
|
||||
|
||||
override fun evaluators(): EvaluatorService = evaluators
|
||||
|
||||
override fun onlineEvaluators(): OnlineEvaluatorService = onlineEvaluators
|
||||
|
||||
override fun feedback(): FeedbackService = feedback
|
||||
|
||||
@@ -88,13 +134,26 @@ class LangsmithClientImpl(private val clientOptions: ClientOptions) : LangsmithC
|
||||
|
||||
override fun annotationQueues(): AnnotationQueueService = annotationQueues
|
||||
|
||||
override fun info(): InfoService = info
|
||||
|
||||
override fun workspaces(): WorkspaceService = workspaces
|
||||
|
||||
override fun repos(): RepoService = repos
|
||||
|
||||
override fun commits(): CommitService = commits
|
||||
|
||||
override fun settings(): SettingService = settings
|
||||
|
||||
override fun close() = clientOptions.close()
|
||||
override fun issues(): IssueService = issues
|
||||
|
||||
override fun sandboxes(): SandboxService = sandboxes
|
||||
|
||||
override fun close() {
|
||||
if (runs.isInitialized()) {
|
||||
runs.value.shutdown()
|
||||
}
|
||||
clientOptions.close()
|
||||
}
|
||||
|
||||
class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
|
||||
LangsmithClient.WithRawResponse {
|
||||
@@ -115,6 +174,22 @@ class LangsmithClientImpl(private val clientOptions: ClientOptions) : LangsmithC
|
||||
RunServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val threads: ThreadService.WithRawResponse by lazy {
|
||||
ThreadServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val traces: TraceService.WithRawResponse by lazy {
|
||||
TraceServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val evaluators: EvaluatorService.WithRawResponse by lazy {
|
||||
EvaluatorServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val onlineEvaluators: OnlineEvaluatorService.WithRawResponse by lazy {
|
||||
OnlineEvaluatorServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val feedback: FeedbackService.WithRawResponse by lazy {
|
||||
FeedbackServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
@@ -127,6 +202,14 @@ class LangsmithClientImpl(private val clientOptions: ClientOptions) : LangsmithC
|
||||
AnnotationQueueServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val info: InfoService.WithRawResponse by lazy {
|
||||
InfoServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val workspaces: WorkspaceService.WithRawResponse by lazy {
|
||||
WorkspaceServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val repos: RepoService.WithRawResponse by lazy {
|
||||
RepoServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
@@ -139,6 +222,14 @@ class LangsmithClientImpl(private val clientOptions: ClientOptions) : LangsmithC
|
||||
SettingServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val issues: IssueService.WithRawResponse by lazy {
|
||||
IssueServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
private val sandboxes: SandboxService.WithRawResponse by lazy {
|
||||
SandboxServiceImpl.WithRawResponseImpl(clientOptions)
|
||||
}
|
||||
|
||||
override fun withOptions(
|
||||
modifier: Consumer<ClientOptions.Builder>
|
||||
): LangsmithClient.WithRawResponse =
|
||||
@@ -154,16 +245,32 @@ class LangsmithClientImpl(private val clientOptions: ClientOptions) : LangsmithC
|
||||
|
||||
override fun runs(): RunService.WithRawResponse = runs
|
||||
|
||||
override fun threads(): ThreadService.WithRawResponse = threads
|
||||
|
||||
override fun traces(): TraceService.WithRawResponse = traces
|
||||
|
||||
override fun evaluators(): EvaluatorService.WithRawResponse = evaluators
|
||||
|
||||
override fun onlineEvaluators(): OnlineEvaluatorService.WithRawResponse = onlineEvaluators
|
||||
|
||||
override fun feedback(): FeedbackService.WithRawResponse = feedback
|
||||
|
||||
override fun public_(): PublicService.WithRawResponse = public_
|
||||
|
||||
override fun annotationQueues(): AnnotationQueueService.WithRawResponse = annotationQueues
|
||||
|
||||
override fun info(): InfoService.WithRawResponse = info
|
||||
|
||||
override fun workspaces(): WorkspaceService.WithRawResponse = workspaces
|
||||
|
||||
override fun repos(): RepoService.WithRawResponse = repos
|
||||
|
||||
override fun commits(): CommitService.WithRawResponse = commits
|
||||
|
||||
override fun settings(): SettingService.WithRawResponse = settings
|
||||
|
||||
override fun issues(): IssueService.WithRawResponse = issues
|
||||
|
||||
override fun sandboxes(): SandboxService.WithRawResponse = sandboxes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.langchain.smith.client
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.langchain.smith.core.MultipartField
|
||||
import com.langchain.smith.core.http.HttpRequestBody
|
||||
import com.langchain.smith.core.http.multipartFormData
|
||||
import com.langchain.smith.models.runs.RunAttachment
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
import com.langchain.smith.models.runs.RunIngestBatchParams
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
internal fun RunIngestBatchParams.toRunMultipartFormData(jsonMapper: JsonMapper): HttpRequestBody? {
|
||||
// Multipart requires run id, trace_id, and dotted_order to address each part correctly.
|
||||
// If a queued run is missing those fields, callers fall back to legacy JSON batch ingest
|
||||
// instead of failing the entire auto-batch.
|
||||
val fields =
|
||||
listOf(
|
||||
multipartFieldsForRuns(
|
||||
jsonMapper,
|
||||
operation = "post",
|
||||
runs = post().orElse(emptyList()),
|
||||
) ?: return null,
|
||||
multipartFieldsForRuns(
|
||||
jsonMapper,
|
||||
operation = "patch",
|
||||
runs = patch().orElse(emptyList()),
|
||||
) ?: return null,
|
||||
)
|
||||
.flatten()
|
||||
.toMap()
|
||||
|
||||
return fields.takeIf { it.isNotEmpty() }?.let { multipartFormData(jsonMapper, it) }
|
||||
}
|
||||
|
||||
private fun multipartFieldsForRuns(
|
||||
jsonMapper: JsonMapper,
|
||||
operation: String,
|
||||
runs: List<RunIngest>,
|
||||
): List<Pair<String, MultipartField<*>>>? =
|
||||
runs.flatMap { run -> multipartFieldsForRun(jsonMapper, operation, run) ?: return null }
|
||||
|
||||
private fun multipartFieldsForRun(
|
||||
jsonMapper: JsonMapper,
|
||||
operation: String,
|
||||
run: RunIngest,
|
||||
): List<Pair<String, MultipartField<*>>>? {
|
||||
val runId = run.id().getOrNull() ?: return null
|
||||
run.traceId().getOrNull() ?: return null
|
||||
run.dottedOrder().getOrNull() ?: return null
|
||||
|
||||
val runFields =
|
||||
jsonMapper
|
||||
.valueToTree<ObjectNode>(run)
|
||||
.fields()
|
||||
.asSequence()
|
||||
.map { it.key to it.value }
|
||||
.toList()
|
||||
val main =
|
||||
jsonMapper
|
||||
.createObjectNode()
|
||||
.setAll<ObjectNode>(
|
||||
runFields
|
||||
.filterNot { (fieldName, _) -> fieldName in MULTIPART_EXCLUDED_FIELDS }
|
||||
.toMap()
|
||||
)
|
||||
val separateFields =
|
||||
MULTIPART_SEPARATE_FIELDS.mapNotNull { fieldName ->
|
||||
runFields
|
||||
.firstOrNull { (name, _) -> name == fieldName }
|
||||
?.second
|
||||
?.takeUnless { it.isNull || it.isMissingNode }
|
||||
?.let { fieldName to it }
|
||||
}
|
||||
|
||||
return listOf("$operation.$runId" to jsonField(jsonMapper, main)) +
|
||||
separateFields.map { (fieldName, value) ->
|
||||
"$operation.$runId.$fieldName" to jsonField(jsonMapper, value)
|
||||
} +
|
||||
attachmentFields(runId, run.attachments())
|
||||
}
|
||||
|
||||
private fun jsonField(jsonMapper: JsonMapper, value: JsonNode): MultipartField<ByteArray> {
|
||||
val bytes = jsonMapper.writeValueAsBytes(value)
|
||||
return MultipartField.builder<ByteArray>()
|
||||
.value(bytes)
|
||||
.contentType("application/json; length=${bytes.size}")
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun attachmentFields(
|
||||
runId: String,
|
||||
attachments: Map<String, RunAttachment>,
|
||||
): List<Pair<String, MultipartField<*>>> =
|
||||
attachments.mapNotNull { (name, attachment) ->
|
||||
if (name.contains('.')) {
|
||||
logger.warn(
|
||||
"Skipping attachment '{}' for run {}: attachment names must not contain periods ('.')",
|
||||
name,
|
||||
runId,
|
||||
)
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val contentType =
|
||||
attachment.length().getOrNull()?.let { "${attachment.contentType()}; length=$it" }
|
||||
?: attachment.contentType()
|
||||
"attachment.$runId.$name" to
|
||||
MultipartField.builder<Any>()
|
||||
.value(attachment.multipartValue())
|
||||
.contentType(contentType)
|
||||
.filename(attachment.filename())
|
||||
.build()
|
||||
}
|
||||
|
||||
private val MULTIPART_SEPARATE_FIELDS =
|
||||
listOf("inputs", "outputs", "events", "extra", "error", "serialized")
|
||||
|
||||
private val MULTIPART_EXCLUDED_FIELDS = MULTIPART_SEPARATE_FIELDS + "attachments"
|
||||
|
||||
private val logger = LoggerFactory.getLogger("com.langchain.smith.client.RunMultipartBatch")
|
||||
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.langchain.smith.core.http.AsyncStreamResponse
|
||||
import com.langchain.smith.core.http.Headers
|
||||
import com.langchain.smith.core.http.HttpClient
|
||||
import com.langchain.smith.core.http.LoggingHttpClient
|
||||
import com.langchain.smith.core.http.PhantomReachableClosingHttpClient
|
||||
import com.langchain.smith.core.http.QueryParams
|
||||
import com.langchain.smith.core.http.RetryingHttpClient
|
||||
@@ -80,6 +81,9 @@ private constructor(
|
||||
/**
|
||||
* Whether to call `validate` on every response before returning it.
|
||||
*
|
||||
* Setting this to `true` is _not_ forwards compatible with new types from the API for existing
|
||||
* fields.
|
||||
*
|
||||
* Defaults to false, which means the shape of the response will not be validated upfront.
|
||||
* Instead, validation will only occur for the parts of the response that are accessed.
|
||||
*/
|
||||
@@ -107,10 +111,20 @@ private constructor(
|
||||
* Defaults to 2.
|
||||
*/
|
||||
@get:JvmName("maxRetries") val maxRetries: Int,
|
||||
/** Whether run create/update calls should be automatically batched for tracing. */
|
||||
@get:JvmName("autoBatchTracing") val autoBatchTracing: Boolean,
|
||||
/**
|
||||
* The level at which to log request and response information.
|
||||
*
|
||||
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
|
||||
*
|
||||
* Defaults to [LogLevel.fromEnv].
|
||||
*/
|
||||
@get:JvmName("logLevel") val logLevel: LogLevel,
|
||||
private val apiKey: String?,
|
||||
private val tenantId: String?,
|
||||
private val bearerToken: String?,
|
||||
private val organizationId: String?,
|
||||
private val oauthAccessToken: String?,
|
||||
private val profileAuth: ProfileAuth?,
|
||||
) {
|
||||
|
||||
init {
|
||||
@@ -130,14 +144,6 @@ private constructor(
|
||||
|
||||
fun tenantId(): Optional<String> = Optional.ofNullable(tenantId)
|
||||
|
||||
/**
|
||||
* Bearer tokens are used to authenticate from the UI. Must also specify x-tenant-id or
|
||||
* x-organization-id (for org scoped apis).
|
||||
*/
|
||||
fun bearerToken(): Optional<String> = Optional.ofNullable(bearerToken)
|
||||
|
||||
fun organizationId(): Optional<String> = Optional.ofNullable(organizationId)
|
||||
|
||||
fun toBuilder() = Builder().from(this)
|
||||
|
||||
companion object {
|
||||
@@ -177,10 +183,12 @@ private constructor(
|
||||
private var responseValidation: Boolean = false
|
||||
private var timeout: Timeout = Timeout.default()
|
||||
private var maxRetries: Int = 2
|
||||
private var autoBatchTracing: Boolean = true
|
||||
private var logLevel: LogLevel = LogLevel.fromEnv()
|
||||
private var apiKey: String? = null
|
||||
private var tenantId: String? = null
|
||||
private var bearerToken: String? = null
|
||||
private var organizationId: String? = null
|
||||
private var oauthAccessToken: String? = null
|
||||
private var profileAuth: ProfileAuth? = null
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun from(clientOptions: ClientOptions) = apply {
|
||||
@@ -196,10 +204,12 @@ private constructor(
|
||||
responseValidation = clientOptions.responseValidation
|
||||
timeout = clientOptions.timeout
|
||||
maxRetries = clientOptions.maxRetries
|
||||
autoBatchTracing = clientOptions.autoBatchTracing
|
||||
logLevel = clientOptions.logLevel
|
||||
apiKey = clientOptions.apiKey
|
||||
tenantId = clientOptions.tenantId
|
||||
bearerToken = clientOptions.bearerToken
|
||||
organizationId = clientOptions.organizationId
|
||||
oauthAccessToken = clientOptions.oauthAccessToken
|
||||
profileAuth = clientOptions.profileAuth
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,6 +289,9 @@ private constructor(
|
||||
/**
|
||||
* Whether to call `validate` on every response before returning it.
|
||||
*
|
||||
* Setting this to `true` is _not_ forwards compatible with new types from the API for
|
||||
* existing fields.
|
||||
*
|
||||
* Defaults to false, which means the shape of the response will not be validated upfront.
|
||||
* Instead, validation will only occur for the parts of the response that are accessed.
|
||||
*/
|
||||
@@ -320,6 +333,25 @@ private constructor(
|
||||
*/
|
||||
fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries }
|
||||
|
||||
/**
|
||||
* Whether run create/update calls should be automatically batched for tracing.
|
||||
*
|
||||
* Defaults to true. Set to false to send run create/update calls synchronously through the
|
||||
* single-run endpoints.
|
||||
*/
|
||||
fun autoBatchTracing(autoBatchTracing: Boolean) = apply {
|
||||
this.autoBatchTracing = autoBatchTracing
|
||||
}
|
||||
|
||||
/**
|
||||
* The level at which to log request and response information.
|
||||
*
|
||||
* [fromEnv] will set the level from environment variables. See [LogLevel.fromEnv].
|
||||
*
|
||||
* Defaults to [LogLevel.fromEnv].
|
||||
*/
|
||||
fun logLevel(logLevel: LogLevel) = apply { this.logLevel = logLevel }
|
||||
|
||||
fun apiKey(apiKey: String?) = apply { this.apiKey = apiKey }
|
||||
|
||||
/** Alias for calling [Builder.apiKey] with `apiKey.orElse(null)`. */
|
||||
@@ -330,21 +362,6 @@ private constructor(
|
||||
/** Alias for calling [Builder.tenantId] with `tenantId.orElse(null)`. */
|
||||
fun tenantId(tenantId: Optional<String>) = tenantId(tenantId.getOrNull())
|
||||
|
||||
/**
|
||||
* Bearer tokens are used to authenticate from the UI. Must also specify x-tenant-id or
|
||||
* x-organization-id (for org scoped apis).
|
||||
*/
|
||||
fun bearerToken(bearerToken: String?) = apply { this.bearerToken = bearerToken }
|
||||
|
||||
/** Alias for calling [Builder.bearerToken] with `bearerToken.orElse(null)`. */
|
||||
fun bearerToken(bearerToken: Optional<String>) = bearerToken(bearerToken.getOrNull())
|
||||
|
||||
fun organizationId(organizationId: String?) = apply { this.organizationId = organizationId }
|
||||
|
||||
/** Alias for calling [Builder.organizationId] with `organizationId.orElse(null)`. */
|
||||
fun organizationId(organizationId: Optional<String>) =
|
||||
organizationId(organizationId.getOrNull())
|
||||
|
||||
fun headers(headers: Headers) = apply {
|
||||
this.headers.clear()
|
||||
putAllHeaders(headers)
|
||||
@@ -432,31 +449,30 @@ private constructor(
|
||||
*
|
||||
* See this table for the available options:
|
||||
*
|
||||
* |Setter |System property |Environment variable |Required|Default value |
|
||||
* |----------------|-----------------------------------|---------------------------|--------|------------------------------------|
|
||||
* |`apiKey` |`langchain.langsmithApiKey` |`LANGSMITH_API_KEY` |false |- |
|
||||
* |`tenantId` |`langchain.langsmithTenantId` |`LANGSMITH_TENANT_ID` |false |- |
|
||||
* |`bearerToken` |`langchain.langsmithBearerToken` |`LANGSMITH_BEARER_TOKEN` |false |- |
|
||||
* |`organizationId`|`langchain.langsmithOrganizationId`|`LANGSMITH_ORGANIZATION_ID`|false |- |
|
||||
* |`baseUrl` |`langchain.baseUrl` |`LANGSMITH_ENDPOINT` |true |`"https://api.smith.langchain.com/"`|
|
||||
* |Setter |System property |Environment variable |Required|Default value |
|
||||
* |----------|-----------------------------|---------------------|--------|------------------------------------|
|
||||
* |`apiKey` |`langchain.langsmithApiKey` |`LANGSMITH_API_KEY` |false |- |
|
||||
* |`tenantId`|`langchain.langsmithTenantId`|`LANGSMITH_TENANT_ID`|false |- |
|
||||
* |`baseUrl` |`langchain.baseUrl` |`LANGSMITH_ENDPOINT` |true |`"https://api.smith.langchain.com/"`|
|
||||
*
|
||||
* System properties take precedence over environment variables.
|
||||
*/
|
||||
fun fromEnv() = apply {
|
||||
(System.getProperty("langchain.baseUrl") ?: System.getenv("LANGSMITH_ENDPOINT"))?.let {
|
||||
baseUrl(it)
|
||||
logLevel(LogLevel.fromEnv())
|
||||
langsmithEndpoint()?.let { baseUrl(it) }
|
||||
langsmithApiKey()?.let { apiKey(it) }
|
||||
langsmithTenantId()?.let { tenantId(it) }
|
||||
System.getenv("LANGCHAIN_CUSTOM_HEADERS")?.let { customHeadersEnv ->
|
||||
for (line in customHeadersEnv.split("\n")) {
|
||||
val colon = line.indexOf(':')
|
||||
if (colon >= 0) {
|
||||
putHeader(line.substring(0, colon).trim(), line.substring(colon + 1).trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
loadProfileClientConfig(jsonMapper, clock, profileNameOverride = profileName())?.let {
|
||||
applyProfileConfig(it)
|
||||
}
|
||||
(System.getProperty("langchain.langsmithApiKey") ?: System.getenv("LANGSMITH_API_KEY"))
|
||||
?.let { apiKey(it) }
|
||||
(System.getProperty("langchain.langsmithTenantId")
|
||||
?: System.getenv("LANGSMITH_TENANT_ID"))
|
||||
?.let { tenantId(it) }
|
||||
(System.getProperty("langchain.langsmithBearerToken")
|
||||
?: System.getenv("LANGSMITH_BEARER_TOKEN"))
|
||||
?.let { bearerToken(it) }
|
||||
(System.getProperty("langchain.langsmithOrganizationId")
|
||||
?: System.getenv("LANGSMITH_ORGANIZATION_ID"))
|
||||
?.let { organizationId(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -503,37 +519,48 @@ private constructor(
|
||||
headers.put("X-Stainless-Runtime", "JRE")
|
||||
headers.put("X-Stainless-Runtime-Version", getJavaVersion())
|
||||
headers.put("X-Stainless-Kotlin-Version", KotlinVersion.CURRENT.toString())
|
||||
apiKey?.let {
|
||||
if (!it.isEmpty()) {
|
||||
headers.put("X-API-Key", it)
|
||||
}
|
||||
}
|
||||
organizationId?.let {
|
||||
if (!it.isEmpty()) {
|
||||
headers.put("X-Organization-Id", it)
|
||||
}
|
||||
}
|
||||
bearerToken?.let {
|
||||
if (!it.isEmpty()) {
|
||||
headers.put("Authorization", "Bearer $it")
|
||||
}
|
||||
}
|
||||
tenantId?.let {
|
||||
if (!it.isEmpty()) {
|
||||
headers.put("X-Tenant-Id", it)
|
||||
}
|
||||
}
|
||||
// We replace after all the default headers to allow end-users to overwrite them.
|
||||
headers.replaceAll(this.headers.build())
|
||||
queryParams.replaceAll(this.queryParams.build())
|
||||
apiKey
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.let {
|
||||
headers.remove("Authorization")
|
||||
headers.replace("X-API-Key", it)
|
||||
}
|
||||
if (apiKey.isNullOrEmpty()) {
|
||||
oauthAccessToken
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.let {
|
||||
val currentHeaders = headers.build()
|
||||
if (
|
||||
currentHeaders.values("X-API-Key").all(String::isBlank) &&
|
||||
currentHeaders.values("Authorization").all(String::isBlank)
|
||||
) {
|
||||
headers.replace("Authorization", "Bearer $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
tenantId?.takeIf { it.isNotEmpty() }?.let { headers.replace("X-Tenant-Id", it) }
|
||||
|
||||
return ClientOptions(
|
||||
httpClient,
|
||||
val retryingHttpClient =
|
||||
RetryingHttpClient.builder()
|
||||
.httpClient(httpClient)
|
||||
.httpClient(
|
||||
LoggingHttpClient.builder()
|
||||
.httpClient(httpClient)
|
||||
.clock(clock)
|
||||
.level(logLevel)
|
||||
.build()
|
||||
)
|
||||
.sleeper(sleeper)
|
||||
.clock(clock)
|
||||
.maxRetries(maxRetries)
|
||||
.build(),
|
||||
.build()
|
||||
|
||||
return ClientOptions(
|
||||
httpClient,
|
||||
profileAuth?.let { ProfileAuthHttpClient(retryingHttpClient, it) }
|
||||
?: retryingHttpClient,
|
||||
checkJacksonVersionCompatibility,
|
||||
jsonMapper,
|
||||
streamHandlerExecutor,
|
||||
@@ -545,12 +572,56 @@ private constructor(
|
||||
responseValidation,
|
||||
timeout,
|
||||
maxRetries,
|
||||
autoBatchTracing,
|
||||
logLevel,
|
||||
apiKey,
|
||||
tenantId,
|
||||
bearerToken,
|
||||
organizationId,
|
||||
oauthAccessToken,
|
||||
profileAuth,
|
||||
)
|
||||
}
|
||||
|
||||
private fun applyProfileConfig(profile: ProfileClientConfig) {
|
||||
if (baseUrl.isNullOrBlank()) {
|
||||
profile.baseUrl?.let { baseUrl(it) }
|
||||
}
|
||||
if (tenantId.isNullOrBlank()) {
|
||||
profile.tenantId?.let { tenantId(it) }
|
||||
}
|
||||
if (!apiKey.isNullOrBlank() || hasAuthHeader()) {
|
||||
return
|
||||
}
|
||||
if (!profile.oauthAccessToken.isNullOrBlank()) {
|
||||
oauthAccessToken = profile.oauthAccessToken
|
||||
profileAuth = profile.profileAuth
|
||||
} else if (profile.profileAuth != null) {
|
||||
profileAuth = profile.profileAuth
|
||||
} else if (!profile.apiKey.isNullOrBlank()) {
|
||||
apiKey(profile.apiKey)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasAuthHeader(): Boolean {
|
||||
val currentHeaders = headers.build()
|
||||
return currentHeaders.values("X-API-Key").any { it.isNotBlank() } ||
|
||||
currentHeaders.values("Authorization").any { it.isNotBlank() }
|
||||
}
|
||||
|
||||
private fun langsmithEndpoint(): String? =
|
||||
System.getProperty("langchain.baseUrl")
|
||||
?: System.getenv("LANGSMITH_ENDPOINT")
|
||||
?: System.getenv("LANGCHAIN_ENDPOINT")
|
||||
|
||||
private fun langsmithApiKey(): String? =
|
||||
System.getProperty("langchain.langsmithApiKey")
|
||||
?: System.getenv("LANGSMITH_API_KEY")
|
||||
?: System.getenv("LANGCHAIN_API_KEY")
|
||||
|
||||
private fun langsmithTenantId(): String? =
|
||||
System.getProperty("langchain.langsmithTenantId")
|
||||
?: System.getenv("LANGSMITH_TENANT_ID")
|
||||
?: System.getenv("LANGSMITH_WORKSPACE_ID")
|
||||
?: System.getenv("LANGCHAIN_WORKSPACE_ID")
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// File generated from our OpenAPI spec by Stainless.
|
||||
|
||||
package com.langchain.smith.core
|
||||
|
||||
/** The level at which to log request and response information. */
|
||||
enum class LogLevel {
|
||||
/** No logging. */
|
||||
OFF,
|
||||
/** Minimal request and response summary logs. No headers or bodies are logged. */
|
||||
INFO,
|
||||
/** [INFO] logs plus details about request failures. */
|
||||
ERROR,
|
||||
/**
|
||||
* Full request and response logs. Sensitive headers are redacted, but sensitive data in request
|
||||
* and response bodies may still be visible.
|
||||
*/
|
||||
DEBUG;
|
||||
|
||||
/** Returns whether this level is at or higher than the given [level]. */
|
||||
fun shouldLog(level: LogLevel): Boolean = ordinal >= level.ordinal
|
||||
|
||||
companion object {
|
||||
|
||||
/** Returns a [LogLevel] based on the `LANGCHAIN_LOG` environment variable. */
|
||||
fun fromEnv() =
|
||||
when (System.getenv("LANGCHAIN_LOG")?.lowercase()) {
|
||||
"info" -> INFO
|
||||
"error" -> ERROR
|
||||
"debug" -> DEBUG
|
||||
else -> OFF
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,9 @@ import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.temporal.ChronoField
|
||||
|
||||
fun jsonMapper(): JsonMapper =
|
||||
fun jsonMapper(): JsonMapper = JSON_MAPPER
|
||||
|
||||
private val JSON_MAPPER: JsonMapper =
|
||||
JsonMapper.builder()
|
||||
.addModule(kotlinModule())
|
||||
.addModule(Jdk8Module())
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.langchain.smith.core
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.langchain.smith.core.http.HttpClient
|
||||
import com.langchain.smith.core.http.HttpRequest
|
||||
import com.langchain.smith.core.http.HttpResponse
|
||||
import java.io.OutputStreamWriter
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.time.Clock
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
internal data class ProfileClientConfig(
|
||||
val baseUrl: String?,
|
||||
val apiKey: String?,
|
||||
val tenantId: String?,
|
||||
val oauthAccessToken: String?,
|
||||
val profileAuth: ProfileAuth?,
|
||||
)
|
||||
|
||||
private const val OAUTH_CLIENT_ID = "langsmith-cli"
|
||||
private val TOKEN_REFRESH_LEEWAY: Duration = Duration.ofMinutes(1)
|
||||
private val TOKEN_REFRESH_TIMEOUT: Duration = Duration.ofSeconds(10)
|
||||
|
||||
internal fun loadProfileClientConfig(
|
||||
jsonMapper: JsonMapper,
|
||||
clock: Clock,
|
||||
configPathOverride: Path? = null,
|
||||
profileNameOverride: String? = null,
|
||||
): ProfileClientConfig? {
|
||||
val configPath = configPathOverride ?: profileConfigPath()
|
||||
if (!Files.isRegularFile(configPath)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val config =
|
||||
runCatching { jsonMapper.readTree(configPath.toFile()) as? ObjectNode }.getOrNull()
|
||||
?: return null
|
||||
val profiles = config["profiles"] as? ObjectNode ?: return null
|
||||
val profileName =
|
||||
profileNameOverride?.takeIf { it.isNotBlank() }
|
||||
?: text(config["current_profile"])
|
||||
?: "default"
|
||||
val profile = profiles[profileName] as? ObjectNode ?: return null
|
||||
|
||||
val oauth = profile["oauth"]
|
||||
val oauthAccessToken = text(oauth?.get("access_token"))
|
||||
val oauthRefreshToken = text(oauth?.get("refresh_token"))
|
||||
|
||||
return ProfileClientConfig(
|
||||
baseUrl = text(profile["api_url"]),
|
||||
apiKey = text(profile["api_key"]),
|
||||
tenantId = text(profile["workspace_id"]),
|
||||
oauthAccessToken = oauthAccessToken,
|
||||
profileAuth =
|
||||
if (oauthAccessToken != null || oauthRefreshToken != null) {
|
||||
ProfileAuth(jsonMapper, clock, configPath, config, profile)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
internal fun profileConfigPath(): Path {
|
||||
val configuredPath =
|
||||
System.getProperty("langchain.langsmithConfigFile")
|
||||
?: System.getenv("LANGSMITH_CONFIG_FILE")
|
||||
return configuredPath?.takeIf { it.isNotBlank() }?.let(Paths::get)
|
||||
?: Paths.get(System.getProperty("user.home"), ".langsmith", "config.json")
|
||||
}
|
||||
|
||||
internal fun profileName(): String? =
|
||||
(System.getProperty("langchain.langsmithProfile") ?: System.getenv("LANGSMITH_PROFILE"))
|
||||
?.takeIf { it.isNotBlank() }
|
||||
|
||||
private fun shouldRefreshProfileToken(oauth: ObjectNode, clock: Clock): Boolean {
|
||||
if (text(oauth["refresh_token"]) == null) {
|
||||
return false
|
||||
}
|
||||
if (text(oauth["access_token"]) == null) {
|
||||
return true
|
||||
}
|
||||
val expiresAt = expiresAt(oauth["expires_at"]) ?: return false
|
||||
return !expiresAt.isAfter(clock.instant().plus(TOKEN_REFRESH_LEEWAY))
|
||||
}
|
||||
|
||||
private fun refreshProfileOAuthToken(
|
||||
jsonMapper: JsonMapper,
|
||||
baseUrl: String,
|
||||
refreshToken: String,
|
||||
): OAuthTokenResponse? {
|
||||
return runCatching {
|
||||
val connection =
|
||||
(URL("${normalizeConfigUrl(baseUrl)}/oauth/token").openConnection()
|
||||
as HttpURLConnection)
|
||||
.apply {
|
||||
requestMethod = "POST"
|
||||
connectTimeout = TOKEN_REFRESH_TIMEOUT.toMillis().toInt()
|
||||
readTimeout = TOKEN_REFRESH_TIMEOUT.toMillis().toInt()
|
||||
doOutput = true
|
||||
setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
val body =
|
||||
formEncode(
|
||||
mapOf(
|
||||
"grant_type" to "refresh_token",
|
||||
"client_id" to OAUTH_CLIENT_ID,
|
||||
"refresh_token" to refreshToken,
|
||||
)
|
||||
)
|
||||
OutputStreamWriter(connection.outputStream, StandardCharsets.UTF_8).use {
|
||||
it.write(body)
|
||||
}
|
||||
if (connection.responseCode !in 200..299) {
|
||||
return@runCatching null
|
||||
}
|
||||
val response = connection.inputStream.use { jsonMapper.readTree(it) }
|
||||
val accessToken = text(response["access_token"]) ?: return@runCatching null
|
||||
OAuthTokenResponse(
|
||||
accessToken = accessToken,
|
||||
refreshToken = text(response["refresh_token"]),
|
||||
expiresInSeconds = response["expires_in"]?.takeIf { it.isNumber }?.asLong(),
|
||||
)
|
||||
}
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
private fun normalizeConfigUrl(baseUrl: String): String =
|
||||
baseUrl.trimEnd('/').removeSuffix("/api/v1")
|
||||
|
||||
private fun formEncode(values: Map<String, String>): String =
|
||||
values.entries.joinToString("&") { (key, value) -> "${urlEncode(key)}=${urlEncode(value)}" }
|
||||
|
||||
private fun urlEncode(value: String): String =
|
||||
URLEncoder.encode(value, StandardCharsets.UTF_8.name())
|
||||
|
||||
private fun text(node: JsonNode?): String? =
|
||||
node?.takeIf { it.isTextual }?.asText()?.trimQuotes()?.takeIf { it.isNotBlank() }
|
||||
|
||||
private fun expiresAt(node: JsonNode?): Instant? =
|
||||
when {
|
||||
node == null -> null
|
||||
node.isNumber -> Instant.ofEpochSecond(node.asLong())
|
||||
node.isTextual ->
|
||||
runCatching { Instant.parse(node.asText()) }
|
||||
.recoverCatching { OffsetDateTime.parse(node.asText()).toInstant() }
|
||||
.getOrNull()
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun String.trimQuotes(): String = trim().trim('"', '\'')
|
||||
|
||||
internal class ProfileAuth(
|
||||
private val jsonMapper: JsonMapper,
|
||||
private val clock: Clock,
|
||||
private val configPath: Path,
|
||||
private val config: ObjectNode,
|
||||
private val profile: ObjectNode,
|
||||
) {
|
||||
private val managedAuthorizationValues = mutableSetOf<String>()
|
||||
|
||||
init {
|
||||
rememberProfileAuthHeader(authHeaderFromProfile())
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun currentAuthHeader(): Pair<String, String>? =
|
||||
authHeaderFromProfile().also { rememberProfileAuthHeader(it) }
|
||||
|
||||
@Synchronized
|
||||
fun isProfileAuthorizationHeader(value: String): Boolean =
|
||||
managedAuthorizationValues.contains(value)
|
||||
|
||||
@Synchronized
|
||||
fun authHeader(): Pair<String, String>? {
|
||||
val oauth = profile["oauth"] as? ObjectNode
|
||||
if (oauth != null && shouldRefreshProfileToken(oauth, clock)) {
|
||||
val refreshToken = text(oauth["refresh_token"])
|
||||
if (refreshToken != null) {
|
||||
val tokenEndpointBaseUrl = text(profile["api_url"]) ?: ClientOptions.PRODUCTION_URL
|
||||
val tokenResponse =
|
||||
refreshProfileOAuthToken(jsonMapper, tokenEndpointBaseUrl, refreshToken)
|
||||
if (tokenResponse != null) {
|
||||
oauth.put("access_token", tokenResponse.accessToken)
|
||||
tokenResponse.refreshToken?.let { oauth.put("refresh_token", it) }
|
||||
tokenResponse.expiresInSeconds?.let {
|
||||
oauth.put("expires_at", clock.instant().plusSeconds(it).toString())
|
||||
}
|
||||
oauth.remove("token_type")
|
||||
oauth.remove("bearer_token")
|
||||
saveConfig()
|
||||
}
|
||||
}
|
||||
}
|
||||
return authHeaderFromProfile().also { rememberProfileAuthHeader(it) }
|
||||
}
|
||||
|
||||
private fun authHeaderFromProfile(): Pair<String, String>? {
|
||||
val oauth = profile["oauth"] as? ObjectNode
|
||||
text(oauth?.get("access_token"))?.let {
|
||||
return "Authorization" to "Bearer $it"
|
||||
}
|
||||
text(profile["api_key"])?.let {
|
||||
return "X-API-Key" to it
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun rememberProfileAuthHeader(header: Pair<String, String>?) {
|
||||
if (header?.first?.equals("Authorization", ignoreCase = true) == true) {
|
||||
managedAuthorizationValues.add(header.second)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveConfig() {
|
||||
runCatching {
|
||||
configPath.parent?.let { Files.createDirectories(it) }
|
||||
jsonMapper.writerWithDefaultPrettyPrinter().writeValue(configPath.toFile(), config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ProfileAuthHttpClient(
|
||||
private val delegate: HttpClient,
|
||||
private val profileAuth: ProfileAuth,
|
||||
) : HttpClient {
|
||||
override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse =
|
||||
delegate.execute(prepareProfileAuthRequest(request), requestOptions)
|
||||
|
||||
override fun executeAsync(
|
||||
request: HttpRequest,
|
||||
requestOptions: RequestOptions,
|
||||
): CompletableFuture<HttpResponse> =
|
||||
delegate.executeAsync(prepareProfileAuthRequest(request), requestOptions)
|
||||
|
||||
override fun close() = delegate.close()
|
||||
|
||||
private fun prepareProfileAuthRequest(request: HttpRequest): HttpRequest {
|
||||
if (request.headers.values("X-API-Key").any(String::isNotBlank)) {
|
||||
return request.toBuilder().removeHeaders("Authorization").build()
|
||||
}
|
||||
val authorizationValues = request.headers.values("Authorization").filter(String::isNotBlank)
|
||||
if (
|
||||
authorizationValues.isNotEmpty() &&
|
||||
authorizationValues.none { profileAuth.isProfileAuthorizationHeader(it) }
|
||||
) {
|
||||
return request
|
||||
}
|
||||
val (name, value) = profileAuth.authHeader() ?: return request
|
||||
val builder = request.toBuilder()
|
||||
if (name.equals("X-API-Key", ignoreCase = true)) {
|
||||
builder.removeHeaders("Authorization")
|
||||
}
|
||||
return builder.replaceHeaders(name, value).build()
|
||||
}
|
||||
}
|
||||
|
||||
private data class OAuthTokenResponse(
|
||||
val accessToken: String,
|
||||
val refreshToken: String?,
|
||||
val expiresInSeconds: Long?,
|
||||
)
|
||||
@@ -3,6 +3,9 @@
|
||||
package com.langchain.smith.core
|
||||
|
||||
import com.langchain.smith.client.LangsmithClient
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
private val logger = LoggerFactory.getLogger("com.langchain.smith.core.Properties")
|
||||
|
||||
fun getOsArch(): String {
|
||||
val osArch = System.getProperty("os.arch")
|
||||
@@ -12,8 +15,10 @@ fun getOsArch(): String {
|
||||
"i386",
|
||||
"x32",
|
||||
"x86" -> "x32"
|
||||
|
||||
"amd64",
|
||||
"x86_64" -> "x64"
|
||||
|
||||
"arm" -> "arm"
|
||||
"aarch64" -> "arm64"
|
||||
else -> "other:$osArch"
|
||||
@@ -34,9 +39,23 @@ fun getOsName(): String {
|
||||
}
|
||||
}
|
||||
|
||||
fun getOsVersion(): String = System.getProperty("os.version", "unknown")
|
||||
fun getOsVersion(): String = System.getProperty("os.version", "unknown") ?: "unknown"
|
||||
|
||||
fun getPackageVersion(): String =
|
||||
LangsmithClient::class.java.`package`.implementationVersion ?: "unknown"
|
||||
fun getPackageVersion(): String {
|
||||
try {
|
||||
val props = java.util.Properties()
|
||||
LangsmithClient::class
|
||||
.java
|
||||
.classLoader
|
||||
.getResourceAsStream("com/langchain/smith/version.properties")
|
||||
?.use { props.load(it) }
|
||||
props.getProperty("version")?.let {
|
||||
return it
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Failed to read version from version.properties", e)
|
||||
}
|
||||
return LangsmithClient::class.java.`package`.implementationVersion ?: "unknown"
|
||||
}
|
||||
|
||||
fun getJavaVersion(): String = System.getProperty("java.version", "unknown")
|
||||
fun getJavaVersion(): String = System.getProperty("java.version", "unknown") ?: "unknown"
|
||||
|
||||
@@ -33,6 +33,15 @@ class RequestOptions private constructor(val responseValidation: Boolean?, val t
|
||||
private var responseValidation: Boolean? = null
|
||||
private var timeout: Timeout? = null
|
||||
|
||||
/**
|
||||
* Whether to call `validate` on the response before returning it.
|
||||
*
|
||||
* Setting this to `true` is _not_ forwards compatible with new types from the API for
|
||||
* existing fields.
|
||||
*
|
||||
* Defaults to false, which means the shape of the response will not be validated upfront.
|
||||
* Instead, validation will only occur for the parts of the response that are accessed.
|
||||
*/
|
||||
fun responseValidation(responseValidation: Boolean) = apply {
|
||||
this.responseValidation = responseValidation
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package com.langchain.smith.core
|
||||
import com.langchain.smith.errors.LangChainInvalidDataException
|
||||
import java.util.Collections
|
||||
import java.util.SortedMap
|
||||
import java.util.SortedSet
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.locks.Lock
|
||||
|
||||
@@ -16,6 +17,11 @@ internal fun <T : Any> T?.getOrThrow(name: String): T =
|
||||
internal fun <T> List<T>.toImmutable(): List<T> =
|
||||
if (isEmpty()) Collections.emptyList() else Collections.unmodifiableList(toList())
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun <V : Comparable<V>> SortedSet<V>.toImmutable(): SortedSet<V> =
|
||||
if (isEmpty()) Collections.emptySortedSet()
|
||||
else Collections.unmodifiableSortedSet(toSortedSet(comparator() ?: Comparator.naturalOrder()))
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun <K, V> Map<K, V>.toImmutable(): Map<K, V> =
|
||||
if (isEmpty()) immutableEmptyMap() else Collections.unmodifiableMap(toMap())
|
||||
|
||||
+231
-86
@@ -7,14 +7,15 @@ package com.langchain.smith.core.http
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType
|
||||
import com.github.luben.zstd.ZstdOutputStream
|
||||
import com.langchain.smith.core.MultipartField
|
||||
import com.langchain.smith.core.toImmutable
|
||||
import com.langchain.smith.errors.LangChainInvalidDataException
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.UUID
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder
|
||||
import org.apache.hc.core5.http.ContentType
|
||||
import org.apache.hc.core5.http.HttpEntity
|
||||
|
||||
@JvmSynthetic
|
||||
internal inline fun <reified T> json(jsonMapper: JsonMapper, value: T): HttpRequestBody =
|
||||
@@ -32,99 +33,243 @@ internal inline fun <reified T> json(jsonMapper: JsonMapper, value: T): HttpRequ
|
||||
override fun close() {}
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun zstd(body: HttpRequestBody): HttpRequestBody =
|
||||
object : HttpRequestBody {
|
||||
|
||||
override fun writeTo(outputStream: OutputStream) {
|
||||
ZstdOutputStream(NonClosingOutputStream(outputStream)).setCloseFrameOnFlush(true).use {
|
||||
body.writeTo(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentType(): String? = body.contentType()
|
||||
|
||||
override fun contentLength(): Long = -1L
|
||||
|
||||
override fun repeatable(): Boolean = body.repeatable()
|
||||
|
||||
override fun close() = body.close()
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun multipartFormData(
|
||||
jsonMapper: JsonMapper,
|
||||
fields: Map<String, MultipartField<*>>,
|
||||
): HttpRequestBody =
|
||||
object : HttpRequestBody {
|
||||
private val entity: HttpEntity by lazy {
|
||||
MultipartEntityBuilder.create()
|
||||
.apply {
|
||||
fields.forEach { (name, field) ->
|
||||
val knownValue = field.value.asKnown().getOrNull()
|
||||
val parts =
|
||||
if (knownValue is InputStream) {
|
||||
// Read directly from the `InputStream` instead of reading it all
|
||||
// into memory due to the `jsonMapper` serialization below.
|
||||
sequenceOf(name to knownValue)
|
||||
} else {
|
||||
val node = jsonMapper.valueToTree<JsonNode>(field.value)
|
||||
serializePart(name, node)
|
||||
}
|
||||
|
||||
parts.forEach { (name, bytes) ->
|
||||
addBinaryBody(
|
||||
name,
|
||||
bytes,
|
||||
ContentType.parseLenient(field.contentType),
|
||||
field.filename().getOrNull(),
|
||||
)
|
||||
}
|
||||
MultipartBody.Builder()
|
||||
.apply {
|
||||
fields.forEach { (name, field) ->
|
||||
val knownValue = field.value.asKnown().getOrNull()
|
||||
val parts =
|
||||
if (knownValue is InputStream) {
|
||||
// Read directly from the `InputStream` instead of reading it all
|
||||
// into memory due to the `jsonMapper` serialization below.
|
||||
sequenceOf(name to knownValue)
|
||||
} else {
|
||||
val node = jsonMapper.valueToTree<JsonNode>(field.value)
|
||||
serializePart(name, node)
|
||||
}
|
||||
|
||||
parts.forEach { (name, bytes) ->
|
||||
val partBody =
|
||||
if (bytes is ByteArrayInputStream) {
|
||||
val byteArray = bytes.readBytes()
|
||||
|
||||
object : HttpRequestBody {
|
||||
|
||||
override fun writeTo(outputStream: OutputStream) {
|
||||
outputStream.write(byteArray)
|
||||
}
|
||||
|
||||
override fun contentType(): String = field.contentType
|
||||
|
||||
override fun contentLength(): Long = byteArray.size.toLong()
|
||||
|
||||
override fun repeatable(): Boolean = true
|
||||
|
||||
override fun close() {}
|
||||
}
|
||||
} else {
|
||||
object : HttpRequestBody {
|
||||
|
||||
override fun writeTo(outputStream: OutputStream) {
|
||||
bytes.copyTo(outputStream)
|
||||
}
|
||||
|
||||
override fun contentType(): String = field.contentType
|
||||
|
||||
override fun contentLength(): Long = -1L
|
||||
|
||||
override fun repeatable(): Boolean = false
|
||||
|
||||
override fun close() = bytes.close()
|
||||
}
|
||||
}
|
||||
|
||||
addPart(
|
||||
MultipartBody.Part.create(
|
||||
name,
|
||||
field.filename().getOrNull(),
|
||||
field.contentType,
|
||||
partBody,
|
||||
)
|
||||
)
|
||||
}
|
||||
.build()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
private class NonClosingOutputStream(private val delegate: OutputStream) : OutputStream() {
|
||||
override fun write(b: Int) = delegate.write(b)
|
||||
|
||||
override fun write(b: ByteArray) = delegate.write(b)
|
||||
|
||||
override fun write(b: ByteArray, off: Int, len: Int) = delegate.write(b, off, len)
|
||||
|
||||
override fun flush() = delegate.flush()
|
||||
|
||||
override fun close() = Unit
|
||||
}
|
||||
|
||||
private fun serializePart(name: String, node: JsonNode): Sequence<Pair<String, InputStream>> =
|
||||
when (node.nodeType) {
|
||||
JsonNodeType.MISSING,
|
||||
JsonNodeType.NULL -> emptySequence()
|
||||
JsonNodeType.BINARY -> sequenceOf(name to node.binaryValue().inputStream())
|
||||
JsonNodeType.STRING -> sequenceOf(name to node.textValue().byteInputStream())
|
||||
JsonNodeType.BOOLEAN -> sequenceOf(name to node.booleanValue().toString().byteInputStream())
|
||||
JsonNodeType.NUMBER -> sequenceOf(name to node.numberValue().toString().byteInputStream())
|
||||
JsonNodeType.ARRAY ->
|
||||
node.elements().asSequence().flatMap { element -> serializePart(name, element) }
|
||||
JsonNodeType.OBJECT ->
|
||||
node.fields().asSequence().flatMap { (key, value) ->
|
||||
serializePart("$name[$key]", value)
|
||||
}
|
||||
JsonNodeType.POJO,
|
||||
null -> throw LangChainInvalidDataException("Unexpected JsonNode type: ${node.nodeType}")
|
||||
}
|
||||
|
||||
private class MultipartBody
|
||||
private constructor(private val boundary: String, private val parts: List<Part>) : HttpRequestBody {
|
||||
private val boundaryBytes: ByteArray = boundary.toByteArray()
|
||||
private val contentType = "multipart/form-data; boundary=$boundary"
|
||||
|
||||
// This must remain in sync with `contentLength`.
|
||||
override fun writeTo(outputStream: OutputStream) {
|
||||
parts.forEach { part ->
|
||||
outputStream.write(DASHDASH)
|
||||
outputStream.write(boundaryBytes)
|
||||
outputStream.write(CRLF)
|
||||
|
||||
outputStream.write(CONTENT_DISPOSITION)
|
||||
outputStream.write(part.contentDisposition.toByteArray())
|
||||
outputStream.write(CRLF)
|
||||
|
||||
outputStream.write(CONTENT_TYPE)
|
||||
outputStream.write(part.contentType.toByteArray())
|
||||
outputStream.write(CRLF)
|
||||
|
||||
outputStream.write(CRLF)
|
||||
part.body.writeTo(outputStream)
|
||||
outputStream.write(CRLF)
|
||||
}
|
||||
|
||||
private fun serializePart(
|
||||
name: String,
|
||||
node: JsonNode,
|
||||
): Sequence<Pair<String, InputStream>> =
|
||||
when (node.nodeType) {
|
||||
JsonNodeType.MISSING,
|
||||
JsonNodeType.NULL -> emptySequence()
|
||||
JsonNodeType.BINARY -> sequenceOf(name to node.binaryValue().inputStream())
|
||||
JsonNodeType.STRING -> sequenceOf(name to node.textValue().inputStream())
|
||||
JsonNodeType.BOOLEAN ->
|
||||
sequenceOf(name to node.booleanValue().toString().inputStream())
|
||||
JsonNodeType.NUMBER ->
|
||||
sequenceOf(name to node.numberValue().toString().inputStream())
|
||||
JsonNodeType.ARRAY ->
|
||||
sequenceOf(
|
||||
name to
|
||||
node
|
||||
.elements()
|
||||
.asSequence()
|
||||
.mapNotNull { element ->
|
||||
when (element.nodeType) {
|
||||
JsonNodeType.MISSING,
|
||||
JsonNodeType.NULL -> null
|
||||
JsonNodeType.STRING -> node.textValue()
|
||||
JsonNodeType.BOOLEAN -> node.booleanValue().toString()
|
||||
JsonNodeType.NUMBER -> node.numberValue().toString()
|
||||
null,
|
||||
JsonNodeType.BINARY,
|
||||
JsonNodeType.ARRAY,
|
||||
JsonNodeType.OBJECT,
|
||||
JsonNodeType.POJO ->
|
||||
throw LangChainInvalidDataException(
|
||||
"Unexpected JsonNode type in array: ${node.nodeType}"
|
||||
)
|
||||
}
|
||||
}
|
||||
.joinToString(",")
|
||||
.inputStream()
|
||||
)
|
||||
JsonNodeType.OBJECT ->
|
||||
node.fields().asSequence().flatMap { (key, value) ->
|
||||
serializePart("$name[$key]", value)
|
||||
}
|
||||
JsonNodeType.POJO,
|
||||
null ->
|
||||
throw LangChainInvalidDataException(
|
||||
"Unexpected JsonNode type: ${node.nodeType}"
|
||||
)
|
||||
outputStream.write(DASHDASH)
|
||||
outputStream.write(boundaryBytes)
|
||||
outputStream.write(DASHDASH)
|
||||
outputStream.write(CRLF)
|
||||
}
|
||||
|
||||
override fun contentType(): String = contentType
|
||||
|
||||
// This must remain in sync with `writeTo`.
|
||||
override fun contentLength(): Long {
|
||||
var byteCount = 0L
|
||||
|
||||
parts.forEach { part ->
|
||||
val contentLength = part.body.contentLength()
|
||||
if (contentLength == -1L) {
|
||||
return -1L
|
||||
}
|
||||
|
||||
private fun String.inputStream(): InputStream = toByteArray().inputStream()
|
||||
byteCount +=
|
||||
DASHDASH.size +
|
||||
boundaryBytes.size +
|
||||
CRLF.size +
|
||||
CONTENT_DISPOSITION.size +
|
||||
part.contentDisposition.toByteArray().size +
|
||||
CRLF.size +
|
||||
CONTENT_TYPE.size +
|
||||
part.contentType.toByteArray().size +
|
||||
CRLF.size +
|
||||
CRLF.size +
|
||||
contentLength +
|
||||
CRLF.size
|
||||
}
|
||||
|
||||
override fun writeTo(outputStream: OutputStream) = entity.writeTo(outputStream)
|
||||
|
||||
override fun contentType(): String = entity.contentType
|
||||
|
||||
override fun contentLength(): Long = entity.contentLength
|
||||
|
||||
override fun repeatable(): Boolean = entity.isRepeatable
|
||||
|
||||
override fun close() = entity.close()
|
||||
byteCount += DASHDASH.size + boundaryBytes.size + DASHDASH.size + CRLF.size
|
||||
return byteCount
|
||||
}
|
||||
|
||||
override fun repeatable(): Boolean = parts.all { it.body.repeatable() }
|
||||
|
||||
override fun close() {
|
||||
parts.forEach { it.body.close() }
|
||||
}
|
||||
|
||||
class Builder {
|
||||
private val boundary = UUID.randomUUID().toString()
|
||||
private val parts: MutableList<Part> = mutableListOf()
|
||||
|
||||
fun addPart(part: Part) = apply { parts.add(part) }
|
||||
|
||||
fun build() = MultipartBody(boundary, parts.toImmutable())
|
||||
}
|
||||
|
||||
class Part
|
||||
private constructor(
|
||||
val contentDisposition: String,
|
||||
val contentType: String,
|
||||
val body: HttpRequestBody,
|
||||
) {
|
||||
companion object {
|
||||
fun create(
|
||||
name: String,
|
||||
filename: String?,
|
||||
contentType: String,
|
||||
body: HttpRequestBody,
|
||||
): Part {
|
||||
val disposition = buildString {
|
||||
append("form-data; name=")
|
||||
appendQuotedString(name)
|
||||
if (filename != null) {
|
||||
append("; filename=")
|
||||
appendQuotedString(filename)
|
||||
}
|
||||
}
|
||||
return Part(disposition, contentType, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val CRLF = byteArrayOf('\r'.code.toByte(), '\n'.code.toByte())
|
||||
private val DASHDASH = byteArrayOf('-'.code.toByte(), '-'.code.toByte())
|
||||
private val CONTENT_DISPOSITION = "Content-Disposition: ".toByteArray()
|
||||
private val CONTENT_TYPE = "Content-Type: ".toByteArray()
|
||||
|
||||
private fun StringBuilder.appendQuotedString(key: String) {
|
||||
append('"')
|
||||
for (ch in key) {
|
||||
when (ch) {
|
||||
'\n' -> append("%0A")
|
||||
'\r' -> append("%0D")
|
||||
'"' -> append("%22")
|
||||
else -> append(ch)
|
||||
}
|
||||
}
|
||||
append('"')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+630
@@ -0,0 +1,630 @@
|
||||
// File generated from our OpenAPI spec by Stainless.
|
||||
|
||||
package com.langchain.smith.core.http
|
||||
|
||||
import com.langchain.smith.core.LogLevel
|
||||
import com.langchain.smith.core.RequestOptions
|
||||
import com.langchain.smith.core.checkRequired
|
||||
import com.langchain.smith.core.toImmutable
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.CharacterCodingException
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.charset.CharsetDecoder
|
||||
import java.nio.charset.CodingErrorAction
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.time.Clock
|
||||
import java.time.Duration
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.SortedSet
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.CompletionException
|
||||
import kotlin.time.toKotlinDuration
|
||||
|
||||
/** A wrapper [HttpClient] around [httpClient] that logs request and response information. */
|
||||
class LoggingHttpClient
|
||||
private constructor(
|
||||
/** The underlying [HttpClient] for making requests. */
|
||||
@get:JvmName("httpClient") val httpClient: HttpClient,
|
||||
/**
|
||||
* Sensitive headers to redact from logs.
|
||||
*
|
||||
* Defaults to `Set.of("authorization", "api-key", "x-api-key", "cookie", "set-cookie",
|
||||
* "x-tenant-id")`.
|
||||
*/
|
||||
@get:JvmName("redactedHeaders") val redactedHeaders: SortedSet<String>,
|
||||
/**
|
||||
* The clock to use for measuring request and response durations.
|
||||
*
|
||||
* This is primarily useful for using a fake clock in tests.
|
||||
*
|
||||
* Defaults to [Clock.systemUTC].
|
||||
*/
|
||||
@get:JvmName("clock") val clock: Clock,
|
||||
/**
|
||||
* The log level to use.
|
||||
*
|
||||
* Pass [LogLevel.fromEnv] to read from environment variables.
|
||||
*/
|
||||
@get:JvmName("level") val level: LogLevel,
|
||||
) : HttpClient {
|
||||
|
||||
override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse {
|
||||
val loggingRequest = logRequest(request)
|
||||
|
||||
val before = OffsetDateTime.now(clock)
|
||||
val response =
|
||||
try {
|
||||
httpClient.execute(loggingRequest, requestOptions)
|
||||
} catch (e: Throwable) {
|
||||
logFailure(e, Duration.between(before, OffsetDateTime.now(clock)))
|
||||
throw e
|
||||
}
|
||||
|
||||
val took = Duration.between(before, OffsetDateTime.now(clock))
|
||||
return logResponse(response, took)
|
||||
}
|
||||
|
||||
override fun executeAsync(
|
||||
request: HttpRequest,
|
||||
requestOptions: RequestOptions,
|
||||
): CompletableFuture<HttpResponse> {
|
||||
val loggingRequest = logRequest(request)
|
||||
|
||||
val before = OffsetDateTime.now(clock)
|
||||
val future =
|
||||
try {
|
||||
httpClient.executeAsync(loggingRequest, requestOptions)
|
||||
} catch (e: Throwable) {
|
||||
logFailure(e, Duration.between(before, OffsetDateTime.now(clock)))
|
||||
throw e
|
||||
}
|
||||
return future.handle { response, error ->
|
||||
val took = Duration.between(before, OffsetDateTime.now(clock))
|
||||
if (error != null) {
|
||||
logFailure(unwrapCompletionException(error), took)
|
||||
throw error
|
||||
}
|
||||
logResponse(response, took)
|
||||
}
|
||||
}
|
||||
|
||||
private fun logRequest(request: HttpRequest): HttpRequest {
|
||||
if (!level.shouldLog(LogLevel.INFO)) {
|
||||
return request
|
||||
}
|
||||
|
||||
System.err.println(
|
||||
buildString {
|
||||
append("--> ${request.method} ${request.url()}")
|
||||
request.body?.let {
|
||||
val length = it.contentLength()
|
||||
append(if (length >= 0) " ($length-byte body)" else " (unknown-length body)")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (!level.shouldLog(LogLevel.DEBUG)) {
|
||||
return request
|
||||
}
|
||||
|
||||
logHeaders(request.headers)
|
||||
|
||||
if (request.body == null) {
|
||||
System.err.println("--> END ${request.method}")
|
||||
System.err.println()
|
||||
return request
|
||||
}
|
||||
|
||||
return request
|
||||
.toBuilder()
|
||||
.body(LoggingHttpRequestBody(request.method, request.body))
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun logResponse(response: HttpResponse, took: Duration): HttpResponse {
|
||||
if (!level.shouldLog(LogLevel.INFO)) {
|
||||
return response
|
||||
}
|
||||
|
||||
val contentLength = response.headers().values("Content-Length").firstOrNull()?.toIntOrNull()
|
||||
System.err.println(
|
||||
"<-- ${response.statusCode()} (${
|
||||
buildString {
|
||||
append(took.format())
|
||||
contentLength?.let { append(", $contentLength-byte body") }
|
||||
}
|
||||
})"
|
||||
)
|
||||
|
||||
if (!level.shouldLog(LogLevel.DEBUG)) {
|
||||
return response
|
||||
}
|
||||
|
||||
logHeaders(response.headers())
|
||||
return LoggingHttpResponse(response)
|
||||
}
|
||||
|
||||
private fun logFailure(error: Throwable, took: Duration) {
|
||||
if (!level.shouldLog(LogLevel.ERROR)) {
|
||||
return
|
||||
}
|
||||
|
||||
System.err.println(
|
||||
buildString {
|
||||
append("<-- !! ${error.javaClass.simpleName}")
|
||||
error.message?.let { append(": $it") }
|
||||
append(" (${took.format()})")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun unwrapCompletionException(error: Throwable): Throwable =
|
||||
if (error is CompletionException && error.cause != null) error.cause!! else error
|
||||
|
||||
private fun logHeaders(headers: Headers) =
|
||||
headers.names().forEach { name ->
|
||||
headers.values(name).forEach { value ->
|
||||
System.err.println("$name: ${if (redactedHeaders.contains(name)) "██" else value}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() = httpClient.close()
|
||||
|
||||
fun toBuilder() = Builder().from(this)
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Returns a mutable builder for constructing an instance of [LoggingHttpClient].
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .httpClient()
|
||||
* .level()
|
||||
* ```
|
||||
*/
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
/** A builder for [LoggingHttpClient]. */
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var httpClient: HttpClient? = null
|
||||
private var redactedHeaders: Set<String> =
|
||||
setOf("authorization", "api-key", "x-api-key", "cookie", "set-cookie", "x-tenant-id")
|
||||
private var clock: Clock = Clock.systemUTC()
|
||||
private var level: LogLevel? = null
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun from(loggingHttpClient: LoggingHttpClient) = apply {
|
||||
httpClient = loggingHttpClient.httpClient
|
||||
redactedHeaders = loggingHttpClient.redactedHeaders
|
||||
clock = loggingHttpClient.clock
|
||||
level = loggingHttpClient.level
|
||||
}
|
||||
|
||||
/** The underlying [HttpClient] for making requests. */
|
||||
fun httpClient(httpClient: HttpClient) = apply { this.httpClient = httpClient }
|
||||
|
||||
/**
|
||||
* Sensitive headers to redact from logs.
|
||||
*
|
||||
* Defaults to `Set.of("authorization", "api-key", "x-api-key", "cookie", "set-cookie",
|
||||
* "x-tenant-id")`.
|
||||
*/
|
||||
fun redactedHeaders(redactedHeaders: Set<String>) = apply {
|
||||
this.redactedHeaders = redactedHeaders
|
||||
}
|
||||
|
||||
/**
|
||||
* The clock to use for measuring request and response durations.
|
||||
*
|
||||
* This is primarily useful for using a fake clock in tests.
|
||||
*
|
||||
* Defaults to [Clock.systemUTC].
|
||||
*/
|
||||
fun clock(clock: Clock) = apply { this.clock = clock }
|
||||
|
||||
/**
|
||||
* The log level to use.
|
||||
*
|
||||
* Pass [LogLevel.fromEnv] to read from environment variables.
|
||||
*/
|
||||
fun level(level: LogLevel) = apply { this.level = level }
|
||||
|
||||
/**
|
||||
* Returns an immutable instance of [LoggingHttpClient].
|
||||
*
|
||||
* Further updates to this [Builder] will not mutate the returned instance.
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .httpClient()
|
||||
* .level()
|
||||
* ```
|
||||
*
|
||||
* @throws IllegalStateException if any required field is unset.
|
||||
*/
|
||||
fun build(): LoggingHttpClient =
|
||||
LoggingHttpClient(
|
||||
checkRequired("httpClient", httpClient),
|
||||
redactedHeaders.toSortedSet(String.CASE_INSENSITIVE_ORDER).toImmutable(),
|
||||
clock,
|
||||
checkRequired("level", level),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An [HttpRequestBody] wrapper that delegates to [body] while also logging line by line as it's
|
||||
* written.
|
||||
*
|
||||
* The logging occurs in a streaming manner with minimal buffering.
|
||||
*/
|
||||
private class LoggingHttpRequestBody(
|
||||
private val method: HttpMethod,
|
||||
private val body: HttpRequestBody,
|
||||
) : HttpRequestBody {
|
||||
|
||||
private val charset by lazy { parseCharset(body.contentType()) }
|
||||
|
||||
override fun writeTo(outputStream: OutputStream) {
|
||||
val loggingOutputStream = LoggingOutputStream(outputStream, charset)
|
||||
body.writeTo(loggingOutputStream)
|
||||
|
||||
loggingOutputStream.flush()
|
||||
System.err.println("--> END $method (${loggingOutputStream.writeCount()}-byte body)")
|
||||
System.err.println()
|
||||
}
|
||||
|
||||
override fun contentType(): String? = body.contentType()
|
||||
|
||||
override fun contentLength(): Long = body.contentLength()
|
||||
|
||||
override fun repeatable(): Boolean = body.repeatable()
|
||||
|
||||
override fun close() = body.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* An [OutputStream] wrapper that delegates to [outputStream] while also logging bytes line by line
|
||||
* as it's written to.
|
||||
*
|
||||
* The written content is assumed to be in the given [charset] and the logging occurs in a streaming
|
||||
* manner with minimal buffering.
|
||||
*/
|
||||
private class LoggingOutputStream(private val outputStream: OutputStream, charset: Charset?) :
|
||||
OutputStream() {
|
||||
|
||||
private val buffer = LoggingBuffer(charset)
|
||||
|
||||
fun writeCount() = buffer.writeCount()
|
||||
|
||||
override fun write(b: Int) {
|
||||
outputStream.write(b)
|
||||
buffer.write(b)
|
||||
}
|
||||
|
||||
override fun write(b: ByteArray, off: Int, len: Int) {
|
||||
outputStream.write(b, off, len)
|
||||
for (i in off until off + len) {
|
||||
buffer.write(b[i].toInt() and 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
/** Prints any currently buffered content. */
|
||||
override fun flush() {
|
||||
buffer.flush()
|
||||
outputStream.flush()
|
||||
}
|
||||
|
||||
override fun close() = outputStream.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* An [HttpResponse] wrapper that delegates to [response] while also logging line-by-line as it's
|
||||
* read.
|
||||
*
|
||||
* The logging occurs in a streaming manner with minimal buffering.
|
||||
*/
|
||||
private class LoggingHttpResponse(private val response: HttpResponse) : HttpResponse {
|
||||
|
||||
private val loggingBody: Lazy<InputStream> = lazy {
|
||||
LoggingInputStream(
|
||||
response.body(),
|
||||
parseCharset(response.headers().values("Content-Type").firstOrNull()),
|
||||
)
|
||||
}
|
||||
|
||||
override fun statusCode(): Int = response.statusCode()
|
||||
|
||||
override fun headers(): Headers = response.headers()
|
||||
|
||||
override fun body(): InputStream = loggingBody.value
|
||||
|
||||
override fun close() {
|
||||
if (loggingBody.isInitialized()) {
|
||||
loggingBody.value.close()
|
||||
}
|
||||
response.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An [InputStream] wrapper that delegates to [inputStream] while also logging bytes line by line as
|
||||
* it's read.
|
||||
*
|
||||
* The contents of [inputStream] are assumed to be in the given [charset] and the logging occurs in
|
||||
* a streaming manner with minimal buffering.
|
||||
*/
|
||||
private class LoggingInputStream(private val inputStream: InputStream, charset: Charset?) :
|
||||
InputStream() {
|
||||
|
||||
private var isDone = false
|
||||
private val buffer = LoggingBuffer(charset)
|
||||
|
||||
override fun read(): Int {
|
||||
if (isDone) {
|
||||
return -1
|
||||
}
|
||||
|
||||
val b = inputStream.read()
|
||||
|
||||
if (b == -1) {
|
||||
markDone()
|
||||
return b
|
||||
}
|
||||
|
||||
buffer.write(b)
|
||||
return b
|
||||
}
|
||||
|
||||
override fun read(b: ByteArray, off: Int, len: Int): Int {
|
||||
if (isDone) {
|
||||
return -1
|
||||
}
|
||||
|
||||
val bytesRead = inputStream.read(b, off, len)
|
||||
|
||||
if (bytesRead == -1) {
|
||||
markDone()
|
||||
return bytesRead
|
||||
}
|
||||
|
||||
for (i in off until off + bytesRead) {
|
||||
buffer.write(b[i].toInt() and 0xFF)
|
||||
}
|
||||
return bytesRead
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (!isDone) {
|
||||
markDone(closedEarly = true)
|
||||
}
|
||||
inputStream.close()
|
||||
}
|
||||
|
||||
private fun markDone(closedEarly: Boolean = false) {
|
||||
isDone = true
|
||||
buffer.flush()
|
||||
val suffix = if (closedEarly) ", closed early" else ""
|
||||
System.err.println("<-- END HTTP (${buffer.writeCount()}-byte body$suffix)")
|
||||
System.err.println()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A byte buffer that prints line by line, using the given [charset], as bytes are written to it.
|
||||
*
|
||||
* When [charset] is `null`, the buffer performs an upfront check to detect binary content. If
|
||||
* non-whitespace ISO control characters are found in the first [PROBABLY_UTF8_CODE_POINT_LIMIT]
|
||||
* code points, body logging is suppressed entirely.
|
||||
*/
|
||||
private class LoggingBuffer(charset: Charset?) {
|
||||
|
||||
private val charset = charset ?: StandardCharsets.UTF_8
|
||||
|
||||
private val decoder: CharsetDecoder =
|
||||
this.charset
|
||||
.newDecoder()
|
||||
.onMalformedInput(CodingErrorAction.REPORT)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||
private var writeCount = 0
|
||||
private val buffer = ByteArrayOutputStream(128)
|
||||
|
||||
/**
|
||||
* Whether logging has been suppressed because the content doesn't appear to be readable text.
|
||||
*
|
||||
* This is only set when [charset] is `null` and the content fails the [isProbablyUtf8] check.
|
||||
*/
|
||||
private var suppressed = false
|
||||
|
||||
/**
|
||||
* Bytes accumulated for the [isProbablyUtf8] check before any lines are printed.
|
||||
*
|
||||
* Once the check passes (or [charset] is non-null), this is set to `null` and bytes flow
|
||||
* directly to [buffer].
|
||||
*/
|
||||
private var prefetchBuffer: ByteArrayOutputStream? =
|
||||
if (charset != null) null else ByteArrayOutputStream(128)
|
||||
|
||||
fun writeCount() = writeCount
|
||||
|
||||
fun write(b: Int) {
|
||||
if (writeCount == 0) {
|
||||
// Print a newline before we start printing anything to separate the printed content
|
||||
// from previous content.
|
||||
System.err.println()
|
||||
}
|
||||
|
||||
writeCount++
|
||||
|
||||
if (suppressed) {
|
||||
return
|
||||
}
|
||||
|
||||
val prefetch = prefetchBuffer
|
||||
if (prefetch != null) {
|
||||
prefetch.write(b)
|
||||
// Continue accumulating until we have enough bytes to decide.
|
||||
if (prefetch.size() < PROBABLY_UTF8_BYTE_LIMIT && b != '\n'.code) {
|
||||
return
|
||||
}
|
||||
// We have enough bytes. Check if the content is probably UTF-8.
|
||||
prefetchBuffer = null
|
||||
val bytes = prefetch.toByteArray()
|
||||
if (!isProbablyUtf8(bytes)) {
|
||||
suppressed = true
|
||||
System.err.println("(binary body omitted)")
|
||||
return
|
||||
}
|
||||
// Content looks like UTF-8. Feed the accumulated bytes into the normal buffer.
|
||||
for (byte in bytes) {
|
||||
writeToBuffer(byte.toInt() and 0xFF)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
writeToBuffer(b)
|
||||
}
|
||||
|
||||
private fun writeToBuffer(b: Int) {
|
||||
if (b == '\n'.code) {
|
||||
flush()
|
||||
return
|
||||
}
|
||||
|
||||
buffer.write(b)
|
||||
}
|
||||
|
||||
/** Prints any currently buffered content. */
|
||||
fun flush() {
|
||||
if (suppressed) {
|
||||
return
|
||||
}
|
||||
|
||||
// If we still have a prefetch buffer when flush is called (body was shorter than the
|
||||
// limit), run the check now.
|
||||
val prefetch = prefetchBuffer
|
||||
if (prefetch != null) {
|
||||
prefetchBuffer = null
|
||||
val bytes = prefetch.toByteArray()
|
||||
if (bytes.isEmpty()) {
|
||||
return
|
||||
}
|
||||
if (!isProbablyUtf8(bytes)) {
|
||||
suppressed = true
|
||||
System.err.println("(binary body omitted)")
|
||||
return
|
||||
}
|
||||
for (byte in bytes) {
|
||||
writeToBuffer(byte.toInt() and 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.size() == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val line =
|
||||
try {
|
||||
decoder.decode(ByteBuffer.wrap(buffer.toByteArray()))
|
||||
} catch (e: CharacterCodingException) {
|
||||
"(omitted line is not valid $charset)"
|
||||
}
|
||||
buffer.reset()
|
||||
System.err.println(line)
|
||||
}
|
||||
}
|
||||
|
||||
/** The maximum number of code points to sample when checking if content is probably UTF-8. */
|
||||
private const val PROBABLY_UTF8_CODE_POINT_LIMIT = 64
|
||||
|
||||
/**
|
||||
* The maximum number of bytes to accumulate before running the [isProbablyUtf8] check. UTF-8 code
|
||||
* points are at most 4 bytes, so this accommodates [PROBABLY_UTF8_CODE_POINT_LIMIT] code points.
|
||||
*/
|
||||
private const val PROBABLY_UTF8_BYTE_LIMIT = PROBABLY_UTF8_CODE_POINT_LIMIT * 4
|
||||
|
||||
/**
|
||||
* Returns `true` if the given [bytes] probably contain human-readable UTF-8 text.
|
||||
*
|
||||
* Decodes up to [PROBABLY_UTF8_CODE_POINT_LIMIT] code points and returns `false` if any
|
||||
* non-whitespace ISO control characters are found, or if the bytes are not valid UTF-8.
|
||||
*/
|
||||
private fun isProbablyUtf8(bytes: ByteArray): Boolean {
|
||||
try {
|
||||
val decoder =
|
||||
StandardCharsets.UTF_8.newDecoder()
|
||||
.onMalformedInput(CodingErrorAction.REPORT)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||
val charBuffer = decoder.decode(ByteBuffer.wrap(bytes))
|
||||
var codePointCount = 0
|
||||
var i = 0
|
||||
while (i < charBuffer.length && codePointCount < PROBABLY_UTF8_CODE_POINT_LIMIT) {
|
||||
val codePoint = Character.codePointAt(charBuffer, i)
|
||||
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
|
||||
return false
|
||||
}
|
||||
i += Character.charCount(codePoint)
|
||||
codePointCount++
|
||||
}
|
||||
return true
|
||||
} catch (e: CharacterCodingException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the [Charset] in the given [contentType] string, or `null` if unspecified. */
|
||||
private fun parseCharset(contentType: String?): Charset? =
|
||||
contentType
|
||||
?.split(";")
|
||||
?.drop(1)
|
||||
?.map { it.trim() }
|
||||
?.firstOrNull { it.startsWith("charset=", ignoreCase = true) }
|
||||
?.substringAfter("=")
|
||||
?.trim()
|
||||
?.removeSurrounding("\"")
|
||||
?.let { runCatching { charset(it) }.getOrNull() }
|
||||
|
||||
/** Formats the [Duration] into a string like "1m 40s 467ms". */
|
||||
private fun Duration.format(): String =
|
||||
toKotlinDuration().toComponents { days, hours, minutes, seconds, nanoseconds ->
|
||||
buildString {
|
||||
val milliseconds = nanoseconds / 1_000_000
|
||||
if (days > 0) {
|
||||
append("${days}d")
|
||||
}
|
||||
if (hours > 0) {
|
||||
if (isNotEmpty()) {
|
||||
append(" ")
|
||||
}
|
||||
append("${hours}h")
|
||||
}
|
||||
if (minutes > 0) {
|
||||
if (isNotEmpty()) {
|
||||
append(" ")
|
||||
}
|
||||
append("${minutes}m")
|
||||
}
|
||||
if (seconds > 0) {
|
||||
if (isNotEmpty()) {
|
||||
append(" ")
|
||||
}
|
||||
append("${seconds}s")
|
||||
}
|
||||
if (milliseconds > 0) {
|
||||
if (isNotEmpty()) {
|
||||
append(" ")
|
||||
}
|
||||
append("${milliseconds}ms")
|
||||
}
|
||||
|
||||
if (isEmpty()) {
|
||||
append("0s")
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.langchain.smith.core.http
|
||||
|
||||
import java.net.Proxy
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.Base64
|
||||
import java.util.Optional
|
||||
|
||||
/**
|
||||
* Provides credentials when an HTTP proxy responds with `407 Proxy Authentication Required`.
|
||||
*
|
||||
* Implementations inspect the 407 [response] (typically its `Proxy-Authenticate` header) and return
|
||||
* the request to retry with a `Proxy-Authorization` header set, or [Optional.empty] to abandon
|
||||
* authentication and surface the 407 to the caller.
|
||||
*
|
||||
* Implementations must be thread-safe; they may be invoked concurrently from multiple HTTP calls.
|
||||
*/
|
||||
fun interface ProxyAuthenticator {
|
||||
|
||||
/**
|
||||
* @param proxy the proxy that produced the challenge, or [Proxy.NO_PROXY] if the route is not
|
||||
* yet established
|
||||
* @param request the request that produced [response]
|
||||
* @param response the 407 challenge response
|
||||
* @return the retry request to send (typically [request] with a `Proxy-Authorization` header
|
||||
* added), or [Optional.empty] to abandon authentication
|
||||
*/
|
||||
fun authenticate(
|
||||
proxy: Proxy,
|
||||
request: HttpRequest,
|
||||
response: HttpResponse,
|
||||
): Optional<HttpRequest>
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* A [ProxyAuthenticator] that uses RFC 7617 Basic authentication with the ISO-8859-1
|
||||
* charset.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun basic(username: String, password: String): ProxyAuthenticator =
|
||||
basic(username, password, StandardCharsets.ISO_8859_1)
|
||||
|
||||
/**
|
||||
* A [ProxyAuthenticator] that uses RFC 7617 Basic authentication with the given [charset].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun basic(username: String, password: String, charset: Charset): ProxyAuthenticator {
|
||||
val token =
|
||||
Base64.getEncoder().encodeToString("$username:$password".toByteArray(charset))
|
||||
val headerValue = "Basic $token"
|
||||
return ProxyAuthenticator { _, request, _ ->
|
||||
Optional.of(
|
||||
request.toBuilder().putHeader("Proxy-Authorization", headerValue).build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,27 +43,7 @@ private constructor(
|
||||
is JsonBoolean -> put(key, value.value.toString())
|
||||
is JsonNumber -> put(key, value.value.toString())
|
||||
is JsonString -> put(key, value.value)
|
||||
is JsonArray ->
|
||||
put(
|
||||
key,
|
||||
value.values
|
||||
.asSequence()
|
||||
.mapNotNull {
|
||||
when (it) {
|
||||
is JsonMissing,
|
||||
is JsonNull -> null
|
||||
is JsonBoolean -> it.value.toString()
|
||||
is JsonNumber -> it.value.toString()
|
||||
is JsonString -> it.value
|
||||
is JsonArray,
|
||||
is JsonObject ->
|
||||
throw IllegalArgumentException(
|
||||
"Cannot comma separate non-primitives in query params"
|
||||
)
|
||||
}
|
||||
}
|
||||
.joinToString(","),
|
||||
)
|
||||
is JsonArray -> value.values.forEach { put(key, it) }
|
||||
is JsonObject ->
|
||||
value.values.forEach { (nestedKey, value) -> put("$key[$nestedKey]", value) }
|
||||
}
|
||||
|
||||
+5
-8
@@ -1,3 +1,5 @@
|
||||
// File generated from our OpenAPI spec by Stainless.
|
||||
|
||||
package com.langchain.smith.core.http
|
||||
|
||||
import com.langchain.smith.core.DefaultSleeper
|
||||
@@ -199,7 +201,7 @@ private constructor(
|
||||
?: headers.values("Retry-After").getOrNull(0)?.let { retryAfter ->
|
||||
retryAfter.toFloatOrNull()?.times(TimeUnit.SECONDS.toNanos(1))
|
||||
?: try {
|
||||
ChronoUnit.MILLIS.between(
|
||||
ChronoUnit.NANOS.between(
|
||||
OffsetDateTime.now(clock),
|
||||
OffsetDateTime.parse(
|
||||
retryAfter,
|
||||
@@ -212,13 +214,8 @@ private constructor(
|
||||
}
|
||||
}
|
||||
?.let { retryAfterNanos ->
|
||||
// If the API asks us to wait a certain amount of time (and it's a reasonable
|
||||
// amount), just
|
||||
// do what it says.
|
||||
val retryAfter = Duration.ofNanos(retryAfterNanos.toLong())
|
||||
if (retryAfter in Duration.ofNanos(0)..Duration.ofMinutes(1)) {
|
||||
return retryAfter
|
||||
}
|
||||
// If the API asks us to wait a certain amount of time, do what it says.
|
||||
return Duration.ofNanos(retryAfterNanos.toLong())
|
||||
}
|
||||
|
||||
// Apply exponential backoff, but not more than the max.
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class BadRequestException
|
||||
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
|
||||
LangChainServiceException("400: $body", cause) {
|
||||
LangChainServiceException(formatMessage(400, body), cause) {
|
||||
|
||||
override fun statusCode(): Int = 400
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ private constructor(
|
||||
private val headers: Headers,
|
||||
private val body: JsonValue,
|
||||
cause: Throwable?,
|
||||
) : LangChainServiceException("$statusCode: $body", cause) {
|
||||
) : LangChainServiceException(formatMessage(statusCode, body), cause) {
|
||||
|
||||
override fun statusCode(): Int = statusCode
|
||||
|
||||
|
||||
+16
@@ -2,6 +2,7 @@
|
||||
|
||||
package com.langchain.smith.errors
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.langchain.smith.core.JsonValue
|
||||
import com.langchain.smith.core.http.Headers
|
||||
|
||||
@@ -14,4 +15,19 @@ protected constructor(message: String, cause: Throwable? = null) :
|
||||
abstract fun headers(): Headers
|
||||
|
||||
abstract fun body(): JsonValue
|
||||
|
||||
companion object {
|
||||
|
||||
/** Formats a status code and [JsonValue] error body into an error message. */
|
||||
@JvmStatic
|
||||
fun formatMessage(statusCode: Int, body: JsonValue): String {
|
||||
val bodyStr =
|
||||
try {
|
||||
ObjectMapper().writeValueAsString(body)
|
||||
} catch (_: Exception) {
|
||||
body.toString()
|
||||
}
|
||||
return "$statusCode: $bodyStr"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class NotFoundException
|
||||
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
|
||||
LangChainServiceException("404: $body", cause) {
|
||||
LangChainServiceException(formatMessage(404, body), cause) {
|
||||
|
||||
override fun statusCode(): Int = 404
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class PermissionDeniedException
|
||||
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
|
||||
LangChainServiceException("403: $body", cause) {
|
||||
LangChainServiceException(formatMessage(403, body), cause) {
|
||||
|
||||
override fun statusCode(): Int = 403
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class RateLimitException
|
||||
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
|
||||
LangChainServiceException("429: $body", cause) {
|
||||
LangChainServiceException(formatMessage(429, body), cause) {
|
||||
|
||||
override fun statusCode(): Int = 429
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class UnauthorizedException
|
||||
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
|
||||
LangChainServiceException("401: $body", cause) {
|
||||
LangChainServiceException(formatMessage(401, body), cause) {
|
||||
|
||||
override fun statusCode(): Int = 401
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ private constructor(
|
||||
private val headers: Headers,
|
||||
private val body: JsonValue,
|
||||
cause: Throwable?,
|
||||
) : LangChainServiceException("$statusCode: $body", cause) {
|
||||
) : LangChainServiceException(formatMessage(statusCode, body), cause) {
|
||||
|
||||
override fun statusCode(): Int = statusCode
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class UnprocessableEntityException
|
||||
private constructor(private val headers: Headers, private val body: JsonValue, cause: Throwable?) :
|
||||
LangChainServiceException("422: $body", cause) {
|
||||
LangChainServiceException(formatMessage(422, body), cause) {
|
||||
|
||||
override fun statusCode(): Int = 422
|
||||
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.models.examples.Example
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
|
||||
/** Pairwise score emitted by a [ComparativeEvaluator] for one or more runs. */
|
||||
data class ComparisonEvaluationResult(
|
||||
val key: String,
|
||||
val scores: Map<String, Number>,
|
||||
val comment: String? = null,
|
||||
val sourceRunId: String? = null,
|
||||
)
|
||||
|
||||
/** Compares runs from two or more experiments on the same dataset example. */
|
||||
fun interface ComparativeEvaluator {
|
||||
fun compareRuns(runs: List<RunIngest>, example: Example): ComparisonEvaluationResult
|
||||
}
|
||||
|
||||
/** Creates a [ComparativeEvaluator] from a comparison function. */
|
||||
fun comparativeEvaluator(
|
||||
func: (List<RunIngest>, Example) -> ComparisonEvaluationResult
|
||||
): ComparativeEvaluator = ComparativeEvaluator { runs, example -> func(runs, example) }
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.models.datasets.comparative.ComparativeCreateResponse
|
||||
import com.langchain.smith.models.examples.Example
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
|
||||
/** One row from a comparative evaluation for a shared dataset example. */
|
||||
data class ComparativeResultRow(
|
||||
val example: Example,
|
||||
val runs: List<RunIngest>,
|
||||
val evaluationResults: Map<String, ComparisonEvaluationResult>,
|
||||
)
|
||||
|
||||
/** Results returned by [evaluateComparative]. */
|
||||
class ComparativeExperimentResults
|
||||
internal constructor(
|
||||
val rows: List<ComparativeResultRow>,
|
||||
val comparativeExperiment: ComparativeCreateResponse?,
|
||||
val url: String?,
|
||||
) {
|
||||
override fun toString(): String = "ComparativeExperimentResults{rows=${rows.size}, url=$url}"
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.models.examples.Example
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
|
||||
/**
|
||||
* Wraps a function as a [RunEvaluator], normalizing common evaluator signatures to `(Run,
|
||||
* Example?)`.
|
||||
*/
|
||||
class DynamicRunEvaluator
|
||||
private constructor(private val evaluator: (RunIngest, Example?) -> EvaluatorOutput) :
|
||||
RunEvaluator {
|
||||
|
||||
override val feedbackKeys: List<String> = emptyList()
|
||||
|
||||
override fun evaluateRun(
|
||||
run: RunIngest,
|
||||
example: Example?,
|
||||
evaluatorRunId: String?,
|
||||
): EvaluationOutput {
|
||||
val sourceRunId = evaluatorRunId ?: newEvaluatorRunId()
|
||||
val raw = evaluator(run, example)
|
||||
return coerceEvaluatorOutput(raw, sourceRunId, defaultKey = "evaluator")
|
||||
}
|
||||
|
||||
override fun toString(): String = "DynamicRunEvaluator{feedbackKeys=$feedbackKeys}"
|
||||
|
||||
companion object {
|
||||
internal fun fromRunExample(
|
||||
func: (RunIngest, Example?) -> EvaluatorOutput
|
||||
): DynamicRunEvaluator = DynamicRunEvaluator(func)
|
||||
|
||||
internal fun fromOutputsReference(
|
||||
func:
|
||||
(outputs: Map<String, Any?>, referenceOutputs: Map<String, Any?>) -> EvaluatorOutput
|
||||
): DynamicRunEvaluator = DynamicRunEvaluator { run, example ->
|
||||
func(runOutputsMap(run), exampleOutputs(example))
|
||||
}
|
||||
|
||||
internal fun fromInputsOutputsReference(
|
||||
func:
|
||||
(
|
||||
inputs: Map<String, Any?>,
|
||||
outputs: Map<String, Any?>,
|
||||
referenceOutputs: Map<String, Any?>,
|
||||
) -> EvaluatorOutput
|
||||
): DynamicRunEvaluator = DynamicRunEvaluator { run, example ->
|
||||
func(exampleInputs(example), runOutputsMap(run), exampleOutputs(example))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a [RunEvaluator] from a function that receives the full [Run] and [Example]. */
|
||||
fun runEvaluator(func: (RunIngest, Example?) -> EvaluatorOutput): RunEvaluator =
|
||||
DynamicRunEvaluator.fromRunExample(func)
|
||||
|
||||
/** Creates a [RunEvaluator] from a function that receives model outputs and reference outputs. */
|
||||
@JvmName("runEvaluatorFromOutputsReference")
|
||||
fun runEvaluator(
|
||||
func: (outputs: Map<String, Any?>, referenceOutputs: Map<String, Any?>) -> EvaluatorOutput
|
||||
): RunEvaluator = DynamicRunEvaluator.fromOutputsReference(func)
|
||||
|
||||
/**
|
||||
* Creates a [RunEvaluator] from a function that receives inputs, outputs, and reference outputs.
|
||||
*/
|
||||
@JvmName("runEvaluatorFromInputsOutputsReference")
|
||||
fun runEvaluator(
|
||||
func:
|
||||
(
|
||||
inputs: Map<String, Any?>,
|
||||
outputs: Map<String, Any?>,
|
||||
referenceOutputs: Map<String, Any?>,
|
||||
) -> EvaluatorOutput
|
||||
): RunEvaluator = DynamicRunEvaluator.fromInputsOutputsReference(func)
|
||||
|
||||
/** We pass the full run and dataset row for evaluators that need trace-level details. */
|
||||
fun interface RunExampleEvaluator {
|
||||
fun evaluate(run: RunIngest, example: Example?): EvaluatorOutput
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a [RunExampleEvaluator] so the evaluate runner can call it like any other [RunEvaluator].
|
||||
*/
|
||||
fun runEvaluator(func: RunExampleEvaluator): RunEvaluator =
|
||||
DynamicRunEvaluator.fromRunExample(func::evaluate)
|
||||
|
||||
/** We pass the app's answer and expected answer for simple match-style evaluators. */
|
||||
fun interface OutputReferenceEvaluator {
|
||||
fun evaluate(outputs: Map<String, Any?>, referenceOutputs: Map<String, Any?>): EvaluatorOutput
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an [OutputReferenceEvaluator] so the evaluate runner can call it like any other
|
||||
* [RunEvaluator].
|
||||
*/
|
||||
@JvmName("runEvaluatorFromOutputReferenceEvaluator")
|
||||
fun runEvaluator(func: OutputReferenceEvaluator): RunEvaluator =
|
||||
DynamicRunEvaluator.fromOutputsReference(func::evaluate)
|
||||
@@ -0,0 +1,165 @@
|
||||
@file:JvmName("Evaluation")
|
||||
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.client.LangsmithClient
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.function.Function
|
||||
|
||||
private val evaluateExecutor =
|
||||
Executors.newCachedThreadPool { runnable ->
|
||||
Thread(runnable, "langsmith-evaluate").apply { isDaemon = true }
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs [target] on a dataset, traces each prediction, scores it with [params.evaluators], and
|
||||
* uploads feedback to LangSmith.
|
||||
*
|
||||
* @param blocking When `true` (default), waits for the evaluation to finish before returning. When
|
||||
* `false`, use [evaluateAsync] instead — Java cannot return in-progress [ExperimentResults].
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun evaluate(
|
||||
client: LangsmithClient,
|
||||
target: (Map<String, Any?>) -> Any?,
|
||||
params: EvaluateParams,
|
||||
blocking: Boolean = true,
|
||||
): ExperimentResults {
|
||||
if (!blocking) {
|
||||
throw IllegalArgumentException(
|
||||
"Non-blocking evaluation requires evaluateAsync(). " +
|
||||
"Call evaluateAsync(client, target, params) instead."
|
||||
)
|
||||
}
|
||||
return EvaluateRunner(client, params).run(target)
|
||||
}
|
||||
|
||||
/** Java-friendly overload using [Function]. */
|
||||
@JvmOverloads
|
||||
fun evaluate(
|
||||
client: LangsmithClient,
|
||||
target: Function<Map<String, Any?>, Any?>,
|
||||
params: EvaluateParams,
|
||||
blocking: Boolean = true,
|
||||
): ExperimentResults = evaluate(client, { inputs -> target.apply(inputs) }, params, blocking)
|
||||
|
||||
/** Runs an [EvaluateTarget] (Runnable-style) on a dataset. */
|
||||
@JvmOverloads
|
||||
fun evaluate(
|
||||
client: LangsmithClient,
|
||||
target: EvaluateTarget,
|
||||
params: EvaluateParams,
|
||||
blocking: Boolean = true,
|
||||
): ExperimentResults = evaluate(client, target::invoke, params, blocking)
|
||||
|
||||
/**
|
||||
* Runs [target] asynchronously. Equivalent to Python `evaluate(..., blocking=False)` — call
|
||||
* [CompletableFuture.join] or [CompletableFuture.get] when results are needed.
|
||||
*/
|
||||
fun evaluateAsync(
|
||||
client: LangsmithClient,
|
||||
target: (Map<String, Any?>) -> Any?,
|
||||
params: EvaluateParams,
|
||||
): CompletableFuture<ExperimentResults> =
|
||||
CompletableFuture.supplyAsync({ EvaluateRunner(client, params).run(target) }, evaluateExecutor)
|
||||
|
||||
/** Async overload for [EvaluateTarget]. */
|
||||
fun evaluateAsync(
|
||||
client: LangsmithClient,
|
||||
target: EvaluateTarget,
|
||||
params: EvaluateParams,
|
||||
): CompletableFuture<ExperimentResults> = evaluateAsync(client, target::invoke, params)
|
||||
|
||||
/** Async overload for Java [Function]. */
|
||||
fun evaluateAsync(
|
||||
client: LangsmithClient,
|
||||
target: Function<Map<String, Any?>, Any?>,
|
||||
params: EvaluateParams,
|
||||
): CompletableFuture<ExperimentResults> =
|
||||
evaluateAsync(client, { inputs -> target.apply(inputs) }, params)
|
||||
|
||||
/**
|
||||
* Evaluates an existing experiment's runs without re-running the target. Mirrors Python
|
||||
* `evaluate_existing` and `evaluate(existing_experiment_id, ...)`.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun evaluateExisting(
|
||||
client: LangsmithClient,
|
||||
experimentId: String,
|
||||
params: EvaluateExistingParams,
|
||||
blocking: Boolean = true,
|
||||
): ExperimentResults {
|
||||
if (!blocking) {
|
||||
throw IllegalArgumentException(
|
||||
"Non-blocking evaluation requires evaluateExistingAsync(). " +
|
||||
"Call evaluateExistingAsync(client, experimentId, params) instead."
|
||||
)
|
||||
}
|
||||
return EvaluateExistingRunner(client, experimentId, params).run()
|
||||
}
|
||||
|
||||
fun evaluateExistingAsync(
|
||||
client: LangsmithClient,
|
||||
experimentId: String,
|
||||
params: EvaluateExistingParams,
|
||||
): CompletableFuture<ExperimentResults> =
|
||||
CompletableFuture.supplyAsync(
|
||||
{ EvaluateExistingRunner(client, experimentId, params).run() },
|
||||
evaluateExecutor,
|
||||
)
|
||||
|
||||
/**
|
||||
* Compares two existing experiments with pairwise evaluators. Mirrors Python `evaluate_comparative`
|
||||
* and `evaluate((experiment_a, experiment_b), ...)`.
|
||||
*/
|
||||
fun evaluateComparative(
|
||||
client: LangsmithClient,
|
||||
experiments: Pair<String, String>,
|
||||
params: EvaluateComparativeParams,
|
||||
): ComparativeExperimentResults = EvaluateComparativeRunner(client, experiments, params).run()
|
||||
|
||||
/** Convenience overload accepting experiment ids as a list. */
|
||||
fun evaluateComparative(
|
||||
client: LangsmithClient,
|
||||
experiments: List<String>,
|
||||
params: EvaluateComparativeParams,
|
||||
): ComparativeExperimentResults {
|
||||
require(experiments.size == 2) {
|
||||
"Comparative evaluation requires exactly 2 experiments, got ${experiments.size}"
|
||||
}
|
||||
return evaluateComparative(client, experiments[0] to experiments[1], params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-scores an existing experiment using evaluators from [EvaluateParams]. Convenience wrapper
|
||||
* around [evaluateExisting] for callers that already hold [EvaluateParams].
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun evaluate(
|
||||
client: LangsmithClient,
|
||||
experimentId: String,
|
||||
params: EvaluateParams,
|
||||
blocking: Boolean = true,
|
||||
): ExperimentResults =
|
||||
evaluateExisting(
|
||||
client,
|
||||
experimentId,
|
||||
EvaluateExistingParams.builder()
|
||||
.evaluators(params.evaluators)
|
||||
.summaryEvaluators(params.summaryEvaluators)
|
||||
.metadata(params.metadata)
|
||||
.maxConcurrency(params.maxConcurrency)
|
||||
.build(),
|
||||
blocking,
|
||||
)
|
||||
|
||||
/**
|
||||
* Compares two existing experiments. Convenience wrapper when callers pass a pair of session ids as
|
||||
* the evaluation target (Python tuple-target style).
|
||||
*/
|
||||
fun evaluate(
|
||||
client: LangsmithClient,
|
||||
experiments: Pair<String, String>,
|
||||
params: EvaluateComparativeParams,
|
||||
): ComparativeExperimentResults = evaluateComparative(client, experiments, params)
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
/** Configuration for [evaluateComparative]. */
|
||||
class EvaluateComparativeParams
|
||||
private constructor(
|
||||
val evaluators: List<ComparativeEvaluator>,
|
||||
val experimentPrefix: String?,
|
||||
val description: String?,
|
||||
val metadata: Map<String, Any>,
|
||||
val maxConcurrency: Int,
|
||||
val loadNested: Boolean,
|
||||
val randomizeOrder: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
class Builder internal constructor() {
|
||||
private var evaluators: MutableList<ComparativeEvaluator> = mutableListOf()
|
||||
private var experimentPrefix: String? = null
|
||||
private var description: String? = null
|
||||
private var metadata: Map<String, Any> = emptyMap()
|
||||
private var maxConcurrency: Int = 5
|
||||
private var loadNested: Boolean = false
|
||||
private var randomizeOrder: Boolean = false
|
||||
|
||||
fun addEvaluator(evaluator: ComparativeEvaluator) = apply { evaluators.add(evaluator) }
|
||||
|
||||
fun evaluators(evaluators: List<ComparativeEvaluator>) = apply {
|
||||
this.evaluators = evaluators.toMutableList()
|
||||
}
|
||||
|
||||
fun experimentPrefix(experimentPrefix: String) = apply {
|
||||
this.experimentPrefix = experimentPrefix
|
||||
}
|
||||
|
||||
fun description(description: String) = apply { this.description = description }
|
||||
|
||||
fun metadata(metadata: Map<String, Any>) = apply { this.metadata = metadata }
|
||||
|
||||
fun maxConcurrency(maxConcurrency: Int) = apply {
|
||||
require(maxConcurrency >= 0) { "maxConcurrency must be >= 0" }
|
||||
this.maxConcurrency = maxConcurrency
|
||||
}
|
||||
|
||||
fun loadNested(loadNested: Boolean) = apply { this.loadNested = loadNested }
|
||||
|
||||
fun randomizeOrder(randomizeOrder: Boolean) = apply { this.randomizeOrder = randomizeOrder }
|
||||
|
||||
fun build(): EvaluateComparativeParams {
|
||||
require(evaluators.isNotEmpty()) { "at least one comparative evaluator is required" }
|
||||
return EvaluateComparativeParams(
|
||||
evaluators = evaluators.toList(),
|
||||
experimentPrefix = experimentPrefix,
|
||||
description = description,
|
||||
metadata = metadata,
|
||||
maxConcurrency = maxConcurrency,
|
||||
loadNested = loadNested,
|
||||
randomizeOrder = randomizeOrder,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.client.LangsmithClient
|
||||
import com.langchain.smith.core.JsonValue
|
||||
import com.langchain.smith.models.datasets.comparative.ComparativeCreateParams
|
||||
import com.langchain.smith.models.examples.Example
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
import com.langchain.smith.models.sessions.TracerSession
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.Logger
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
/** Runs pairwise evaluators over two existing experiments. */
|
||||
internal class EvaluateComparativeRunner(
|
||||
private val client: LangsmithClient,
|
||||
private val experimentIds: Pair<String, String>,
|
||||
private val params: EvaluateComparativeParams,
|
||||
) {
|
||||
private val logger = Logger.getLogger(EvaluateComparativeRunner::class.java.name)
|
||||
|
||||
fun run(): ComparativeExperimentResults {
|
||||
require(params.maxConcurrency >= 0) { "maxConcurrency must be >= 0" }
|
||||
|
||||
val projects =
|
||||
listOf(
|
||||
loadExperiment(client, experimentIds.first),
|
||||
loadExperiment(client, experimentIds.second),
|
||||
)
|
||||
|
||||
val datasetIds =
|
||||
projects.map { project ->
|
||||
project.referenceDatasetId().orElseThrow {
|
||||
IllegalStateException("Experiment ${project.id()} has no reference dataset")
|
||||
}
|
||||
}
|
||||
require(datasetIds.distinct().size == 1) {
|
||||
"All experiments must have the same reference dataset"
|
||||
}
|
||||
val datasetId = datasetIds.first()
|
||||
|
||||
val experimentName = resolveComparativeName(projects)
|
||||
val comparativeExperimentId = UUID.randomUUID().toString()
|
||||
val comparativeExperiment =
|
||||
client
|
||||
.datasets()
|
||||
.comparative()
|
||||
.create(
|
||||
ComparativeCreateParams.builder()
|
||||
.id(comparativeExperimentId)
|
||||
.name(experimentName)
|
||||
.description(params.description)
|
||||
.experimentIds(projects.map { it.id() })
|
||||
.referenceDatasetId(datasetId)
|
||||
.apply {
|
||||
if (params.metadata.isNotEmpty()) {
|
||||
val extraBuilder = ComparativeCreateParams.Extra.builder()
|
||||
params.metadata.forEach { (key, value) ->
|
||||
extraBuilder.putAdditionalProperty(key, JsonValue.from(value))
|
||||
}
|
||||
extra(extraBuilder.build())
|
||||
}
|
||||
}
|
||||
.build()
|
||||
)
|
||||
|
||||
val runsByExperiment =
|
||||
projects.map { project ->
|
||||
loadRunsForExperiment(client, project.id(), params.loadNested)
|
||||
}
|
||||
|
||||
val exampleIntersection =
|
||||
runsByExperiment
|
||||
.map { runs ->
|
||||
runs.mapNotNull { run -> run.referenceExampleId().getOrNull() }.toSet()
|
||||
}
|
||||
.reduce { acc, ids -> acc.intersect(ids) }
|
||||
|
||||
val examplesById =
|
||||
loadExamplesByIds(
|
||||
client,
|
||||
datasetId,
|
||||
exampleIntersection.toList(),
|
||||
experimentDatasetVersion(projects.first()),
|
||||
)
|
||||
|
||||
val runsByExample = linkedMapOf<String, MutableList<RunIngest>>()
|
||||
runsByExperiment.forEach { runs ->
|
||||
runs.forEach { run ->
|
||||
val exampleId = run.referenceExampleId().getOrNull() ?: return@forEach
|
||||
if (exampleId !in exampleIntersection) {
|
||||
return@forEach
|
||||
}
|
||||
runsByExample.getOrPut(exampleId) { mutableListOf() }.add(run)
|
||||
}
|
||||
}
|
||||
|
||||
val rows = compareExamples(runsByExample, examplesById, comparativeExperiment.id())
|
||||
val url = buildComparativeUrl(projects, comparativeExperiment.id(), datasetId)
|
||||
|
||||
return ComparativeExperimentResults(
|
||||
rows = rows,
|
||||
comparativeExperiment = comparativeExperiment,
|
||||
url = url,
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveComparativeName(projects: List<TracerSession>): String {
|
||||
params.experimentPrefix?.let { prefix ->
|
||||
return "$prefix-${UUID.randomUUID().toString().take(8)}"
|
||||
}
|
||||
val names = projects.mapNotNull { it.name().orElse(null) }
|
||||
return if (names.size == projects.size) {
|
||||
names.joinToString(" vs. ") + "-" + UUID.randomUUID().toString().take(4)
|
||||
} else {
|
||||
"comparative-${UUID.randomUUID().toString().take(8)}"
|
||||
}
|
||||
}
|
||||
|
||||
private fun compareExamples(
|
||||
runsByExample: Map<String, List<RunIngest>>,
|
||||
examplesById: Map<String, Example>,
|
||||
comparativeExperimentId: String,
|
||||
): List<ComparativeResultRow> {
|
||||
if (params.maxConcurrency == 0 || runsByExample.size <= 1) {
|
||||
return runsByExample.mapNotNull { (exampleId, runs) ->
|
||||
val example = examplesById[exampleId] ?: return@mapNotNull null
|
||||
compareExample(example, runs, comparativeExperimentId)
|
||||
}
|
||||
}
|
||||
|
||||
val poolSize = minOf(params.maxConcurrency, runsByExample.size)
|
||||
val executor = Executors.newFixedThreadPool(poolSize)
|
||||
return try {
|
||||
runsByExample
|
||||
.map { (exampleId, runs) ->
|
||||
executor.submit(
|
||||
Callable {
|
||||
val example = examplesById[exampleId] ?: return@Callable null
|
||||
compareExample(example, runs, comparativeExperimentId)
|
||||
}
|
||||
)
|
||||
}
|
||||
.mapNotNull { it.get() }
|
||||
} finally {
|
||||
executor.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
private fun compareExample(
|
||||
example: Example,
|
||||
runs: List<RunIngest>,
|
||||
comparativeExperimentId: String,
|
||||
): ComparativeResultRow {
|
||||
val orderedRuns =
|
||||
if (params.randomizeOrder) {
|
||||
runs.shuffled()
|
||||
} else {
|
||||
runs
|
||||
}
|
||||
|
||||
val evaluationResults = linkedMapOf<String, ComparisonEvaluationResult>()
|
||||
for (evaluator in params.evaluators) {
|
||||
try {
|
||||
val result = evaluator.compareRuns(orderedRuns, example)
|
||||
evaluationResults["feedback.${result.key}"] = result
|
||||
val feedbackGroupId = UUID.randomUUID().toString()
|
||||
logComparativeEvaluationFeedback(
|
||||
client,
|
||||
result,
|
||||
orderedRuns,
|
||||
comparativeExperimentId,
|
||||
feedbackGroupId,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logger.log(
|
||||
Level.SEVERE,
|
||||
"Error running comparative evaluator $evaluator on example ${example.id()}",
|
||||
e,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return ComparativeResultRow(
|
||||
example = example,
|
||||
runs = orderedRuns,
|
||||
evaluationResults = evaluationResults,
|
||||
)
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
/** How to handle errors when the target function fails on a dataset row. */
|
||||
enum class EvaluateErrorHandling {
|
||||
/** Log the error and include the run in the experiment (default). */
|
||||
LOG,
|
||||
|
||||
/** Log the error and omit the run from the experiment. */
|
||||
IGNORE,
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
/** Configuration for [evaluateExisting]. */
|
||||
class EvaluateExistingParams
|
||||
private constructor(
|
||||
val evaluators: List<RunEvaluator>,
|
||||
val summaryEvaluators: List<SummaryEvaluator>,
|
||||
val metadata: Map<String, Any>,
|
||||
val maxConcurrency: Int,
|
||||
val loadNested: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
class Builder internal constructor() {
|
||||
private var evaluators: MutableList<RunEvaluator> = mutableListOf()
|
||||
private var summaryEvaluators: MutableList<SummaryEvaluator> = mutableListOf()
|
||||
private var metadata: Map<String, Any> = emptyMap()
|
||||
private var maxConcurrency: Int = 0
|
||||
private var loadNested: Boolean = false
|
||||
|
||||
fun addEvaluator(evaluator: RunEvaluator) = apply { evaluators.add(evaluator) }
|
||||
|
||||
fun evaluators(evaluators: List<RunEvaluator>) = apply {
|
||||
this.evaluators = evaluators.toMutableList()
|
||||
}
|
||||
|
||||
fun addSummaryEvaluator(evaluator: SummaryEvaluator) = apply {
|
||||
summaryEvaluators.add(evaluator)
|
||||
}
|
||||
|
||||
fun summaryEvaluators(evaluators: List<SummaryEvaluator>) = apply {
|
||||
summaryEvaluators = evaluators.toMutableList()
|
||||
}
|
||||
|
||||
fun metadata(metadata: Map<String, Any>) = apply { this.metadata = metadata }
|
||||
|
||||
fun maxConcurrency(maxConcurrency: Int) = apply {
|
||||
require(maxConcurrency >= 0) { "maxConcurrency must be >= 0" }
|
||||
this.maxConcurrency = maxConcurrency
|
||||
}
|
||||
|
||||
fun loadNested(loadNested: Boolean) = apply { this.loadNested = loadNested }
|
||||
|
||||
fun build(): EvaluateExistingParams {
|
||||
require(evaluators.isNotEmpty() || summaryEvaluators.isNotEmpty()) {
|
||||
"at least one evaluator or summary evaluator is required"
|
||||
}
|
||||
return EvaluateExistingParams(
|
||||
evaluators = evaluators.toList(),
|
||||
summaryEvaluators = summaryEvaluators.toList(),
|
||||
metadata = metadata,
|
||||
maxConcurrency = maxConcurrency,
|
||||
loadNested = loadNested,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.client.LangsmithClient
|
||||
import com.langchain.smith.models.examples.Example
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.Logger
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
/** Scores existing experiment runs without re-running the target. */
|
||||
internal class EvaluateExistingRunner(
|
||||
private val client: LangsmithClient,
|
||||
private val experimentId: String,
|
||||
private val params: EvaluateExistingParams,
|
||||
) {
|
||||
private val logger = Logger.getLogger(EvaluateExistingRunner::class.java.name)
|
||||
|
||||
fun run(): ExperimentResults {
|
||||
require(params.maxConcurrency >= 0) { "maxConcurrency must be >= 0 (0 = sequential)" }
|
||||
|
||||
val session = loadExperiment(client, experimentId)
|
||||
val experimentName = session.name().orElse(experimentId)
|
||||
val datasetId =
|
||||
session.referenceDatasetId().orElseThrow {
|
||||
IllegalStateException("Experiment $experimentId has no reference dataset")
|
||||
}
|
||||
|
||||
val runs = loadRunsForExperiment(client, experimentId, params.loadNested)
|
||||
require(runs.isNotEmpty()) { "No runs found for experiment $experimentId" }
|
||||
|
||||
val exampleIds = runs.mapNotNull { run -> run.referenceExampleId().getOrNull() }.distinct()
|
||||
val examplesById =
|
||||
loadExamplesByIds(client, datasetId, exampleIds, experimentDatasetVersion(session))
|
||||
|
||||
val rows = scoreRuns(runs, examplesById)
|
||||
val summaryResults = applySummaryEvaluators(rows, experimentId)
|
||||
val url = buildExperimentUrl(session, datasetId)
|
||||
|
||||
return ExperimentResults(
|
||||
experimentName = experimentName,
|
||||
experimentId = experimentId,
|
||||
datasetId = datasetId,
|
||||
rows = rows,
|
||||
summaryResults = summaryResults,
|
||||
url = url,
|
||||
)
|
||||
}
|
||||
|
||||
private fun scoreRuns(
|
||||
runs: List<RunIngest>,
|
||||
examplesById: Map<String, Example>,
|
||||
): List<ExperimentResultRow> {
|
||||
if (params.maxConcurrency == 0 || runs.size <= 1) {
|
||||
return runs.mapNotNull { run -> scoreRun(run, examplesById) }
|
||||
}
|
||||
|
||||
val poolSize = minOf(params.maxConcurrency, runs.size)
|
||||
val executor = Executors.newFixedThreadPool(poolSize)
|
||||
return try {
|
||||
runs
|
||||
.map { run -> executor.submit(Callable { scoreRun(run, examplesById) }) }
|
||||
.mapNotNull { it.get() }
|
||||
} finally {
|
||||
executor.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
private fun scoreRun(run: RunIngest, examplesById: Map<String, Example>): ExperimentResultRow? {
|
||||
val exampleId = run.referenceExampleId().getOrNull() ?: return null
|
||||
val example = examplesById[exampleId] ?: return null
|
||||
|
||||
val evaluationResults =
|
||||
params.evaluators.flatMap { evaluator -> runEvaluator(evaluator, run, example) }
|
||||
|
||||
return ExperimentResultRow(
|
||||
run = run,
|
||||
example = example,
|
||||
evaluationResults = EvaluationResults(evaluationResults),
|
||||
)
|
||||
}
|
||||
|
||||
private fun runEvaluator(
|
||||
evaluator: RunEvaluator,
|
||||
run: RunIngest,
|
||||
example: Example,
|
||||
): List<EvaluationResult> {
|
||||
val evaluatorRunId = newEvaluatorRunId()
|
||||
return try {
|
||||
val response = evaluator.evaluateRun(run, example, evaluatorRunId)
|
||||
logEvaluationFeedback(client, response, run)
|
||||
selectEvaluationResults(response)
|
||||
} catch (e: Exception) {
|
||||
logger.log(Level.SEVERE, "Error running evaluator $evaluator on run ${run.id()}", e)
|
||||
val keys = evaluator.feedbackKeys
|
||||
if (keys.isEmpty()) {
|
||||
throw e
|
||||
}
|
||||
val errorResponse =
|
||||
EvaluationResults(
|
||||
keys.map { key ->
|
||||
EvaluationResult(
|
||||
key = key,
|
||||
sourceRunId = evaluatorRunId,
|
||||
comment = e.toString(),
|
||||
extra = mapOf("error" to true),
|
||||
)
|
||||
}
|
||||
)
|
||||
logEvaluationFeedback(client, errorResponse, run)
|
||||
errorResponse.results
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySummaryEvaluators(
|
||||
rows: List<ExperimentResultRow>,
|
||||
sessionId: String,
|
||||
): EvaluationResults {
|
||||
if (params.summaryEvaluators.isEmpty()) {
|
||||
return EvaluationResults(emptyList())
|
||||
}
|
||||
|
||||
val runs = rows.map { it.run }
|
||||
val examples = rows.map { it.example }
|
||||
val aggregateResults = mutableListOf<EvaluationResult>()
|
||||
|
||||
for (evaluator in params.summaryEvaluators) {
|
||||
try {
|
||||
val response = evaluator.evaluate(runs, examples)
|
||||
aggregateResults.addAll(selectEvaluationResults(response))
|
||||
logSummaryEvaluationFeedback(client, response, sessionId)
|
||||
} catch (e: Exception) {
|
||||
logger.log(Level.SEVERE, "Error running summary evaluator $evaluator", e)
|
||||
}
|
||||
}
|
||||
|
||||
return EvaluationResults(aggregateResults)
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.client.LangsmithClient
|
||||
import com.langchain.smith.core.JsonValue
|
||||
import com.langchain.smith.models.feedback.FeedbackCreateSchema
|
||||
import com.langchain.smith.models.feedback.ModelFeedbackSource
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
/** Turns an evaluator response into a flat list of [EvaluationResult]. */
|
||||
internal fun selectEvaluationResults(output: EvaluationOutput): List<EvaluationResult> =
|
||||
when (output) {
|
||||
is EvaluationResult -> listOf(output)
|
||||
is EvaluationResults -> output.results
|
||||
}
|
||||
|
||||
/** Posts evaluator feedback for a prediction run. */
|
||||
internal fun logEvaluationFeedback(
|
||||
client: LangsmithClient,
|
||||
evaluatorResponse: EvaluationOutput,
|
||||
run: RunIngest,
|
||||
) {
|
||||
for (result in selectEvaluationResults(evaluatorResponse)) {
|
||||
client.feedback().create(buildFeedbackCreateSchema(result, run))
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildFeedbackCreateSchema(
|
||||
result: EvaluationResult,
|
||||
run: RunIngest,
|
||||
): FeedbackCreateSchema {
|
||||
val builder = FeedbackCreateSchema.builder().key(result.key)
|
||||
|
||||
val runId = result.targetRunId ?: run.id().getOrNull()
|
||||
if (runId != null) {
|
||||
builder.runId(runId)
|
||||
}
|
||||
|
||||
builder.feedbackSource(
|
||||
ModelFeedbackSource.builder().type(ModelFeedbackSource.Type.MODEL).build()
|
||||
)
|
||||
|
||||
val score = result.score
|
||||
if (score != null) {
|
||||
builder.score(score.toDouble())
|
||||
}
|
||||
|
||||
applyFeedbackValue(builder, result.value)
|
||||
|
||||
val comment = result.comment
|
||||
if (comment != null) {
|
||||
builder.comment(comment)
|
||||
}
|
||||
|
||||
val feedbackConfig = result.feedbackConfig
|
||||
if (feedbackConfig != null) {
|
||||
builder.feedbackConfig(feedbackConfig)
|
||||
}
|
||||
|
||||
run.traceId().getOrNull()?.let { builder.traceId(it) }
|
||||
run.sessionId().getOrNull()?.let { builder.sessionId(it) }
|
||||
run.startTime().getOrNull()?.let { startTime ->
|
||||
runStartTime(startTime)?.let { builder.startTime(it) }
|
||||
}
|
||||
|
||||
val correction = result.correction
|
||||
if (correction != null) {
|
||||
builder.correction(
|
||||
FeedbackCreateSchema.Correction.ofUnionMember0(
|
||||
FeedbackCreateSchema.Correction.UnionMember0.builder()
|
||||
.putAllAdditionalProperties(
|
||||
correction.mapValues { (_, value) -> JsonValue.from(value) }
|
||||
)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val error = result.extra?.get("error") as? Boolean
|
||||
if (error == true) {
|
||||
builder.error(true)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
internal fun applyFeedbackValue(builder: FeedbackCreateSchema.Builder, value: Any?) {
|
||||
when (value) {
|
||||
null -> Unit
|
||||
is Number ->
|
||||
builder.value(
|
||||
when (value) {
|
||||
is Double -> value
|
||||
is Float -> value.toDouble()
|
||||
else -> value.toDouble()
|
||||
}
|
||||
)
|
||||
is Boolean -> builder.value(value)
|
||||
is String -> builder.value(value)
|
||||
is Map<*, *> -> {
|
||||
@Suppress("UNCHECKED_CAST") val stringMap = value as Map<String, Any?>
|
||||
builder.value(
|
||||
FeedbackCreateSchema.Value.ofUnionMember3(
|
||||
FeedbackCreateSchema.Value.UnionMember3.builder()
|
||||
.putAllAdditionalProperties(
|
||||
stringMap.mapValues { (_, v) -> JsonValue.from(v) }
|
||||
)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> builder.value(value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
internal fun runStartTime(startTime: String): OffsetDateTime? =
|
||||
try {
|
||||
OffsetDateTime.parse(startTime)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.models.examples.Example
|
||||
|
||||
/** Configuration for [evaluate]. */
|
||||
class EvaluateParams
|
||||
private constructor(
|
||||
internal val data: EvaluateData,
|
||||
val evaluators: List<RunEvaluator>,
|
||||
val summaryEvaluators: List<SummaryEvaluator>,
|
||||
val experimentPrefix: String,
|
||||
val experimentName: String?,
|
||||
val experimentId: String?,
|
||||
val description: String?,
|
||||
val metadata: Map<String, Any>,
|
||||
val maxConcurrency: Int,
|
||||
val numRepetitions: Int,
|
||||
val uploadResults: Boolean,
|
||||
val errorHandling: EvaluateErrorHandling,
|
||||
) {
|
||||
companion object {
|
||||
/** Creates a new [Builder]. */
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
/** Builds [EvaluateParams] with sensible defaults. */
|
||||
class Builder internal constructor() {
|
||||
private var data: EvaluateData? = null
|
||||
private var evaluators: MutableList<RunEvaluator> = mutableListOf()
|
||||
private var summaryEvaluators: MutableList<SummaryEvaluator> = mutableListOf()
|
||||
private var experimentPrefix: String = DEFAULT_EXPERIMENT_PREFIX
|
||||
private var experimentName: String? = null
|
||||
private var experimentId: String? = null
|
||||
private var description: String? = null
|
||||
private var metadata: Map<String, Any> = emptyMap()
|
||||
private var maxConcurrency: Int = 0
|
||||
private var numRepetitions: Int = 1
|
||||
private var uploadResults: Boolean = true
|
||||
private var errorHandling: EvaluateErrorHandling = EvaluateErrorHandling.LOG
|
||||
|
||||
/** Dataset name or ID whose examples will be evaluated. */
|
||||
fun data(datasetNameOrId: String) = apply {
|
||||
require(datasetNameOrId.isNotBlank()) { "datasetNameOrId must not be blank" }
|
||||
data = EvaluateData.Dataset(datasetNameOrId)
|
||||
}
|
||||
|
||||
/** In-memory examples to evaluate (for example after creating a dataset locally). */
|
||||
fun data(examples: List<Example>) = apply {
|
||||
require(examples.isNotEmpty()) { "examples must not be empty" }
|
||||
data = EvaluateData.Examples(examples)
|
||||
}
|
||||
|
||||
fun addEvaluator(evaluator: RunEvaluator) = apply { evaluators.add(evaluator) }
|
||||
|
||||
fun evaluators(evaluators: List<RunEvaluator>) = apply {
|
||||
this.evaluators = evaluators.toMutableList()
|
||||
}
|
||||
|
||||
fun addSummaryEvaluator(evaluator: SummaryEvaluator) = apply {
|
||||
summaryEvaluators.add(evaluator)
|
||||
}
|
||||
|
||||
fun summaryEvaluators(evaluators: List<SummaryEvaluator>) = apply {
|
||||
summaryEvaluators = evaluators.toMutableList()
|
||||
}
|
||||
|
||||
/** Prefix used when generating a unique experiment name. */
|
||||
fun experimentPrefix(experimentPrefix: String) = apply {
|
||||
this.experimentPrefix = experimentPrefix
|
||||
}
|
||||
|
||||
/** Explicit experiment name; overrides [experimentPrefix] when set. */
|
||||
fun experimentName(experimentName: String) = apply { this.experimentName = experimentName }
|
||||
|
||||
/**
|
||||
* Existing experiment session id to extend with additional runs. When set, a new session is
|
||||
* not created.
|
||||
*/
|
||||
fun experimentId(experimentId: String) = apply { this.experimentId = experimentId }
|
||||
|
||||
fun description(description: String) = apply { this.description = description }
|
||||
|
||||
fun metadata(metadata: Map<String, Any>) = apply { this.metadata = metadata }
|
||||
|
||||
/**
|
||||
* Maximum number of examples to evaluate concurrently. `0` runs sequentially. Values
|
||||
* greater than `0` use a fixed thread pool of that size.
|
||||
*/
|
||||
fun maxConcurrency(maxConcurrency: Int) = apply {
|
||||
require(maxConcurrency >= 0) { "maxConcurrency must be >= 0" }
|
||||
this.maxConcurrency = maxConcurrency
|
||||
}
|
||||
|
||||
/** Number of times to evaluate each dataset example. */
|
||||
fun numRepetitions(numRepetitions: Int) = apply {
|
||||
require(numRepetitions >= 1) { "numRepetitions must be >= 1" }
|
||||
this.numRepetitions = numRepetitions
|
||||
}
|
||||
|
||||
/** When false, runs and feedback are kept local and not uploaded to LangSmith. */
|
||||
fun uploadResults(uploadResults: Boolean) = apply { this.uploadResults = uploadResults }
|
||||
|
||||
fun errorHandling(errorHandling: EvaluateErrorHandling) = apply {
|
||||
this.errorHandling = errorHandling
|
||||
}
|
||||
|
||||
fun build(): EvaluateParams {
|
||||
val resolvedData =
|
||||
data
|
||||
?: throw IllegalArgumentException(
|
||||
"data is required (dataset name/id or examples)"
|
||||
)
|
||||
require(evaluators.isNotEmpty() || summaryEvaluators.isNotEmpty()) {
|
||||
"at least one evaluator or summary evaluator is required"
|
||||
}
|
||||
require(experimentId == null || experimentName == null) {
|
||||
"experimentId and experimentName cannot both be set"
|
||||
}
|
||||
return EvaluateParams(
|
||||
data = resolvedData,
|
||||
evaluators = evaluators.toList(),
|
||||
summaryEvaluators = summaryEvaluators.toList(),
|
||||
experimentPrefix = experimentPrefix,
|
||||
experimentName = experimentName,
|
||||
experimentId = experimentId,
|
||||
description = description,
|
||||
metadata = metadata,
|
||||
maxConcurrency = maxConcurrency,
|
||||
numRepetitions = numRepetitions,
|
||||
uploadResults = uploadResults,
|
||||
errorHandling = errorHandling,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class EvaluateData {
|
||||
data class Dataset(val identifier: String) : EvaluateData()
|
||||
|
||||
data class Examples(val examples: List<Example>) : EvaluateData()
|
||||
}
|
||||
|
||||
internal const val DEFAULT_EXPERIMENT_PREFIX = "experiment"
|
||||
@@ -0,0 +1,374 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.client.LangsmithClient
|
||||
import com.langchain.smith.core.JsonValue
|
||||
import com.langchain.smith.models.examples.Example
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
import com.langchain.smith.models.sessions.SessionCreateParams
|
||||
import com.langchain.smith.models.sessions.SessionUpdateParams
|
||||
import com.langchain.smith.models.sessions.TracerSessionWithoutVirtualFields
|
||||
import com.langchain.smith.tracing.RunTree
|
||||
import com.langchain.smith.tracing.TraceConfig
|
||||
import com.langchain.smith.tracing.getCurrentRunTree
|
||||
import com.langchain.smith.tracing.traceable
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.Logger
|
||||
|
||||
/** Runs your app on each dataset row, scores the results, and records everything in LangSmith. */
|
||||
internal class EvaluateRunner(
|
||||
private val client: LangsmithClient,
|
||||
private val params: EvaluateParams,
|
||||
) {
|
||||
private val logger = Logger.getLogger(EvaluateRunner::class.java.name)
|
||||
|
||||
/** End-to-end evaluate flow: load rows, open an experiment, run and score each one. */
|
||||
fun run(target: (Map<String, Any?>) -> Any?): ExperimentResults {
|
||||
require(params.maxConcurrency >= 0) { "maxConcurrency must be >= 0 (0 = sequential)" }
|
||||
|
||||
val examples = expandExamples(loadExamples(), params.numRepetitions)
|
||||
require(examples.isNotEmpty()) { "No examples found to evaluate" }
|
||||
|
||||
val datasetId = examples.first().datasetId()
|
||||
val sessionInfo = resolveExperimentSession(examples.size, datasetId)
|
||||
val experimentName = sessionInfo.first
|
||||
val sessionId = sessionInfo.second
|
||||
|
||||
val rows =
|
||||
evaluateExamples(
|
||||
target = target,
|
||||
examples = examples,
|
||||
experimentName = experimentName,
|
||||
sessionId = sessionId,
|
||||
)
|
||||
|
||||
val summaryResults = applySummaryEvaluators(rows, sessionId)
|
||||
|
||||
if (params.uploadResults && sessionId != null) {
|
||||
updateExperimentMetadata(sessionId, examples)
|
||||
}
|
||||
|
||||
// LangSmith UI URLs are not exposed on the Java session schema yet.
|
||||
val url: String? = null
|
||||
|
||||
return ExperimentResults(
|
||||
experimentName = experimentName,
|
||||
experimentId = sessionId,
|
||||
datasetId = datasetId,
|
||||
rows = rows,
|
||||
summaryResults = summaryResults,
|
||||
url = url,
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveExperimentSession(
|
||||
numExamples: Int,
|
||||
datasetId: String,
|
||||
): Pair<String, String?> {
|
||||
params.experimentId?.let { existingId ->
|
||||
val session = loadExperiment(client, existingId)
|
||||
val name = session.name().orElse(existingId)
|
||||
return name to existingId
|
||||
}
|
||||
|
||||
val experimentName =
|
||||
params.experimentName ?: generateExperimentName(params.experimentPrefix)
|
||||
|
||||
if (!params.uploadResults) {
|
||||
return experimentName to null
|
||||
}
|
||||
|
||||
val session = createExperimentSession(experimentName, datasetId, numExamples)
|
||||
return experimentName to session.id()
|
||||
}
|
||||
|
||||
private fun evaluateExamples(
|
||||
target: (Map<String, Any?>) -> Any?,
|
||||
examples: List<Example>,
|
||||
experimentName: String,
|
||||
sessionId: String?,
|
||||
): List<ExperimentResultRow> {
|
||||
if (params.maxConcurrency == 0 || examples.size <= 1) {
|
||||
return examples.mapNotNull { example ->
|
||||
evaluateExample(
|
||||
target = target,
|
||||
example = example,
|
||||
experimentName = experimentName,
|
||||
sessionId = sessionId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val poolSize = minOf(params.maxConcurrency, examples.size)
|
||||
val executor = Executors.newFixedThreadPool(poolSize)
|
||||
return try {
|
||||
val futures =
|
||||
examples.map { example ->
|
||||
executor.submit(
|
||||
Callable {
|
||||
evaluateExample(
|
||||
target = target,
|
||||
example = example,
|
||||
experimentName = experimentName,
|
||||
sessionId = sessionId,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
futures.mapNotNull { it.get() }
|
||||
} finally {
|
||||
executor.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadExamples(): List<Example> =
|
||||
when (val data = params.data) {
|
||||
is EvaluateData.Dataset -> listExamples(client, data.identifier)
|
||||
is EvaluateData.Examples -> data.examples
|
||||
}
|
||||
|
||||
private fun createExperimentSession(
|
||||
experimentName: String,
|
||||
datasetId: String,
|
||||
numExamples: Int,
|
||||
): TracerSessionWithoutVirtualFields {
|
||||
val sessionMetadata = buildMap {
|
||||
putAll(params.metadata)
|
||||
put("__ls_runner", "java_sdk_evaluate")
|
||||
put("num_repetitions", params.numRepetitions)
|
||||
}
|
||||
|
||||
val extraBuilder = SessionCreateParams.Extra.builder()
|
||||
sessionMetadata.forEach { (key, value) ->
|
||||
extraBuilder.putAdditionalProperty(key, JsonValue.from(value))
|
||||
}
|
||||
|
||||
var candidateName = experimentName
|
||||
repeat(MAX_NAME_COLLISION_RETRIES) {
|
||||
try {
|
||||
return client
|
||||
.sessions()
|
||||
.create(
|
||||
SessionCreateParams.builder()
|
||||
.name(candidateName)
|
||||
.description(params.description)
|
||||
.referenceDatasetId(datasetId)
|
||||
.numExamples(numExamples.toLong())
|
||||
.startTime(OffsetDateTime.now())
|
||||
.extra(extraBuilder.build())
|
||||
.build()
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
candidateName = "$experimentName-${UUID.randomUUID().toString().take(6)}"
|
||||
}
|
||||
}
|
||||
throw IllegalStateException(
|
||||
"Could not create experiment session after $MAX_NAME_COLLISION_RETRIES attempts"
|
||||
)
|
||||
}
|
||||
|
||||
private fun evaluateExample(
|
||||
target: (Map<String, Any?>) -> Any?,
|
||||
example: Example,
|
||||
experimentName: String,
|
||||
sessionId: String?,
|
||||
): ExperimentResultRow? {
|
||||
val inputs = exampleInputs(example)
|
||||
val runMetadata = buildMap {
|
||||
putAll(params.metadata)
|
||||
put("example_version", exampleVersion(example))
|
||||
}
|
||||
|
||||
val run =
|
||||
if (params.uploadResults) {
|
||||
runTracedTarget(target, inputs, example, experimentName, sessionId, runMetadata)
|
||||
} else {
|
||||
runLocalTarget(target, inputs, example)
|
||||
} ?: return null
|
||||
|
||||
val evaluationResults =
|
||||
params.evaluators.flatMap { evaluator -> runEvaluator(evaluator, run, example) }
|
||||
|
||||
return ExperimentResultRow(
|
||||
run = run,
|
||||
example = example,
|
||||
evaluationResults = EvaluationResults(evaluationResults),
|
||||
)
|
||||
}
|
||||
|
||||
private fun runTracedTarget(
|
||||
target: (Map<String, Any?>) -> Any?,
|
||||
inputs: Map<String, Any?>,
|
||||
example: Example,
|
||||
experimentName: String,
|
||||
sessionId: String?,
|
||||
runMetadata: Map<String, Any>,
|
||||
): RunIngest? {
|
||||
var runTree: RunTree? = null
|
||||
val wrappedTarget: (Map<String, Any?>) -> Any? = { exampleInputs ->
|
||||
target(exampleInputs).also { runTree = getCurrentRunTree() }
|
||||
}
|
||||
|
||||
val traced =
|
||||
traceable(
|
||||
wrappedTarget,
|
||||
TraceConfig(
|
||||
name = "Target",
|
||||
client = client,
|
||||
projectName = experimentName,
|
||||
referenceExampleId =
|
||||
if (params.errorHandling == EvaluateErrorHandling.LOG) example.id()
|
||||
else null,
|
||||
sessionId = sessionId,
|
||||
metadata = runMetadata,
|
||||
tracingEnabled = true,
|
||||
),
|
||||
)
|
||||
|
||||
try {
|
||||
traced(inputs)
|
||||
} catch (e: Exception) {
|
||||
logger.log(Level.SEVERE, "Error running target function", e)
|
||||
if (params.errorHandling == EvaluateErrorHandling.IGNORE) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return runTree?.buildRunData()
|
||||
?: if (params.errorHandling == EvaluateErrorHandling.IGNORE) {
|
||||
null
|
||||
} else {
|
||||
throw IllegalStateException("Failed to capture run for example ${example.id()}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun runLocalTarget(
|
||||
target: (Map<String, Any?>) -> Any?,
|
||||
inputs: Map<String, Any?>,
|
||||
example: Example,
|
||||
): RunIngest? {
|
||||
return try {
|
||||
val output = target(inputs)
|
||||
val outputsBuilder = RunIngest.Outputs.builder()
|
||||
when (output) {
|
||||
is Map<*, *> ->
|
||||
output.forEach { (key, value) ->
|
||||
outputsBuilder.putAdditionalProperty(key.toString(), JsonValue.from(value))
|
||||
}
|
||||
else -> outputsBuilder.putAdditionalProperty("output", JsonValue.from(output))
|
||||
}
|
||||
RunIngest.builder()
|
||||
.id(UUID.randomUUID().toString())
|
||||
.referenceExampleId(example.id())
|
||||
.outputs(outputsBuilder.build())
|
||||
.build()
|
||||
} catch (e: Exception) {
|
||||
logger.log(Level.SEVERE, "Error running target function", e)
|
||||
if (params.errorHandling == EvaluateErrorHandling.IGNORE) {
|
||||
null
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun runEvaluator(
|
||||
evaluator: RunEvaluator,
|
||||
run: RunIngest,
|
||||
example: Example,
|
||||
): List<EvaluationResult> {
|
||||
val evaluatorRunId = newEvaluatorRunId()
|
||||
return try {
|
||||
val response = evaluator.evaluateRun(run, example, evaluatorRunId)
|
||||
if (params.uploadResults) {
|
||||
logEvaluationFeedback(client, response, run)
|
||||
}
|
||||
selectEvaluationResults(response)
|
||||
} catch (e: Exception) {
|
||||
logger.log(Level.SEVERE, "Error running evaluator $evaluator on run ${run.id()}", e)
|
||||
val keys = evaluator.feedbackKeys
|
||||
if (keys.isEmpty()) {
|
||||
throw e
|
||||
}
|
||||
val errorResponse =
|
||||
EvaluationResults(
|
||||
keys.map { key ->
|
||||
EvaluationResult(
|
||||
key = key,
|
||||
sourceRunId = evaluatorRunId,
|
||||
comment = e.toString(),
|
||||
extra = mapOf("error" to true),
|
||||
)
|
||||
}
|
||||
)
|
||||
if (params.uploadResults) {
|
||||
logEvaluationFeedback(client, errorResponse, run)
|
||||
}
|
||||
errorResponse.results
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySummaryEvaluators(
|
||||
rows: List<ExperimentResultRow>,
|
||||
sessionId: String?,
|
||||
): EvaluationResults {
|
||||
if (params.summaryEvaluators.isEmpty()) {
|
||||
return EvaluationResults(emptyList())
|
||||
}
|
||||
|
||||
val runs = rows.map { it.run }
|
||||
val examples = rows.map { it.example }
|
||||
val aggregateResults = mutableListOf<EvaluationResult>()
|
||||
|
||||
for (evaluator in params.summaryEvaluators) {
|
||||
try {
|
||||
val response = evaluator.evaluate(runs, examples)
|
||||
aggregateResults.addAll(selectEvaluationResults(response))
|
||||
if (params.uploadResults && sessionId != null) {
|
||||
logSummaryEvaluationFeedback(client, response, sessionId)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.log(Level.SEVERE, "Error running summary evaluator $evaluator", e)
|
||||
}
|
||||
}
|
||||
|
||||
return EvaluationResults(aggregateResults)
|
||||
}
|
||||
|
||||
private fun updateExperimentMetadata(sessionId: String, examples: List<Example>) {
|
||||
try {
|
||||
val projectMetadata = buildMap {
|
||||
putAll(params.metadata)
|
||||
put("dataset_version", datasetVersion(examples))
|
||||
}
|
||||
val extraBuilder = SessionUpdateParams.Extra.builder()
|
||||
projectMetadata.forEach { (key, value) ->
|
||||
extraBuilder.putAdditionalProperty(key, JsonValue.from(value))
|
||||
}
|
||||
client
|
||||
.sessions()
|
||||
.update(
|
||||
sessionId,
|
||||
SessionUpdateParams.builder().extra(extraBuilder.build()).build(),
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logger.log(Level.WARNING, "Failed to update experiment metadata for $sessionId", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun datasetVersion(examples: List<Example>): String? {
|
||||
val modifiedAt =
|
||||
examples.mapNotNull { example ->
|
||||
example.modifiedAt().orElseGet { example.createdAt().orElse(null) }
|
||||
}
|
||||
val maxModifiedAt = modifiedAt.maxOrNull() ?: return null
|
||||
return maxModifiedAt.toString()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAX_NAME_COLLISION_RETRIES = 10
|
||||
}
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.client.LangsmithClient
|
||||
import com.langchain.smith.core.JsonValue
|
||||
import com.langchain.smith.models.datasets.DatasetListParams
|
||||
import com.langchain.smith.models.examples.Example
|
||||
import com.langchain.smith.models.examples.ExampleListParams
|
||||
import com.langchain.smith.models.feedback.FeedbackCreateSchema
|
||||
import com.langchain.smith.models.feedback.ModelFeedbackSource
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
import com.langchain.smith.models.runs.RunQueryParams
|
||||
import com.langchain.smith.models.runs.RunSchema
|
||||
import com.langchain.smith.models.sessions.TracerSession
|
||||
import java.util.UUID
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
internal fun expandExamples(examples: List<Example>, numRepetitions: Int): List<Example> =
|
||||
if (numRepetitions <= 1) {
|
||||
examples
|
||||
} else {
|
||||
List(numRepetitions) { examples }.flatten()
|
||||
}
|
||||
|
||||
internal fun resolveDatasetId(client: LangsmithClient, datasetIdentifier: String): String {
|
||||
if (isUuid(datasetIdentifier)) {
|
||||
return datasetIdentifier
|
||||
}
|
||||
val matches =
|
||||
client.datasets().list(DatasetListParams.builder().name(datasetIdentifier).build()).items()
|
||||
require(matches.isNotEmpty()) {
|
||||
"No dataset found with name '$datasetIdentifier'. Create the dataset first or pass its UUID."
|
||||
}
|
||||
require(matches.size == 1) {
|
||||
"Multiple datasets named '$datasetIdentifier'; pass the dataset UUID instead."
|
||||
}
|
||||
return matches.single().id()
|
||||
}
|
||||
|
||||
internal fun listExamples(client: LangsmithClient, datasetIdentifier: String): List<Example> {
|
||||
val datasetId = resolveDatasetId(client, datasetIdentifier)
|
||||
return listDatasetExamples(client, datasetId)
|
||||
}
|
||||
|
||||
internal fun listDatasetExamples(
|
||||
client: LangsmithClient,
|
||||
datasetId: String,
|
||||
asOf: String? = null,
|
||||
): List<Example> {
|
||||
val builder = ExampleListParams.builder().dataset(datasetId)
|
||||
asOf?.let { builder.asOf(it) }
|
||||
return client.examples().list(builder.build()).autoPager().toList()
|
||||
}
|
||||
|
||||
internal fun experimentDatasetVersion(session: TracerSession): String? {
|
||||
val extra = session.extra().orElse(null) ?: return null
|
||||
val props = extra._additionalProperties()
|
||||
props["dataset_version"]?.convert(String::class.java)?.let {
|
||||
return it
|
||||
}
|
||||
return props["metadata"]?.accept(
|
||||
object : JsonValue.Visitor<String?> {
|
||||
override fun visitObject(values: Map<String, JsonValue>): String? =
|
||||
values["dataset_version"]?.convert(String::class.java)
|
||||
|
||||
override fun visitDefault(): String? = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
internal fun loadExperiment(client: LangsmithClient, experiment: String): TracerSession =
|
||||
client.sessions().retrieve(experiment)
|
||||
|
||||
internal fun loadRunsForExperiment(
|
||||
client: LangsmithClient,
|
||||
sessionId: String,
|
||||
loadNested: Boolean,
|
||||
): List<RunIngest> {
|
||||
val deadlineMs = System.currentTimeMillis() + RUN_QUERY_TIMEOUT_MS
|
||||
var delayMs = RUN_QUERY_INITIAL_DELAY_MS
|
||||
while (true) {
|
||||
client.runs().flush()
|
||||
val runs = queryRunsForExperiment(client, sessionId, loadNested)
|
||||
if (runs.isNotEmpty() || System.currentTimeMillis() >= deadlineMs) {
|
||||
return runs
|
||||
}
|
||||
Thread.sleep(delayMs)
|
||||
delayMs = minOf(delayMs * 2, RUN_QUERY_MAX_DELAY_MS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun queryRunsForExperiment(
|
||||
client: LangsmithClient,
|
||||
sessionId: String,
|
||||
loadNested: Boolean,
|
||||
): List<RunIngest> {
|
||||
val paramsBuilder = RunQueryParams.builder().session(listOf(sessionId)).limit(100L)
|
||||
if (!loadNested) {
|
||||
paramsBuilder.isRoot(true)
|
||||
}
|
||||
return client.runs().query(paramsBuilder.build()).autoPager().toList().map {
|
||||
it.toEvaluateRun()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun RunSchema.toEvaluateRun(): RunIngest {
|
||||
val builder = RunIngest.builder().id(id()).sessionId(sessionId()).traceId(traceId())
|
||||
referenceExampleId().getOrNull()?.let { builder.referenceExampleId(it) }
|
||||
startTime().getOrNull()?.let { builder.startTime(it.toString()) }
|
||||
outputs().getOrNull()?.let { schemaOutputs ->
|
||||
val outputsBuilder = RunIngest.Outputs.builder()
|
||||
schemaOutputs._additionalProperties().forEach { (key, value) ->
|
||||
outputsBuilder.putAdditionalProperty(key, value)
|
||||
}
|
||||
builder.outputs(outputsBuilder.build())
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
internal fun loadExamplesByIds(
|
||||
client: LangsmithClient,
|
||||
datasetId: String,
|
||||
exampleIds: List<String>,
|
||||
asOf: String? = null,
|
||||
): Map<String, Example> {
|
||||
if (exampleIds.isEmpty()) {
|
||||
return emptyMap()
|
||||
}
|
||||
val wanted = exampleIds.toSet()
|
||||
return listDatasetExamples(client, datasetId, asOf)
|
||||
.filter { it.id() in wanted }
|
||||
.associateBy { it.id() }
|
||||
}
|
||||
|
||||
internal fun buildExperimentUrl(
|
||||
session: TracerSession?,
|
||||
@Suppress("UNUSED_PARAMETER") datasetId: String,
|
||||
): String? = null
|
||||
|
||||
internal fun buildComparativeUrl(
|
||||
experiments: List<TracerSession>,
|
||||
comparativeExperimentId: String,
|
||||
datasetId: String,
|
||||
): String? = null
|
||||
|
||||
internal fun isUuid(value: String): Boolean =
|
||||
try {
|
||||
UUID.fromString(value)
|
||||
true
|
||||
} catch (_: IllegalArgumentException) {
|
||||
false
|
||||
}
|
||||
|
||||
internal fun generateExperimentName(prefix: String): String =
|
||||
"$prefix-${UUID.randomUUID().toString().take(8)}"
|
||||
|
||||
internal fun exampleVersion(example: Example): String {
|
||||
val timestamp = example.modifiedAt().orElseGet { example.createdAt().orElse(null) }
|
||||
return timestamp?.toString() ?: java.time.OffsetDateTime.now().toString()
|
||||
}
|
||||
|
||||
internal fun logSummaryEvaluationFeedback(
|
||||
client: LangsmithClient,
|
||||
evaluatorResponse: EvaluationOutput,
|
||||
projectId: String,
|
||||
) {
|
||||
for (result in selectEvaluationResults(evaluatorResponse)) {
|
||||
client.feedback().create(buildProjectFeedbackCreateSchema(result, projectId))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun logComparativeEvaluationFeedback(
|
||||
client: LangsmithClient,
|
||||
result: ComparisonEvaluationResult,
|
||||
runs: List<RunIngest>,
|
||||
comparativeExperimentId: String,
|
||||
feedbackGroupId: String,
|
||||
) {
|
||||
val comments =
|
||||
if (result.comment != null) {
|
||||
result.scores.keys.associateWith { result.comment }
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
val runsById = runs.associateBy { it.id().getOrNull().orEmpty() }
|
||||
for ((runId, score) in result.scores) {
|
||||
val run = runsById[runId]
|
||||
val builder =
|
||||
FeedbackCreateSchema.builder()
|
||||
.key(result.key)
|
||||
.runId(runId)
|
||||
.score(score.toDouble())
|
||||
.comparativeExperimentId(comparativeExperimentId)
|
||||
.feedbackGroupId(feedbackGroupId)
|
||||
.feedbackSource(
|
||||
ModelFeedbackSource.builder().type(ModelFeedbackSource.Type.MODEL).build()
|
||||
)
|
||||
comments[runId]?.let { builder.comment(it) }
|
||||
run?.sessionId()?.getOrNull()?.let { builder.sessionId(it) }
|
||||
run?.startTime()?.getOrNull()?.let { startTime ->
|
||||
runStartTime(startTime)?.let { builder.startTime(it) }
|
||||
}
|
||||
client.feedback().create(builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildProjectFeedbackCreateSchema(
|
||||
result: EvaluationResult,
|
||||
projectId: String,
|
||||
): FeedbackCreateSchema {
|
||||
val builder = FeedbackCreateSchema.builder().key(result.key).sessionId(projectId)
|
||||
|
||||
builder.feedbackSource(
|
||||
ModelFeedbackSource.builder().type(ModelFeedbackSource.Type.MODEL).build()
|
||||
)
|
||||
|
||||
val score = result.score
|
||||
if (score != null) {
|
||||
builder.score(score.toDouble())
|
||||
}
|
||||
|
||||
applyFeedbackValue(builder, result.value)
|
||||
|
||||
val comment = result.comment
|
||||
if (comment != null) {
|
||||
builder.comment(comment)
|
||||
}
|
||||
|
||||
val feedbackConfig = result.feedbackConfig
|
||||
if (feedbackConfig != null) {
|
||||
builder.feedbackConfig(feedbackConfig)
|
||||
}
|
||||
|
||||
val correction = result.correction
|
||||
if (correction != null) {
|
||||
builder.correction(
|
||||
FeedbackCreateSchema.Correction.ofUnionMember0(
|
||||
FeedbackCreateSchema.Correction.UnionMember0.builder()
|
||||
.putAllAdditionalProperties(
|
||||
correction.mapValues { (_, value) -> JsonValue.from(value) }
|
||||
)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val error = result.extra?.get("error") as? Boolean
|
||||
if (error == true) {
|
||||
builder.error(true)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private const val RUN_QUERY_TIMEOUT_MS = 30_000L
|
||||
private const val RUN_QUERY_INITIAL_DELAY_MS = 500L
|
||||
private const val RUN_QUERY_MAX_DELAY_MS = 2_000L
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import java.util.function.Function
|
||||
|
||||
/**
|
||||
* Runnable-style evaluation target. Mirrors Python's `Runnable.invoke` / chain target support
|
||||
* without requiring a LangChain dependency.
|
||||
*/
|
||||
fun interface EvaluateTarget {
|
||||
fun invoke(inputs: Map<String, Any?>): Any?
|
||||
}
|
||||
|
||||
/** Wraps a Kotlin lambda as an [EvaluateTarget]. */
|
||||
fun evaluateTarget(func: (Map<String, Any?>) -> Any?): EvaluateTarget = EvaluateTarget(func::invoke)
|
||||
|
||||
/** Wraps a Java [Function] as an [EvaluateTarget]. */
|
||||
fun evaluateTarget(func: Function<Map<String, Any?>, Any?>): EvaluateTarget =
|
||||
EvaluateTarget { inputs ->
|
||||
func.apply(inputs)
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.models.feedback.FeedbackCreateSchema
|
||||
|
||||
/**
|
||||
* Normalized output from [RunEvaluator.evaluateRun], matching Python's `Union[EvaluationResult,
|
||||
* EvaluationResults]`.
|
||||
*/
|
||||
sealed interface EvaluationOutput
|
||||
|
||||
/** A single evaluation feedback metric produced by a [RunEvaluator]. */
|
||||
data class EvaluationResult(
|
||||
val key: String,
|
||||
val score: Number? = null,
|
||||
val value: Any? = null,
|
||||
val metadata: Map<String, Any>? = null,
|
||||
val comment: String? = null,
|
||||
val correction: Map<String, Any>? = null,
|
||||
val evaluatorInfo: Map<String, Any> = emptyMap(),
|
||||
val feedbackConfig: FeedbackCreateSchema.FeedbackConfig? = null,
|
||||
val sourceRunId: String? = null,
|
||||
val targetRunId: String? = null,
|
||||
val extra: Map<String, Any>? = null,
|
||||
) : EvaluationOutput
|
||||
|
||||
/** Batch evaluation results returned when an evaluator emits multiple metrics at once. */
|
||||
data class EvaluationResults(val results: List<EvaluationResult>) : EvaluationOutput
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference
|
||||
import com.langchain.smith.core.JsonValue
|
||||
import com.langchain.smith.models.examples.Example
|
||||
import com.langchain.smith.models.feedback.FeedbackCreateSchema
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
import java.util.UUID
|
||||
|
||||
private val stringAnyMapType = object : TypeReference<Map<String, Any?>>() {}
|
||||
|
||||
private val feedbackConfigType = object : TypeReference<FeedbackCreateSchema.FeedbackConfig>() {}
|
||||
|
||||
/** Gets the app's answer from a finished run as a simple map. */
|
||||
internal fun runOutputsMap(run: RunIngest): Map<String, Any?> =
|
||||
run.outputs().map { it._additionalProperties().toPlainMap() }.orElse(emptyMap())
|
||||
|
||||
/** Gets the inputs sent to the app for one dataset row as a simple map. */
|
||||
internal fun exampleInputs(example: Example?): Map<String, Any?> =
|
||||
example?.inputs()?.let { it._additionalProperties().toPlainMap() } ?: emptyMap()
|
||||
|
||||
/** Gets the expected outputs stored on a dataset row as a simple map. */
|
||||
internal fun exampleOutputs(example: Example?): Map<String, Any?> =
|
||||
example?.outputs()?.map { it._additionalProperties().toPlainMap() }?.orElse(emptyMap())
|
||||
?: emptyMap()
|
||||
|
||||
/** Unpacks stored fields into plain maps that evaluators can read and compare. */
|
||||
private fun Map<String, JsonValue>.toPlainMap(): Map<String, Any?> =
|
||||
JsonValue.from(this).convert(stringAnyMapType) ?: emptyMap()
|
||||
|
||||
/** Accepts whatever an evaluator returned and shapes it into a standard map first. */
|
||||
internal fun formatEvaluatorResult(result: EvaluatorOutput): Map<String, Any?> =
|
||||
when (result) {
|
||||
is EvaluationResult ->
|
||||
mapOf("key" to result.key, "score" to result.score, "value" to result.value)
|
||||
is EvaluationResults -> mapOf("results" to result.results)
|
||||
is Boolean -> mapOf("score" to if (result) 1 else 0)
|
||||
is Number -> mapOf("score" to result)
|
||||
is String -> mapOf("value" to result)
|
||||
is List<*> -> formatResultList(result)
|
||||
is Map<*, *> -> result.asStringKeyMap()
|
||||
null -> throw IllegalArgumentException("Evaluator returned null")
|
||||
else ->
|
||||
throw IllegalArgumentException(
|
||||
"Expected EvaluationResult, EvaluationResults, Map, String, Boolean, Number, or List. Got $result"
|
||||
)
|
||||
}
|
||||
|
||||
private fun formatResultList(result: List<*>): Map<String, Any?> {
|
||||
if (result.isEmpty()) {
|
||||
throw IllegalArgumentException(
|
||||
"Expected a non-empty list of evaluation results, got empty list"
|
||||
)
|
||||
}
|
||||
if (result.any { it !is Map<*, *> && it !is EvaluationResult }) {
|
||||
throw IllegalArgumentException(
|
||||
"Expected a list of maps or EvaluationResult objects, got $result"
|
||||
)
|
||||
}
|
||||
return mapOf("results" to result)
|
||||
}
|
||||
|
||||
// Silencing a compile-time warning because we already handle the wrong type case when the code
|
||||
// runs.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun Map<*, *>.asStringKeyMap(): Map<String, Any?> = this as Map<String, Any?>
|
||||
|
||||
private fun Any.requireStringKeyMap(): Map<String, Any?> =
|
||||
(this as? Map<*, *>)?.asStringKeyMap()
|
||||
?: throw IllegalArgumentException(
|
||||
"Expected EvaluationResult or Map with metric fields, got $this"
|
||||
)
|
||||
|
||||
private fun parseFeedbackConfig(value: Any?): FeedbackCreateSchema.FeedbackConfig? =
|
||||
when (value) {
|
||||
null -> null
|
||||
is FeedbackCreateSchema.FeedbackConfig -> value
|
||||
is Map<*, *> ->
|
||||
JsonValue.from(value).convert(feedbackConfigType)
|
||||
?: throw IllegalArgumentException("Invalid feedbackConfig: $value")
|
||||
else ->
|
||||
throw IllegalArgumentException(
|
||||
"Expected feedbackConfig as FeedbackConfig or Map, got $value"
|
||||
)
|
||||
}
|
||||
|
||||
/** Builds one scored feedback item from an evaluator's return value. */
|
||||
internal fun coerceEvaluationResult(
|
||||
result: Any,
|
||||
sourceRunId: String,
|
||||
defaultKey: String,
|
||||
allowMissingKey: Boolean = false,
|
||||
): EvaluationResult {
|
||||
if (result is EvaluationResult) {
|
||||
return result.copy(sourceRunId = result.sourceRunId ?: sourceRunId)
|
||||
}
|
||||
val map = result.requireStringKeyMap()
|
||||
if (map.isEmpty()) {
|
||||
throw IllegalArgumentException("Expected a non-empty evaluation result map, got $map")
|
||||
}
|
||||
val key =
|
||||
map["key"] as? String
|
||||
?: defaultKey.takeIf { allowMissingKey }
|
||||
?: throw IllegalArgumentException("Evaluation result map must include a 'key' field")
|
||||
if ("score" !in map && "value" !in map && "comment" !in map) {
|
||||
throw IllegalArgumentException(
|
||||
"Evaluation result map must include at least one of score, value, or comment"
|
||||
)
|
||||
}
|
||||
return EvaluationResult(
|
||||
key = key,
|
||||
score = map["score"] as? Number,
|
||||
value = map["value"],
|
||||
metadata = map["metadata"] as? Map<String, Any>,
|
||||
comment = map["comment"] as? String,
|
||||
correction = map["correction"] as? Map<String, Any>,
|
||||
evaluatorInfo = map["evaluatorInfo"] as? Map<String, Any> ?: emptyMap(),
|
||||
feedbackConfig = parseFeedbackConfig(map["feedbackConfig"] ?: map["feedback_config"]),
|
||||
sourceRunId = sourceRunId,
|
||||
targetRunId = map["targetRunId"] as? String,
|
||||
extra = map["extra"] as? Map<String, Any>,
|
||||
)
|
||||
}
|
||||
|
||||
/** Normalizes any evaluator return into one or more scored feedback items. */
|
||||
internal fun coerceEvaluatorOutput(
|
||||
output: EvaluatorOutput,
|
||||
sourceRunId: String,
|
||||
defaultKey: String,
|
||||
): EvaluationOutput =
|
||||
when (output) {
|
||||
is EvaluationResult -> output.copy(sourceRunId = output.sourceRunId ?: sourceRunId)
|
||||
is EvaluationResults ->
|
||||
EvaluationResults(
|
||||
output.results.map { coerceEvaluationResult(it, sourceRunId, defaultKey) }
|
||||
)
|
||||
else -> {
|
||||
val map = formatEvaluatorResult(output)
|
||||
if ("results" in map) {
|
||||
@Suppress("UNCHECKED_CAST") val results = map["results"] as List<Any>
|
||||
EvaluationResults(
|
||||
results.map {
|
||||
coerceEvaluationResult(it, sourceRunId, defaultKey, allowMissingKey = false)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
coerceEvaluationResult(map, sourceRunId, defaultKey, allowMissingKey = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a unique id for this evaluator scoring step. */
|
||||
internal fun newEvaluatorRunId(): String = UUID.randomUUID().toString()
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.models.examples.Example
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
|
||||
/** One dataset row evaluated in an experiment. */
|
||||
data class ExperimentResultRow(
|
||||
val run: RunIngest,
|
||||
val example: Example,
|
||||
val evaluationResults: EvaluationResults,
|
||||
) {
|
||||
override fun toString(): String =
|
||||
"ExperimentResultRow{exampleId=${example.id()}, runId=${run.id().orElse(null)}, " +
|
||||
"results=${evaluationResults.results.size}}"
|
||||
}
|
||||
|
||||
/** Results returned by [evaluate]. */
|
||||
class ExperimentResults
|
||||
internal constructor(
|
||||
val experimentName: String,
|
||||
/** LangSmith experiment session id. */
|
||||
val experimentId: String?,
|
||||
val datasetId: String,
|
||||
val rows: List<ExperimentResultRow>,
|
||||
val summaryResults: EvaluationResults = EvaluationResults(emptyList()),
|
||||
val url: String? = null,
|
||||
) {
|
||||
override fun toString(): String =
|
||||
"ExperimentResults{experimentName=$experimentName, experimentId=$experimentId, " +
|
||||
"datasetId=$datasetId, rows=${rows.size}, summaryResults=${summaryResults.results.size}}"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.models.examples.Example
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
|
||||
/** Evaluates a single prediction [run] against an optional dataset [example]. */
|
||||
interface RunEvaluator {
|
||||
fun evaluateRun(run: RunIngest, example: Example?, evaluatorRunId: String?): EvaluationOutput
|
||||
|
||||
fun evaluateRun(run: RunIngest, example: Example?): EvaluationOutput =
|
||||
evaluateRun(run, example, null)
|
||||
|
||||
fun evaluateRun(run: RunIngest): EvaluationOutput = evaluateRun(run, null, null)
|
||||
|
||||
/** Feedback keys this evaluator emits, when statically known. */
|
||||
val feedbackKeys: List<String>
|
||||
get() = emptyList()
|
||||
}
|
||||
|
||||
/** Raw return values accepted from custom evaluator lambdas before normalization. */
|
||||
typealias EvaluatorOutput = Any
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.langchain.smith.evaluation
|
||||
|
||||
import com.langchain.smith.models.examples.Example
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
|
||||
/** Evaluates aggregate metrics across all runs in an experiment. */
|
||||
fun interface SummaryEvaluator {
|
||||
fun evaluate(runs: List<RunIngest>, examples: List<Example>): EvaluationOutput
|
||||
}
|
||||
|
||||
/** Creates a [SummaryEvaluator] from a function over all runs and examples. */
|
||||
fun summaryEvaluator(func: (List<RunIngest>, List<Example>) -> EvaluatorOutput): SummaryEvaluator =
|
||||
SummaryEvaluator { runs, examples ->
|
||||
coerceSummaryOutput(func(runs, examples), newEvaluatorRunId())
|
||||
}
|
||||
|
||||
internal fun coerceSummaryOutput(output: EvaluatorOutput, sourceRunId: String): EvaluationOutput =
|
||||
coerceEvaluatorOutput(output, sourceRunId, defaultKey = "summary")
|
||||
+91
-1
@@ -85,6 +85,12 @@ private constructor(
|
||||
*/
|
||||
fun reservationMinutes(): Optional<Long> = body.reservationMinutes()
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun reviewerAccessMode(): Optional<String> = body.reviewerAccessMode()
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
@@ -175,6 +181,14 @@ private constructor(
|
||||
*/
|
||||
fun _reservationMinutes(): JsonField<Long> = body._reservationMinutes()
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [reviewerAccessMode].
|
||||
*
|
||||
* Unlike [reviewerAccessMode], this method doesn't throw if the JSON field has an unexpected
|
||||
* type.
|
||||
*/
|
||||
fun _reviewerAccessMode(): JsonField<String> = body._reviewerAccessMode()
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [rubricInstructions].
|
||||
*
|
||||
@@ -422,6 +436,21 @@ private constructor(
|
||||
body.reservationMinutes(reservationMinutes)
|
||||
}
|
||||
|
||||
fun reviewerAccessMode(reviewerAccessMode: String) = apply {
|
||||
body.reviewerAccessMode(reviewerAccessMode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets [Builder.reviewerAccessMode] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.reviewerAccessMode] with a well-typed [String] value
|
||||
* instead. This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun reviewerAccessMode(reviewerAccessMode: JsonField<String>) = apply {
|
||||
body.reviewerAccessMode(reviewerAccessMode)
|
||||
}
|
||||
|
||||
fun rubricInstructions(rubricInstructions: String?) = apply {
|
||||
body.rubricInstructions(rubricInstructions)
|
||||
}
|
||||
@@ -659,6 +688,7 @@ private constructor(
|
||||
private val metadata: JsonField<Metadata>,
|
||||
private val numReviewersPerItem: JsonField<Long>,
|
||||
private val reservationMinutes: JsonField<Long>,
|
||||
private val reviewerAccessMode: JsonField<String>,
|
||||
private val rubricInstructions: JsonField<String>,
|
||||
private val rubricItems: JsonField<List<AnnotationQueueRubricItemSchema>>,
|
||||
private val sessionIds: JsonField<List<String>>,
|
||||
@@ -691,6 +721,9 @@ private constructor(
|
||||
@JsonProperty("reservation_minutes")
|
||||
@ExcludeMissing
|
||||
reservationMinutes: JsonField<Long> = JsonMissing.of(),
|
||||
@JsonProperty("reviewer_access_mode")
|
||||
@ExcludeMissing
|
||||
reviewerAccessMode: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("rubric_instructions")
|
||||
@ExcludeMissing
|
||||
rubricInstructions: JsonField<String> = JsonMissing.of(),
|
||||
@@ -713,6 +746,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
rubricInstructions,
|
||||
rubricItems,
|
||||
sessionIds,
|
||||
@@ -777,6 +811,13 @@ private constructor(
|
||||
fun reservationMinutes(): Optional<Long> =
|
||||
reservationMinutes.getOptional("reservation_minutes")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
*/
|
||||
fun reviewerAccessMode(): Optional<String> =
|
||||
reviewerAccessMode.getOptional("reviewer_access_mode")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
@@ -882,6 +923,16 @@ private constructor(
|
||||
@ExcludeMissing
|
||||
fun _reservationMinutes(): JsonField<Long> = reservationMinutes
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [reviewerAccessMode].
|
||||
*
|
||||
* Unlike [reviewerAccessMode], this method doesn't throw if the JSON field has an
|
||||
* unexpected type.
|
||||
*/
|
||||
@JsonProperty("reviewer_access_mode")
|
||||
@ExcludeMissing
|
||||
fun _reviewerAccessMode(): JsonField<String> = reviewerAccessMode
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [rubricInstructions].
|
||||
*
|
||||
@@ -956,6 +1007,7 @@ private constructor(
|
||||
private var metadata: JsonField<Metadata> = JsonMissing.of()
|
||||
private var numReviewersPerItem: JsonField<Long> = JsonMissing.of()
|
||||
private var reservationMinutes: JsonField<Long> = JsonMissing.of()
|
||||
private var reviewerAccessMode: JsonField<String> = JsonMissing.of()
|
||||
private var rubricInstructions: JsonField<String> = JsonMissing.of()
|
||||
private var rubricItems: JsonField<MutableList<AnnotationQueueRubricItemSchema>>? = null
|
||||
private var sessionIds: JsonField<MutableList<String>>? = null
|
||||
@@ -973,6 +1025,7 @@ private constructor(
|
||||
metadata = body.metadata
|
||||
numReviewersPerItem = body.numReviewersPerItem
|
||||
reservationMinutes = body.reservationMinutes
|
||||
reviewerAccessMode = body.reviewerAccessMode
|
||||
rubricInstructions = body.rubricInstructions
|
||||
rubricItems = body.rubricItems.map { it.toMutableList() }
|
||||
sessionIds = body.sessionIds.map { it.toMutableList() }
|
||||
@@ -1150,6 +1203,20 @@ private constructor(
|
||||
this.reservationMinutes = reservationMinutes
|
||||
}
|
||||
|
||||
fun reviewerAccessMode(reviewerAccessMode: String) =
|
||||
reviewerAccessMode(JsonField.of(reviewerAccessMode))
|
||||
|
||||
/**
|
||||
* Sets [Builder.reviewerAccessMode] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.reviewerAccessMode] with a well-typed [String] value
|
||||
* instead. This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun reviewerAccessMode(reviewerAccessMode: JsonField<String>) = apply {
|
||||
this.reviewerAccessMode = reviewerAccessMode
|
||||
}
|
||||
|
||||
fun rubricInstructions(rubricInstructions: String?) =
|
||||
rubricInstructions(JsonField.ofNullable(rubricInstructions))
|
||||
|
||||
@@ -1284,6 +1351,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
rubricInstructions,
|
||||
(rubricItems ?: JsonMissing.of()).map { it.toImmutable() },
|
||||
(sessionIds ?: JsonMissing.of()).map { it.toImmutable() },
|
||||
@@ -1294,6 +1362,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): Body = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -1308,6 +1385,7 @@ private constructor(
|
||||
metadata().ifPresent { it.validate() }
|
||||
numReviewersPerItem()
|
||||
reservationMinutes()
|
||||
reviewerAccessMode()
|
||||
rubricInstructions()
|
||||
rubricItems().ifPresent { it.forEach { it.validate() } }
|
||||
sessionIds()
|
||||
@@ -1340,6 +1418,7 @@ private constructor(
|
||||
(metadata.asKnown().getOrNull()?.validity() ?: 0) +
|
||||
(if (numReviewersPerItem.asKnown().isPresent) 1 else 0) +
|
||||
(if (reservationMinutes.asKnown().isPresent) 1 else 0) +
|
||||
(if (reviewerAccessMode.asKnown().isPresent) 1 else 0) +
|
||||
(if (rubricInstructions.asKnown().isPresent) 1 else 0) +
|
||||
(rubricItems.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) +
|
||||
(sessionIds.asKnown().getOrNull()?.size ?: 0) +
|
||||
@@ -1360,6 +1439,7 @@ private constructor(
|
||||
metadata == other.metadata &&
|
||||
numReviewersPerItem == other.numReviewersPerItem &&
|
||||
reservationMinutes == other.reservationMinutes &&
|
||||
reviewerAccessMode == other.reviewerAccessMode &&
|
||||
rubricInstructions == other.rubricInstructions &&
|
||||
rubricItems == other.rubricItems &&
|
||||
sessionIds == other.sessionIds &&
|
||||
@@ -1378,6 +1458,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
rubricInstructions,
|
||||
rubricItems,
|
||||
sessionIds,
|
||||
@@ -1389,7 +1470,7 @@ private constructor(
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"Body{name=$name, id=$id, createdAt=$createdAt, defaultDataset=$defaultDataset, description=$description, enableReservations=$enableReservations, metadata=$metadata, numReviewersPerItem=$numReviewersPerItem, reservationMinutes=$reservationMinutes, rubricInstructions=$rubricInstructions, rubricItems=$rubricItems, sessionIds=$sessionIds, updatedAt=$updatedAt, additionalProperties=$additionalProperties}"
|
||||
"Body{name=$name, id=$id, createdAt=$createdAt, defaultDataset=$defaultDataset, description=$description, enableReservations=$enableReservations, metadata=$metadata, numReviewersPerItem=$numReviewersPerItem, reservationMinutes=$reservationMinutes, reviewerAccessMode=$reviewerAccessMode, rubricInstructions=$rubricInstructions, rubricItems=$rubricItems, sessionIds=$sessionIds, updatedAt=$updatedAt, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
class Metadata
|
||||
@@ -1450,6 +1531,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): Metadata = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+9
@@ -433,6 +433,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): Body = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+8
@@ -81,6 +81,14 @@ private constructor(private val additionalProperties: MutableMap<String, JsonVal
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): AnnotationQueueCreateRunStatusResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+8
@@ -78,6 +78,14 @@ private constructor(private val additionalProperties: MutableMap<String, JsonVal
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): AnnotationQueueDeleteResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+91
-4
@@ -37,6 +37,12 @@ private constructor(
|
||||
*/
|
||||
fun endTime(): Optional<OffsetDateTime> = body.endTime()
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun includeAnnotatorDetail(): Optional<Boolean> = body.includeAnnotatorDetail()
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
@@ -50,6 +56,14 @@ private constructor(
|
||||
*/
|
||||
fun _endTime(): JsonField<OffsetDateTime> = body._endTime()
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [includeAnnotatorDetail].
|
||||
*
|
||||
* Unlike [includeAnnotatorDetail], this method doesn't throw if the JSON field has an
|
||||
* unexpected type.
|
||||
*/
|
||||
fun _includeAnnotatorDetail(): JsonField<Boolean> = body._includeAnnotatorDetail()
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [startTime].
|
||||
*
|
||||
@@ -104,6 +118,7 @@ private constructor(
|
||||
* This is generally only useful if you are already constructing the body separately.
|
||||
* Otherwise, it's more convenient to use the top-level setters instead:
|
||||
* - [endTime]
|
||||
* - [includeAnnotatorDetail]
|
||||
* - [startTime]
|
||||
*/
|
||||
fun body(body: Body) = apply { this.body = body.toBuilder() }
|
||||
@@ -122,6 +137,21 @@ private constructor(
|
||||
*/
|
||||
fun endTime(endTime: JsonField<OffsetDateTime>) = apply { body.endTime(endTime) }
|
||||
|
||||
fun includeAnnotatorDetail(includeAnnotatorDetail: Boolean) = apply {
|
||||
body.includeAnnotatorDetail(includeAnnotatorDetail)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets [Builder.includeAnnotatorDetail] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.includeAnnotatorDetail] with a well-typed [Boolean]
|
||||
* value instead. This method is primarily for setting the field to an undocumented or not
|
||||
* yet supported value.
|
||||
*/
|
||||
fun includeAnnotatorDetail(includeAnnotatorDetail: JsonField<Boolean>) = apply {
|
||||
body.includeAnnotatorDetail(includeAnnotatorDetail)
|
||||
}
|
||||
|
||||
fun startTime(startTime: OffsetDateTime?) = apply { body.startTime(startTime) }
|
||||
|
||||
/** Alias for calling [Builder.startTime] with `startTime.orElse(null)`. */
|
||||
@@ -284,6 +314,7 @@ private constructor(
|
||||
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
|
||||
private constructor(
|
||||
private val endTime: JsonField<OffsetDateTime>,
|
||||
private val includeAnnotatorDetail: JsonField<Boolean>,
|
||||
private val startTime: JsonField<OffsetDateTime>,
|
||||
private val additionalProperties: MutableMap<String, JsonValue>,
|
||||
) {
|
||||
@@ -293,10 +324,13 @@ private constructor(
|
||||
@JsonProperty("end_time")
|
||||
@ExcludeMissing
|
||||
endTime: JsonField<OffsetDateTime> = JsonMissing.of(),
|
||||
@JsonProperty("include_annotator_detail")
|
||||
@ExcludeMissing
|
||||
includeAnnotatorDetail: JsonField<Boolean> = JsonMissing.of(),
|
||||
@JsonProperty("start_time")
|
||||
@ExcludeMissing
|
||||
startTime: JsonField<OffsetDateTime> = JsonMissing.of(),
|
||||
) : this(endTime, startTime, mutableMapOf())
|
||||
) : this(endTime, includeAnnotatorDetail, startTime, mutableMapOf())
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
@@ -304,6 +338,13 @@ private constructor(
|
||||
*/
|
||||
fun endTime(): Optional<OffsetDateTime> = endTime.getOptional("end_time")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
*/
|
||||
fun includeAnnotatorDetail(): Optional<Boolean> =
|
||||
includeAnnotatorDetail.getOptional("include_annotator_detail")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
@@ -319,6 +360,16 @@ private constructor(
|
||||
@ExcludeMissing
|
||||
fun _endTime(): JsonField<OffsetDateTime> = endTime
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [includeAnnotatorDetail].
|
||||
*
|
||||
* Unlike [includeAnnotatorDetail], this method doesn't throw if the JSON field has an
|
||||
* unexpected type.
|
||||
*/
|
||||
@JsonProperty("include_annotator_detail")
|
||||
@ExcludeMissing
|
||||
fun _includeAnnotatorDetail(): JsonField<Boolean> = includeAnnotatorDetail
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [startTime].
|
||||
*
|
||||
@@ -350,12 +401,14 @@ private constructor(
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var endTime: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
private var includeAnnotatorDetail: JsonField<Boolean> = JsonMissing.of()
|
||||
private var startTime: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun from(body: Body) = apply {
|
||||
endTime = body.endTime
|
||||
includeAnnotatorDetail = body.includeAnnotatorDetail
|
||||
startTime = body.startTime
|
||||
additionalProperties = body.additionalProperties.toMutableMap()
|
||||
}
|
||||
@@ -374,6 +427,20 @@ private constructor(
|
||||
*/
|
||||
fun endTime(endTime: JsonField<OffsetDateTime>) = apply { this.endTime = endTime }
|
||||
|
||||
fun includeAnnotatorDetail(includeAnnotatorDetail: Boolean) =
|
||||
includeAnnotatorDetail(JsonField.of(includeAnnotatorDetail))
|
||||
|
||||
/**
|
||||
* Sets [Builder.includeAnnotatorDetail] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.includeAnnotatorDetail] with a well-typed [Boolean]
|
||||
* value instead. This method is primarily for setting the field to an undocumented or
|
||||
* not yet supported value.
|
||||
*/
|
||||
fun includeAnnotatorDetail(includeAnnotatorDetail: JsonField<Boolean>) = apply {
|
||||
this.includeAnnotatorDetail = includeAnnotatorDetail
|
||||
}
|
||||
|
||||
fun startTime(startTime: OffsetDateTime?) = startTime(JsonField.ofNullable(startTime))
|
||||
|
||||
/** Alias for calling [Builder.startTime] with `startTime.orElse(null)`. */
|
||||
@@ -414,17 +481,33 @@ private constructor(
|
||||
*
|
||||
* Further updates to this [Builder] will not mutate the returned instance.
|
||||
*/
|
||||
fun build(): Body = Body(endTime, startTime, additionalProperties.toMutableMap())
|
||||
fun build(): Body =
|
||||
Body(
|
||||
endTime,
|
||||
includeAnnotatorDetail,
|
||||
startTime,
|
||||
additionalProperties.toMutableMap(),
|
||||
)
|
||||
}
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): Body = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
}
|
||||
|
||||
endTime()
|
||||
includeAnnotatorDetail()
|
||||
startTime()
|
||||
validated = true
|
||||
}
|
||||
@@ -446,6 +529,7 @@ private constructor(
|
||||
@JvmSynthetic
|
||||
internal fun validity(): Int =
|
||||
(if (endTime.asKnown().isPresent) 1 else 0) +
|
||||
(if (includeAnnotatorDetail.asKnown().isPresent) 1 else 0) +
|
||||
(if (startTime.asKnown().isPresent) 1 else 0)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -455,16 +539,19 @@ private constructor(
|
||||
|
||||
return other is Body &&
|
||||
endTime == other.endTime &&
|
||||
includeAnnotatorDetail == other.includeAnnotatorDetail &&
|
||||
startTime == other.startTime &&
|
||||
additionalProperties == other.additionalProperties
|
||||
}
|
||||
|
||||
private val hashCode: Int by lazy { Objects.hash(endTime, startTime, additionalProperties) }
|
||||
private val hashCode: Int by lazy {
|
||||
Objects.hash(endTime, includeAnnotatorDetail, startTime, additionalProperties)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"Body{endTime=$endTime, startTime=$startTime, additionalProperties=$additionalProperties}"
|
||||
"Body{endTime=$endTime, includeAnnotatorDetail=$includeAnnotatorDetail, startTime=$startTime, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
||||
+8
@@ -78,6 +78,14 @@ private constructor(private val additionalProperties: MutableMap<String, JsonVal
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): AnnotationQueueExportResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+85
-4
@@ -19,6 +19,7 @@ import com.langchain.smith.core.toImmutable
|
||||
import com.langchain.smith.errors.LangChainInvalidDataException
|
||||
import java.util.Collections
|
||||
import java.util.Objects
|
||||
import java.util.Optional
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
/** Populate annotation queue with runs from an experiment. */
|
||||
@@ -41,6 +42,12 @@ private constructor(
|
||||
*/
|
||||
fun sessionIds(): List<String> = body.sessionIds()
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun extendTraceRetention(): Optional<Boolean> = body.extendTraceRetention()
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [queueId].
|
||||
*
|
||||
@@ -55,6 +62,14 @@ private constructor(
|
||||
*/
|
||||
fun _sessionIds(): JsonField<List<String>> = body._sessionIds()
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [extendTraceRetention].
|
||||
*
|
||||
* Unlike [extendTraceRetention], this method doesn't throw if the JSON field has an unexpected
|
||||
* type.
|
||||
*/
|
||||
fun _extendTraceRetention(): JsonField<Boolean> = body._extendTraceRetention()
|
||||
|
||||
fun _additionalBodyProperties(): Map<String, JsonValue> = body._additionalProperties()
|
||||
|
||||
/** Additional headers to send with the request. */
|
||||
@@ -101,6 +116,7 @@ private constructor(
|
||||
* Otherwise, it's more convenient to use the top-level setters instead:
|
||||
* - [queueId]
|
||||
* - [sessionIds]
|
||||
* - [extendTraceRetention]
|
||||
*/
|
||||
fun body(body: Body) = apply { this.body = body.toBuilder() }
|
||||
|
||||
@@ -132,6 +148,21 @@ private constructor(
|
||||
*/
|
||||
fun addSessionId(sessionId: String) = apply { body.addSessionId(sessionId) }
|
||||
|
||||
fun extendTraceRetention(extendTraceRetention: Boolean) = apply {
|
||||
body.extendTraceRetention(extendTraceRetention)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets [Builder.extendTraceRetention] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.extendTraceRetention] with a well-typed [Boolean] value
|
||||
* instead. This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun extendTraceRetention(extendTraceRetention: JsonField<Boolean>) = apply {
|
||||
body.extendTraceRetention(extendTraceRetention)
|
||||
}
|
||||
|
||||
fun additionalBodyProperties(additionalBodyProperties: Map<String, JsonValue>) = apply {
|
||||
body.additionalProperties(additionalBodyProperties)
|
||||
}
|
||||
@@ -281,6 +312,7 @@ private constructor(
|
||||
private constructor(
|
||||
private val queueId: JsonField<String>,
|
||||
private val sessionIds: JsonField<List<String>>,
|
||||
private val extendTraceRetention: JsonField<Boolean>,
|
||||
private val additionalProperties: MutableMap<String, JsonValue>,
|
||||
) {
|
||||
|
||||
@@ -290,7 +322,10 @@ private constructor(
|
||||
@JsonProperty("session_ids")
|
||||
@ExcludeMissing
|
||||
sessionIds: JsonField<List<String>> = JsonMissing.of(),
|
||||
) : this(queueId, sessionIds, mutableMapOf())
|
||||
@JsonProperty("extend_trace_retention")
|
||||
@ExcludeMissing
|
||||
extendTraceRetention: JsonField<Boolean> = JsonMissing.of(),
|
||||
) : this(queueId, sessionIds, extendTraceRetention, mutableMapOf())
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type or is
|
||||
@@ -304,6 +339,13 @@ private constructor(
|
||||
*/
|
||||
fun sessionIds(): List<String> = sessionIds.getRequired("session_ids")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
*/
|
||||
fun extendTraceRetention(): Optional<Boolean> =
|
||||
extendTraceRetention.getOptional("extend_trace_retention")
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [queueId].
|
||||
*
|
||||
@@ -320,6 +362,16 @@ private constructor(
|
||||
@ExcludeMissing
|
||||
fun _sessionIds(): JsonField<List<String>> = sessionIds
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [extendTraceRetention].
|
||||
*
|
||||
* Unlike [extendTraceRetention], this method doesn't throw if the JSON field has an
|
||||
* unexpected type.
|
||||
*/
|
||||
@JsonProperty("extend_trace_retention")
|
||||
@ExcludeMissing
|
||||
fun _extendTraceRetention(): JsonField<Boolean> = extendTraceRetention
|
||||
|
||||
@JsonAnySetter
|
||||
private fun putAdditionalProperty(key: String, value: JsonValue) {
|
||||
additionalProperties.put(key, value)
|
||||
@@ -351,12 +403,14 @@ private constructor(
|
||||
|
||||
private var queueId: JsonField<String>? = null
|
||||
private var sessionIds: JsonField<MutableList<String>>? = null
|
||||
private var extendTraceRetention: JsonField<Boolean> = JsonMissing.of()
|
||||
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun from(body: Body) = apply {
|
||||
queueId = body.queueId
|
||||
sessionIds = body.sessionIds.map { it.toMutableList() }
|
||||
extendTraceRetention = body.extendTraceRetention
|
||||
additionalProperties = body.additionalProperties.toMutableMap()
|
||||
}
|
||||
|
||||
@@ -396,6 +450,20 @@ private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun extendTraceRetention(extendTraceRetention: Boolean) =
|
||||
extendTraceRetention(JsonField.of(extendTraceRetention))
|
||||
|
||||
/**
|
||||
* Sets [Builder.extendTraceRetention] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.extendTraceRetention] with a well-typed [Boolean]
|
||||
* value instead. This method is primarily for setting the field to an undocumented or
|
||||
* not yet supported value.
|
||||
*/
|
||||
fun extendTraceRetention(extendTraceRetention: JsonField<Boolean>) = apply {
|
||||
this.extendTraceRetention = extendTraceRetention
|
||||
}
|
||||
|
||||
fun additionalProperties(additionalProperties: Map<String, JsonValue>) = apply {
|
||||
this.additionalProperties.clear()
|
||||
putAllAdditionalProperties(additionalProperties)
|
||||
@@ -432,12 +500,22 @@ private constructor(
|
||||
Body(
|
||||
checkRequired("queueId", queueId),
|
||||
checkRequired("sessionIds", sessionIds).map { it.toImmutable() },
|
||||
extendTraceRetention,
|
||||
additionalProperties.toMutableMap(),
|
||||
)
|
||||
}
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): Body = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -445,6 +523,7 @@ private constructor(
|
||||
|
||||
queueId()
|
||||
sessionIds()
|
||||
extendTraceRetention()
|
||||
validated = true
|
||||
}
|
||||
|
||||
@@ -465,7 +544,8 @@ private constructor(
|
||||
@JvmSynthetic
|
||||
internal fun validity(): Int =
|
||||
(if (queueId.asKnown().isPresent) 1 else 0) +
|
||||
(sessionIds.asKnown().getOrNull()?.size ?: 0)
|
||||
(sessionIds.asKnown().getOrNull()?.size ?: 0) +
|
||||
(if (extendTraceRetention.asKnown().isPresent) 1 else 0)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
@@ -475,17 +555,18 @@ private constructor(
|
||||
return other is Body &&
|
||||
queueId == other.queueId &&
|
||||
sessionIds == other.sessionIds &&
|
||||
extendTraceRetention == other.extendTraceRetention &&
|
||||
additionalProperties == other.additionalProperties
|
||||
}
|
||||
|
||||
private val hashCode: Int by lazy {
|
||||
Objects.hash(queueId, sessionIds, additionalProperties)
|
||||
Objects.hash(queueId, sessionIds, extendTraceRetention, additionalProperties)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"Body{queueId=$queueId, sessionIds=$sessionIds, additionalProperties=$additionalProperties}"
|
||||
"Body{queueId=$queueId, sessionIds=$sessionIds, extendTraceRetention=$extendTraceRetention, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
||||
+8
@@ -80,6 +80,14 @@ private constructor(private val additionalProperties: MutableMap<String, JsonVal
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): AnnotationQueuePopulateResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+68
-3
@@ -17,6 +17,7 @@ import kotlin.jvm.optionals.getOrNull
|
||||
/** Get Annotation Queues */
|
||||
class AnnotationQueueRetrieveAnnotationQueuesParams
|
||||
private constructor(
|
||||
private val assignedToMe: Boolean?,
|
||||
private val datasetId: String?,
|
||||
private val ids: List<String>?,
|
||||
private val limit: Long?,
|
||||
@@ -24,11 +25,15 @@ private constructor(
|
||||
private val nameContains: String?,
|
||||
private val offset: Long?,
|
||||
private val queueType: QueueType?,
|
||||
private val sortBy: String?,
|
||||
private val sortByDesc: Boolean?,
|
||||
private val tagValueId: List<String>?,
|
||||
private val additionalHeaders: Headers,
|
||||
private val additionalQueryParams: QueryParams,
|
||||
) : Params {
|
||||
|
||||
fun assignedToMe(): Optional<Boolean> = Optional.ofNullable(assignedToMe)
|
||||
|
||||
fun datasetId(): Optional<String> = Optional.ofNullable(datasetId)
|
||||
|
||||
fun ids(): Optional<List<String>> = Optional.ofNullable(ids)
|
||||
@@ -43,6 +48,10 @@ private constructor(
|
||||
|
||||
fun queueType(): Optional<QueueType> = Optional.ofNullable(queueType)
|
||||
|
||||
fun sortBy(): Optional<String> = Optional.ofNullable(sortBy)
|
||||
|
||||
fun sortByDesc(): Optional<Boolean> = Optional.ofNullable(sortByDesc)
|
||||
|
||||
fun tagValueId(): Optional<List<String>> = Optional.ofNullable(tagValueId)
|
||||
|
||||
/** Additional headers to send with the request. */
|
||||
@@ -67,6 +76,7 @@ private constructor(
|
||||
/** A builder for [AnnotationQueueRetrieveAnnotationQueuesParams]. */
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var assignedToMe: Boolean? = null
|
||||
private var datasetId: String? = null
|
||||
private var ids: MutableList<String>? = null
|
||||
private var limit: Long? = null
|
||||
@@ -74,6 +84,8 @@ private constructor(
|
||||
private var nameContains: String? = null
|
||||
private var offset: Long? = null
|
||||
private var queueType: QueueType? = null
|
||||
private var sortBy: String? = null
|
||||
private var sortByDesc: Boolean? = null
|
||||
private var tagValueId: MutableList<String>? = null
|
||||
private var additionalHeaders: Headers.Builder = Headers.builder()
|
||||
private var additionalQueryParams: QueryParams.Builder = QueryParams.builder()
|
||||
@@ -83,6 +95,7 @@ private constructor(
|
||||
annotationQueueRetrieveAnnotationQueuesParams:
|
||||
AnnotationQueueRetrieveAnnotationQueuesParams
|
||||
) = apply {
|
||||
assignedToMe = annotationQueueRetrieveAnnotationQueuesParams.assignedToMe
|
||||
datasetId = annotationQueueRetrieveAnnotationQueuesParams.datasetId
|
||||
ids = annotationQueueRetrieveAnnotationQueuesParams.ids?.toMutableList()
|
||||
limit = annotationQueueRetrieveAnnotationQueuesParams.limit
|
||||
@@ -90,6 +103,8 @@ private constructor(
|
||||
nameContains = annotationQueueRetrieveAnnotationQueuesParams.nameContains
|
||||
offset = annotationQueueRetrieveAnnotationQueuesParams.offset
|
||||
queueType = annotationQueueRetrieveAnnotationQueuesParams.queueType
|
||||
sortBy = annotationQueueRetrieveAnnotationQueuesParams.sortBy
|
||||
sortByDesc = annotationQueueRetrieveAnnotationQueuesParams.sortByDesc
|
||||
tagValueId = annotationQueueRetrieveAnnotationQueuesParams.tagValueId?.toMutableList()
|
||||
additionalHeaders =
|
||||
annotationQueueRetrieveAnnotationQueuesParams.additionalHeaders.toBuilder()
|
||||
@@ -97,6 +112,18 @@ private constructor(
|
||||
annotationQueueRetrieveAnnotationQueuesParams.additionalQueryParams.toBuilder()
|
||||
}
|
||||
|
||||
fun assignedToMe(assignedToMe: Boolean?) = apply { this.assignedToMe = assignedToMe }
|
||||
|
||||
/**
|
||||
* Alias for [Builder.assignedToMe].
|
||||
*
|
||||
* This unboxed primitive overload exists for backwards compatibility.
|
||||
*/
|
||||
fun assignedToMe(assignedToMe: Boolean) = assignedToMe(assignedToMe as Boolean?)
|
||||
|
||||
/** Alias for calling [Builder.assignedToMe] with `assignedToMe.orElse(null)`. */
|
||||
fun assignedToMe(assignedToMe: Optional<Boolean>) = assignedToMe(assignedToMe.getOrNull())
|
||||
|
||||
fun datasetId(datasetId: String?) = apply { this.datasetId = datasetId }
|
||||
|
||||
/** Alias for calling [Builder.datasetId] with `datasetId.orElse(null)`. */
|
||||
@@ -153,6 +180,23 @@ private constructor(
|
||||
/** Alias for calling [Builder.queueType] with `queueType.orElse(null)`. */
|
||||
fun queueType(queueType: Optional<QueueType>) = queueType(queueType.getOrNull())
|
||||
|
||||
fun sortBy(sortBy: String?) = apply { this.sortBy = sortBy }
|
||||
|
||||
/** Alias for calling [Builder.sortBy] with `sortBy.orElse(null)`. */
|
||||
fun sortBy(sortBy: Optional<String>) = sortBy(sortBy.getOrNull())
|
||||
|
||||
fun sortByDesc(sortByDesc: Boolean?) = apply { this.sortByDesc = sortByDesc }
|
||||
|
||||
/**
|
||||
* Alias for [Builder.sortByDesc].
|
||||
*
|
||||
* This unboxed primitive overload exists for backwards compatibility.
|
||||
*/
|
||||
fun sortByDesc(sortByDesc: Boolean) = sortByDesc(sortByDesc as Boolean?)
|
||||
|
||||
/** Alias for calling [Builder.sortByDesc] with `sortByDesc.orElse(null)`. */
|
||||
fun sortByDesc(sortByDesc: Optional<Boolean>) = sortByDesc(sortByDesc.getOrNull())
|
||||
|
||||
fun tagValueId(tagValueId: List<String>?) = apply {
|
||||
this.tagValueId = tagValueId?.toMutableList()
|
||||
}
|
||||
@@ -274,6 +318,7 @@ private constructor(
|
||||
*/
|
||||
fun build(): AnnotationQueueRetrieveAnnotationQueuesParams =
|
||||
AnnotationQueueRetrieveAnnotationQueuesParams(
|
||||
assignedToMe,
|
||||
datasetId,
|
||||
ids?.toImmutable(),
|
||||
limit,
|
||||
@@ -281,6 +326,8 @@ private constructor(
|
||||
nameContains,
|
||||
offset,
|
||||
queueType,
|
||||
sortBy,
|
||||
sortByDesc,
|
||||
tagValueId?.toImmutable(),
|
||||
additionalHeaders.build(),
|
||||
additionalQueryParams.build(),
|
||||
@@ -292,14 +339,17 @@ private constructor(
|
||||
override fun _queryParams(): QueryParams =
|
||||
QueryParams.builder()
|
||||
.apply {
|
||||
assignedToMe?.let { put("assigned_to_me", it.toString()) }
|
||||
datasetId?.let { put("dataset_id", it) }
|
||||
ids?.let { put("ids", it.joinToString(",")) }
|
||||
ids?.forEach { put("ids", it) }
|
||||
limit?.let { put("limit", it.toString()) }
|
||||
name?.let { put("name", it) }
|
||||
nameContains?.let { put("name_contains", it) }
|
||||
offset?.let { put("offset", it.toString()) }
|
||||
queueType?.let { put("queue_type", it.toString()) }
|
||||
tagValueId?.let { put("tag_value_id", it.joinToString(",")) }
|
||||
sortBy?.let { put("sort_by", it) }
|
||||
sortByDesc?.let { put("sort_by_desc", it.toString()) }
|
||||
tagValueId?.forEach { put("tag_value_id", it) }
|
||||
putAll(additionalQueryParams)
|
||||
}
|
||||
.build()
|
||||
@@ -395,6 +445,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): QueueType = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -439,6 +498,7 @@ private constructor(
|
||||
}
|
||||
|
||||
return other is AnnotationQueueRetrieveAnnotationQueuesParams &&
|
||||
assignedToMe == other.assignedToMe &&
|
||||
datasetId == other.datasetId &&
|
||||
ids == other.ids &&
|
||||
limit == other.limit &&
|
||||
@@ -446,6 +506,8 @@ private constructor(
|
||||
nameContains == other.nameContains &&
|
||||
offset == other.offset &&
|
||||
queueType == other.queueType &&
|
||||
sortBy == other.sortBy &&
|
||||
sortByDesc == other.sortByDesc &&
|
||||
tagValueId == other.tagValueId &&
|
||||
additionalHeaders == other.additionalHeaders &&
|
||||
additionalQueryParams == other.additionalQueryParams
|
||||
@@ -453,6 +515,7 @@ private constructor(
|
||||
|
||||
override fun hashCode(): Int =
|
||||
Objects.hash(
|
||||
assignedToMe,
|
||||
datasetId,
|
||||
ids,
|
||||
limit,
|
||||
@@ -460,11 +523,13 @@ private constructor(
|
||||
nameContains,
|
||||
offset,
|
||||
queueType,
|
||||
sortBy,
|
||||
sortByDesc,
|
||||
tagValueId,
|
||||
additionalHeaders,
|
||||
additionalQueryParams,
|
||||
)
|
||||
|
||||
override fun toString() =
|
||||
"AnnotationQueueRetrieveAnnotationQueuesParams{datasetId=$datasetId, ids=$ids, limit=$limit, name=$name, nameContains=$nameContains, offset=$offset, queueType=$queueType, tagValueId=$tagValueId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
|
||||
"AnnotationQueueRetrieveAnnotationQueuesParams{assignedToMe=$assignedToMe, datasetId=$datasetId, ids=$ids, limit=$limit, name=$name, nameContains=$nameContains, offset=$offset, queueType=$queueType, sortBy=$sortBy, sortByDesc=$sortByDesc, tagValueId=$tagValueId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
|
||||
}
|
||||
|
||||
+367
-1
@@ -11,6 +11,7 @@ import com.langchain.smith.core.ExcludeMissing
|
||||
import com.langchain.smith.core.JsonField
|
||||
import com.langchain.smith.core.JsonMissing
|
||||
import com.langchain.smith.core.JsonValue
|
||||
import com.langchain.smith.core.checkKnown
|
||||
import com.langchain.smith.core.checkRequired
|
||||
import com.langchain.smith.core.toImmutable
|
||||
import com.langchain.smith.errors.LangChainInvalidDataException
|
||||
@@ -29,6 +30,7 @@ private constructor(
|
||||
private val queueType: JsonField<QueueType>,
|
||||
private val tenantId: JsonField<String>,
|
||||
private val totalRuns: JsonField<Long>,
|
||||
private val assignedReviewers: JsonField<List<AssignedReviewer>>,
|
||||
private val createdAt: JsonField<OffsetDateTime>,
|
||||
private val defaultDataset: JsonField<String>,
|
||||
private val description: JsonField<String>,
|
||||
@@ -36,6 +38,7 @@ private constructor(
|
||||
private val metadata: JsonField<Metadata>,
|
||||
private val numReviewersPerItem: JsonField<Long>,
|
||||
private val reservationMinutes: JsonField<Long>,
|
||||
private val reviewerAccessMode: JsonField<String>,
|
||||
private val runRuleId: JsonField<String>,
|
||||
private val sourceRuleId: JsonField<String>,
|
||||
private val updatedAt: JsonField<OffsetDateTime>,
|
||||
@@ -51,6 +54,9 @@ private constructor(
|
||||
queueType: JsonField<QueueType> = JsonMissing.of(),
|
||||
@JsonProperty("tenant_id") @ExcludeMissing tenantId: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("total_runs") @ExcludeMissing totalRuns: JsonField<Long> = JsonMissing.of(),
|
||||
@JsonProperty("assigned_reviewers")
|
||||
@ExcludeMissing
|
||||
assignedReviewers: JsonField<List<AssignedReviewer>> = JsonMissing.of(),
|
||||
@JsonProperty("created_at")
|
||||
@ExcludeMissing
|
||||
createdAt: JsonField<OffsetDateTime> = JsonMissing.of(),
|
||||
@@ -70,6 +76,9 @@ private constructor(
|
||||
@JsonProperty("reservation_minutes")
|
||||
@ExcludeMissing
|
||||
reservationMinutes: JsonField<Long> = JsonMissing.of(),
|
||||
@JsonProperty("reviewer_access_mode")
|
||||
@ExcludeMissing
|
||||
reviewerAccessMode: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("run_rule_id")
|
||||
@ExcludeMissing
|
||||
runRuleId: JsonField<String> = JsonMissing.of(),
|
||||
@@ -85,6 +94,7 @@ private constructor(
|
||||
queueType,
|
||||
tenantId,
|
||||
totalRuns,
|
||||
assignedReviewers,
|
||||
createdAt,
|
||||
defaultDataset,
|
||||
description,
|
||||
@@ -92,6 +102,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
runRuleId,
|
||||
sourceRuleId,
|
||||
updatedAt,
|
||||
@@ -128,6 +139,13 @@ private constructor(
|
||||
*/
|
||||
fun totalRuns(): Long = totalRuns.getRequired("total_runs")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun assignedReviewers(): Optional<List<AssignedReviewer>> =
|
||||
assignedReviewers.getOptional("assigned_reviewers")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
@@ -172,6 +190,13 @@ private constructor(
|
||||
*/
|
||||
fun reservationMinutes(): Optional<Long> = reservationMinutes.getOptional("reservation_minutes")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun reviewerAccessMode(): Optional<String> =
|
||||
reviewerAccessMode.getOptional("reviewer_access_mode")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
@@ -225,6 +250,16 @@ private constructor(
|
||||
*/
|
||||
@JsonProperty("total_runs") @ExcludeMissing fun _totalRuns(): JsonField<Long> = totalRuns
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [assignedReviewers].
|
||||
*
|
||||
* Unlike [assignedReviewers], this method doesn't throw if the JSON field has an unexpected
|
||||
* type.
|
||||
*/
|
||||
@JsonProperty("assigned_reviewers")
|
||||
@ExcludeMissing
|
||||
fun _assignedReviewers(): JsonField<List<AssignedReviewer>> = assignedReviewers
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [createdAt].
|
||||
*
|
||||
@@ -287,6 +322,16 @@ private constructor(
|
||||
@ExcludeMissing
|
||||
fun _reservationMinutes(): JsonField<Long> = reservationMinutes
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [reviewerAccessMode].
|
||||
*
|
||||
* Unlike [reviewerAccessMode], this method doesn't throw if the JSON field has an unexpected
|
||||
* type.
|
||||
*/
|
||||
@JsonProperty("reviewer_access_mode")
|
||||
@ExcludeMissing
|
||||
fun _reviewerAccessMode(): JsonField<String> = reviewerAccessMode
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [runRuleId].
|
||||
*
|
||||
@@ -350,6 +395,7 @@ private constructor(
|
||||
private var queueType: JsonField<QueueType>? = null
|
||||
private var tenantId: JsonField<String>? = null
|
||||
private var totalRuns: JsonField<Long>? = null
|
||||
private var assignedReviewers: JsonField<MutableList<AssignedReviewer>>? = null
|
||||
private var createdAt: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
private var defaultDataset: JsonField<String> = JsonMissing.of()
|
||||
private var description: JsonField<String> = JsonMissing.of()
|
||||
@@ -357,6 +403,7 @@ private constructor(
|
||||
private var metadata: JsonField<Metadata> = JsonMissing.of()
|
||||
private var numReviewersPerItem: JsonField<Long> = JsonMissing.of()
|
||||
private var reservationMinutes: JsonField<Long> = JsonMissing.of()
|
||||
private var reviewerAccessMode: JsonField<String> = JsonMissing.of()
|
||||
private var runRuleId: JsonField<String> = JsonMissing.of()
|
||||
private var sourceRuleId: JsonField<String> = JsonMissing.of()
|
||||
private var updatedAt: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
@@ -372,6 +419,10 @@ private constructor(
|
||||
queueType = annotationQueueRetrieveAnnotationQueuesResponse.queueType
|
||||
tenantId = annotationQueueRetrieveAnnotationQueuesResponse.tenantId
|
||||
totalRuns = annotationQueueRetrieveAnnotationQueuesResponse.totalRuns
|
||||
assignedReviewers =
|
||||
annotationQueueRetrieveAnnotationQueuesResponse.assignedReviewers.map {
|
||||
it.toMutableList()
|
||||
}
|
||||
createdAt = annotationQueueRetrieveAnnotationQueuesResponse.createdAt
|
||||
defaultDataset = annotationQueueRetrieveAnnotationQueuesResponse.defaultDataset
|
||||
description = annotationQueueRetrieveAnnotationQueuesResponse.description
|
||||
@@ -380,6 +431,7 @@ private constructor(
|
||||
numReviewersPerItem =
|
||||
annotationQueueRetrieveAnnotationQueuesResponse.numReviewersPerItem
|
||||
reservationMinutes = annotationQueueRetrieveAnnotationQueuesResponse.reservationMinutes
|
||||
reviewerAccessMode = annotationQueueRetrieveAnnotationQueuesResponse.reviewerAccessMode
|
||||
runRuleId = annotationQueueRetrieveAnnotationQueuesResponse.runRuleId
|
||||
sourceRuleId = annotationQueueRetrieveAnnotationQueuesResponse.sourceRuleId
|
||||
updatedAt = annotationQueueRetrieveAnnotationQueuesResponse.updatedAt
|
||||
@@ -438,6 +490,32 @@ private constructor(
|
||||
*/
|
||||
fun totalRuns(totalRuns: JsonField<Long>) = apply { this.totalRuns = totalRuns }
|
||||
|
||||
fun assignedReviewers(assignedReviewers: List<AssignedReviewer>) =
|
||||
assignedReviewers(JsonField.of(assignedReviewers))
|
||||
|
||||
/**
|
||||
* Sets [Builder.assignedReviewers] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.assignedReviewers] with a well-typed
|
||||
* `List<AssignedReviewer>` value instead. This method is primarily for setting the field to
|
||||
* an undocumented or not yet supported value.
|
||||
*/
|
||||
fun assignedReviewers(assignedReviewers: JsonField<List<AssignedReviewer>>) = apply {
|
||||
this.assignedReviewers = assignedReviewers.map { it.toMutableList() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single [AssignedReviewer] to [assignedReviewers].
|
||||
*
|
||||
* @throws IllegalStateException if the field was previously set to a non-list.
|
||||
*/
|
||||
fun addAssignedReviewer(assignedReviewer: AssignedReviewer) = apply {
|
||||
assignedReviewers =
|
||||
(assignedReviewers ?: JsonField.of(mutableListOf())).also {
|
||||
checkKnown("assignedReviewers", it).add(assignedReviewer)
|
||||
}
|
||||
}
|
||||
|
||||
fun createdAt(createdAt: OffsetDateTime) = createdAt(JsonField.of(createdAt))
|
||||
|
||||
/**
|
||||
@@ -579,6 +657,20 @@ private constructor(
|
||||
this.reservationMinutes = reservationMinutes
|
||||
}
|
||||
|
||||
fun reviewerAccessMode(reviewerAccessMode: String) =
|
||||
reviewerAccessMode(JsonField.of(reviewerAccessMode))
|
||||
|
||||
/**
|
||||
* Sets [Builder.reviewerAccessMode] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.reviewerAccessMode] with a well-typed [String] value
|
||||
* instead. This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun reviewerAccessMode(reviewerAccessMode: JsonField<String>) = apply {
|
||||
this.reviewerAccessMode = reviewerAccessMode
|
||||
}
|
||||
|
||||
fun runRuleId(runRuleId: String?) = runRuleId(JsonField.ofNullable(runRuleId))
|
||||
|
||||
/** Alias for calling [Builder.runRuleId] with `runRuleId.orElse(null)`. */
|
||||
@@ -662,6 +754,7 @@ private constructor(
|
||||
checkRequired("queueType", queueType),
|
||||
checkRequired("tenantId", tenantId),
|
||||
checkRequired("totalRuns", totalRuns),
|
||||
(assignedReviewers ?: JsonMissing.of()).map { it.toImmutable() },
|
||||
createdAt,
|
||||
defaultDataset,
|
||||
description,
|
||||
@@ -669,6 +762,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
runRuleId,
|
||||
sourceRuleId,
|
||||
updatedAt,
|
||||
@@ -678,6 +772,14 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): AnnotationQueueRetrieveAnnotationQueuesResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -688,6 +790,7 @@ private constructor(
|
||||
queueType().validate()
|
||||
tenantId()
|
||||
totalRuns()
|
||||
assignedReviewers().ifPresent { it.forEach { it.validate() } }
|
||||
createdAt()
|
||||
defaultDataset()
|
||||
description()
|
||||
@@ -695,6 +798,7 @@ private constructor(
|
||||
metadata().ifPresent { it.validate() }
|
||||
numReviewersPerItem()
|
||||
reservationMinutes()
|
||||
reviewerAccessMode()
|
||||
runRuleId()
|
||||
sourceRuleId()
|
||||
updatedAt()
|
||||
@@ -721,6 +825,7 @@ private constructor(
|
||||
(queueType.asKnown().getOrNull()?.validity() ?: 0) +
|
||||
(if (tenantId.asKnown().isPresent) 1 else 0) +
|
||||
(if (totalRuns.asKnown().isPresent) 1 else 0) +
|
||||
(assignedReviewers.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) +
|
||||
(if (createdAt.asKnown().isPresent) 1 else 0) +
|
||||
(if (defaultDataset.asKnown().isPresent) 1 else 0) +
|
||||
(if (description.asKnown().isPresent) 1 else 0) +
|
||||
@@ -728,6 +833,7 @@ private constructor(
|
||||
(metadata.asKnown().getOrNull()?.validity() ?: 0) +
|
||||
(if (numReviewersPerItem.asKnown().isPresent) 1 else 0) +
|
||||
(if (reservationMinutes.asKnown().isPresent) 1 else 0) +
|
||||
(if (reviewerAccessMode.asKnown().isPresent) 1 else 0) +
|
||||
(if (runRuleId.asKnown().isPresent) 1 else 0) +
|
||||
(if (sourceRuleId.asKnown().isPresent) 1 else 0) +
|
||||
(if (updatedAt.asKnown().isPresent) 1 else 0)
|
||||
@@ -823,6 +929,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): QueueType = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -861,6 +976,244 @@ private constructor(
|
||||
override fun toString() = value.toString()
|
||||
}
|
||||
|
||||
/** Identity info for an assigned reviewer on an annotation queue. */
|
||||
class AssignedReviewer
|
||||
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
|
||||
private constructor(
|
||||
private val id: JsonField<String>,
|
||||
private val email: JsonField<String>,
|
||||
private val name: JsonField<String>,
|
||||
private val additionalProperties: MutableMap<String, JsonValue>,
|
||||
) {
|
||||
|
||||
@JsonCreator
|
||||
private constructor(
|
||||
@JsonProperty("id") @ExcludeMissing id: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("email") @ExcludeMissing email: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("name") @ExcludeMissing name: JsonField<String> = JsonMissing.of(),
|
||||
) : this(id, email, name, mutableMapOf())
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type or is
|
||||
* unexpectedly missing or null (e.g. if the server responded with an unexpected value).
|
||||
*/
|
||||
fun id(): String = id.getRequired("id")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
*/
|
||||
fun email(): Optional<String> = email.getOptional("email")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
*/
|
||||
fun name(): Optional<String> = name.getOptional("name")
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [id].
|
||||
*
|
||||
* Unlike [id], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("id") @ExcludeMissing fun _id(): JsonField<String> = id
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [email].
|
||||
*
|
||||
* Unlike [email], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("email") @ExcludeMissing fun _email(): JsonField<String> = email
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [name].
|
||||
*
|
||||
* Unlike [name], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("name") @ExcludeMissing fun _name(): JsonField<String> = name
|
||||
|
||||
@JsonAnySetter
|
||||
private fun putAdditionalProperty(key: String, value: JsonValue) {
|
||||
additionalProperties.put(key, value)
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
@ExcludeMissing
|
||||
fun _additionalProperties(): Map<String, JsonValue> =
|
||||
Collections.unmodifiableMap(additionalProperties)
|
||||
|
||||
fun toBuilder() = Builder().from(this)
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Returns a mutable builder for constructing an instance of [AssignedReviewer].
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .id()
|
||||
* ```
|
||||
*/
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
/** A builder for [AssignedReviewer]. */
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var id: JsonField<String>? = null
|
||||
private var email: JsonField<String> = JsonMissing.of()
|
||||
private var name: JsonField<String> = JsonMissing.of()
|
||||
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun from(assignedReviewer: AssignedReviewer) = apply {
|
||||
id = assignedReviewer.id
|
||||
email = assignedReviewer.email
|
||||
name = assignedReviewer.name
|
||||
additionalProperties = assignedReviewer.additionalProperties.toMutableMap()
|
||||
}
|
||||
|
||||
fun id(id: String) = id(JsonField.of(id))
|
||||
|
||||
/**
|
||||
* Sets [Builder.id] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.id] with a well-typed [String] value instead. This
|
||||
* method is primarily for setting the field to an undocumented or not yet supported
|
||||
* value.
|
||||
*/
|
||||
fun id(id: JsonField<String>) = apply { this.id = id }
|
||||
|
||||
fun email(email: String?) = email(JsonField.ofNullable(email))
|
||||
|
||||
/** Alias for calling [Builder.email] with `email.orElse(null)`. */
|
||||
fun email(email: Optional<String>) = email(email.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.email] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.email] with a well-typed [String] value instead.
|
||||
* This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun email(email: JsonField<String>) = apply { this.email = email }
|
||||
|
||||
fun name(name: String?) = name(JsonField.ofNullable(name))
|
||||
|
||||
/** Alias for calling [Builder.name] with `name.orElse(null)`. */
|
||||
fun name(name: Optional<String>) = name(name.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.name] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.name] with a well-typed [String] value instead. This
|
||||
* method is primarily for setting the field to an undocumented or not yet supported
|
||||
* value.
|
||||
*/
|
||||
fun name(name: JsonField<String>) = apply { this.name = name }
|
||||
|
||||
fun additionalProperties(additionalProperties: Map<String, JsonValue>) = apply {
|
||||
this.additionalProperties.clear()
|
||||
putAllAdditionalProperties(additionalProperties)
|
||||
}
|
||||
|
||||
fun putAdditionalProperty(key: String, value: JsonValue) = apply {
|
||||
additionalProperties.put(key, value)
|
||||
}
|
||||
|
||||
fun putAllAdditionalProperties(additionalProperties: Map<String, JsonValue>) = apply {
|
||||
this.additionalProperties.putAll(additionalProperties)
|
||||
}
|
||||
|
||||
fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) }
|
||||
|
||||
fun removeAllAdditionalProperties(keys: Set<String>) = apply {
|
||||
keys.forEach(::removeAdditionalProperty)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable instance of [AssignedReviewer].
|
||||
*
|
||||
* Further updates to this [Builder] will not mutate the returned instance.
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .id()
|
||||
* ```
|
||||
*
|
||||
* @throws IllegalStateException if any required field is unset.
|
||||
*/
|
||||
fun build(): AssignedReviewer =
|
||||
AssignedReviewer(
|
||||
checkRequired("id", id),
|
||||
email,
|
||||
name,
|
||||
additionalProperties.toMutableMap(),
|
||||
)
|
||||
}
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): AssignedReviewer = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
}
|
||||
|
||||
id()
|
||||
email()
|
||||
name()
|
||||
validated = true
|
||||
}
|
||||
|
||||
fun isValid(): Boolean =
|
||||
try {
|
||||
validate()
|
||||
true
|
||||
} catch (e: LangChainInvalidDataException) {
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a score indicating how many valid values are contained in this object
|
||||
* recursively.
|
||||
*
|
||||
* Used for best match union deserialization.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
internal fun validity(): Int =
|
||||
(if (id.asKnown().isPresent) 1 else 0) +
|
||||
(if (email.asKnown().isPresent) 1 else 0) +
|
||||
(if (name.asKnown().isPresent) 1 else 0)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
|
||||
return other is AssignedReviewer &&
|
||||
id == other.id &&
|
||||
email == other.email &&
|
||||
name == other.name &&
|
||||
additionalProperties == other.additionalProperties
|
||||
}
|
||||
|
||||
private val hashCode: Int by lazy { Objects.hash(id, email, name, additionalProperties) }
|
||||
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"AssignedReviewer{id=$id, email=$email, name=$name, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
class Metadata
|
||||
@JsonCreator
|
||||
private constructor(
|
||||
@@ -919,6 +1272,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): Metadata = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -971,6 +1333,7 @@ private constructor(
|
||||
queueType == other.queueType &&
|
||||
tenantId == other.tenantId &&
|
||||
totalRuns == other.totalRuns &&
|
||||
assignedReviewers == other.assignedReviewers &&
|
||||
createdAt == other.createdAt &&
|
||||
defaultDataset == other.defaultDataset &&
|
||||
description == other.description &&
|
||||
@@ -978,6 +1341,7 @@ private constructor(
|
||||
metadata == other.metadata &&
|
||||
numReviewersPerItem == other.numReviewersPerItem &&
|
||||
reservationMinutes == other.reservationMinutes &&
|
||||
reviewerAccessMode == other.reviewerAccessMode &&
|
||||
runRuleId == other.runRuleId &&
|
||||
sourceRuleId == other.sourceRuleId &&
|
||||
updatedAt == other.updatedAt &&
|
||||
@@ -991,6 +1355,7 @@ private constructor(
|
||||
queueType,
|
||||
tenantId,
|
||||
totalRuns,
|
||||
assignedReviewers,
|
||||
createdAt,
|
||||
defaultDataset,
|
||||
description,
|
||||
@@ -998,6 +1363,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
runRuleId,
|
||||
sourceRuleId,
|
||||
updatedAt,
|
||||
@@ -1008,5 +1374,5 @@ private constructor(
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"AnnotationQueueRetrieveAnnotationQueuesResponse{id=$id, name=$name, queueType=$queueType, tenantId=$tenantId, totalRuns=$totalRuns, createdAt=$createdAt, defaultDataset=$defaultDataset, description=$description, enableReservations=$enableReservations, metadata=$metadata, numReviewersPerItem=$numReviewersPerItem, reservationMinutes=$reservationMinutes, runRuleId=$runRuleId, sourceRuleId=$sourceRuleId, updatedAt=$updatedAt, additionalProperties=$additionalProperties}"
|
||||
"AnnotationQueueRetrieveAnnotationQueuesResponse{id=$id, name=$name, queueType=$queueType, tenantId=$tenantId, totalRuns=$totalRuns, assignedReviewers=$assignedReviewers, createdAt=$createdAt, defaultDataset=$defaultDataset, description=$description, enableReservations=$enableReservations, metadata=$metadata, numReviewersPerItem=$numReviewersPerItem, reservationMinutes=$reservationMinutes, reviewerAccessMode=$reviewerAccessMode, runRuleId=$runRuleId, sourceRuleId=$sourceRuleId, updatedAt=$updatedAt, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
+364
-1
@@ -29,6 +29,7 @@ private constructor(
|
||||
private val name: JsonField<String>,
|
||||
private val queueType: JsonField<QueueType>,
|
||||
private val tenantId: JsonField<String>,
|
||||
private val assignedReviewers: JsonField<List<AssignedReviewer>>,
|
||||
private val createdAt: JsonField<OffsetDateTime>,
|
||||
private val defaultDataset: JsonField<String>,
|
||||
private val description: JsonField<String>,
|
||||
@@ -36,6 +37,7 @@ private constructor(
|
||||
private val metadata: JsonField<Metadata>,
|
||||
private val numReviewersPerItem: JsonField<Long>,
|
||||
private val reservationMinutes: JsonField<Long>,
|
||||
private val reviewerAccessMode: JsonField<String>,
|
||||
private val rubricInstructions: JsonField<String>,
|
||||
private val rubricItems: JsonField<List<AnnotationQueueRubricItemSchema>>,
|
||||
private val runRuleId: JsonField<String>,
|
||||
@@ -52,6 +54,9 @@ private constructor(
|
||||
@ExcludeMissing
|
||||
queueType: JsonField<QueueType> = JsonMissing.of(),
|
||||
@JsonProperty("tenant_id") @ExcludeMissing tenantId: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("assigned_reviewers")
|
||||
@ExcludeMissing
|
||||
assignedReviewers: JsonField<List<AssignedReviewer>> = JsonMissing.of(),
|
||||
@JsonProperty("created_at")
|
||||
@ExcludeMissing
|
||||
createdAt: JsonField<OffsetDateTime> = JsonMissing.of(),
|
||||
@@ -71,6 +76,9 @@ private constructor(
|
||||
@JsonProperty("reservation_minutes")
|
||||
@ExcludeMissing
|
||||
reservationMinutes: JsonField<Long> = JsonMissing.of(),
|
||||
@JsonProperty("reviewer_access_mode")
|
||||
@ExcludeMissing
|
||||
reviewerAccessMode: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("rubric_instructions")
|
||||
@ExcludeMissing
|
||||
rubricInstructions: JsonField<String> = JsonMissing.of(),
|
||||
@@ -91,6 +99,7 @@ private constructor(
|
||||
name,
|
||||
queueType,
|
||||
tenantId,
|
||||
assignedReviewers,
|
||||
createdAt,
|
||||
defaultDataset,
|
||||
description,
|
||||
@@ -98,6 +107,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
rubricInstructions,
|
||||
rubricItems,
|
||||
runRuleId,
|
||||
@@ -130,6 +140,13 @@ private constructor(
|
||||
*/
|
||||
fun tenantId(): String = tenantId.getRequired("tenant_id")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun assignedReviewers(): Optional<List<AssignedReviewer>> =
|
||||
assignedReviewers.getOptional("assigned_reviewers")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
@@ -174,6 +191,13 @@ private constructor(
|
||||
*/
|
||||
fun reservationMinutes(): Optional<Long> = reservationMinutes.getOptional("reservation_minutes")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun reviewerAccessMode(): Optional<String> =
|
||||
reviewerAccessMode.getOptional("reviewer_access_mode")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
@@ -234,6 +258,16 @@ private constructor(
|
||||
*/
|
||||
@JsonProperty("tenant_id") @ExcludeMissing fun _tenantId(): JsonField<String> = tenantId
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [assignedReviewers].
|
||||
*
|
||||
* Unlike [assignedReviewers], this method doesn't throw if the JSON field has an unexpected
|
||||
* type.
|
||||
*/
|
||||
@JsonProperty("assigned_reviewers")
|
||||
@ExcludeMissing
|
||||
fun _assignedReviewers(): JsonField<List<AssignedReviewer>> = assignedReviewers
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [createdAt].
|
||||
*
|
||||
@@ -296,6 +330,16 @@ private constructor(
|
||||
@ExcludeMissing
|
||||
fun _reservationMinutes(): JsonField<Long> = reservationMinutes
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [reviewerAccessMode].
|
||||
*
|
||||
* Unlike [reviewerAccessMode], this method doesn't throw if the JSON field has an unexpected
|
||||
* type.
|
||||
*/
|
||||
@JsonProperty("reviewer_access_mode")
|
||||
@ExcludeMissing
|
||||
fun _reviewerAccessMode(): JsonField<String> = reviewerAccessMode
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [rubricInstructions].
|
||||
*
|
||||
@@ -376,6 +420,7 @@ private constructor(
|
||||
private var name: JsonField<String>? = null
|
||||
private var queueType: JsonField<QueueType>? = null
|
||||
private var tenantId: JsonField<String>? = null
|
||||
private var assignedReviewers: JsonField<MutableList<AssignedReviewer>>? = null
|
||||
private var createdAt: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
private var defaultDataset: JsonField<String> = JsonMissing.of()
|
||||
private var description: JsonField<String> = JsonMissing.of()
|
||||
@@ -383,6 +428,7 @@ private constructor(
|
||||
private var metadata: JsonField<Metadata> = JsonMissing.of()
|
||||
private var numReviewersPerItem: JsonField<Long> = JsonMissing.of()
|
||||
private var reservationMinutes: JsonField<Long> = JsonMissing.of()
|
||||
private var reviewerAccessMode: JsonField<String> = JsonMissing.of()
|
||||
private var rubricInstructions: JsonField<String> = JsonMissing.of()
|
||||
private var rubricItems: JsonField<MutableList<AnnotationQueueRubricItemSchema>>? = null
|
||||
private var runRuleId: JsonField<String> = JsonMissing.of()
|
||||
@@ -397,6 +443,8 @@ private constructor(
|
||||
name = annotationQueueRetrieveResponse.name
|
||||
queueType = annotationQueueRetrieveResponse.queueType
|
||||
tenantId = annotationQueueRetrieveResponse.tenantId
|
||||
assignedReviewers =
|
||||
annotationQueueRetrieveResponse.assignedReviewers.map { it.toMutableList() }
|
||||
createdAt = annotationQueueRetrieveResponse.createdAt
|
||||
defaultDataset = annotationQueueRetrieveResponse.defaultDataset
|
||||
description = annotationQueueRetrieveResponse.description
|
||||
@@ -404,6 +452,7 @@ private constructor(
|
||||
metadata = annotationQueueRetrieveResponse.metadata
|
||||
numReviewersPerItem = annotationQueueRetrieveResponse.numReviewersPerItem
|
||||
reservationMinutes = annotationQueueRetrieveResponse.reservationMinutes
|
||||
reviewerAccessMode = annotationQueueRetrieveResponse.reviewerAccessMode
|
||||
rubricInstructions = annotationQueueRetrieveResponse.rubricInstructions
|
||||
rubricItems = annotationQueueRetrieveResponse.rubricItems.map { it.toMutableList() }
|
||||
runRuleId = annotationQueueRetrieveResponse.runRuleId
|
||||
@@ -454,6 +503,32 @@ private constructor(
|
||||
*/
|
||||
fun tenantId(tenantId: JsonField<String>) = apply { this.tenantId = tenantId }
|
||||
|
||||
fun assignedReviewers(assignedReviewers: List<AssignedReviewer>) =
|
||||
assignedReviewers(JsonField.of(assignedReviewers))
|
||||
|
||||
/**
|
||||
* Sets [Builder.assignedReviewers] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.assignedReviewers] with a well-typed
|
||||
* `List<AssignedReviewer>` value instead. This method is primarily for setting the field to
|
||||
* an undocumented or not yet supported value.
|
||||
*/
|
||||
fun assignedReviewers(assignedReviewers: JsonField<List<AssignedReviewer>>) = apply {
|
||||
this.assignedReviewers = assignedReviewers.map { it.toMutableList() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single [AssignedReviewer] to [assignedReviewers].
|
||||
*
|
||||
* @throws IllegalStateException if the field was previously set to a non-list.
|
||||
*/
|
||||
fun addAssignedReviewer(assignedReviewer: AssignedReviewer) = apply {
|
||||
assignedReviewers =
|
||||
(assignedReviewers ?: JsonField.of(mutableListOf())).also {
|
||||
checkKnown("assignedReviewers", it).add(assignedReviewer)
|
||||
}
|
||||
}
|
||||
|
||||
fun createdAt(createdAt: OffsetDateTime) = createdAt(JsonField.of(createdAt))
|
||||
|
||||
/**
|
||||
@@ -595,6 +670,20 @@ private constructor(
|
||||
this.reservationMinutes = reservationMinutes
|
||||
}
|
||||
|
||||
fun reviewerAccessMode(reviewerAccessMode: String) =
|
||||
reviewerAccessMode(JsonField.of(reviewerAccessMode))
|
||||
|
||||
/**
|
||||
* Sets [Builder.reviewerAccessMode] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.reviewerAccessMode] with a well-typed [String] value
|
||||
* instead. This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun reviewerAccessMode(reviewerAccessMode: JsonField<String>) = apply {
|
||||
this.reviewerAccessMode = reviewerAccessMode
|
||||
}
|
||||
|
||||
fun rubricInstructions(rubricInstructions: String?) =
|
||||
rubricInstructions(JsonField.ofNullable(rubricInstructions))
|
||||
|
||||
@@ -726,6 +815,7 @@ private constructor(
|
||||
checkRequired("name", name),
|
||||
checkRequired("queueType", queueType),
|
||||
checkRequired("tenantId", tenantId),
|
||||
(assignedReviewers ?: JsonMissing.of()).map { it.toImmutable() },
|
||||
createdAt,
|
||||
defaultDataset,
|
||||
description,
|
||||
@@ -733,6 +823,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
rubricInstructions,
|
||||
(rubricItems ?: JsonMissing.of()).map { it.toImmutable() },
|
||||
runRuleId,
|
||||
@@ -744,6 +835,14 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): AnnotationQueueRetrieveResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -753,6 +852,7 @@ private constructor(
|
||||
name()
|
||||
queueType().validate()
|
||||
tenantId()
|
||||
assignedReviewers().ifPresent { it.forEach { it.validate() } }
|
||||
createdAt()
|
||||
defaultDataset()
|
||||
description()
|
||||
@@ -760,6 +860,7 @@ private constructor(
|
||||
metadata().ifPresent { it.validate() }
|
||||
numReviewersPerItem()
|
||||
reservationMinutes()
|
||||
reviewerAccessMode()
|
||||
rubricInstructions()
|
||||
rubricItems().ifPresent { it.forEach { it.validate() } }
|
||||
runRuleId()
|
||||
@@ -787,6 +888,7 @@ private constructor(
|
||||
(if (name.asKnown().isPresent) 1 else 0) +
|
||||
(queueType.asKnown().getOrNull()?.validity() ?: 0) +
|
||||
(if (tenantId.asKnown().isPresent) 1 else 0) +
|
||||
(assignedReviewers.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) +
|
||||
(if (createdAt.asKnown().isPresent) 1 else 0) +
|
||||
(if (defaultDataset.asKnown().isPresent) 1 else 0) +
|
||||
(if (description.asKnown().isPresent) 1 else 0) +
|
||||
@@ -794,6 +896,7 @@ private constructor(
|
||||
(metadata.asKnown().getOrNull()?.validity() ?: 0) +
|
||||
(if (numReviewersPerItem.asKnown().isPresent) 1 else 0) +
|
||||
(if (reservationMinutes.asKnown().isPresent) 1 else 0) +
|
||||
(if (reviewerAccessMode.asKnown().isPresent) 1 else 0) +
|
||||
(if (rubricInstructions.asKnown().isPresent) 1 else 0) +
|
||||
(rubricItems.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) +
|
||||
(if (runRuleId.asKnown().isPresent) 1 else 0) +
|
||||
@@ -891,6 +994,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): QueueType = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -929,6 +1041,244 @@ private constructor(
|
||||
override fun toString() = value.toString()
|
||||
}
|
||||
|
||||
/** Identity info for an assigned reviewer on an annotation queue. */
|
||||
class AssignedReviewer
|
||||
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
|
||||
private constructor(
|
||||
private val id: JsonField<String>,
|
||||
private val email: JsonField<String>,
|
||||
private val name: JsonField<String>,
|
||||
private val additionalProperties: MutableMap<String, JsonValue>,
|
||||
) {
|
||||
|
||||
@JsonCreator
|
||||
private constructor(
|
||||
@JsonProperty("id") @ExcludeMissing id: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("email") @ExcludeMissing email: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("name") @ExcludeMissing name: JsonField<String> = JsonMissing.of(),
|
||||
) : this(id, email, name, mutableMapOf())
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type or is
|
||||
* unexpectedly missing or null (e.g. if the server responded with an unexpected value).
|
||||
*/
|
||||
fun id(): String = id.getRequired("id")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
*/
|
||||
fun email(): Optional<String> = email.getOptional("email")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
*/
|
||||
fun name(): Optional<String> = name.getOptional("name")
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [id].
|
||||
*
|
||||
* Unlike [id], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("id") @ExcludeMissing fun _id(): JsonField<String> = id
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [email].
|
||||
*
|
||||
* Unlike [email], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("email") @ExcludeMissing fun _email(): JsonField<String> = email
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [name].
|
||||
*
|
||||
* Unlike [name], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("name") @ExcludeMissing fun _name(): JsonField<String> = name
|
||||
|
||||
@JsonAnySetter
|
||||
private fun putAdditionalProperty(key: String, value: JsonValue) {
|
||||
additionalProperties.put(key, value)
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
@ExcludeMissing
|
||||
fun _additionalProperties(): Map<String, JsonValue> =
|
||||
Collections.unmodifiableMap(additionalProperties)
|
||||
|
||||
fun toBuilder() = Builder().from(this)
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Returns a mutable builder for constructing an instance of [AssignedReviewer].
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .id()
|
||||
* ```
|
||||
*/
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
/** A builder for [AssignedReviewer]. */
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var id: JsonField<String>? = null
|
||||
private var email: JsonField<String> = JsonMissing.of()
|
||||
private var name: JsonField<String> = JsonMissing.of()
|
||||
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun from(assignedReviewer: AssignedReviewer) = apply {
|
||||
id = assignedReviewer.id
|
||||
email = assignedReviewer.email
|
||||
name = assignedReviewer.name
|
||||
additionalProperties = assignedReviewer.additionalProperties.toMutableMap()
|
||||
}
|
||||
|
||||
fun id(id: String) = id(JsonField.of(id))
|
||||
|
||||
/**
|
||||
* Sets [Builder.id] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.id] with a well-typed [String] value instead. This
|
||||
* method is primarily for setting the field to an undocumented or not yet supported
|
||||
* value.
|
||||
*/
|
||||
fun id(id: JsonField<String>) = apply { this.id = id }
|
||||
|
||||
fun email(email: String?) = email(JsonField.ofNullable(email))
|
||||
|
||||
/** Alias for calling [Builder.email] with `email.orElse(null)`. */
|
||||
fun email(email: Optional<String>) = email(email.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.email] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.email] with a well-typed [String] value instead.
|
||||
* This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun email(email: JsonField<String>) = apply { this.email = email }
|
||||
|
||||
fun name(name: String?) = name(JsonField.ofNullable(name))
|
||||
|
||||
/** Alias for calling [Builder.name] with `name.orElse(null)`. */
|
||||
fun name(name: Optional<String>) = name(name.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.name] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.name] with a well-typed [String] value instead. This
|
||||
* method is primarily for setting the field to an undocumented or not yet supported
|
||||
* value.
|
||||
*/
|
||||
fun name(name: JsonField<String>) = apply { this.name = name }
|
||||
|
||||
fun additionalProperties(additionalProperties: Map<String, JsonValue>) = apply {
|
||||
this.additionalProperties.clear()
|
||||
putAllAdditionalProperties(additionalProperties)
|
||||
}
|
||||
|
||||
fun putAdditionalProperty(key: String, value: JsonValue) = apply {
|
||||
additionalProperties.put(key, value)
|
||||
}
|
||||
|
||||
fun putAllAdditionalProperties(additionalProperties: Map<String, JsonValue>) = apply {
|
||||
this.additionalProperties.putAll(additionalProperties)
|
||||
}
|
||||
|
||||
fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) }
|
||||
|
||||
fun removeAllAdditionalProperties(keys: Set<String>) = apply {
|
||||
keys.forEach(::removeAdditionalProperty)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable instance of [AssignedReviewer].
|
||||
*
|
||||
* Further updates to this [Builder] will not mutate the returned instance.
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .id()
|
||||
* ```
|
||||
*
|
||||
* @throws IllegalStateException if any required field is unset.
|
||||
*/
|
||||
fun build(): AssignedReviewer =
|
||||
AssignedReviewer(
|
||||
checkRequired("id", id),
|
||||
email,
|
||||
name,
|
||||
additionalProperties.toMutableMap(),
|
||||
)
|
||||
}
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): AssignedReviewer = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
}
|
||||
|
||||
id()
|
||||
email()
|
||||
name()
|
||||
validated = true
|
||||
}
|
||||
|
||||
fun isValid(): Boolean =
|
||||
try {
|
||||
validate()
|
||||
true
|
||||
} catch (e: LangChainInvalidDataException) {
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a score indicating how many valid values are contained in this object
|
||||
* recursively.
|
||||
*
|
||||
* Used for best match union deserialization.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
internal fun validity(): Int =
|
||||
(if (id.asKnown().isPresent) 1 else 0) +
|
||||
(if (email.asKnown().isPresent) 1 else 0) +
|
||||
(if (name.asKnown().isPresent) 1 else 0)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
|
||||
return other is AssignedReviewer &&
|
||||
id == other.id &&
|
||||
email == other.email &&
|
||||
name == other.name &&
|
||||
additionalProperties == other.additionalProperties
|
||||
}
|
||||
|
||||
private val hashCode: Int by lazy { Objects.hash(id, email, name, additionalProperties) }
|
||||
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"AssignedReviewer{id=$id, email=$email, name=$name, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
class Metadata
|
||||
@JsonCreator
|
||||
private constructor(
|
||||
@@ -987,6 +1337,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): Metadata = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -1038,6 +1397,7 @@ private constructor(
|
||||
name == other.name &&
|
||||
queueType == other.queueType &&
|
||||
tenantId == other.tenantId &&
|
||||
assignedReviewers == other.assignedReviewers &&
|
||||
createdAt == other.createdAt &&
|
||||
defaultDataset == other.defaultDataset &&
|
||||
description == other.description &&
|
||||
@@ -1045,6 +1405,7 @@ private constructor(
|
||||
metadata == other.metadata &&
|
||||
numReviewersPerItem == other.numReviewersPerItem &&
|
||||
reservationMinutes == other.reservationMinutes &&
|
||||
reviewerAccessMode == other.reviewerAccessMode &&
|
||||
rubricInstructions == other.rubricInstructions &&
|
||||
rubricItems == other.rubricItems &&
|
||||
runRuleId == other.runRuleId &&
|
||||
@@ -1059,6 +1420,7 @@ private constructor(
|
||||
name,
|
||||
queueType,
|
||||
tenantId,
|
||||
assignedReviewers,
|
||||
createdAt,
|
||||
defaultDataset,
|
||||
description,
|
||||
@@ -1066,6 +1428,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
rubricInstructions,
|
||||
rubricItems,
|
||||
runRuleId,
|
||||
@@ -1078,5 +1441,5 @@ private constructor(
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"AnnotationQueueRetrieveResponse{id=$id, name=$name, queueType=$queueType, tenantId=$tenantId, createdAt=$createdAt, defaultDataset=$defaultDataset, description=$description, enableReservations=$enableReservations, metadata=$metadata, numReviewersPerItem=$numReviewersPerItem, reservationMinutes=$reservationMinutes, rubricInstructions=$rubricInstructions, rubricItems=$rubricItems, runRuleId=$runRuleId, sourceRuleId=$sourceRuleId, updatedAt=$updatedAt, additionalProperties=$additionalProperties}"
|
||||
"AnnotationQueueRetrieveResponse{id=$id, name=$name, queueType=$queueType, tenantId=$tenantId, assignedReviewers=$assignedReviewers, createdAt=$createdAt, defaultDataset=$defaultDataset, description=$description, enableReservations=$enableReservations, metadata=$metadata, numReviewersPerItem=$numReviewersPerItem, reservationMinutes=$reservationMinutes, reviewerAccessMode=$reviewerAccessMode, rubricInstructions=$rubricInstructions, rubricItems=$rubricItems, runRuleId=$runRuleId, sourceRuleId=$sourceRuleId, updatedAt=$updatedAt, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
+168
-3
@@ -2,9 +2,13 @@
|
||||
|
||||
package com.langchain.smith.models.annotationqueues
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator
|
||||
import com.langchain.smith.core.Enum
|
||||
import com.langchain.smith.core.JsonField
|
||||
import com.langchain.smith.core.Params
|
||||
import com.langchain.smith.core.http.Headers
|
||||
import com.langchain.smith.core.http.QueryParams
|
||||
import com.langchain.smith.errors.LangChainInvalidDataException
|
||||
import java.util.Objects
|
||||
import java.util.Optional
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
@@ -13,12 +17,15 @@ import kotlin.jvm.optionals.getOrNull
|
||||
class AnnotationQueueRetrieveSizeParams
|
||||
private constructor(
|
||||
private val queueId: String?,
|
||||
private val status: Status?,
|
||||
private val additionalHeaders: Headers,
|
||||
private val additionalQueryParams: QueryParams,
|
||||
) : Params {
|
||||
|
||||
fun queueId(): Optional<String> = Optional.ofNullable(queueId)
|
||||
|
||||
fun status(): Optional<Status> = Optional.ofNullable(status)
|
||||
|
||||
/** Additional headers to send with the request. */
|
||||
fun _additionalHeaders(): Headers = additionalHeaders
|
||||
|
||||
@@ -42,6 +49,7 @@ private constructor(
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var queueId: String? = null
|
||||
private var status: Status? = null
|
||||
private var additionalHeaders: Headers.Builder = Headers.builder()
|
||||
private var additionalQueryParams: QueryParams.Builder = QueryParams.builder()
|
||||
|
||||
@@ -49,6 +57,7 @@ private constructor(
|
||||
internal fun from(annotationQueueRetrieveSizeParams: AnnotationQueueRetrieveSizeParams) =
|
||||
apply {
|
||||
queueId = annotationQueueRetrieveSizeParams.queueId
|
||||
status = annotationQueueRetrieveSizeParams.status
|
||||
additionalHeaders = annotationQueueRetrieveSizeParams.additionalHeaders.toBuilder()
|
||||
additionalQueryParams =
|
||||
annotationQueueRetrieveSizeParams.additionalQueryParams.toBuilder()
|
||||
@@ -59,6 +68,11 @@ private constructor(
|
||||
/** Alias for calling [Builder.queueId] with `queueId.orElse(null)`. */
|
||||
fun queueId(queueId: Optional<String>) = queueId(queueId.getOrNull())
|
||||
|
||||
fun status(status: Status?) = apply { this.status = status }
|
||||
|
||||
/** Alias for calling [Builder.status] with `status.orElse(null)`. */
|
||||
fun status(status: Optional<Status>) = status(status.getOrNull())
|
||||
|
||||
fun additionalHeaders(additionalHeaders: Headers) = apply {
|
||||
this.additionalHeaders.clear()
|
||||
putAllAdditionalHeaders(additionalHeaders)
|
||||
@@ -165,6 +179,7 @@ private constructor(
|
||||
fun build(): AnnotationQueueRetrieveSizeParams =
|
||||
AnnotationQueueRetrieveSizeParams(
|
||||
queueId,
|
||||
status,
|
||||
additionalHeaders.build(),
|
||||
additionalQueryParams.build(),
|
||||
)
|
||||
@@ -178,7 +193,155 @@ private constructor(
|
||||
|
||||
override fun _headers(): Headers = additionalHeaders
|
||||
|
||||
override fun _queryParams(): QueryParams = additionalQueryParams
|
||||
override fun _queryParams(): QueryParams =
|
||||
QueryParams.builder()
|
||||
.apply {
|
||||
status?.let { put("status", it.toString()) }
|
||||
putAll(additionalQueryParams)
|
||||
}
|
||||
.build()
|
||||
|
||||
class Status @JsonCreator private constructor(private val value: JsonField<String>) : Enum {
|
||||
|
||||
/**
|
||||
* Returns this class instance's raw value.
|
||||
*
|
||||
* This is usually only useful if this instance was deserialized from data that doesn't
|
||||
* match any known member, and you want to know that value. For example, if the SDK is on an
|
||||
* older version than the API, then the API may respond with new members that the SDK is
|
||||
* unaware of.
|
||||
*/
|
||||
@com.fasterxml.jackson.annotation.JsonValue fun _value(): JsonField<String> = value
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmField val NEEDS_MY_REVIEW = of("needs_my_review")
|
||||
|
||||
@JvmField val NEEDS_OTHERS_REVIEW = of("needs_others_review")
|
||||
|
||||
@JvmField val COMPLETED = of("completed")
|
||||
|
||||
@JvmStatic fun of(value: String) = Status(JsonField.of(value))
|
||||
}
|
||||
|
||||
/** An enum containing [Status]'s known values. */
|
||||
enum class Known {
|
||||
NEEDS_MY_REVIEW,
|
||||
NEEDS_OTHERS_REVIEW,
|
||||
COMPLETED,
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum containing [Status]'s known values, as well as an [_UNKNOWN] member.
|
||||
*
|
||||
* An instance of [Status] can contain an unknown value in a couple of cases:
|
||||
* - It was deserialized from data that doesn't match any known member. For example, if the
|
||||
* SDK is on an older version than the API, then the API may respond with new members that
|
||||
* the SDK is unaware of.
|
||||
* - It was constructed with an arbitrary value using the [of] method.
|
||||
*/
|
||||
enum class Value {
|
||||
NEEDS_MY_REVIEW,
|
||||
NEEDS_OTHERS_REVIEW,
|
||||
COMPLETED,
|
||||
/** An enum member indicating that [Status] was instantiated with an unknown value. */
|
||||
_UNKNOWN,
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an enum member corresponding to this class instance's value, or [Value._UNKNOWN]
|
||||
* if the class was instantiated with an unknown value.
|
||||
*
|
||||
* Use the [known] method instead if you're certain the value is always known or if you want
|
||||
* to throw for the unknown case.
|
||||
*/
|
||||
fun value(): Value =
|
||||
when (this) {
|
||||
NEEDS_MY_REVIEW -> Value.NEEDS_MY_REVIEW
|
||||
NEEDS_OTHERS_REVIEW -> Value.NEEDS_OTHERS_REVIEW
|
||||
COMPLETED -> Value.COMPLETED
|
||||
else -> Value._UNKNOWN
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an enum member corresponding to this class instance's value.
|
||||
*
|
||||
* Use the [value] method instead if you're uncertain the value is always known and don't
|
||||
* want to throw for the unknown case.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if this class instance's value is a not a known
|
||||
* member.
|
||||
*/
|
||||
fun known(): Known =
|
||||
when (this) {
|
||||
NEEDS_MY_REVIEW -> Known.NEEDS_MY_REVIEW
|
||||
NEEDS_OTHERS_REVIEW -> Known.NEEDS_OTHERS_REVIEW
|
||||
COMPLETED -> Known.COMPLETED
|
||||
else -> throw LangChainInvalidDataException("Unknown Status: $value")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this class instance's primitive wire representation.
|
||||
*
|
||||
* This differs from the [toString] method because that method is primarily for debugging
|
||||
* and generally doesn't throw.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if this class instance's value does not have the
|
||||
* expected primitive type.
|
||||
*/
|
||||
fun asString(): String =
|
||||
_value().asString().orElseThrow {
|
||||
LangChainInvalidDataException("Value is not a String")
|
||||
}
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): Status = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
}
|
||||
|
||||
known()
|
||||
validated = true
|
||||
}
|
||||
|
||||
fun isValid(): Boolean =
|
||||
try {
|
||||
validate()
|
||||
true
|
||||
} catch (e: LangChainInvalidDataException) {
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a score indicating how many valid values are contained in this object
|
||||
* recursively.
|
||||
*
|
||||
* Used for best match union deserialization.
|
||||
*/
|
||||
@JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
|
||||
return other is Status && value == other.value
|
||||
}
|
||||
|
||||
override fun hashCode() = value.hashCode()
|
||||
|
||||
override fun toString() = value.toString()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
@@ -187,12 +350,14 @@ private constructor(
|
||||
|
||||
return other is AnnotationQueueRetrieveSizeParams &&
|
||||
queueId == other.queueId &&
|
||||
status == other.status &&
|
||||
additionalHeaders == other.additionalHeaders &&
|
||||
additionalQueryParams == other.additionalQueryParams
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(queueId, additionalHeaders, additionalQueryParams)
|
||||
override fun hashCode(): Int =
|
||||
Objects.hash(queueId, status, additionalHeaders, additionalQueryParams)
|
||||
|
||||
override fun toString() =
|
||||
"AnnotationQueueRetrieveSizeParams{queueId=$queueId, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
|
||||
"AnnotationQueueRetrieveSizeParams{queueId=$queueId, status=$status, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
|
||||
}
|
||||
|
||||
+75
-1
@@ -23,6 +23,7 @@ class AnnotationQueueRubricItemSchema
|
||||
private constructor(
|
||||
private val feedbackKey: JsonField<String>,
|
||||
private val description: JsonField<String>,
|
||||
private val isAssertion: JsonField<Boolean>,
|
||||
private val isRequired: JsonField<Boolean>,
|
||||
private val scoreDescriptions: JsonField<ScoreDescriptions>,
|
||||
private val valueDescriptions: JsonField<ValueDescriptions>,
|
||||
@@ -37,6 +38,9 @@ private constructor(
|
||||
@JsonProperty("description")
|
||||
@ExcludeMissing
|
||||
description: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("is_assertion")
|
||||
@ExcludeMissing
|
||||
isAssertion: JsonField<Boolean> = JsonMissing.of(),
|
||||
@JsonProperty("is_required")
|
||||
@ExcludeMissing
|
||||
isRequired: JsonField<Boolean> = JsonMissing.of(),
|
||||
@@ -49,6 +53,7 @@ private constructor(
|
||||
) : this(
|
||||
feedbackKey,
|
||||
description,
|
||||
isAssertion,
|
||||
isRequired,
|
||||
scoreDescriptions,
|
||||
valueDescriptions,
|
||||
@@ -67,6 +72,12 @@ private constructor(
|
||||
*/
|
||||
fun description(): Optional<String> = description.getOptional("description")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun isAssertion(): Optional<Boolean> = isAssertion.getOptional("is_assertion")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
@@ -103,6 +114,15 @@ private constructor(
|
||||
*/
|
||||
@JsonProperty("description") @ExcludeMissing fun _description(): JsonField<String> = description
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [isAssertion].
|
||||
*
|
||||
* Unlike [isAssertion], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("is_assertion")
|
||||
@ExcludeMissing
|
||||
fun _isAssertion(): JsonField<Boolean> = isAssertion
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [isRequired].
|
||||
*
|
||||
@@ -161,6 +181,7 @@ private constructor(
|
||||
|
||||
private var feedbackKey: JsonField<String>? = null
|
||||
private var description: JsonField<String> = JsonMissing.of()
|
||||
private var isAssertion: JsonField<Boolean> = JsonMissing.of()
|
||||
private var isRequired: JsonField<Boolean> = JsonMissing.of()
|
||||
private var scoreDescriptions: JsonField<ScoreDescriptions> = JsonMissing.of()
|
||||
private var valueDescriptions: JsonField<ValueDescriptions> = JsonMissing.of()
|
||||
@@ -171,6 +192,7 @@ private constructor(
|
||||
apply {
|
||||
feedbackKey = annotationQueueRubricItemSchema.feedbackKey
|
||||
description = annotationQueueRubricItemSchema.description
|
||||
isAssertion = annotationQueueRubricItemSchema.isAssertion
|
||||
isRequired = annotationQueueRubricItemSchema.isRequired
|
||||
scoreDescriptions = annotationQueueRubricItemSchema.scoreDescriptions
|
||||
valueDescriptions = annotationQueueRubricItemSchema.valueDescriptions
|
||||
@@ -203,6 +225,27 @@ private constructor(
|
||||
*/
|
||||
fun description(description: JsonField<String>) = apply { this.description = description }
|
||||
|
||||
fun isAssertion(isAssertion: Boolean?) = isAssertion(JsonField.ofNullable(isAssertion))
|
||||
|
||||
/**
|
||||
* Alias for [Builder.isAssertion].
|
||||
*
|
||||
* This unboxed primitive overload exists for backwards compatibility.
|
||||
*/
|
||||
fun isAssertion(isAssertion: Boolean) = isAssertion(isAssertion as Boolean?)
|
||||
|
||||
/** Alias for calling [Builder.isAssertion] with `isAssertion.orElse(null)`. */
|
||||
fun isAssertion(isAssertion: Optional<Boolean>) = isAssertion(isAssertion.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.isAssertion] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.isAssertion] with a well-typed [Boolean] value instead.
|
||||
* This method is primarily for setting the field to an undocumented or not yet supported
|
||||
* value.
|
||||
*/
|
||||
fun isAssertion(isAssertion: JsonField<Boolean>) = apply { this.isAssertion = isAssertion }
|
||||
|
||||
fun isRequired(isRequired: Boolean?) = isRequired(JsonField.ofNullable(isRequired))
|
||||
|
||||
/**
|
||||
@@ -295,6 +338,7 @@ private constructor(
|
||||
AnnotationQueueRubricItemSchema(
|
||||
checkRequired("feedbackKey", feedbackKey),
|
||||
description,
|
||||
isAssertion,
|
||||
isRequired,
|
||||
scoreDescriptions,
|
||||
valueDescriptions,
|
||||
@@ -304,6 +348,14 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): AnnotationQueueRubricItemSchema = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -311,6 +363,7 @@ private constructor(
|
||||
|
||||
feedbackKey()
|
||||
description()
|
||||
isAssertion()
|
||||
isRequired()
|
||||
scoreDescriptions().ifPresent { it.validate() }
|
||||
valueDescriptions().ifPresent { it.validate() }
|
||||
@@ -334,6 +387,7 @@ private constructor(
|
||||
internal fun validity(): Int =
|
||||
(if (feedbackKey.asKnown().isPresent) 1 else 0) +
|
||||
(if (description.asKnown().isPresent) 1 else 0) +
|
||||
(if (isAssertion.asKnown().isPresent) 1 else 0) +
|
||||
(if (isRequired.asKnown().isPresent) 1 else 0) +
|
||||
(scoreDescriptions.asKnown().getOrNull()?.validity() ?: 0) +
|
||||
(valueDescriptions.asKnown().getOrNull()?.validity() ?: 0)
|
||||
@@ -396,6 +450,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): ScoreDescriptions = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -495,6 +558,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): ValueDescriptions = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -544,6 +616,7 @@ private constructor(
|
||||
return other is AnnotationQueueRubricItemSchema &&
|
||||
feedbackKey == other.feedbackKey &&
|
||||
description == other.description &&
|
||||
isAssertion == other.isAssertion &&
|
||||
isRequired == other.isRequired &&
|
||||
scoreDescriptions == other.scoreDescriptions &&
|
||||
valueDescriptions == other.valueDescriptions &&
|
||||
@@ -554,6 +627,7 @@ private constructor(
|
||||
Objects.hash(
|
||||
feedbackKey,
|
||||
description,
|
||||
isAssertion,
|
||||
isRequired,
|
||||
scoreDescriptions,
|
||||
valueDescriptions,
|
||||
@@ -564,5 +638,5 @@ private constructor(
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"AnnotationQueueRubricItemSchema{feedbackKey=$feedbackKey, description=$description, isRequired=$isRequired, scoreDescriptions=$scoreDescriptions, valueDescriptions=$valueDescriptions, additionalProperties=$additionalProperties}"
|
||||
"AnnotationQueueRubricItemSchema{feedbackKey=$feedbackKey, description=$description, isAssertion=$isAssertion, isRequired=$isRequired, scoreDescriptions=$scoreDescriptions, valueDescriptions=$valueDescriptions, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
+364
-1
@@ -11,6 +11,7 @@ import com.langchain.smith.core.ExcludeMissing
|
||||
import com.langchain.smith.core.JsonField
|
||||
import com.langchain.smith.core.JsonMissing
|
||||
import com.langchain.smith.core.JsonValue
|
||||
import com.langchain.smith.core.checkKnown
|
||||
import com.langchain.smith.core.checkRequired
|
||||
import com.langchain.smith.core.toImmutable
|
||||
import com.langchain.smith.errors.LangChainInvalidDataException
|
||||
@@ -28,6 +29,7 @@ private constructor(
|
||||
private val name: JsonField<String>,
|
||||
private val queueType: JsonField<QueueType>,
|
||||
private val tenantId: JsonField<String>,
|
||||
private val assignedReviewers: JsonField<List<AssignedReviewer>>,
|
||||
private val createdAt: JsonField<OffsetDateTime>,
|
||||
private val defaultDataset: JsonField<String>,
|
||||
private val description: JsonField<String>,
|
||||
@@ -35,6 +37,7 @@ private constructor(
|
||||
private val metadata: JsonField<Metadata>,
|
||||
private val numReviewersPerItem: JsonField<Long>,
|
||||
private val reservationMinutes: JsonField<Long>,
|
||||
private val reviewerAccessMode: JsonField<String>,
|
||||
private val runRuleId: JsonField<String>,
|
||||
private val sourceRuleId: JsonField<String>,
|
||||
private val updatedAt: JsonField<OffsetDateTime>,
|
||||
@@ -49,6 +52,9 @@ private constructor(
|
||||
@ExcludeMissing
|
||||
queueType: JsonField<QueueType> = JsonMissing.of(),
|
||||
@JsonProperty("tenant_id") @ExcludeMissing tenantId: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("assigned_reviewers")
|
||||
@ExcludeMissing
|
||||
assignedReviewers: JsonField<List<AssignedReviewer>> = JsonMissing.of(),
|
||||
@JsonProperty("created_at")
|
||||
@ExcludeMissing
|
||||
createdAt: JsonField<OffsetDateTime> = JsonMissing.of(),
|
||||
@@ -68,6 +74,9 @@ private constructor(
|
||||
@JsonProperty("reservation_minutes")
|
||||
@ExcludeMissing
|
||||
reservationMinutes: JsonField<Long> = JsonMissing.of(),
|
||||
@JsonProperty("reviewer_access_mode")
|
||||
@ExcludeMissing
|
||||
reviewerAccessMode: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("run_rule_id")
|
||||
@ExcludeMissing
|
||||
runRuleId: JsonField<String> = JsonMissing.of(),
|
||||
@@ -82,6 +91,7 @@ private constructor(
|
||||
name,
|
||||
queueType,
|
||||
tenantId,
|
||||
assignedReviewers,
|
||||
createdAt,
|
||||
defaultDataset,
|
||||
description,
|
||||
@@ -89,6 +99,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
runRuleId,
|
||||
sourceRuleId,
|
||||
updatedAt,
|
||||
@@ -119,6 +130,13 @@ private constructor(
|
||||
*/
|
||||
fun tenantId(): String = tenantId.getRequired("tenant_id")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun assignedReviewers(): Optional<List<AssignedReviewer>> =
|
||||
assignedReviewers.getOptional("assigned_reviewers")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
@@ -163,6 +181,13 @@ private constructor(
|
||||
*/
|
||||
fun reservationMinutes(): Optional<Long> = reservationMinutes.getOptional("reservation_minutes")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun reviewerAccessMode(): Optional<String> =
|
||||
reviewerAccessMode.getOptional("reviewer_access_mode")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
@@ -209,6 +234,16 @@ private constructor(
|
||||
*/
|
||||
@JsonProperty("tenant_id") @ExcludeMissing fun _tenantId(): JsonField<String> = tenantId
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [assignedReviewers].
|
||||
*
|
||||
* Unlike [assignedReviewers], this method doesn't throw if the JSON field has an unexpected
|
||||
* type.
|
||||
*/
|
||||
@JsonProperty("assigned_reviewers")
|
||||
@ExcludeMissing
|
||||
fun _assignedReviewers(): JsonField<List<AssignedReviewer>> = assignedReviewers
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [createdAt].
|
||||
*
|
||||
@@ -271,6 +306,16 @@ private constructor(
|
||||
@ExcludeMissing
|
||||
fun _reservationMinutes(): JsonField<Long> = reservationMinutes
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [reviewerAccessMode].
|
||||
*
|
||||
* Unlike [reviewerAccessMode], this method doesn't throw if the JSON field has an unexpected
|
||||
* type.
|
||||
*/
|
||||
@JsonProperty("reviewer_access_mode")
|
||||
@ExcludeMissing
|
||||
fun _reviewerAccessMode(): JsonField<String> = reviewerAccessMode
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [runRuleId].
|
||||
*
|
||||
@@ -331,6 +376,7 @@ private constructor(
|
||||
private var name: JsonField<String>? = null
|
||||
private var queueType: JsonField<QueueType>? = null
|
||||
private var tenantId: JsonField<String>? = null
|
||||
private var assignedReviewers: JsonField<MutableList<AssignedReviewer>>? = null
|
||||
private var createdAt: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
private var defaultDataset: JsonField<String> = JsonMissing.of()
|
||||
private var description: JsonField<String> = JsonMissing.of()
|
||||
@@ -338,6 +384,7 @@ private constructor(
|
||||
private var metadata: JsonField<Metadata> = JsonMissing.of()
|
||||
private var numReviewersPerItem: JsonField<Long> = JsonMissing.of()
|
||||
private var reservationMinutes: JsonField<Long> = JsonMissing.of()
|
||||
private var reviewerAccessMode: JsonField<String> = JsonMissing.of()
|
||||
private var runRuleId: JsonField<String> = JsonMissing.of()
|
||||
private var sourceRuleId: JsonField<String> = JsonMissing.of()
|
||||
private var updatedAt: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
@@ -349,6 +396,7 @@ private constructor(
|
||||
name = annotationQueueSchema.name
|
||||
queueType = annotationQueueSchema.queueType
|
||||
tenantId = annotationQueueSchema.tenantId
|
||||
assignedReviewers = annotationQueueSchema.assignedReviewers.map { it.toMutableList() }
|
||||
createdAt = annotationQueueSchema.createdAt
|
||||
defaultDataset = annotationQueueSchema.defaultDataset
|
||||
description = annotationQueueSchema.description
|
||||
@@ -356,6 +404,7 @@ private constructor(
|
||||
metadata = annotationQueueSchema.metadata
|
||||
numReviewersPerItem = annotationQueueSchema.numReviewersPerItem
|
||||
reservationMinutes = annotationQueueSchema.reservationMinutes
|
||||
reviewerAccessMode = annotationQueueSchema.reviewerAccessMode
|
||||
runRuleId = annotationQueueSchema.runRuleId
|
||||
sourceRuleId = annotationQueueSchema.sourceRuleId
|
||||
updatedAt = annotationQueueSchema.updatedAt
|
||||
@@ -403,6 +452,32 @@ private constructor(
|
||||
*/
|
||||
fun tenantId(tenantId: JsonField<String>) = apply { this.tenantId = tenantId }
|
||||
|
||||
fun assignedReviewers(assignedReviewers: List<AssignedReviewer>) =
|
||||
assignedReviewers(JsonField.of(assignedReviewers))
|
||||
|
||||
/**
|
||||
* Sets [Builder.assignedReviewers] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.assignedReviewers] with a well-typed
|
||||
* `List<AssignedReviewer>` value instead. This method is primarily for setting the field to
|
||||
* an undocumented or not yet supported value.
|
||||
*/
|
||||
fun assignedReviewers(assignedReviewers: JsonField<List<AssignedReviewer>>) = apply {
|
||||
this.assignedReviewers = assignedReviewers.map { it.toMutableList() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single [AssignedReviewer] to [assignedReviewers].
|
||||
*
|
||||
* @throws IllegalStateException if the field was previously set to a non-list.
|
||||
*/
|
||||
fun addAssignedReviewer(assignedReviewer: AssignedReviewer) = apply {
|
||||
assignedReviewers =
|
||||
(assignedReviewers ?: JsonField.of(mutableListOf())).also {
|
||||
checkKnown("assignedReviewers", it).add(assignedReviewer)
|
||||
}
|
||||
}
|
||||
|
||||
fun createdAt(createdAt: OffsetDateTime) = createdAt(JsonField.of(createdAt))
|
||||
|
||||
/**
|
||||
@@ -544,6 +619,20 @@ private constructor(
|
||||
this.reservationMinutes = reservationMinutes
|
||||
}
|
||||
|
||||
fun reviewerAccessMode(reviewerAccessMode: String) =
|
||||
reviewerAccessMode(JsonField.of(reviewerAccessMode))
|
||||
|
||||
/**
|
||||
* Sets [Builder.reviewerAccessMode] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.reviewerAccessMode] with a well-typed [String] value
|
||||
* instead. This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun reviewerAccessMode(reviewerAccessMode: JsonField<String>) = apply {
|
||||
this.reviewerAccessMode = reviewerAccessMode
|
||||
}
|
||||
|
||||
fun runRuleId(runRuleId: String?) = runRuleId(JsonField.ofNullable(runRuleId))
|
||||
|
||||
/** Alias for calling [Builder.runRuleId] with `runRuleId.orElse(null)`. */
|
||||
@@ -625,6 +714,7 @@ private constructor(
|
||||
checkRequired("name", name),
|
||||
checkRequired("queueType", queueType),
|
||||
checkRequired("tenantId", tenantId),
|
||||
(assignedReviewers ?: JsonMissing.of()).map { it.toImmutable() },
|
||||
createdAt,
|
||||
defaultDataset,
|
||||
description,
|
||||
@@ -632,6 +722,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
runRuleId,
|
||||
sourceRuleId,
|
||||
updatedAt,
|
||||
@@ -641,6 +732,14 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): AnnotationQueueSchema = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -650,6 +749,7 @@ private constructor(
|
||||
name()
|
||||
queueType().validate()
|
||||
tenantId()
|
||||
assignedReviewers().ifPresent { it.forEach { it.validate() } }
|
||||
createdAt()
|
||||
defaultDataset()
|
||||
description()
|
||||
@@ -657,6 +757,7 @@ private constructor(
|
||||
metadata().ifPresent { it.validate() }
|
||||
numReviewersPerItem()
|
||||
reservationMinutes()
|
||||
reviewerAccessMode()
|
||||
runRuleId()
|
||||
sourceRuleId()
|
||||
updatedAt()
|
||||
@@ -682,6 +783,7 @@ private constructor(
|
||||
(if (name.asKnown().isPresent) 1 else 0) +
|
||||
(queueType.asKnown().getOrNull()?.validity() ?: 0) +
|
||||
(if (tenantId.asKnown().isPresent) 1 else 0) +
|
||||
(assignedReviewers.asKnown().getOrNull()?.sumOf { it.validity().toInt() } ?: 0) +
|
||||
(if (createdAt.asKnown().isPresent) 1 else 0) +
|
||||
(if (defaultDataset.asKnown().isPresent) 1 else 0) +
|
||||
(if (description.asKnown().isPresent) 1 else 0) +
|
||||
@@ -689,6 +791,7 @@ private constructor(
|
||||
(metadata.asKnown().getOrNull()?.validity() ?: 0) +
|
||||
(if (numReviewersPerItem.asKnown().isPresent) 1 else 0) +
|
||||
(if (reservationMinutes.asKnown().isPresent) 1 else 0) +
|
||||
(if (reviewerAccessMode.asKnown().isPresent) 1 else 0) +
|
||||
(if (runRuleId.asKnown().isPresent) 1 else 0) +
|
||||
(if (sourceRuleId.asKnown().isPresent) 1 else 0) +
|
||||
(if (updatedAt.asKnown().isPresent) 1 else 0)
|
||||
@@ -784,6 +887,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): QueueType = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -822,6 +934,244 @@ private constructor(
|
||||
override fun toString() = value.toString()
|
||||
}
|
||||
|
||||
/** Identity info for an assigned reviewer on an annotation queue. */
|
||||
class AssignedReviewer
|
||||
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
|
||||
private constructor(
|
||||
private val id: JsonField<String>,
|
||||
private val email: JsonField<String>,
|
||||
private val name: JsonField<String>,
|
||||
private val additionalProperties: MutableMap<String, JsonValue>,
|
||||
) {
|
||||
|
||||
@JsonCreator
|
||||
private constructor(
|
||||
@JsonProperty("id") @ExcludeMissing id: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("email") @ExcludeMissing email: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("name") @ExcludeMissing name: JsonField<String> = JsonMissing.of(),
|
||||
) : this(id, email, name, mutableMapOf())
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type or is
|
||||
* unexpectedly missing or null (e.g. if the server responded with an unexpected value).
|
||||
*/
|
||||
fun id(): String = id.getRequired("id")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
*/
|
||||
fun email(): Optional<String> = email.getOptional("email")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
*/
|
||||
fun name(): Optional<String> = name.getOptional("name")
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [id].
|
||||
*
|
||||
* Unlike [id], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("id") @ExcludeMissing fun _id(): JsonField<String> = id
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [email].
|
||||
*
|
||||
* Unlike [email], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("email") @ExcludeMissing fun _email(): JsonField<String> = email
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [name].
|
||||
*
|
||||
* Unlike [name], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("name") @ExcludeMissing fun _name(): JsonField<String> = name
|
||||
|
||||
@JsonAnySetter
|
||||
private fun putAdditionalProperty(key: String, value: JsonValue) {
|
||||
additionalProperties.put(key, value)
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
@ExcludeMissing
|
||||
fun _additionalProperties(): Map<String, JsonValue> =
|
||||
Collections.unmodifiableMap(additionalProperties)
|
||||
|
||||
fun toBuilder() = Builder().from(this)
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Returns a mutable builder for constructing an instance of [AssignedReviewer].
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .id()
|
||||
* ```
|
||||
*/
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
/** A builder for [AssignedReviewer]. */
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var id: JsonField<String>? = null
|
||||
private var email: JsonField<String> = JsonMissing.of()
|
||||
private var name: JsonField<String> = JsonMissing.of()
|
||||
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun from(assignedReviewer: AssignedReviewer) = apply {
|
||||
id = assignedReviewer.id
|
||||
email = assignedReviewer.email
|
||||
name = assignedReviewer.name
|
||||
additionalProperties = assignedReviewer.additionalProperties.toMutableMap()
|
||||
}
|
||||
|
||||
fun id(id: String) = id(JsonField.of(id))
|
||||
|
||||
/**
|
||||
* Sets [Builder.id] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.id] with a well-typed [String] value instead. This
|
||||
* method is primarily for setting the field to an undocumented or not yet supported
|
||||
* value.
|
||||
*/
|
||||
fun id(id: JsonField<String>) = apply { this.id = id }
|
||||
|
||||
fun email(email: String?) = email(JsonField.ofNullable(email))
|
||||
|
||||
/** Alias for calling [Builder.email] with `email.orElse(null)`. */
|
||||
fun email(email: Optional<String>) = email(email.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.email] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.email] with a well-typed [String] value instead.
|
||||
* This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun email(email: JsonField<String>) = apply { this.email = email }
|
||||
|
||||
fun name(name: String?) = name(JsonField.ofNullable(name))
|
||||
|
||||
/** Alias for calling [Builder.name] with `name.orElse(null)`. */
|
||||
fun name(name: Optional<String>) = name(name.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.name] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.name] with a well-typed [String] value instead. This
|
||||
* method is primarily for setting the field to an undocumented or not yet supported
|
||||
* value.
|
||||
*/
|
||||
fun name(name: JsonField<String>) = apply { this.name = name }
|
||||
|
||||
fun additionalProperties(additionalProperties: Map<String, JsonValue>) = apply {
|
||||
this.additionalProperties.clear()
|
||||
putAllAdditionalProperties(additionalProperties)
|
||||
}
|
||||
|
||||
fun putAdditionalProperty(key: String, value: JsonValue) = apply {
|
||||
additionalProperties.put(key, value)
|
||||
}
|
||||
|
||||
fun putAllAdditionalProperties(additionalProperties: Map<String, JsonValue>) = apply {
|
||||
this.additionalProperties.putAll(additionalProperties)
|
||||
}
|
||||
|
||||
fun removeAdditionalProperty(key: String) = apply { additionalProperties.remove(key) }
|
||||
|
||||
fun removeAllAdditionalProperties(keys: Set<String>) = apply {
|
||||
keys.forEach(::removeAdditionalProperty)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable instance of [AssignedReviewer].
|
||||
*
|
||||
* Further updates to this [Builder] will not mutate the returned instance.
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .id()
|
||||
* ```
|
||||
*
|
||||
* @throws IllegalStateException if any required field is unset.
|
||||
*/
|
||||
fun build(): AssignedReviewer =
|
||||
AssignedReviewer(
|
||||
checkRequired("id", id),
|
||||
email,
|
||||
name,
|
||||
additionalProperties.toMutableMap(),
|
||||
)
|
||||
}
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): AssignedReviewer = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
}
|
||||
|
||||
id()
|
||||
email()
|
||||
name()
|
||||
validated = true
|
||||
}
|
||||
|
||||
fun isValid(): Boolean =
|
||||
try {
|
||||
validate()
|
||||
true
|
||||
} catch (e: LangChainInvalidDataException) {
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a score indicating how many valid values are contained in this object
|
||||
* recursively.
|
||||
*
|
||||
* Used for best match union deserialization.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
internal fun validity(): Int =
|
||||
(if (id.asKnown().isPresent) 1 else 0) +
|
||||
(if (email.asKnown().isPresent) 1 else 0) +
|
||||
(if (name.asKnown().isPresent) 1 else 0)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
|
||||
return other is AssignedReviewer &&
|
||||
id == other.id &&
|
||||
email == other.email &&
|
||||
name == other.name &&
|
||||
additionalProperties == other.additionalProperties
|
||||
}
|
||||
|
||||
private val hashCode: Int by lazy { Objects.hash(id, email, name, additionalProperties) }
|
||||
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"AssignedReviewer{id=$id, email=$email, name=$name, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
class Metadata
|
||||
@JsonCreator
|
||||
private constructor(
|
||||
@@ -880,6 +1230,15 @@ private constructor(
|
||||
|
||||
private var validated: Boolean = false
|
||||
|
||||
/**
|
||||
* Validates that the types of all values in this object match their expected types
|
||||
* recursively.
|
||||
*
|
||||
* This method is _not_ forwards compatible with new types from the API for existing fields.
|
||||
*
|
||||
* @throws LangChainInvalidDataException if any value type in this object doesn't match its
|
||||
* expected type.
|
||||
*/
|
||||
fun validate(): Metadata = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -931,6 +1290,7 @@ private constructor(
|
||||
name == other.name &&
|
||||
queueType == other.queueType &&
|
||||
tenantId == other.tenantId &&
|
||||
assignedReviewers == other.assignedReviewers &&
|
||||
createdAt == other.createdAt &&
|
||||
defaultDataset == other.defaultDataset &&
|
||||
description == other.description &&
|
||||
@@ -938,6 +1298,7 @@ private constructor(
|
||||
metadata == other.metadata &&
|
||||
numReviewersPerItem == other.numReviewersPerItem &&
|
||||
reservationMinutes == other.reservationMinutes &&
|
||||
reviewerAccessMode == other.reviewerAccessMode &&
|
||||
runRuleId == other.runRuleId &&
|
||||
sourceRuleId == other.sourceRuleId &&
|
||||
updatedAt == other.updatedAt &&
|
||||
@@ -950,6 +1311,7 @@ private constructor(
|
||||
name,
|
||||
queueType,
|
||||
tenantId,
|
||||
assignedReviewers,
|
||||
createdAt,
|
||||
defaultDataset,
|
||||
description,
|
||||
@@ -957,6 +1319,7 @@ private constructor(
|
||||
metadata,
|
||||
numReviewersPerItem,
|
||||
reservationMinutes,
|
||||
reviewerAccessMode,
|
||||
runRuleId,
|
||||
sourceRuleId,
|
||||
updatedAt,
|
||||
@@ -967,5 +1330,5 @@ private constructor(
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"AnnotationQueueSchema{id=$id, name=$name, queueType=$queueType, tenantId=$tenantId, createdAt=$createdAt, defaultDataset=$defaultDataset, description=$description, enableReservations=$enableReservations, metadata=$metadata, numReviewersPerItem=$numReviewersPerItem, reservationMinutes=$reservationMinutes, runRuleId=$runRuleId, sourceRuleId=$sourceRuleId, updatedAt=$updatedAt, additionalProperties=$additionalProperties}"
|
||||
"AnnotationQueueSchema{id=$id, name=$name, queueType=$queueType, tenantId=$tenantId, assignedReviewers=$assignedReviewers, createdAt=$createdAt, defaultDataset=$defaultDataset, description=$description, enableReservations=$enableReservations, metadata=$metadata, numReviewersPerItem=$numReviewersPerItem, reservationMinutes=$reservationMinutes, reviewerAccessMode=$reviewerAccessMode, runRuleId=$runRuleId, sourceRuleId=$sourceRuleId, updatedAt=$updatedAt, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user