mirror of
https://github.com/langchain-ai/langsmith-java.git
synced 2026-07-25 21:25:45 -04:00
Compare commits
165 Commits
v0.1.0-alpha.27
...
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 |
@@ -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,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."
|
||||
@@ -21,14 +21,14 @@ jobs:
|
||||
lint:
|
||||
timeout-minutes: 15
|
||||
name: lint
|
||||
runs-on: ${{ github.repository == 'stainless-sdks/langsmith-api-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
|
||||
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: |
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
cache: gradle
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f # v6
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
|
||||
|
||||
- name: Run lints
|
||||
run: ./scripts/lint
|
||||
@@ -48,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' }}
|
||||
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: |
|
||||
@@ -64,47 +64,11 @@ jobs:
|
||||
cache: gradle
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f # v6
|
||||
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' &&
|
||||
!startsWith(github.ref, 'refs/heads/stl/')
|
||||
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' &&
|
||||
!startsWith(github.ref, 'refs/heads/stl/')
|
||||
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@ac2d340dc04d9e1113182899e983b5400c17cda1 # v3
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
8
|
||||
21
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f # v6
|
||||
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
|
||||
@@ -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:
|
||||
@@ -17,10 +16,10 @@ jobs:
|
||||
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: |
|
||||
@@ -36,7 +35,11 @@ jobs:
|
||||
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 }}
|
||||
|
||||
@@ -15,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
|
||||
+10
@@ -1,5 +1,15 @@
|
||||
.prism.log
|
||||
.stdy.log
|
||||
.env
|
||||
.env.*
|
||||
*.pem
|
||||
*.key
|
||||
*.crt
|
||||
credentials.json
|
||||
node_modules/
|
||||
__pycache__/
|
||||
.venv/
|
||||
.DS_Store
|
||||
.gradle
|
||||
.idea
|
||||
.kotlin
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.1.0-alpha.27"
|
||||
".": "0.1.0-beta.19"
|
||||
}
|
||||
+1
-4
@@ -1,4 +1 @@
|
||||
configured_endpoints: 103
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/langsmith%2Flangsmith-api-e0b16776094774319c261f2beb677d5d1ce7ec9550df814146ec29d72ce2c290.yml
|
||||
openapi_spec_hash: e023928a065bc07daa64d8ae211b36aa
|
||||
config_hash: c217b34d458f1b65f260715693d447f9
|
||||
configured_endpoints: 149
|
||||
|
||||
@@ -44,6 +44,10 @@ fun format(variables: Map<String, Any>): PromptMessages {
|
||||
|
||||
## 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
|
||||
@@ -229,6 +233,12 @@ 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:
|
||||
|
||||
+413
@@ -1,5 +1,418 @@
|
||||
# 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)
|
||||
|
||||
+1
-1
@@ -119,4 +119,4 @@ See [AGENTS.md](AGENTS.md) for detailed Kotlin conventions, including:
|
||||
|
||||
- **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.18.2 is the default, but the SDK is compatible with 2.13.4+. Tests compile against 2.14.0.
|
||||
- **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.27)
|
||||
[](https://javadoc.io/doc/com.langchain.smith/langsmith-java/0.1.0-alpha.27)
|
||||
[](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.27).
|
||||
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.27")
|
||||
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.27")
|
||||
<dependency>
|
||||
<groupId>com.langchain.smith</groupId>
|
||||
<artifactId>langsmith-java</artifactId>
|
||||
<version>0.1.0-alpha.27</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.27" // 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.1.20"
|
||||
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.1.20")
|
||||
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.89.0")
|
||||
palantir("com.palantir.javaformat:palantir-java-format:2.93.0")
|
||||
}
|
||||
|
||||
fun registerPalantir(
|
||||
|
||||
@@ -33,6 +33,10 @@ 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)
|
||||
}
|
||||
|
||||
@@ -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://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-9.4.1-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
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/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/.
|
||||
|
||||
Vendored
+82
-93
@@ -1,93 +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
|
||||
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
: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()
|
||||
}
|
||||
|
||||
@@ -19,38 +19,45 @@ 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.6")
|
||||
api("com.fasterxml.jackson.core:jackson-databind:2.18.6")
|
||||
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.6")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.6")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.6")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.6")
|
||||
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")
|
||||
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")
|
||||
@@ -66,29 +73,39 @@ dependencies {
|
||||
compileOnly("com.anthropic:anthropic-java:2.18.0")
|
||||
testImplementation("com.anthropic:anthropic-java:2.18.0")
|
||||
|
||||
// Security: constrain vulnerable transitive test dependencies (from wiremock-jre8).
|
||||
// Security: constrain vulnerable transitive test dependencies.
|
||||
// These constraints apply to the test scope only and do not affect published artifacts.
|
||||
constraints {
|
||||
// CVE-2024-13009, CVE-2025-5115, CVE-2024-22201, CVE-2023-36478
|
||||
testImplementation("org.eclipse.jetty:jetty-server") { version { require("9.4.57.v20241219") } }
|
||||
testImplementation("org.eclipse.jetty.http2:http2-common") { version { require("9.4.57.v20241219") } }
|
||||
testImplementation("org.eclipse.jetty.http2:http2-hpack") { version { require("9.4.57.v20241219") } }
|
||||
// CVE-2023-6481, CVE-2023-6378
|
||||
testImplementation("ch.qos.logback:logback-core") { version { require("1.2.13") } }
|
||||
testImplementation("ch.qos.logback:logback-classic") { version { require("1.2.13") } }
|
||||
// CVE-2025-48976, CVE-2023-24998
|
||||
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") } }
|
||||
// CVE-2024-47554
|
||||
testImplementation("commons-io:commons-io") { version { require("2.14.0") } }
|
||||
// CVE-2023-1370
|
||||
testImplementation("net.minidev:json-smart") { version { require("2.4.9") } }
|
||||
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")
|
||||
|
||||
+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
|
||||
}
|
||||
}
|
||||
|
||||
+125
-5
@@ -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 {
|
||||
@@ -37,7 +53,7 @@ class LangsmithClientAsyncImpl(private val clientOptions: ClientOptions) : Langs
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
+110
-3
@@ -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 {
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -506,35 +522,45 @@ private constructor(
|
||||
// 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?.let {
|
||||
if (!it.isEmpty()) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
organizationId?.let {
|
||||
if (!it.isEmpty()) {
|
||||
headers.replace("X-Organization-Id", it)
|
||||
}
|
||||
}
|
||||
bearerToken?.let {
|
||||
if (!it.isEmpty()) {
|
||||
headers.replace("Authorization", "Bearer $it")
|
||||
}
|
||||
}
|
||||
tenantId?.let {
|
||||
if (!it.isEmpty()) {
|
||||
headers.replace("X-Tenant-Id", 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,
|
||||
@@ -546,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?,
|
||||
)
|
||||
@@ -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())
|
||||
|
||||
+33
-25
@@ -7,6 +7,7 @@ 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
|
||||
@@ -32,6 +33,25 @@ 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,
|
||||
@@ -100,6 +120,18 @@ internal fun multipartFormData(
|
||||
}
|
||||
.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,
|
||||
@@ -109,31 +141,7 @@ private fun serializePart(name: String, node: JsonNode): Sequence<Pair<String, I
|
||||
JsonNodeType.BOOLEAN -> sequenceOf(name to node.booleanValue().toString().byteInputStream())
|
||||
JsonNodeType.NUMBER -> sequenceOf(name to node.numberValue().toString().byteInputStream())
|
||||
JsonNodeType.ARRAY ->
|
||||
sequenceOf(
|
||||
name to
|
||||
node
|
||||
.elements()
|
||||
.asSequence()
|
||||
.mapNotNull { element ->
|
||||
when (element.nodeType) {
|
||||
JsonNodeType.MISSING,
|
||||
JsonNodeType.NULL -> null
|
||||
JsonNodeType.STRING -> element.textValue()
|
||||
JsonNodeType.BOOLEAN -> element.booleanValue().toString()
|
||||
JsonNodeType.NUMBER -> element.numberValue().toString()
|
||||
null,
|
||||
JsonNodeType.BINARY,
|
||||
JsonNodeType.ARRAY,
|
||||
JsonNodeType.OBJECT,
|
||||
JsonNodeType.POJO ->
|
||||
throw LangChainInvalidDataException(
|
||||
"Unexpected JsonNode type in array: ${element.nodeType}"
|
||||
)
|
||||
}
|
||||
}
|
||||
.joinToString(",")
|
||||
.byteInputStream()
|
||||
)
|
||||
node.elements().asSequence().flatMap { element -> serializePart(name, element) }
|
||||
JsonNodeType.OBJECT ->
|
||||
node.fields().asSequence().flatMap { (key, value) ->
|
||||
serializePart("$name[$key]", value)
|
||||
|
||||
+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) }
|
||||
}
|
||||
|
||||
+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")
|
||||
+18
@@ -1362,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
|
||||
@@ -1522,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
|
||||
|
||||
+9
@@ -492,6 +492,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
@@ -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
|
||||
|
||||
+47
-3
@@ -25,6 +25,8 @@ 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,
|
||||
@@ -46,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. */
|
||||
@@ -78,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()
|
||||
@@ -95,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()
|
||||
@@ -170,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()
|
||||
}
|
||||
@@ -299,6 +326,8 @@ private constructor(
|
||||
nameContains,
|
||||
offset,
|
||||
queueType,
|
||||
sortBy,
|
||||
sortByDesc,
|
||||
tagValueId?.toImmutable(),
|
||||
additionalHeaders.build(),
|
||||
additionalQueryParams.build(),
|
||||
@@ -312,13 +341,15 @@ private constructor(
|
||||
.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()
|
||||
@@ -414,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
|
||||
@@ -466,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
|
||||
@@ -481,11 +523,13 @@ private constructor(
|
||||
nameContains,
|
||||
offset,
|
||||
queueType,
|
||||
sortBy,
|
||||
sortByDesc,
|
||||
tagValueId,
|
||||
additionalHeaders,
|
||||
additionalQueryParams,
|
||||
)
|
||||
|
||||
override fun toString() =
|
||||
"AnnotationQueueRetrieveAnnotationQueuesParams{assignedToMe=$assignedToMe, 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}"
|
||||
}
|
||||
|
||||
+35
@@ -772,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
|
||||
@@ -921,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
|
||||
@@ -1137,6 +1154,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(): AssignedReviewer = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -1246,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
|
||||
|
||||
+35
@@ -835,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
|
||||
@@ -986,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
|
||||
@@ -1202,6 +1219,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(): AssignedReviewer = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -1311,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
|
||||
|
||||
+9
@@ -296,6 +296,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(): Status = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+26
@@ -348,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
|
||||
@@ -442,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
|
||||
@@ -541,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
|
||||
|
||||
+35
@@ -732,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
|
||||
@@ -879,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
|
||||
@@ -1095,6 +1112,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(): AssignedReviewer = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -1204,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
|
||||
|
||||
+8
@@ -128,6 +128,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(): AnnotationQueueSizeSchema = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+104
@@ -1115,6 +1115,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
|
||||
@@ -1224,6 +1233,35 @@ private constructor(
|
||||
|
||||
fun _json(): Optional<JsonValue> = Optional.ofNullable(_json)
|
||||
|
||||
/**
|
||||
* Maps this instance's current variant to a value of type [T] using the given [visitor].
|
||||
*
|
||||
* Note that this method is _not_ forwards compatible with new variants from the API, unless
|
||||
* [visitor] overrides [Visitor.unknown]. To handle variants not known to this version of
|
||||
* the SDK gracefully, consider overriding [Visitor.unknown]:
|
||||
* ```java
|
||||
* import com.langchain.smith.core.JsonValue;
|
||||
* import java.util.Optional;
|
||||
*
|
||||
* Optional<String> result = metadata.accept(new Metadata.Visitor<Optional<String>>() {
|
||||
* @Override
|
||||
* public Optional<String> visitUnionMember0(UnionMember0 unionMember0) {
|
||||
* return Optional.of(unionMember0.toString());
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
*
|
||||
* @Override
|
||||
* public Optional<String> unknown(JsonValue json) {
|
||||
* // Or inspect the `json`.
|
||||
* return Optional.empty();
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @throws LangChainInvalidDataException if [Visitor.unknown] is not overridden in [visitor]
|
||||
* and the current variant is unknown.
|
||||
*/
|
||||
fun <T> accept(visitor: Visitor<T>): T =
|
||||
when {
|
||||
unionMember0 != null -> visitor.visitUnionMember0(unionMember0)
|
||||
@@ -1233,6 +1271,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
|
||||
@@ -1438,6 +1485,16 @@ 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(): UnionMember0 = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -1503,6 +1560,35 @@ private constructor(
|
||||
|
||||
fun _json(): Optional<JsonValue> = Optional.ofNullable(_json)
|
||||
|
||||
/**
|
||||
* Maps this instance's current variant to a value of type [T] using the given [visitor].
|
||||
*
|
||||
* Note that this method is _not_ forwards compatible with new variants from the API, unless
|
||||
* [visitor] overrides [Visitor.unknown]. To handle variants not known to this version of
|
||||
* the SDK gracefully, consider overriding [Visitor.unknown]:
|
||||
* ```java
|
||||
* import com.langchain.smith.core.JsonValue;
|
||||
* import java.util.Optional;
|
||||
*
|
||||
* Optional<String> result = numReviewersPerItem.accept(new NumReviewersPerItem.Visitor<Optional<String>>() {
|
||||
* @Override
|
||||
* public Optional<String> visitInteger(Long integer) {
|
||||
* return Optional.of(integer.toString());
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
*
|
||||
* @Override
|
||||
* public Optional<String> unknown(JsonValue json) {
|
||||
* // Or inspect the `json`.
|
||||
* return Optional.empty();
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @throws LangChainInvalidDataException if [Visitor.unknown] is not overridden in [visitor]
|
||||
* and the current variant is unknown.
|
||||
*/
|
||||
fun <T> accept(visitor: Visitor<T>): T =
|
||||
when {
|
||||
integer != null -> visitor.visitInteger(integer)
|
||||
@@ -1512,6 +1598,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(): NumReviewersPerItem = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -1750,6 +1845,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(): ReviewerAccessMode = 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(): AnnotationQueueUpdateResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+194
-1
@@ -76,6 +76,7 @@ private constructor(
|
||||
private val s3Urls: JsonField<S3Urls>,
|
||||
private val serialized: JsonField<Serialized>,
|
||||
private val shareToken: JsonField<String>,
|
||||
private val sourceProposedExampleId: JsonField<String>,
|
||||
private val startTime: JsonField<OffsetDateTime>,
|
||||
private val tags: JsonField<List<String>>,
|
||||
private val threadId: JsonField<String>,
|
||||
@@ -218,6 +219,9 @@ private constructor(
|
||||
@JsonProperty("share_token")
|
||||
@ExcludeMissing
|
||||
shareToken: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("source_proposed_example_id")
|
||||
@ExcludeMissing
|
||||
sourceProposedExampleId: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("start_time")
|
||||
@ExcludeMissing
|
||||
startTime: JsonField<OffsetDateTime> = JsonMissing.of(),
|
||||
@@ -294,6 +298,7 @@ private constructor(
|
||||
s3Urls,
|
||||
serialized,
|
||||
shareToken,
|
||||
sourceProposedExampleId,
|
||||
startTime,
|
||||
tags,
|
||||
threadId,
|
||||
@@ -619,6 +624,13 @@ private constructor(
|
||||
*/
|
||||
fun shareToken(): Optional<String> = shareToken.getOptional("share_token")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun sourceProposedExampleId(): Optional<String> =
|
||||
sourceProposedExampleId.getOptional("source_proposed_example_id")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
@@ -1106,6 +1118,16 @@ private constructor(
|
||||
*/
|
||||
@JsonProperty("share_token") @ExcludeMissing fun _shareToken(): JsonField<String> = shareToken
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [sourceProposedExampleId].
|
||||
*
|
||||
* Unlike [sourceProposedExampleId], this method doesn't throw if the JSON field has an
|
||||
* unexpected type.
|
||||
*/
|
||||
@JsonProperty("source_proposed_example_id")
|
||||
@ExcludeMissing
|
||||
fun _sourceProposedExampleId(): JsonField<String> = sourceProposedExampleId
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [startTime].
|
||||
*
|
||||
@@ -1283,6 +1305,7 @@ private constructor(
|
||||
private var s3Urls: JsonField<S3Urls> = JsonMissing.of()
|
||||
private var serialized: JsonField<Serialized> = JsonMissing.of()
|
||||
private var shareToken: JsonField<String> = JsonMissing.of()
|
||||
private var sourceProposedExampleId: JsonField<String> = JsonMissing.of()
|
||||
private var startTime: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
private var tags: JsonField<MutableList<String>>? = null
|
||||
private var threadId: JsonField<String> = JsonMissing.of()
|
||||
@@ -1353,6 +1376,7 @@ private constructor(
|
||||
s3Urls = runSchemaWithAnnotationQueueInfo.s3Urls
|
||||
serialized = runSchemaWithAnnotationQueueInfo.serialized
|
||||
shareToken = runSchemaWithAnnotationQueueInfo.shareToken
|
||||
sourceProposedExampleId = runSchemaWithAnnotationQueueInfo.sourceProposedExampleId
|
||||
startTime = runSchemaWithAnnotationQueueInfo.startTime
|
||||
tags = runSchemaWithAnnotationQueueInfo.tags.map { it.toMutableList() }
|
||||
threadId = runSchemaWithAnnotationQueueInfo.threadId
|
||||
@@ -2212,6 +2236,27 @@ private constructor(
|
||||
*/
|
||||
fun shareToken(shareToken: JsonField<String>) = apply { this.shareToken = shareToken }
|
||||
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: String?) =
|
||||
sourceProposedExampleId(JsonField.ofNullable(sourceProposedExampleId))
|
||||
|
||||
/**
|
||||
* Alias for calling [Builder.sourceProposedExampleId] with
|
||||
* `sourceProposedExampleId.orElse(null)`.
|
||||
*/
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: Optional<String>) =
|
||||
sourceProposedExampleId(sourceProposedExampleId.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.sourceProposedExampleId] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.sourceProposedExampleId] with a well-typed [String]
|
||||
* value instead. This method is primarily for setting the field to an undocumented or not
|
||||
* yet supported value.
|
||||
*/
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: JsonField<String>) = apply {
|
||||
this.sourceProposedExampleId = sourceProposedExampleId
|
||||
}
|
||||
|
||||
fun startTime(startTime: OffsetDateTime) = startTime(JsonField.of(startTime))
|
||||
|
||||
/**
|
||||
@@ -2481,6 +2526,7 @@ private constructor(
|
||||
s3Urls,
|
||||
serialized,
|
||||
shareToken,
|
||||
sourceProposedExampleId,
|
||||
startTime,
|
||||
(tags ?: JsonMissing.of()).map { it.toImmutable() },
|
||||
threadId,
|
||||
@@ -2498,6 +2544,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(): RunSchemaWithAnnotationQueueInfo = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -2553,6 +2607,7 @@ private constructor(
|
||||
s3Urls().ifPresent { it.validate() }
|
||||
serialized().ifPresent { it.validate() }
|
||||
shareToken()
|
||||
sourceProposedExampleId()
|
||||
startTime()
|
||||
tags()
|
||||
threadId()
|
||||
@@ -2632,6 +2687,7 @@ private constructor(
|
||||
(s3Urls.asKnown().getOrNull()?.validity() ?: 0) +
|
||||
(serialized.asKnown().getOrNull()?.validity() ?: 0) +
|
||||
(if (shareToken.asKnown().isPresent) 1 else 0) +
|
||||
(if (sourceProposedExampleId.asKnown().isPresent) 1 else 0) +
|
||||
(if (startTime.asKnown().isPresent) 1 else 0) +
|
||||
(tags.asKnown().getOrNull()?.size ?: 0) +
|
||||
(if (threadId.asKnown().isPresent) 1 else 0) +
|
||||
@@ -2705,6 +2761,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(): CompletionCostDetails = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -2809,6 +2874,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(): CompletionTokenDetails = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -2910,6 +2984,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(): Event = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -3009,6 +3092,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(): Extra = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -3108,6 +3200,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(): FeedbackStats = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -3207,6 +3308,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(): Inputs = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -3306,6 +3416,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(): InputsS3Urls = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -3405,6 +3524,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(): Message = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -3504,6 +3632,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(): Outputs = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -3603,6 +3740,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(): OutputsS3Urls = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -3702,6 +3848,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(): PromptCostDetails = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -3801,6 +3956,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(): PromptTokenDetails = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -3900,6 +4064,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(): S3Urls = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -3999,6 +4172,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(): Serialized = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -4131,6 +4313,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(): TraceTier = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -4225,6 +4416,7 @@ private constructor(
|
||||
s3Urls == other.s3Urls &&
|
||||
serialized == other.serialized &&
|
||||
shareToken == other.shareToken &&
|
||||
sourceProposedExampleId == other.sourceProposedExampleId &&
|
||||
startTime == other.startTime &&
|
||||
tags == other.tags &&
|
||||
threadId == other.threadId &&
|
||||
@@ -4291,6 +4483,7 @@ private constructor(
|
||||
s3Urls,
|
||||
serialized,
|
||||
shareToken,
|
||||
sourceProposedExampleId,
|
||||
startTime,
|
||||
tags,
|
||||
threadId,
|
||||
@@ -4309,5 +4502,5 @@ private constructor(
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"RunSchemaWithAnnotationQueueInfo{id=$id, appPath=$appPath, dottedOrder=$dottedOrder, name=$name, queueRunId=$queueRunId, runType=$runType, sessionId=$sessionId, status=$status, traceId=$traceId, addedAt=$addedAt, childRunIds=$childRunIds, completedBy=$completedBy, completionCost=$completionCost, completionCostDetails=$completionCostDetails, completionTokenDetails=$completionTokenDetails, completionTokens=$completionTokens, directChildRunIds=$directChildRunIds, effectiveAddedAt=$effectiveAddedAt, endTime=$endTime, error=$error, events=$events, executionOrder=$executionOrder, extra=$extra, feedbackStats=$feedbackStats, firstTokenTime=$firstTokenTime, inDataset=$inDataset, inputs=$inputs, inputsPreview=$inputsPreview, inputsS3Urls=$inputsS3Urls, lastQueuedAt=$lastQueuedAt, lastReviewedTime=$lastReviewedTime, manifestId=$manifestId, manifestS3Id=$manifestS3Id, messages=$messages, outputs=$outputs, outputsPreview=$outputsPreview, outputsS3Urls=$outputsS3Urls, parentRunId=$parentRunId, parentRunIds=$parentRunIds, priceModelId=$priceModelId, promptCost=$promptCost, promptCostDetails=$promptCostDetails, promptTokenDetails=$promptTokenDetails, promptTokens=$promptTokens, referenceDatasetId=$referenceDatasetId, referenceExampleId=$referenceExampleId, reservedBy=$reservedBy, s3Urls=$s3Urls, serialized=$serialized, shareToken=$shareToken, startTime=$startTime, tags=$tags, threadId=$threadId, totalCost=$totalCost, totalTokens=$totalTokens, traceFirstReceivedAt=$traceFirstReceivedAt, traceMaxStartTime=$traceMaxStartTime, traceMinStartTime=$traceMinStartTime, traceTier=$traceTier, traceUpgrade=$traceUpgrade, ttlSeconds=$ttlSeconds, additionalProperties=$additionalProperties}"
|
||||
"RunSchemaWithAnnotationQueueInfo{id=$id, appPath=$appPath, dottedOrder=$dottedOrder, name=$name, queueRunId=$queueRunId, runType=$runType, sessionId=$sessionId, status=$status, traceId=$traceId, addedAt=$addedAt, childRunIds=$childRunIds, completedBy=$completedBy, completionCost=$completionCost, completionCostDetails=$completionCostDetails, completionTokenDetails=$completionTokenDetails, completionTokens=$completionTokens, directChildRunIds=$directChildRunIds, effectiveAddedAt=$effectiveAddedAt, endTime=$endTime, error=$error, events=$events, executionOrder=$executionOrder, extra=$extra, feedbackStats=$feedbackStats, firstTokenTime=$firstTokenTime, inDataset=$inDataset, inputs=$inputs, inputsPreview=$inputsPreview, inputsS3Urls=$inputsS3Urls, lastQueuedAt=$lastQueuedAt, lastReviewedTime=$lastReviewedTime, manifestId=$manifestId, manifestS3Id=$manifestS3Id, messages=$messages, outputs=$outputs, outputsPreview=$outputsPreview, outputsS3Urls=$outputsS3Urls, parentRunId=$parentRunId, parentRunIds=$parentRunIds, priceModelId=$priceModelId, promptCost=$promptCost, promptCostDetails=$promptCostDetails, promptTokenDetails=$promptTokenDetails, promptTokens=$promptTokens, referenceDatasetId=$referenceDatasetId, referenceExampleId=$referenceExampleId, reservedBy=$reservedBy, s3Urls=$s3Urls, serialized=$serialized, shareToken=$shareToken, sourceProposedExampleId=$sourceProposedExampleId, startTime=$startTime, tags=$tags, threadId=$threadId, totalCost=$totalCost, totalTokens=$totalTokens, traceFirstReceivedAt=$traceFirstReceivedAt, traceMaxStartTime=$traceMaxStartTime, traceMinStartTime=$traceMinStartTime, traceTier=$traceTier, traceUpgrade=$traceUpgrade, ttlSeconds=$ttlSeconds, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
+565
@@ -0,0 +1,565 @@
|
||||
// File generated from our OpenAPI spec by Stainless.
|
||||
|
||||
package com.langchain.smith.models.annotationqueues.runs
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter
|
||||
import com.fasterxml.jackson.annotation.JsonCreator
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
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.Params
|
||||
import com.langchain.smith.core.checkRequired
|
||||
import com.langchain.smith.core.http.Headers
|
||||
import com.langchain.smith.core.http.QueryParams
|
||||
import com.langchain.smith.core.toImmutable
|
||||
import com.langchain.smith.errors.LangChainInvalidDataException
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.Collections
|
||||
import java.util.Objects
|
||||
import java.util.Optional
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
/** Add Runs To Annotation Queue By Key */
|
||||
class RunCreateByKeyParams
|
||||
private constructor(
|
||||
private val queueId: String?,
|
||||
private val extendTraceRetention: Boolean?,
|
||||
private val body: List<Body>,
|
||||
private val additionalHeaders: Headers,
|
||||
private val additionalQueryParams: QueryParams,
|
||||
) : Params {
|
||||
|
||||
fun queueId(): Optional<String> = Optional.ofNullable(queueId)
|
||||
|
||||
fun extendTraceRetention(): Optional<Boolean> = Optional.ofNullable(extendTraceRetention)
|
||||
|
||||
fun body(): List<Body> = body
|
||||
|
||||
/** Additional headers to send with the request. */
|
||||
fun _additionalHeaders(): Headers = additionalHeaders
|
||||
|
||||
/** Additional query param to send with the request. */
|
||||
fun _additionalQueryParams(): QueryParams = additionalQueryParams
|
||||
|
||||
fun toBuilder() = Builder().from(this)
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Returns a mutable builder for constructing an instance of [RunCreateByKeyParams].
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .body()
|
||||
* ```
|
||||
*/
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
/** A builder for [RunCreateByKeyParams]. */
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var queueId: String? = null
|
||||
private var extendTraceRetention: Boolean? = null
|
||||
private var body: MutableList<Body>? = null
|
||||
private var additionalHeaders: Headers.Builder = Headers.builder()
|
||||
private var additionalQueryParams: QueryParams.Builder = QueryParams.builder()
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun from(runCreateByKeyParams: RunCreateByKeyParams) = apply {
|
||||
queueId = runCreateByKeyParams.queueId
|
||||
extendTraceRetention = runCreateByKeyParams.extendTraceRetention
|
||||
body = runCreateByKeyParams.body.toMutableList()
|
||||
additionalHeaders = runCreateByKeyParams.additionalHeaders.toBuilder()
|
||||
additionalQueryParams = runCreateByKeyParams.additionalQueryParams.toBuilder()
|
||||
}
|
||||
|
||||
fun queueId(queueId: String?) = apply { this.queueId = queueId }
|
||||
|
||||
/** Alias for calling [Builder.queueId] with `queueId.orElse(null)`. */
|
||||
fun queueId(queueId: Optional<String>) = queueId(queueId.getOrNull())
|
||||
|
||||
fun extendTraceRetention(extendTraceRetention: Boolean?) = apply {
|
||||
this.extendTraceRetention = extendTraceRetention
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for [Builder.extendTraceRetention].
|
||||
*
|
||||
* This unboxed primitive overload exists for backwards compatibility.
|
||||
*/
|
||||
fun extendTraceRetention(extendTraceRetention: Boolean) =
|
||||
extendTraceRetention(extendTraceRetention as Boolean?)
|
||||
|
||||
/**
|
||||
* Alias for calling [Builder.extendTraceRetention] with
|
||||
* `extendTraceRetention.orElse(null)`.
|
||||
*/
|
||||
fun extendTraceRetention(extendTraceRetention: Optional<Boolean>) =
|
||||
extendTraceRetention(extendTraceRetention.getOrNull())
|
||||
|
||||
fun body(body: List<Body>) = apply { this.body = body.toMutableList() }
|
||||
|
||||
/**
|
||||
* Adds a single [Body] to [Builder.body].
|
||||
*
|
||||
* @throws IllegalStateException if the field was previously set to a non-list.
|
||||
*/
|
||||
fun addBody(body: Body) = apply {
|
||||
this.body = (this.body ?: mutableListOf()).apply { add(body) }
|
||||
}
|
||||
|
||||
fun additionalHeaders(additionalHeaders: Headers) = apply {
|
||||
this.additionalHeaders.clear()
|
||||
putAllAdditionalHeaders(additionalHeaders)
|
||||
}
|
||||
|
||||
fun additionalHeaders(additionalHeaders: Map<String, Iterable<String>>) = apply {
|
||||
this.additionalHeaders.clear()
|
||||
putAllAdditionalHeaders(additionalHeaders)
|
||||
}
|
||||
|
||||
fun putAdditionalHeader(name: String, value: String) = apply {
|
||||
additionalHeaders.put(name, value)
|
||||
}
|
||||
|
||||
fun putAdditionalHeaders(name: String, values: Iterable<String>) = apply {
|
||||
additionalHeaders.put(name, values)
|
||||
}
|
||||
|
||||
fun putAllAdditionalHeaders(additionalHeaders: Headers) = apply {
|
||||
this.additionalHeaders.putAll(additionalHeaders)
|
||||
}
|
||||
|
||||
fun putAllAdditionalHeaders(additionalHeaders: Map<String, Iterable<String>>) = apply {
|
||||
this.additionalHeaders.putAll(additionalHeaders)
|
||||
}
|
||||
|
||||
fun replaceAdditionalHeaders(name: String, value: String) = apply {
|
||||
additionalHeaders.replace(name, value)
|
||||
}
|
||||
|
||||
fun replaceAdditionalHeaders(name: String, values: Iterable<String>) = apply {
|
||||
additionalHeaders.replace(name, values)
|
||||
}
|
||||
|
||||
fun replaceAllAdditionalHeaders(additionalHeaders: Headers) = apply {
|
||||
this.additionalHeaders.replaceAll(additionalHeaders)
|
||||
}
|
||||
|
||||
fun replaceAllAdditionalHeaders(additionalHeaders: Map<String, Iterable<String>>) = apply {
|
||||
this.additionalHeaders.replaceAll(additionalHeaders)
|
||||
}
|
||||
|
||||
fun removeAdditionalHeaders(name: String) = apply { additionalHeaders.remove(name) }
|
||||
|
||||
fun removeAllAdditionalHeaders(names: Set<String>) = apply {
|
||||
additionalHeaders.removeAll(names)
|
||||
}
|
||||
|
||||
fun additionalQueryParams(additionalQueryParams: QueryParams) = apply {
|
||||
this.additionalQueryParams.clear()
|
||||
putAllAdditionalQueryParams(additionalQueryParams)
|
||||
}
|
||||
|
||||
fun additionalQueryParams(additionalQueryParams: Map<String, Iterable<String>>) = apply {
|
||||
this.additionalQueryParams.clear()
|
||||
putAllAdditionalQueryParams(additionalQueryParams)
|
||||
}
|
||||
|
||||
fun putAdditionalQueryParam(key: String, value: String) = apply {
|
||||
additionalQueryParams.put(key, value)
|
||||
}
|
||||
|
||||
fun putAdditionalQueryParams(key: String, values: Iterable<String>) = apply {
|
||||
additionalQueryParams.put(key, values)
|
||||
}
|
||||
|
||||
fun putAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply {
|
||||
this.additionalQueryParams.putAll(additionalQueryParams)
|
||||
}
|
||||
|
||||
fun putAllAdditionalQueryParams(additionalQueryParams: Map<String, Iterable<String>>) =
|
||||
apply {
|
||||
this.additionalQueryParams.putAll(additionalQueryParams)
|
||||
}
|
||||
|
||||
fun replaceAdditionalQueryParams(key: String, value: String) = apply {
|
||||
additionalQueryParams.replace(key, value)
|
||||
}
|
||||
|
||||
fun replaceAdditionalQueryParams(key: String, values: Iterable<String>) = apply {
|
||||
additionalQueryParams.replace(key, values)
|
||||
}
|
||||
|
||||
fun replaceAllAdditionalQueryParams(additionalQueryParams: QueryParams) = apply {
|
||||
this.additionalQueryParams.replaceAll(additionalQueryParams)
|
||||
}
|
||||
|
||||
fun replaceAllAdditionalQueryParams(additionalQueryParams: Map<String, Iterable<String>>) =
|
||||
apply {
|
||||
this.additionalQueryParams.replaceAll(additionalQueryParams)
|
||||
}
|
||||
|
||||
fun removeAdditionalQueryParams(key: String) = apply { additionalQueryParams.remove(key) }
|
||||
|
||||
fun removeAllAdditionalQueryParams(keys: Set<String>) = apply {
|
||||
additionalQueryParams.removeAll(keys)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable instance of [RunCreateByKeyParams].
|
||||
*
|
||||
* Further updates to this [Builder] will not mutate the returned instance.
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .body()
|
||||
* ```
|
||||
*
|
||||
* @throws IllegalStateException if any required field is unset.
|
||||
*/
|
||||
fun build(): RunCreateByKeyParams =
|
||||
RunCreateByKeyParams(
|
||||
queueId,
|
||||
extendTraceRetention,
|
||||
checkRequired("body", body).toImmutable(),
|
||||
additionalHeaders.build(),
|
||||
additionalQueryParams.build(),
|
||||
)
|
||||
}
|
||||
|
||||
fun _body(): List<Body> = body
|
||||
|
||||
fun _pathParam(index: Int): String =
|
||||
when (index) {
|
||||
0 -> queueId ?: ""
|
||||
else -> ""
|
||||
}
|
||||
|
||||
override fun _headers(): Headers = additionalHeaders
|
||||
|
||||
override fun _queryParams(): QueryParams =
|
||||
QueryParams.builder()
|
||||
.apply {
|
||||
extendTraceRetention?.let { put("extend_trace_retention", it.toString()) }
|
||||
putAll(additionalQueryParams)
|
||||
}
|
||||
.build()
|
||||
|
||||
/** Add run to AQ by SmithDB key. is_root derived server-side (LSAQ-141). */
|
||||
class Body
|
||||
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
|
||||
private constructor(
|
||||
private val runId: JsonField<String>,
|
||||
private val sessionId: JsonField<String>,
|
||||
private val startTime: JsonField<OffsetDateTime>,
|
||||
private val sourceProposedExampleId: JsonField<String>,
|
||||
private val additionalProperties: MutableMap<String, JsonValue>,
|
||||
) {
|
||||
|
||||
@JsonCreator
|
||||
private constructor(
|
||||
@JsonProperty("run_id") @ExcludeMissing runId: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("session_id")
|
||||
@ExcludeMissing
|
||||
sessionId: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("start_time")
|
||||
@ExcludeMissing
|
||||
startTime: JsonField<OffsetDateTime> = JsonMissing.of(),
|
||||
@JsonProperty("source_proposed_example_id")
|
||||
@ExcludeMissing
|
||||
sourceProposedExampleId: JsonField<String> = JsonMissing.of(),
|
||||
) : this(runId, sessionId, startTime, sourceProposedExampleId, 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 runId(): String = runId.getRequired("run_id")
|
||||
|
||||
/**
|
||||
* @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 sessionId(): String = sessionId.getRequired("session_id")
|
||||
|
||||
/**
|
||||
* @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 startTime(): OffsetDateTime = startTime.getRequired("start_time")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if
|
||||
* the server responded with an unexpected value).
|
||||
*/
|
||||
fun sourceProposedExampleId(): Optional<String> =
|
||||
sourceProposedExampleId.getOptional("source_proposed_example_id")
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [runId].
|
||||
*
|
||||
* Unlike [runId], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("run_id") @ExcludeMissing fun _runId(): JsonField<String> = runId
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [sessionId].
|
||||
*
|
||||
* Unlike [sessionId], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("session_id") @ExcludeMissing fun _sessionId(): JsonField<String> = sessionId
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [startTime].
|
||||
*
|
||||
* Unlike [startTime], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("start_time")
|
||||
@ExcludeMissing
|
||||
fun _startTime(): JsonField<OffsetDateTime> = startTime
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [sourceProposedExampleId].
|
||||
*
|
||||
* Unlike [sourceProposedExampleId], this method doesn't throw if the JSON field has an
|
||||
* unexpected type.
|
||||
*/
|
||||
@JsonProperty("source_proposed_example_id")
|
||||
@ExcludeMissing
|
||||
fun _sourceProposedExampleId(): JsonField<String> = sourceProposedExampleId
|
||||
|
||||
@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 [Body].
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .runId()
|
||||
* .sessionId()
|
||||
* .startTime()
|
||||
* ```
|
||||
*/
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
/** A builder for [Body]. */
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var runId: JsonField<String>? = null
|
||||
private var sessionId: JsonField<String>? = null
|
||||
private var startTime: JsonField<OffsetDateTime>? = null
|
||||
private var sourceProposedExampleId: JsonField<String> = JsonMissing.of()
|
||||
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun from(body: Body) = apply {
|
||||
runId = body.runId
|
||||
sessionId = body.sessionId
|
||||
startTime = body.startTime
|
||||
sourceProposedExampleId = body.sourceProposedExampleId
|
||||
additionalProperties = body.additionalProperties.toMutableMap()
|
||||
}
|
||||
|
||||
fun runId(runId: String) = runId(JsonField.of(runId))
|
||||
|
||||
/**
|
||||
* Sets [Builder.runId] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.runId] with a well-typed [String] value instead.
|
||||
* This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun runId(runId: JsonField<String>) = apply { this.runId = runId }
|
||||
|
||||
fun sessionId(sessionId: String) = sessionId(JsonField.of(sessionId))
|
||||
|
||||
/**
|
||||
* Sets [Builder.sessionId] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.sessionId] with a well-typed [String] value instead.
|
||||
* This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun sessionId(sessionId: JsonField<String>) = apply { this.sessionId = sessionId }
|
||||
|
||||
fun startTime(startTime: OffsetDateTime) = startTime(JsonField.of(startTime))
|
||||
|
||||
/**
|
||||
* Sets [Builder.startTime] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.startTime] with a well-typed [OffsetDateTime] value
|
||||
* instead. This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun startTime(startTime: JsonField<OffsetDateTime>) = apply {
|
||||
this.startTime = startTime
|
||||
}
|
||||
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: String?) =
|
||||
sourceProposedExampleId(JsonField.ofNullable(sourceProposedExampleId))
|
||||
|
||||
/**
|
||||
* Alias for calling [Builder.sourceProposedExampleId] with
|
||||
* `sourceProposedExampleId.orElse(null)`.
|
||||
*/
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: Optional<String>) =
|
||||
sourceProposedExampleId(sourceProposedExampleId.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.sourceProposedExampleId] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.sourceProposedExampleId] with a well-typed [String]
|
||||
* value instead. This method is primarily for setting the field to an undocumented or
|
||||
* not yet supported value.
|
||||
*/
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: JsonField<String>) = apply {
|
||||
this.sourceProposedExampleId = sourceProposedExampleId
|
||||
}
|
||||
|
||||
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 [Body].
|
||||
*
|
||||
* Further updates to this [Builder] will not mutate the returned instance.
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .runId()
|
||||
* .sessionId()
|
||||
* .startTime()
|
||||
* ```
|
||||
*
|
||||
* @throws IllegalStateException if any required field is unset.
|
||||
*/
|
||||
fun build(): Body =
|
||||
Body(
|
||||
checkRequired("runId", runId),
|
||||
checkRequired("sessionId", sessionId),
|
||||
checkRequired("startTime", startTime),
|
||||
sourceProposedExampleId,
|
||||
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
|
||||
}
|
||||
|
||||
runId()
|
||||
sessionId()
|
||||
startTime()
|
||||
sourceProposedExampleId()
|
||||
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 (runId.asKnown().isPresent) 1 else 0) +
|
||||
(if (sessionId.asKnown().isPresent) 1 else 0) +
|
||||
(if (startTime.asKnown().isPresent) 1 else 0) +
|
||||
(if (sourceProposedExampleId.asKnown().isPresent) 1 else 0)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
|
||||
return other is Body &&
|
||||
runId == other.runId &&
|
||||
sessionId == other.sessionId &&
|
||||
startTime == other.startTime &&
|
||||
sourceProposedExampleId == other.sourceProposedExampleId &&
|
||||
additionalProperties == other.additionalProperties
|
||||
}
|
||||
|
||||
private val hashCode: Int by lazy {
|
||||
Objects.hash(runId, sessionId, startTime, sourceProposedExampleId, additionalProperties)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"Body{runId=$runId, sessionId=$sessionId, startTime=$startTime, sourceProposedExampleId=$sourceProposedExampleId, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
|
||||
return other is RunCreateByKeyParams &&
|
||||
queueId == other.queueId &&
|
||||
extendTraceRetention == other.extendTraceRetention &&
|
||||
body == other.body &&
|
||||
additionalHeaders == other.additionalHeaders &&
|
||||
additionalQueryParams == other.additionalQueryParams
|
||||
}
|
||||
|
||||
override fun hashCode(): Int =
|
||||
Objects.hash(queueId, extendTraceRetention, body, additionalHeaders, additionalQueryParams)
|
||||
|
||||
override fun toString() =
|
||||
"RunCreateByKeyParams{queueId=$queueId, extendTraceRetention=$extendTraceRetention, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
|
||||
}
|
||||
+386
@@ -0,0 +1,386 @@
|
||||
// File generated from our OpenAPI spec by Stainless.
|
||||
|
||||
package com.langchain.smith.models.annotationqueues.runs
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter
|
||||
import com.fasterxml.jackson.annotation.JsonCreator
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
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.checkRequired
|
||||
import com.langchain.smith.errors.LangChainInvalidDataException
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.Collections
|
||||
import java.util.Objects
|
||||
import java.util.Optional
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
class RunCreateByKeyResponse
|
||||
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
|
||||
private constructor(
|
||||
private val id: JsonField<String>,
|
||||
private val queueId: JsonField<String>,
|
||||
private val runId: JsonField<String>,
|
||||
private val addedAt: JsonField<OffsetDateTime>,
|
||||
private val lastReviewedTime: JsonField<OffsetDateTime>,
|
||||
private val sourceProposedExampleId: JsonField<String>,
|
||||
private val additionalProperties: MutableMap<String, JsonValue>,
|
||||
) {
|
||||
|
||||
@JsonCreator
|
||||
private constructor(
|
||||
@JsonProperty("id") @ExcludeMissing id: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("queue_id") @ExcludeMissing queueId: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("run_id") @ExcludeMissing runId: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("added_at")
|
||||
@ExcludeMissing
|
||||
addedAt: JsonField<OffsetDateTime> = JsonMissing.of(),
|
||||
@JsonProperty("last_reviewed_time")
|
||||
@ExcludeMissing
|
||||
lastReviewedTime: JsonField<OffsetDateTime> = JsonMissing.of(),
|
||||
@JsonProperty("source_proposed_example_id")
|
||||
@ExcludeMissing
|
||||
sourceProposedExampleId: JsonField<String> = JsonMissing.of(),
|
||||
) : this(id, queueId, runId, addedAt, lastReviewedTime, sourceProposedExampleId, 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 or is
|
||||
* unexpectedly missing or null (e.g. if the server responded with an unexpected value).
|
||||
*/
|
||||
fun queueId(): String = queueId.getRequired("queue_id")
|
||||
|
||||
/**
|
||||
* @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 runId(): String = runId.getRequired("run_id")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun addedAt(): Optional<OffsetDateTime> = addedAt.getOptional("added_at")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun lastReviewedTime(): Optional<OffsetDateTime> =
|
||||
lastReviewedTime.getOptional("last_reviewed_time")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun sourceProposedExampleId(): Optional<String> =
|
||||
sourceProposedExampleId.getOptional("source_proposed_example_id")
|
||||
|
||||
/**
|
||||
* 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 [queueId].
|
||||
*
|
||||
* Unlike [queueId], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("queue_id") @ExcludeMissing fun _queueId(): JsonField<String> = queueId
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [runId].
|
||||
*
|
||||
* Unlike [runId], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("run_id") @ExcludeMissing fun _runId(): JsonField<String> = runId
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [addedAt].
|
||||
*
|
||||
* Unlike [addedAt], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("added_at") @ExcludeMissing fun _addedAt(): JsonField<OffsetDateTime> = addedAt
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [lastReviewedTime].
|
||||
*
|
||||
* Unlike [lastReviewedTime], this method doesn't throw if the JSON field has an unexpected
|
||||
* type.
|
||||
*/
|
||||
@JsonProperty("last_reviewed_time")
|
||||
@ExcludeMissing
|
||||
fun _lastReviewedTime(): JsonField<OffsetDateTime> = lastReviewedTime
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [sourceProposedExampleId].
|
||||
*
|
||||
* Unlike [sourceProposedExampleId], this method doesn't throw if the JSON field has an
|
||||
* unexpected type.
|
||||
*/
|
||||
@JsonProperty("source_proposed_example_id")
|
||||
@ExcludeMissing
|
||||
fun _sourceProposedExampleId(): JsonField<String> = sourceProposedExampleId
|
||||
|
||||
@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 [RunCreateByKeyResponse].
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .id()
|
||||
* .queueId()
|
||||
* .runId()
|
||||
* ```
|
||||
*/
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
/** A builder for [RunCreateByKeyResponse]. */
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var id: JsonField<String>? = null
|
||||
private var queueId: JsonField<String>? = null
|
||||
private var runId: JsonField<String>? = null
|
||||
private var addedAt: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
private var lastReviewedTime: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
private var sourceProposedExampleId: JsonField<String> = JsonMissing.of()
|
||||
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun from(runCreateByKeyResponse: RunCreateByKeyResponse) = apply {
|
||||
id = runCreateByKeyResponse.id
|
||||
queueId = runCreateByKeyResponse.queueId
|
||||
runId = runCreateByKeyResponse.runId
|
||||
addedAt = runCreateByKeyResponse.addedAt
|
||||
lastReviewedTime = runCreateByKeyResponse.lastReviewedTime
|
||||
sourceProposedExampleId = runCreateByKeyResponse.sourceProposedExampleId
|
||||
additionalProperties = runCreateByKeyResponse.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 queueId(queueId: String) = queueId(JsonField.of(queueId))
|
||||
|
||||
/**
|
||||
* Sets [Builder.queueId] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.queueId] with a well-typed [String] value instead. This
|
||||
* method is primarily for setting the field to an undocumented or not yet supported value.
|
||||
*/
|
||||
fun queueId(queueId: JsonField<String>) = apply { this.queueId = queueId }
|
||||
|
||||
fun runId(runId: String) = runId(JsonField.of(runId))
|
||||
|
||||
/**
|
||||
* Sets [Builder.runId] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.runId] with a well-typed [String] value instead. This
|
||||
* method is primarily for setting the field to an undocumented or not yet supported value.
|
||||
*/
|
||||
fun runId(runId: JsonField<String>) = apply { this.runId = runId }
|
||||
|
||||
fun addedAt(addedAt: OffsetDateTime) = addedAt(JsonField.of(addedAt))
|
||||
|
||||
/**
|
||||
* Sets [Builder.addedAt] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.addedAt] with a well-typed [OffsetDateTime] value
|
||||
* instead. This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun addedAt(addedAt: JsonField<OffsetDateTime>) = apply { this.addedAt = addedAt }
|
||||
|
||||
fun lastReviewedTime(lastReviewedTime: OffsetDateTime?) =
|
||||
lastReviewedTime(JsonField.ofNullable(lastReviewedTime))
|
||||
|
||||
/** Alias for calling [Builder.lastReviewedTime] with `lastReviewedTime.orElse(null)`. */
|
||||
fun lastReviewedTime(lastReviewedTime: Optional<OffsetDateTime>) =
|
||||
lastReviewedTime(lastReviewedTime.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.lastReviewedTime] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.lastReviewedTime] with a well-typed [OffsetDateTime]
|
||||
* value instead. This method is primarily for setting the field to an undocumented or not
|
||||
* yet supported value.
|
||||
*/
|
||||
fun lastReviewedTime(lastReviewedTime: JsonField<OffsetDateTime>) = apply {
|
||||
this.lastReviewedTime = lastReviewedTime
|
||||
}
|
||||
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: String?) =
|
||||
sourceProposedExampleId(JsonField.ofNullable(sourceProposedExampleId))
|
||||
|
||||
/**
|
||||
* Alias for calling [Builder.sourceProposedExampleId] with
|
||||
* `sourceProposedExampleId.orElse(null)`.
|
||||
*/
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: Optional<String>) =
|
||||
sourceProposedExampleId(sourceProposedExampleId.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.sourceProposedExampleId] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.sourceProposedExampleId] with a well-typed [String]
|
||||
* value instead. This method is primarily for setting the field to an undocumented or not
|
||||
* yet supported value.
|
||||
*/
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: JsonField<String>) = apply {
|
||||
this.sourceProposedExampleId = sourceProposedExampleId
|
||||
}
|
||||
|
||||
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 [RunCreateByKeyResponse].
|
||||
*
|
||||
* Further updates to this [Builder] will not mutate the returned instance.
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .id()
|
||||
* .queueId()
|
||||
* .runId()
|
||||
* ```
|
||||
*
|
||||
* @throws IllegalStateException if any required field is unset.
|
||||
*/
|
||||
fun build(): RunCreateByKeyResponse =
|
||||
RunCreateByKeyResponse(
|
||||
checkRequired("id", id),
|
||||
checkRequired("queueId", queueId),
|
||||
checkRequired("runId", runId),
|
||||
addedAt,
|
||||
lastReviewedTime,
|
||||
sourceProposedExampleId,
|
||||
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(): RunCreateByKeyResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
}
|
||||
|
||||
id()
|
||||
queueId()
|
||||
runId()
|
||||
addedAt()
|
||||
lastReviewedTime()
|
||||
sourceProposedExampleId()
|
||||
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 (queueId.asKnown().isPresent) 1 else 0) +
|
||||
(if (runId.asKnown().isPresent) 1 else 0) +
|
||||
(if (addedAt.asKnown().isPresent) 1 else 0) +
|
||||
(if (lastReviewedTime.asKnown().isPresent) 1 else 0) +
|
||||
(if (sourceProposedExampleId.asKnown().isPresent) 1 else 0)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
|
||||
return other is RunCreateByKeyResponse &&
|
||||
id == other.id &&
|
||||
queueId == other.queueId &&
|
||||
runId == other.runId &&
|
||||
addedAt == other.addedAt &&
|
||||
lastReviewedTime == other.lastReviewedTime &&
|
||||
sourceProposedExampleId == other.sourceProposedExampleId &&
|
||||
additionalProperties == other.additionalProperties
|
||||
}
|
||||
|
||||
private val hashCode: Int by lazy {
|
||||
Objects.hash(
|
||||
id,
|
||||
queueId,
|
||||
runId,
|
||||
addedAt,
|
||||
lastReviewedTime,
|
||||
sourceProposedExampleId,
|
||||
additionalProperties,
|
||||
)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"RunCreateByKeyResponse{id=$id, queueId=$queueId, runId=$runId, addedAt=$addedAt, lastReviewedTime=$lastReviewedTime, sourceProposedExampleId=$sourceProposedExampleId, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
+384
-16
@@ -38,6 +38,7 @@ import kotlin.jvm.optionals.getOrNull
|
||||
class RunCreateParams
|
||||
private constructor(
|
||||
private val queueId: String?,
|
||||
private val extendTraceRetention: Boolean?,
|
||||
private val body: Body,
|
||||
private val additionalHeaders: Headers,
|
||||
private val additionalQueryParams: QueryParams,
|
||||
@@ -45,6 +46,8 @@ private constructor(
|
||||
|
||||
fun queueId(): Optional<String> = Optional.ofNullable(queueId)
|
||||
|
||||
fun extendTraceRetention(): Optional<Boolean> = Optional.ofNullable(extendTraceRetention)
|
||||
|
||||
fun body(): Body = body
|
||||
|
||||
/** Additional headers to send with the request. */
|
||||
@@ -72,6 +75,7 @@ private constructor(
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var queueId: String? = null
|
||||
private var extendTraceRetention: Boolean? = null
|
||||
private var body: Body? = null
|
||||
private var additionalHeaders: Headers.Builder = Headers.builder()
|
||||
private var additionalQueryParams: QueryParams.Builder = QueryParams.builder()
|
||||
@@ -79,6 +83,7 @@ private constructor(
|
||||
@JvmSynthetic
|
||||
internal fun from(runCreateParams: RunCreateParams) = apply {
|
||||
queueId = runCreateParams.queueId
|
||||
extendTraceRetention = runCreateParams.extendTraceRetention
|
||||
body = runCreateParams.body
|
||||
additionalHeaders = runCreateParams.additionalHeaders.toBuilder()
|
||||
additionalQueryParams = runCreateParams.additionalQueryParams.toBuilder()
|
||||
@@ -89,6 +94,25 @@ private constructor(
|
||||
/** Alias for calling [Builder.queueId] with `queueId.orElse(null)`. */
|
||||
fun queueId(queueId: Optional<String>) = queueId(queueId.getOrNull())
|
||||
|
||||
fun extendTraceRetention(extendTraceRetention: Boolean?) = apply {
|
||||
this.extendTraceRetention = extendTraceRetention
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for [Builder.extendTraceRetention].
|
||||
*
|
||||
* This unboxed primitive overload exists for backwards compatibility.
|
||||
*/
|
||||
fun extendTraceRetention(extendTraceRetention: Boolean) =
|
||||
extendTraceRetention(extendTraceRetention as Boolean?)
|
||||
|
||||
/**
|
||||
* Alias for calling [Builder.extendTraceRetention] with
|
||||
* `extendTraceRetention.orElse(null)`.
|
||||
*/
|
||||
fun extendTraceRetention(extendTraceRetention: Optional<Boolean>) =
|
||||
extendTraceRetention(extendTraceRetention.getOrNull())
|
||||
|
||||
fun body(body: Body) = apply { this.body = body }
|
||||
|
||||
/** Alias for calling [body] with `Body.ofRunsUuidArray(runsUuidArray)`. */
|
||||
@@ -100,9 +124,17 @@ private constructor(
|
||||
* `Body.ofRunsAnnotationQueueRunAddSchemaArray(runsAnnotationQueueRunAddSchemaArray)`.
|
||||
*/
|
||||
fun bodyOfRunsAnnotationQueueRunAddSchemaArray(
|
||||
runsAnnotationQueueRunAddSchemaArray: List<Body.AnnotationQueueRunAddSchema>
|
||||
runsAnnotationQueueRunAddSchemaArray: List<Body.AddRunToQueueRequest>
|
||||
) = body(Body.ofRunsAnnotationQueueRunAddSchemaArray(runsAnnotationQueueRunAddSchemaArray))
|
||||
|
||||
/**
|
||||
* Alias for calling [body] with
|
||||
* `Body.ofAnnotationQueueRunAddSchemas(annotationQueueRunAddSchemas)`.
|
||||
*/
|
||||
fun bodyOfAnnotationQueueRunAddSchemas(
|
||||
annotationQueueRunAddSchemas: List<Body.AnnotationQueueRunAddSchema>
|
||||
) = body(Body.ofAnnotationQueueRunAddSchemas(annotationQueueRunAddSchemas))
|
||||
|
||||
fun additionalHeaders(additionalHeaders: Headers) = apply {
|
||||
this.additionalHeaders.clear()
|
||||
putAllAdditionalHeaders(additionalHeaders)
|
||||
@@ -216,6 +248,7 @@ private constructor(
|
||||
fun build(): RunCreateParams =
|
||||
RunCreateParams(
|
||||
queueId,
|
||||
extendTraceRetention,
|
||||
checkRequired("body", body),
|
||||
additionalHeaders.build(),
|
||||
additionalQueryParams.build(),
|
||||
@@ -232,34 +265,78 @@ private constructor(
|
||||
|
||||
override fun _headers(): Headers = additionalHeaders
|
||||
|
||||
override fun _queryParams(): QueryParams = additionalQueryParams
|
||||
override fun _queryParams(): QueryParams =
|
||||
QueryParams.builder()
|
||||
.apply {
|
||||
extendTraceRetention?.let { put("extend_trace_retention", it.toString()) }
|
||||
putAll(additionalQueryParams)
|
||||
}
|
||||
.build()
|
||||
|
||||
@JsonDeserialize(using = Body.Deserializer::class)
|
||||
@JsonSerialize(using = Body.Serializer::class)
|
||||
class Body
|
||||
private constructor(
|
||||
private val runsUuidArray: List<String>? = null,
|
||||
private val runsAnnotationQueueRunAddSchemaArray: List<AnnotationQueueRunAddSchema>? = null,
|
||||
private val runsAnnotationQueueRunAddSchemaArray: List<AddRunToQueueRequest>? = null,
|
||||
private val annotationQueueRunAddSchemas: List<AnnotationQueueRunAddSchema>? = null,
|
||||
private val _json: JsonValue? = null,
|
||||
) {
|
||||
|
||||
fun runsUuidArray(): Optional<List<String>> = Optional.ofNullable(runsUuidArray)
|
||||
|
||||
fun runsAnnotationQueueRunAddSchemaArray(): Optional<List<AnnotationQueueRunAddSchema>> =
|
||||
fun runsAnnotationQueueRunAddSchemaArray(): Optional<List<AddRunToQueueRequest>> =
|
||||
Optional.ofNullable(runsAnnotationQueueRunAddSchemaArray)
|
||||
|
||||
fun annotationQueueRunAddSchemas(): Optional<List<AnnotationQueueRunAddSchema>> =
|
||||
Optional.ofNullable(annotationQueueRunAddSchemas)
|
||||
|
||||
fun isRunsUuidArray(): Boolean = runsUuidArray != null
|
||||
|
||||
fun isRunsAnnotationQueueRunAddSchemaArray(): Boolean =
|
||||
runsAnnotationQueueRunAddSchemaArray != null
|
||||
|
||||
fun isAnnotationQueueRunAddSchemas(): Boolean = annotationQueueRunAddSchemas != null
|
||||
|
||||
fun asRunsUuidArray(): List<String> = runsUuidArray.getOrThrow("runsUuidArray")
|
||||
|
||||
fun asRunsAnnotationQueueRunAddSchemaArray(): List<AnnotationQueueRunAddSchema> =
|
||||
fun asRunsAnnotationQueueRunAddSchemaArray(): List<AddRunToQueueRequest> =
|
||||
runsAnnotationQueueRunAddSchemaArray.getOrThrow("runsAnnotationQueueRunAddSchemaArray")
|
||||
|
||||
fun asAnnotationQueueRunAddSchemas(): List<AnnotationQueueRunAddSchema> =
|
||||
annotationQueueRunAddSchemas.getOrThrow("annotationQueueRunAddSchemas")
|
||||
|
||||
fun _json(): Optional<JsonValue> = Optional.ofNullable(_json)
|
||||
|
||||
/**
|
||||
* Maps this instance's current variant to a value of type [T] using the given [visitor].
|
||||
*
|
||||
* Note that this method is _not_ forwards compatible with new variants from the API, unless
|
||||
* [visitor] overrides [Visitor.unknown]. To handle variants not known to this version of
|
||||
* the SDK gracefully, consider overriding [Visitor.unknown]:
|
||||
* ```java
|
||||
* import com.langchain.smith.core.JsonValue;
|
||||
* import java.util.Optional;
|
||||
*
|
||||
* Optional<String> result = body.accept(new Body.Visitor<Optional<String>>() {
|
||||
* @Override
|
||||
* public Optional<String> visitRunsUuidArray(List<String> runsUuidArray) {
|
||||
* return Optional.of(runsUuidArray.toString());
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
*
|
||||
* @Override
|
||||
* public Optional<String> unknown(JsonValue json) {
|
||||
* // Or inspect the `json`.
|
||||
* return Optional.empty();
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @throws LangChainInvalidDataException if [Visitor.unknown] is not overridden in [visitor]
|
||||
* and the current variant is unknown.
|
||||
*/
|
||||
fun <T> accept(visitor: Visitor<T>): T =
|
||||
when {
|
||||
runsUuidArray != null -> visitor.visitRunsUuidArray(runsUuidArray)
|
||||
@@ -267,11 +344,22 @@ private constructor(
|
||||
visitor.visitRunsAnnotationQueueRunAddSchemaArray(
|
||||
runsAnnotationQueueRunAddSchemaArray
|
||||
)
|
||||
annotationQueueRunAddSchemas != null ->
|
||||
visitor.visitAnnotationQueueRunAddSchemas(annotationQueueRunAddSchemas)
|
||||
else -> visitor.unknown(_json)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -282,10 +370,16 @@ private constructor(
|
||||
override fun visitRunsUuidArray(runsUuidArray: List<String>) {}
|
||||
|
||||
override fun visitRunsAnnotationQueueRunAddSchemaArray(
|
||||
runsAnnotationQueueRunAddSchemaArray: List<AnnotationQueueRunAddSchema>
|
||||
runsAnnotationQueueRunAddSchemaArray: List<AddRunToQueueRequest>
|
||||
) {
|
||||
runsAnnotationQueueRunAddSchemaArray.forEach { it.validate() }
|
||||
}
|
||||
|
||||
override fun visitAnnotationQueueRunAddSchemas(
|
||||
annotationQueueRunAddSchemas: List<AnnotationQueueRunAddSchema>
|
||||
) {
|
||||
annotationQueueRunAddSchemas.forEach { it.validate() }
|
||||
}
|
||||
}
|
||||
)
|
||||
validated = true
|
||||
@@ -313,9 +407,13 @@ private constructor(
|
||||
runsUuidArray.size
|
||||
|
||||
override fun visitRunsAnnotationQueueRunAddSchemaArray(
|
||||
runsAnnotationQueueRunAddSchemaArray: List<AnnotationQueueRunAddSchema>
|
||||
runsAnnotationQueueRunAddSchemaArray: List<AddRunToQueueRequest>
|
||||
) = runsAnnotationQueueRunAddSchemaArray.sumOf { it.validity().toInt() }
|
||||
|
||||
override fun visitAnnotationQueueRunAddSchemas(
|
||||
annotationQueueRunAddSchemas: List<AnnotationQueueRunAddSchema>
|
||||
) = annotationQueueRunAddSchemas.sumOf { it.validity().toInt() }
|
||||
|
||||
override fun unknown(json: JsonValue?) = 0
|
||||
}
|
||||
)
|
||||
@@ -327,17 +425,25 @@ private constructor(
|
||||
|
||||
return other is Body &&
|
||||
runsUuidArray == other.runsUuidArray &&
|
||||
runsAnnotationQueueRunAddSchemaArray == other.runsAnnotationQueueRunAddSchemaArray
|
||||
runsAnnotationQueueRunAddSchemaArray ==
|
||||
other.runsAnnotationQueueRunAddSchemaArray &&
|
||||
annotationQueueRunAddSchemas == other.annotationQueueRunAddSchemas
|
||||
}
|
||||
|
||||
override fun hashCode(): Int =
|
||||
Objects.hash(runsUuidArray, runsAnnotationQueueRunAddSchemaArray)
|
||||
Objects.hash(
|
||||
runsUuidArray,
|
||||
runsAnnotationQueueRunAddSchemaArray,
|
||||
annotationQueueRunAddSchemas,
|
||||
)
|
||||
|
||||
override fun toString(): String =
|
||||
when {
|
||||
runsUuidArray != null -> "Body{runsUuidArray=$runsUuidArray}"
|
||||
runsAnnotationQueueRunAddSchemaArray != null ->
|
||||
"Body{runsAnnotationQueueRunAddSchemaArray=$runsAnnotationQueueRunAddSchemaArray}"
|
||||
annotationQueueRunAddSchemas != null ->
|
||||
"Body{annotationQueueRunAddSchemas=$annotationQueueRunAddSchemas}"
|
||||
_json != null -> "Body{_unknown=$_json}"
|
||||
else -> throw IllegalStateException("Invalid Body")
|
||||
}
|
||||
@@ -350,12 +456,17 @@ private constructor(
|
||||
|
||||
@JvmStatic
|
||||
fun ofRunsAnnotationQueueRunAddSchemaArray(
|
||||
runsAnnotationQueueRunAddSchemaArray: List<AnnotationQueueRunAddSchema>
|
||||
runsAnnotationQueueRunAddSchemaArray: List<AddRunToQueueRequest>
|
||||
) =
|
||||
Body(
|
||||
runsAnnotationQueueRunAddSchemaArray =
|
||||
runsAnnotationQueueRunAddSchemaArray.toImmutable()
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun ofAnnotationQueueRunAddSchemas(
|
||||
annotationQueueRunAddSchemas: List<AnnotationQueueRunAddSchema>
|
||||
) = Body(annotationQueueRunAddSchemas = annotationQueueRunAddSchemas.toImmutable())
|
||||
}
|
||||
|
||||
/** An interface that defines how to map each variant of [Body] to a value of type [T]. */
|
||||
@@ -364,7 +475,11 @@ private constructor(
|
||||
fun visitRunsUuidArray(runsUuidArray: List<String>): T
|
||||
|
||||
fun visitRunsAnnotationQueueRunAddSchemaArray(
|
||||
runsAnnotationQueueRunAddSchemaArray: List<AnnotationQueueRunAddSchema>
|
||||
runsAnnotationQueueRunAddSchemaArray: List<AddRunToQueueRequest>
|
||||
): T
|
||||
|
||||
fun visitAnnotationQueueRunAddSchemas(
|
||||
annotationQueueRunAddSchemas: List<AnnotationQueueRunAddSchema>
|
||||
): T
|
||||
|
||||
/**
|
||||
@@ -391,13 +506,15 @@ private constructor(
|
||||
tryDeserialize(node, jacksonTypeRef<List<String>>())?.let {
|
||||
Body(runsUuidArray = it, _json = json)
|
||||
},
|
||||
tryDeserialize(node, jacksonTypeRef<List<AddRunToQueueRequest>>())
|
||||
?.let {
|
||||
Body(runsAnnotationQueueRunAddSchemaArray = it, _json = json)
|
||||
},
|
||||
tryDeserialize(
|
||||
node,
|
||||
jacksonTypeRef<List<AnnotationQueueRunAddSchema>>(),
|
||||
)
|
||||
?.let {
|
||||
Body(runsAnnotationQueueRunAddSchemaArray = it, _json = json)
|
||||
},
|
||||
?.let { Body(annotationQueueRunAddSchemas = it, _json = json) },
|
||||
)
|
||||
.filterNotNull()
|
||||
.allMaxBy { it.validity() }
|
||||
@@ -426,12 +543,242 @@ private constructor(
|
||||
value.runsUuidArray != null -> generator.writeObject(value.runsUuidArray)
|
||||
value.runsAnnotationQueueRunAddSchemaArray != null ->
|
||||
generator.writeObject(value.runsAnnotationQueueRunAddSchemaArray)
|
||||
value.annotationQueueRunAddSchemas != null ->
|
||||
generator.writeObject(value.annotationQueueRunAddSchemas)
|
||||
value._json != null -> generator.writeObject(value._json)
|
||||
else -> throw IllegalStateException("Invalid Body")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single run to AQ (CH path) with an optional back-pointer to the issues-agent
|
||||
* proposal that seeded this add. Use when bulk-adding runs that come from different
|
||||
* proposals — each row carries its own source_proposed_example_id. For unrelated bulk adds,
|
||||
* prefer plain List[UUID] on the same endpoint.
|
||||
*/
|
||||
class AddRunToQueueRequest
|
||||
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
|
||||
private constructor(
|
||||
private val runId: JsonField<String>,
|
||||
private val sourceProposedExampleId: JsonField<String>,
|
||||
private val additionalProperties: MutableMap<String, JsonValue>,
|
||||
) {
|
||||
|
||||
@JsonCreator
|
||||
private constructor(
|
||||
@JsonProperty("run_id") @ExcludeMissing runId: JsonField<String> = JsonMissing.of(),
|
||||
@JsonProperty("source_proposed_example_id")
|
||||
@ExcludeMissing
|
||||
sourceProposedExampleId: JsonField<String> = JsonMissing.of(),
|
||||
) : this(runId, sourceProposedExampleId, 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 runId(): String = runId.getRequired("run_id")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g.
|
||||
* if the server responded with an unexpected value).
|
||||
*/
|
||||
fun sourceProposedExampleId(): Optional<String> =
|
||||
sourceProposedExampleId.getOptional("source_proposed_example_id")
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [runId].
|
||||
*
|
||||
* Unlike [runId], this method doesn't throw if the JSON field has an unexpected type.
|
||||
*/
|
||||
@JsonProperty("run_id") @ExcludeMissing fun _runId(): JsonField<String> = runId
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [sourceProposedExampleId].
|
||||
*
|
||||
* Unlike [sourceProposedExampleId], this method doesn't throw if the JSON field has an
|
||||
* unexpected type.
|
||||
*/
|
||||
@JsonProperty("source_proposed_example_id")
|
||||
@ExcludeMissing
|
||||
fun _sourceProposedExampleId(): JsonField<String> = sourceProposedExampleId
|
||||
|
||||
@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 [AddRunToQueueRequest].
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .runId()
|
||||
* ```
|
||||
*/
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
/** A builder for [AddRunToQueueRequest]. */
|
||||
class Builder internal constructor() {
|
||||
|
||||
private var runId: JsonField<String>? = null
|
||||
private var sourceProposedExampleId: JsonField<String> = JsonMissing.of()
|
||||
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun from(addRunToQueueRequest: AddRunToQueueRequest) = apply {
|
||||
runId = addRunToQueueRequest.runId
|
||||
sourceProposedExampleId = addRunToQueueRequest.sourceProposedExampleId
|
||||
additionalProperties = addRunToQueueRequest.additionalProperties.toMutableMap()
|
||||
}
|
||||
|
||||
fun runId(runId: String) = runId(JsonField.of(runId))
|
||||
|
||||
/**
|
||||
* Sets [Builder.runId] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.runId] with a well-typed [String] value instead.
|
||||
* This method is primarily for setting the field to an undocumented or not yet
|
||||
* supported value.
|
||||
*/
|
||||
fun runId(runId: JsonField<String>) = apply { this.runId = runId }
|
||||
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: String?) =
|
||||
sourceProposedExampleId(JsonField.ofNullable(sourceProposedExampleId))
|
||||
|
||||
/**
|
||||
* Alias for calling [Builder.sourceProposedExampleId] with
|
||||
* `sourceProposedExampleId.orElse(null)`.
|
||||
*/
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: Optional<String>) =
|
||||
sourceProposedExampleId(sourceProposedExampleId.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.sourceProposedExampleId] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.sourceProposedExampleId] with a well-typed
|
||||
* [String] value instead. This method is primarily for setting the field to an
|
||||
* undocumented or not yet supported value.
|
||||
*/
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: JsonField<String>) = apply {
|
||||
this.sourceProposedExampleId = sourceProposedExampleId
|
||||
}
|
||||
|
||||
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 [AddRunToQueueRequest].
|
||||
*
|
||||
* Further updates to this [Builder] will not mutate the returned instance.
|
||||
*
|
||||
* The following fields are required:
|
||||
* ```java
|
||||
* .runId()
|
||||
* ```
|
||||
*
|
||||
* @throws IllegalStateException if any required field is unset.
|
||||
*/
|
||||
fun build(): AddRunToQueueRequest =
|
||||
AddRunToQueueRequest(
|
||||
checkRequired("runId", runId),
|
||||
sourceProposedExampleId,
|
||||
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(): AddRunToQueueRequest = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
}
|
||||
|
||||
runId()
|
||||
sourceProposedExampleId()
|
||||
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 (runId.asKnown().isPresent) 1 else 0) +
|
||||
(if (sourceProposedExampleId.asKnown().isPresent) 1 else 0)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
|
||||
return other is AddRunToQueueRequest &&
|
||||
runId == other.runId &&
|
||||
sourceProposedExampleId == other.sourceProposedExampleId &&
|
||||
additionalProperties == other.additionalProperties
|
||||
}
|
||||
|
||||
private val hashCode: Int by lazy {
|
||||
Objects.hash(runId, sourceProposedExampleId, additionalProperties)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"AddRunToQueueRequest{runId=$runId, sourceProposedExampleId=$sourceProposedExampleId, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
/** Deprecated: use plain UUID list or AddRunToQueueByKeyRequest instead. */
|
||||
class AnnotationQueueRunAddSchema
|
||||
@JsonCreator(mode = JsonCreator.Mode.DISABLED)
|
||||
@@ -776,6 +1123,16 @@ 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(): AnnotationQueueRunAddSchema = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -907,6 +1264,16 @@ 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(): TraceTier = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -986,14 +1353,15 @@ private constructor(
|
||||
|
||||
return other is RunCreateParams &&
|
||||
queueId == other.queueId &&
|
||||
extendTraceRetention == other.extendTraceRetention &&
|
||||
body == other.body &&
|
||||
additionalHeaders == other.additionalHeaders &&
|
||||
additionalQueryParams == other.additionalQueryParams
|
||||
}
|
||||
|
||||
override fun hashCode(): Int =
|
||||
Objects.hash(queueId, body, additionalHeaders, additionalQueryParams)
|
||||
Objects.hash(queueId, extendTraceRetention, body, additionalHeaders, additionalQueryParams)
|
||||
|
||||
override fun toString() =
|
||||
"RunCreateParams{queueId=$queueId, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
|
||||
"RunCreateParams{queueId=$queueId, extendTraceRetention=$extendTraceRetention, body=$body, additionalHeaders=$additionalHeaders, additionalQueryParams=$additionalQueryParams}"
|
||||
}
|
||||
|
||||
+68
-4
@@ -26,6 +26,7 @@ private constructor(
|
||||
private val runId: JsonField<String>,
|
||||
private val addedAt: JsonField<OffsetDateTime>,
|
||||
private val lastReviewedTime: JsonField<OffsetDateTime>,
|
||||
private val sourceProposedExampleId: JsonField<String>,
|
||||
private val additionalProperties: MutableMap<String, JsonValue>,
|
||||
) {
|
||||
|
||||
@@ -40,7 +41,10 @@ private constructor(
|
||||
@JsonProperty("last_reviewed_time")
|
||||
@ExcludeMissing
|
||||
lastReviewedTime: JsonField<OffsetDateTime> = JsonMissing.of(),
|
||||
) : this(id, queueId, runId, addedAt, lastReviewedTime, mutableMapOf())
|
||||
@JsonProperty("source_proposed_example_id")
|
||||
@ExcludeMissing
|
||||
sourceProposedExampleId: JsonField<String> = JsonMissing.of(),
|
||||
) : this(id, queueId, runId, addedAt, lastReviewedTime, sourceProposedExampleId, mutableMapOf())
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type or is
|
||||
@@ -73,6 +77,13 @@ private constructor(
|
||||
fun lastReviewedTime(): Optional<OffsetDateTime> =
|
||||
lastReviewedTime.getOptional("last_reviewed_time")
|
||||
|
||||
/**
|
||||
* @throws LangChainInvalidDataException if the JSON field has an unexpected type (e.g. if the
|
||||
* server responded with an unexpected value).
|
||||
*/
|
||||
fun sourceProposedExampleId(): Optional<String> =
|
||||
sourceProposedExampleId.getOptional("source_proposed_example_id")
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [id].
|
||||
*
|
||||
@@ -111,6 +122,16 @@ private constructor(
|
||||
@ExcludeMissing
|
||||
fun _lastReviewedTime(): JsonField<OffsetDateTime> = lastReviewedTime
|
||||
|
||||
/**
|
||||
* Returns the raw JSON value of [sourceProposedExampleId].
|
||||
*
|
||||
* Unlike [sourceProposedExampleId], this method doesn't throw if the JSON field has an
|
||||
* unexpected type.
|
||||
*/
|
||||
@JsonProperty("source_proposed_example_id")
|
||||
@ExcludeMissing
|
||||
fun _sourceProposedExampleId(): JsonField<String> = sourceProposedExampleId
|
||||
|
||||
@JsonAnySetter
|
||||
private fun putAdditionalProperty(key: String, value: JsonValue) {
|
||||
additionalProperties.put(key, value)
|
||||
@@ -146,6 +167,7 @@ private constructor(
|
||||
private var runId: JsonField<String>? = null
|
||||
private var addedAt: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
private var lastReviewedTime: JsonField<OffsetDateTime> = JsonMissing.of()
|
||||
private var sourceProposedExampleId: JsonField<String> = JsonMissing.of()
|
||||
private var additionalProperties: MutableMap<String, JsonValue> = mutableMapOf()
|
||||
|
||||
@JvmSynthetic
|
||||
@@ -155,6 +177,7 @@ private constructor(
|
||||
runId = runCreateResponse.runId
|
||||
addedAt = runCreateResponse.addedAt
|
||||
lastReviewedTime = runCreateResponse.lastReviewedTime
|
||||
sourceProposedExampleId = runCreateResponse.sourceProposedExampleId
|
||||
additionalProperties = runCreateResponse.additionalProperties.toMutableMap()
|
||||
}
|
||||
|
||||
@@ -217,6 +240,27 @@ private constructor(
|
||||
this.lastReviewedTime = lastReviewedTime
|
||||
}
|
||||
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: String?) =
|
||||
sourceProposedExampleId(JsonField.ofNullable(sourceProposedExampleId))
|
||||
|
||||
/**
|
||||
* Alias for calling [Builder.sourceProposedExampleId] with
|
||||
* `sourceProposedExampleId.orElse(null)`.
|
||||
*/
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: Optional<String>) =
|
||||
sourceProposedExampleId(sourceProposedExampleId.getOrNull())
|
||||
|
||||
/**
|
||||
* Sets [Builder.sourceProposedExampleId] to an arbitrary JSON value.
|
||||
*
|
||||
* You should usually call [Builder.sourceProposedExampleId] with a well-typed [String]
|
||||
* value instead. This method is primarily for setting the field to an undocumented or not
|
||||
* yet supported value.
|
||||
*/
|
||||
fun sourceProposedExampleId(sourceProposedExampleId: JsonField<String>) = apply {
|
||||
this.sourceProposedExampleId = sourceProposedExampleId
|
||||
}
|
||||
|
||||
fun additionalProperties(additionalProperties: Map<String, JsonValue>) = apply {
|
||||
this.additionalProperties.clear()
|
||||
putAllAdditionalProperties(additionalProperties)
|
||||
@@ -257,12 +301,21 @@ private constructor(
|
||||
checkRequired("runId", runId),
|
||||
addedAt,
|
||||
lastReviewedTime,
|
||||
sourceProposedExampleId,
|
||||
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(): RunCreateResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
@@ -273,6 +326,7 @@ private constructor(
|
||||
runId()
|
||||
addedAt()
|
||||
lastReviewedTime()
|
||||
sourceProposedExampleId()
|
||||
validated = true
|
||||
}
|
||||
|
||||
@@ -295,7 +349,8 @@ private constructor(
|
||||
(if (queueId.asKnown().isPresent) 1 else 0) +
|
||||
(if (runId.asKnown().isPresent) 1 else 0) +
|
||||
(if (addedAt.asKnown().isPresent) 1 else 0) +
|
||||
(if (lastReviewedTime.asKnown().isPresent) 1 else 0)
|
||||
(if (lastReviewedTime.asKnown().isPresent) 1 else 0) +
|
||||
(if (sourceProposedExampleId.asKnown().isPresent) 1 else 0)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
@@ -308,15 +363,24 @@ private constructor(
|
||||
runId == other.runId &&
|
||||
addedAt == other.addedAt &&
|
||||
lastReviewedTime == other.lastReviewedTime &&
|
||||
sourceProposedExampleId == other.sourceProposedExampleId &&
|
||||
additionalProperties == other.additionalProperties
|
||||
}
|
||||
|
||||
private val hashCode: Int by lazy {
|
||||
Objects.hash(id, queueId, runId, addedAt, lastReviewedTime, additionalProperties)
|
||||
Objects.hash(
|
||||
id,
|
||||
queueId,
|
||||
runId,
|
||||
addedAt,
|
||||
lastReviewedTime,
|
||||
sourceProposedExampleId,
|
||||
additionalProperties,
|
||||
)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = hashCode
|
||||
|
||||
override fun toString() =
|
||||
"RunCreateResponse{id=$id, queueId=$queueId, runId=$runId, addedAt=$addedAt, lastReviewedTime=$lastReviewedTime, additionalProperties=$additionalProperties}"
|
||||
"RunCreateResponse{id=$id, queueId=$queueId, runId=$runId, addedAt=$addedAt, lastReviewedTime=$lastReviewedTime, sourceProposedExampleId=$sourceProposedExampleId, additionalProperties=$additionalProperties}"
|
||||
}
|
||||
|
||||
+9
@@ -524,6 +524,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
@@ -75,6 +75,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(): RunDeleteAllResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+8
@@ -75,6 +75,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(): RunDeleteQueueResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+9
@@ -367,6 +367,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(): Status = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+9
@@ -450,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(): Body = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+8
@@ -74,6 +74,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(): RunUpdateResponse = apply {
|
||||
if (validated) {
|
||||
return@apply
|
||||
|
||||
+9
@@ -505,6 +505,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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user