Compare commits

...

573 Commits

Author SHA1 Message Date
KiewanVillatel 2775de9d25 release: 0.1.0-beta.19 (#195) 2026-07-23 12:31:02 +02:00
github-actions[bot] 2ab482c2d3 release: 0.1.0-beta.19 2026-07-23 10:21:10 +00:00
KiewanVillatel b1649d975e Release SDK updates 2026-07-23 12:20:48 +02:00
stlc-bot aece72d035 refactor(runs): remove SmithDB v2 endpoint flag [LSO-3391]
Stainless-Generated-From: eaf6eeeffa6089b29a6ed890ffccada85c435446
2026-07-23 00:21:12 +00:00
stlc-bot aa4be05c11 feat(backend): update CRUD endpoint to allow retrieving / storing chart series metadata
Stainless-Generated-From: d6a6962cef9066a8de2683d0a48553057474595b
2026-07-22 19:30:16 +00:00
stlc-bot 8aded40800 feat(backend): Allow storing grid layout to custom dashboards
Stainless-Generated-From: 235750773cea09074a5f805563dba096b19f5c6a
2026-07-22 18:27:41 +00:00
stlc-bot 6165434d21 feat(sandboxes): free-form labels on sandboxes and snapshots
Stainless-Generated-From: 5a8ad04cc2a2a4afaa4f310a862d9701806a1662
2026-07-22 14:07:11 +00:00
stlc-bot 95da1e9b94 revert(runs): require session in POST /runs/stats
Stainless-Generated-From: 76e29824c8418e6e3596ff4fde3f2abec3fd34f9
2026-07-22 12:49:59 +00:00
stlc-bot eace8c3f99 feat(sandboxes): let proxy rules contribute sandbox env vars
Stainless-Generated-From: 3d4147438f77389052680dd6bf85a9c90ab4d373
2026-07-22 10:42:41 +00:00
stlc-bot 056dbd3673 fix(runs): require session in POST /runs/stats
Stainless-Generated-From: b24d4f534d67323bbf8765d82bb0843c24030da5
2026-07-22 10:25:34 +00:00
stlc-bot e7fe407f74 fix: support last_queued_at in v2 runs query
Stainless-Generated-From: da0d737eda0ece655bc0779537309b706951bb74
2026-07-21 23:20:53 +00:00
stlc-bot f6608288d4 feat(host): p50/p99 run queue wait-time chart in project monitoring [LSD-1547]
Stainless-Generated-From: 17fb5d12f51ab1746f996098443e5b69b023ca82
2026-07-21 20:22:16 +00:00
stlc-bot b072b2e2fc fix(evaluators): persist and wire through playground_settings_id for llm-as-judge oauth models [part of ENT-1402]
Stainless-Generated-From: 809ff1ccff334354d5ce4e55c50e5f4974bcf13f
2026-07-21 15:57:35 +00:00
stlc-bot 391b75ca3b Build SDK
Stainless-Generated-From: df7d0b0d0f4e763f9a168447fe5c95f5efdf25fa
2026-07-20 13:51:31 +00:00
KiewanVillatel 42326ef486 Merge branch 'main' into stlc/from-prod 2026-07-20 15:04:28 +02:00
KiewanVillatel b6d079b991 ci: sync from prod now targets main instead of next (#48)
## Summary

- Updates `stlc-sync-from-production.yml` to check out `main` (instead
of `next`) and open back-sync PRs targeting `main`
- Aligns with the removal of the `next` branch and the shift to a
direct-to-main flow

## Test plan

- [ ] Trigger `stlc: sync from production` via workflow_dispatch and
confirm the PR it creates targets `main`

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-20 14:39:17 +02:00
KiewanVillatel 804022edf1 release: 0.1.0-beta.18 (#193) 2026-07-20 12:55:54 +02:00
github-actions[bot] feb140e343 release: 0.1.0-beta.18 2026-07-20 10:26:58 +00:00
KiewanVillatel 0d0a8f5ffb Release SDK updates (#191) 2026-07-20 12:26:41 +02:00
stlc-bot 57da309643 feat(runs): add GET /v2/runs/{run_id}/url endpoint and SDK runs.get_url method
Stainless-Generated-From: 12ef224c44ef834db59a6216eb1010f87947b165
2026-07-20 10:12:06 +00:00
stlc-bot 388984c005 fix: build, lint, and test failures (feat(gateway): rate limit policy CRUD, validation, and materialization [ENT-1138]) 2026-07-17 21:55:39 +00:00
KiewanVillatel 283c4c2bcf release: 0.1.0-beta.17 (#190) 2026-07-17 20:47:23 +02:00
github-actions[bot] b8f1caebe1 release: 0.1.0-beta.17 2026-07-17 18:39:52 +00:00
KiewanVillatel b64cfc2ca8 Release SDK updates (#189) 2026-07-17 20:39:32 +02:00
stlc-bot 101472f4ce feat(runs/v2): make start_time optional in GET /v2/runs/:id
Stainless-Generated-From: fc698ee51e0e6b4b1cb0389217ce572774ac2dba
2026-07-17 17:39:28 +00:00
KiewanVillatel df8f95af97 chore: remove stlc-promote-to-main workflow (#45)
## Summary
- Removes `.github/workflows/stlc-promote-to-main.yml`, which triggered
a `next → main` ff-only merge in `langchainplus` whenever the next→main
PR in this repo was approved or CI passed on `next`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-17 10:56:47 +00:00
KiewanVillatel 6720c1f0e4 ci: run stainless file check on PRs targeting main (#46)
## Summary

- Changes the `check-stainless-file-modification` workflow trigger from
PRs targeting `next` to PRs targeting `main`

## Why

The check was not running on PRs that target `main` directly (e.g.
[#45](https://github.com/langchain-ai/langsmith-java-staging/pull/45)),
leaving Stainless-generated file modifications undetected on that path.

## Test plan

- [ ] Verify the check runs on a future PR targeting `main`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-17 12:56:13 +02:00
stlc-bot 2f1ca870dd docs: minor rewording of List Commits endpoint description
Stainless-Generated-From: cfaab39cb776a2dff32d8108ebd8c219682c0d92
2026-07-17 09:29:49 +00:00
Kiewan Villatel 7ba0bad56f Build SDK
Stainless-Generated-From: d378b7c5267d135b1741402378738a3d01eeb862
2026-07-17 10:42:39 +02:00
KiewanVillatel 68707629d2 release: 0.1.0-beta.16 (#188) 2026-07-17 09:40:57 +02:00
github-actions[bot] 3880cb324a release: 0.1.0-beta.16 2026-07-17 07:31:20 +00:00
KiewanVillatel 37d650a362 Release SDK updates (#187) 2026-07-17 09:30:59 +02:00
KiewanVillatel cff262179d Merge branch 'main' into stainless/release 2026-07-17 09:23:19 +02:00
KiewanVillatel a19f9aae0b stlc: use main branch, remove next→main promotion
## Summary

This PR is part of migrating stlc to land SDK commits directly on `main`
instead of going through the `next` buffer branch.

- Update `stlc-seal-custom-code.yml` trigger from `branches: [next]` to
`branches: [main]` so the seal workflow fires on pushes to `main`
- Delete `stlc-promote-to-main.yml` entirely — this workflow promoted
sealed commits from `next` into `main`, which is no longer needed once
`next` is removed from the flow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-17 09:19:55 +02:00
stlc-bot 1409dd89d8 fix: remove July 2026 premier plan
Stainless-Generated-From: 09c5b50a463efb9be1cd10176e2bc88fd9e2fc4a
2026-07-16 22:11:59 +00:00
stlc-bot 839107858c chore: new unshare endpoint that doesn't require lookup
Stainless-Generated-From: 3c2f57d95968959d58e0bafbea6a5d0b24077e75
2026-07-16 21:51:34 +00:00
stlc-bot 8eb067064e fix(datasets): serve public dataset sessions via Go, fixing SmithDB-only 501
Stainless-Generated-From: cbd1d7df666a4e821e8ac118fd84eaa8e5acdb00
2026-07-16 16:24:08 +00:00
stlc-bot 306a079479 chore: Stainless SDK methods for V2 public-run APIs
Stainless-Generated-From: 0c767292f9a7937b0d09cb8261ae2c873a41ce2d
2026-07-16 15:15:00 +00:00
John Kennedy 8b1dff9a1e fix: patch 6 high-severity security alerts (#42)
## Security Alert Patch

Resolves all 6 open high-severity Dependabot alerts in the Maven
dependency graph.

### Packages Updated

| Package | Resolved change | Strategy | Scope | CVEs resolved |
|---|---|---|---|---|
| com.fasterxml.jackson.core:jackson-databind | 2.15.3 -> 2.18.8; 2.21.0
-> 2.21.4 | Parent bump for Palantir plus constrained Dokka classpaths |
Build/dev only | CVE-2026-54512, CVE-2026-54513 |
| org.bouncycastle:bcpg-jdk18on | 1.80 -> 1.84 | Constraint on Kotlin
publishing validation | Build/dev only | CVE-2026-3505 |
| org.codehaus.plexus:plexus-utils | 4.0.2 -> 4.0.3 | Constraint on
Shadow plugin classpath | Test/build only | CVE-2025-67030 |

### Strategy Notes

- Palantir Java Format moves from 2.89.0 to 2.93.0, the first release
that declares patched Jackson 2.21.4.
- Dokka 2.2.0 is the latest release and still declares Jackson 2.15.3,
so its buildscript and worker configurations are constrained to 2.18.8.
Remove the constraint when Dokka publishes a patched dependency graph.
- Kotlin publishing validation selects Bouncy Castle 1.80 internally, so
that isolated configuration is constrained to 1.84.
- Shadow 8.3.8 still declares Plexus Utils 4.0.2. Shadow 9.4.3 was
assessed but failed the repository R8 compatibility test, so the plugin
remains at 8.3.8 with a buildscript-only Plexus constraint.

### Advisory Details

- CVE-2026-54512 / GHSA-j3rv-43j4-c7qm: Jackson polymorphic type
validator bypass via generic type parameters.
- CVE-2026-54513 / GHSA-rmj7-2vxq-3g9f: Jackson array subtype allowlist
bypass.
- CVE-2026-3505 / GHSA-cj8j-37rh-8475: Bouncy Castle uncontrolled
resource consumption.
- CVE-2025-67030 / GHSA-6fmv-xxpf-w3cw: Plexus Utils directory traversal
in extractFile.

### Linear Tickets

No matching open Linear tickets found.

### Verification

- [x] All project dependency reports contain no affected vulnerable
versions
- [x] Kotlin and Java lint pass
- [x] Full Gradle test suite passes
- [x] ProGuard and R8 compatibility checks pass
- [x] Dokka Javadoc generation passes
- [x] git diff --check passes
2026-07-16 11:55:45 +02:00
stlc-bot 50525423cc feat(feedback): support GET /feedback for thread AQs
Stainless-Generated-From: 50cfa58b8547d1cd99da2b22d9081517d3cbbf92
2026-07-15 18:50:28 +00:00
stlc-bot d53da42ffa feat: add run rule evaluator tracing flag [LSO-3323]
Stainless-Generated-From: df568b7188e650a63d7521cb0b0321fc4332db75
2026-07-15 17:01:02 +00:00
stlc-bot cbc00807ee feat(engine): issue lifecycle backend + webhook status filter (2/8)
Stainless-Generated-From: c6ff8a64c58e6a3d43bf068dd92639cc39e2aa0b
2026-07-15 16:42:11 +00:00
KiewanVillatel 2cf0ff6e08 release: 0.1.0-beta.15 (#186) 2026-07-15 10:03:57 +02:00
github-actions[bot] 8e2a0583bf release: 0.1.0-beta.15 2026-07-15 07:53:08 +00:00
KiewanVillatel 63ca829e51 Release SDK updates (#185) 2026-07-15 09:52:50 +02:00
stlc-bot d35ada22c7 chore: update smithdb proto generated code
Stainless-Generated-From: 2945c07633971e09ac433ae6fac86ff1d3695ed1
2026-07-14 23:17:38 +00:00
stlc-bot 2c9704f3c0 feat(context-hub): fire commit webhook and deliver async
Stainless-Generated-From: c28c9c538349263a012361131369c1a2892b246b
2026-07-14 17:46:20 +00:00
stlc-bot b2ed882d0e feat: add July 2026 startup and premier plan tiers
Stainless-Generated-From: 5cd04e44b56fb36e6ec5b7d431198df237532e00
2026-07-14 17:04:24 +00:00
stlc-bot 99fe7dbe33 feat(sandboxes): add preserve_memory_on_stop create attribute
Stainless-Generated-From: c005594f452b7d91aad0d624bc9ebbbef0d8d692
2026-07-14 12:58:43 +00:00
KiewanVillatel c3764a059c release: 0.1.0-beta.14 (#184) 2026-07-14 11:37:58 +02:00
github-actions[bot] 3f086d2302 release: 0.1.0-beta.14 2026-07-14 09:30:30 +00:00
KiewanVillatel ca210b2c66 Release SDK updates (#183) 2026-07-14 11:30:12 +02:00
John Kennedy e639046090 fix: patch remaining Java security alerts (#38)
## Summary

Patch the remaining open Java/Maven Dependabot alerts on the Stainless
next branch.

## Changes

- Upgrade Jackson modules from 2.22.0 to 2.22.1.
- Pin vulnerable test dependencies to patched versions:
  - Bouncy Castle modules: 1.84
  - Log4j Core: 2.25.4
  - Plexus Utils: 4.0.3
  - Commons Lang: 3.18.0
- Use explicit versions for the test dependencies so the dependency
graph recognizes the fixes.

## Security Alerts

Resolves the affected alerts for Jackson Databind/Core, Bouncy Castle,
Log4j Core, Plexus Utils, and Commons Lang, including:

- GHSA-5jmj-h7xm-6q6v
- GHSA-5hh8-q8hv-fr38
- GHSA-9fxm-vc8v-hj55
- GHSA-hgj6-7826-r7m5
- GHSA-rmj7-2vxq-3g9f
- GHSA-j3rv-43j4-c7qm
- GHSA-rcqc-6cw3-h962
- GHSA-cj8j-37rh-8475
- GHSA-c3fc-8qff-9hwx
- GHSA-wg6q-6289-32hp
- GHSA-445c-vh5m-36rj
- GHSA-6hg6-v5c8-fphq
- GHSA-3pxv-7cmr-fjr4
- GHSA-vc5p-v9hr-52mj
- GHSA-6fmv-xxpf-w3cw
- GHSA-72hv-8253-57qq

## Verification

- Core test suite passed.
- Kotlin formatting passed.
- Gradle dependency resolution confirms Jackson Databind 2.22.1.
- Diff check passed.
2026-07-14 01:56:47 -07:00
stlc-bot 1893f83cf2 fix(smith-sdks): name thread stats response model ThreadStats
Stainless-Generated-From: 08528562024b8bc13c762ccb8a943824136e51ab
2026-07-14 08:05:19 +00:00
stlc-bot 63bde3c6e4 fix(smith-go): default time bounds for POST /v2/threads/query
Stainless-Generated-From: 0fe434729c46373eaa6f2c4fcba16fa6b674e9c6
2026-07-13 16:27:40 +00:00
stlc-bot 9cfd26bef9 refactor(sandboxes): make context hub sync a generic mount
Stainless-Generated-From: 8d75a97626cfea68319b3656b34756d2a0100b30
2026-07-12 22:00:12 +00:00
stlc-bot 17551decf7 feat(evaluators): expose is_managed on Go evaluator reads [LSE-2532]
Stainless-Generated-From: 4fdcf56badac7a5800d4d83afc3fdbde1fe3d09e
2026-07-10 19:00:10 +00:00
stlc-bot 0a29041897 feat(dashboards): convert prebuilts to v2 charts on clone [LSO-3306, LSO-3322]
Stainless-Generated-From: 9a6d3abdfd5cd0109e650ac59f56cbd95ff275f2
2026-07-10 16:03:05 +00:00
stlc-bot 12185beb4c feat: forward filter param to thread stats API
Stainless-Generated-From: b4b2f4867f918b220c363931b43145dbeeea5b07
2026-07-09 22:28:22 +00:00
stlc-bot 18d7867206 feat(sdk): wire annotation-queue runs/by-key into stainless [LSAQ-141]
Stainless-Generated-From: a2b46fa9a70c37b435ed42d063f79de82478c6ed
2026-07-09 21:49:37 +00:00
KiewanVillatel a80e55d510 release: 0.1.0-beta.13 (#182) 2026-07-08 22:19:49 +02:00
github-actions[bot] b8f033e3d9 release: 0.1.0-beta.13 2026-07-08 20:10:44 +00:00
KiewanVillatel 38646cb1fa fix: re-emit commits missed by release-please due to timestamp ordering (#181)
refactor(sandboxes): resolve default sandbox via snapshot tag instead of blueprint

feat(backend): extend_trace_retention field for annotation queue adds

feat: Adds attachment support to Java multipart requests (#33)
2026-07-08 22:10:26 +02:00
stlc-bot 59d3e33e71 docs(runs): document that limit does not apply when querying by trace ID
Stainless-Generated-From: d8187437e161077c4005caca7dc16759de02a5e0
2026-07-08 18:40:44 +00:00
Kiewan Villatel c35c92289b Fix release please 2026-07-08 20:17:12 +02:00
Jacob Lee c22f69aac3 Release SDK updates (#180) 2026-07-08 09:26:12 -07:00
Quentin Brosse 57f86d2e60 release: 0.1.0-beta.12 (#178) 2026-07-08 14:43:20 +02:00
github-actions[bot] 141e3e5603 release: 0.1.0-beta.12 2026-07-08 12:01:47 +00:00
Quentin Brosse 5d5112753c Release SDK updates (#179) 2026-07-08 14:01:25 +02:00
Jacob Lee 13b36e3f11 feat: Adds attachment support to Java multipart requests (#33)
Adds multipart run attachment support to the Java SDK.

- Added `RunAttachment` for binary run attachments.
- Added per-run attachments on `Run`.
- Added direct multipart ingest APIs:
  - `client.runs().multipartIngest(createRuns, updateRuns)`
  - async/raw response variants
- Multipart serialization now sends:
  - run payloads as `post.{runId}` / `patch.{runId}`
  - large fields separately, e.g. `post.{runId}.inputs`
  - attachments as `attachment.{runId}.{attachmentName}`
- `InputStream` attachments are treated as non-repeatable so retry
behavior is safe.

Java:
```java
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
import com.langchain.smith.models.runs.Run;
import com.langchain.smith.models.runs.RunAttachment;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;

public class MultipartRunAttachmentExample {
    public static void main(String[] args) {
        var client = LangsmithOkHttpClient.builder()
            .apiKey(System.getenv("LANGSMITH_API_KEY"))
            .build();

        var runId = UUID.randomUUID().toString();

        var run = Run.builder()
            .id(runId)
            .traceId(runId)
            .dottedOrder("20250101T000000000Z" + runId)
            .name("run-with-attachment")
            .runType(Run.RunType.CHAIN)
            .putAttachment(
                "input-file",
                RunAttachment.of(
                    "text/plain",
                    "hello from an attachment".getBytes(StandardCharsets.UTF_8)
                )
            )
            .build();

        client.runs().multipartIngest(List.of(run), List.of());

        client.close();
    }
}
```

Kotlin:

```kotlin
var runId = UUID.randomUUID().toString();

var run = Run.builder()
    .id(runId)
    .traceId(runId)
    .dottedOrder("20250101T000000000Z" + runId)
    .name("run-with-attachment")
    .runType(Run.RunType.CHAIN)
    .putAttachment(
        "input-file",
        RunAttachment.of(
            "text/plain",
            "hello from an attachment".getBytes(StandardCharsets.UTF_8)
        )
    )
    .build();

client.runs().multipartIngest(List.of(run), List.of());
```
2026-07-07 15:56:59 -07:00
stlc-bot fcc7ab733b feat(backend): extend_trace_retention field for annotation queue adds
Stainless-Generated-From: 6e5f8b8939ccbe3b430dcd2e24d162f1a2a9cd1c
2026-07-07 17:54:22 +00:00
stlc-bot a07babf48b refactor(sandboxes): resolve default sandbox via snapshot tag instead of blueprint
Stainless-Generated-From: 543ad09db4e939e4c6fac45a471bdbf33106b7f5
2026-07-07 17:32:58 +00:00
KiewanVillatel fdba8e6123 fix: remove duplicate threads/traces declarations in client impls (#35)
## Summary

- Removes duplicate `private val threads` and `private val traces`
property declarations in `LangsmithClientImpl` and
`LangsmithClientAsyncImpl`
- Removes duplicate `override fun threads()` and `override fun traces()`
method overrides in both classes
- The commit `feat(smith-sdks): expose threads and traces v2 endpoints
in public SDK` accidentally added each declaration twice, causing Kotlin
compiler error: `Conflicting declarations`

Fixes the build failure in [PR
#34](https://github.com/langchain-ai/langsmith-java-staging/pull/34).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 14:25:40 +02:00
KiewanVillatel bf2443da84 Release SDK updates (#176) 2026-07-07 13:28:27 +02:00
stlc-bot 3bc6173328 feat(smith-sdks): expose threads and traces v2 endpoints in public SDK
Stainless-Generated-From: 2c83167dfb83a332ca7f2df2729a3a69f237b0f0
2026-07-07 10:30:49 +00:00
stlc-bot d71cff9f59 feat(stainless)!: skip sessions resource for python and typescript
Stainless-Generated-From: 43e94bbe9420618dc2c99ff41a91606f550319b6
2026-07-07 10:16:41 +00:00
stlc-bot e2fc8a84dd Build SDK
Stainless-Generated-From: 105d7387fdada55b829c002388f740a5a8a88f39
2026-07-06 18:50:20 +00:00
stlc-bot 8e17124b68 feat(managed-eval): expose is_managed_evaluator on run-rule read/write paths
Stainless-Generated-From: c961e1af0cc0c71a209a4948f8fd37d4d2c86149
2026-07-06 17:58:20 +00:00
stlc-bot 6ab6fa281f chore(stainless): skip all datasets endpoints in Python/TS SDKs except experiment-runs
Stainless-Generated-From: c8dbb4a59b1515fe90aa6ea4868afc57b24565c4
2026-07-06 17:42:51 +00:00
stlc-bot 585b12dd26 fix(smith-sdks): remove dead retrieve_thread_preview SDK mapping
Stainless-Generated-From: a18af293d2eef2eab3c41770c9ff79c56e5a29c1
2026-07-06 17:04:09 +00:00
stlc-bot 4513b3ffb0 fix(smith-sdks): rename dataset runs POST methods from create to query
Stainless-Generated-From: 9c0c2fbf221fc0ca137a78a5d855f1c52fb0e89d
2026-07-05 23:51:35 +00:00
KiewanVillatel b00f843bc8 ci: retrigger next -> main promotion when checks finish (#29)
## Summary
- Approving the `next -> main` PR before CI checks finish never
re-dispatched the promotion once checks passed, since
`pull_request_review` only fires once on submission.
- Adds a `check_suite: [completed]` trigger to
`stlc-promote-to-main.yml`. On that event, a new gate step looks up the
open `next -> main` PR and checks `reviewDecision == APPROVED` via `gh
pr view`; only then does it proceed to dispatch the same
`git-ff-only-merge` workflow as before.
- Existing `pull_request_review` trigger behavior is unchanged.

## Test plan
- [ ] Approve a `next -> main` PR while checks are still running,
confirm the promotion workflow fires once checks complete successfully.
- [ ] Confirm approving after checks already passed still dispatches
promotion immediately (existing path).
- [ ] Confirm no dispatch happens if checks complete but the PR isn't
approved yet.
2026-07-03 10:40:40 +02:00
KiewanVillatel 5b3f2edfce release: 0.1.0-beta.11 (#174) 2026-07-02 20:26:47 +02:00
github-actions[bot] f6ea8673f6 release: 0.1.0-beta.11 2026-07-02 17:35:22 +00:00
KiewanVillatel 539f2893ea Release SDK updates (#175) 2026-07-02 19:35:00 +02:00
stlc-bot e3b478437e fix: restore title/additionalProperties on v2 RunResponse JSON fields for Stainless
Stainless-Generated-From: 945d660b43c31978b891013d15740bacec141f9b
2026-07-02 15:56:31 +00:00
KiewanVillatel c01f895e57 Release SDK updates (#173) 2026-07-02 14:00:48 +02:00
John Kennedy 6b52583c7c Bump vulnerable dependency versions (#27)
## Summary
- bump Jackson, Spring Boot example, WireMock/Jetty test dependencies,
and vulnerable transitive constraints
- update Spring Boot 3 annotation import and dependency security notes

## Testing
- mise x java@temurin-21.0.11+10.0.LTS -- sfw ./gradlew test
2026-07-02 09:28:27 +02:00
sineha-mani 384c001825 feat(evaluation): remaining python-style evaluate entry points (#18)
## Summary
This change adds the remaining Python-style evaluate entry points on top
of #17.

Feature | New in #18?
-- | --
EvaluateTarget (Runnable-style target) | 
evaluate(client, EvaluateTarget, params) | 
evaluateExisting() / evaluateExistingAsync() | 
evaluate(client, experimentId, params) convenience overload | 
evaluateComparative() | 
evaluate(client, Pair(expA, expB), params) convenience overload | 
ComparativeEvaluator / comparativeEvaluator() | 
EvaluateExistingParams | 
EvaluateComparativeParams | 
ComparativeExperimentResults | 

References
[LSDK-241](https://linear.app/langchain/issue/LSDK-241/add-evaluate-to-langsmith-java-sdk-example-release)

<img width="1173" height="508" alt="Screenshot 2026-06-25 at 3 01 38 PM"
src="https://github.com/user-attachments/assets/a86df925-3657-4ad2-ba21-e250b9f5b4b7"
/>

## Test plan
- [x] `./gradlew :langsmith-java-core:test --tests
"com.langchain.smith.evaluation.*"` passes
- [X] Ran locally with a script

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 15:27:36 -07:00
stlc-bot a00b1f18ca refactor!: disallow run_count session sort
Stainless-Generated-From: 8c7f6e5f06da792243633325c29fe1a621869ce0
2026-07-01 21:51:05 +00:00
stlc-bot 48b3b08984 ci(stlc): show a clear message when SDK preview build hits a code conflict
Stainless-Generated-From: 8ddeeb1a988a3c864df094292e3a925f3bcccea6
2026-07-01 16:36:24 +00:00
KiewanVillatel 7a1922239b release: 0.1.0-beta.10 (#172) 2026-07-01 10:40:36 +02:00
github-actions[bot] 8b752b109b release: 0.1.0-beta.10 2026-07-01 08:31:15 +00:00
KiewanVillatel 636b67ce06 Release SDK updates (#171) 2026-07-01 10:30:56 +02:00
stlc-bot 2d4900b16b chore(examples): dual-write source run lookup coordinates in Python
Stainless-Generated-From: e59106795c2f894f41350588bd5052cba69938f9
2026-06-30 23:25:18 +00:00
KiewanVillatel 4d45358e0e fix: update hand-written code to use RunIngest after stainless rename (#25)
## Summary

- Updates all hand-written source files to use `RunIngest` instead of
`Run` following the stainless rename in `chore(stainless): rename run
models in runs resource`
- Applies ktfmt formatting to changed files
- Updates test files and example module that were also broken by the
rename

This is a cherry-pick of the fixes from [PR
#24](https://github.com/langchain-ai/langsmith-java-staging/pull/24)
targeting `next` so that the `next → main` promotion PR (#22) can build
successfully.

## Test plan

- [x] `./scripts/build` passes locally
- [x] CI passes on this PR

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 13:41:58 -07:00
KiewanVillatel 10a1f9bcbb fix(ci): add GH_REPO to release-please trigger publish step (#24)
## Summary

- The `release-please.yml` workflow's "Trigger publish" step calls `gh
workflow run` but the workflow has no `actions/checkout` step, so `gh`
cannot detect the repository from git context and fails with: `fatal:
not a git repository`
- This caused `v0.1.0-beta.9` (and `beta.8`) publish to not auto-trigger
— `beta.8` required a manual `workflow_dispatch`, and `beta.9` was never
published
- The retry of the release-please run (which GitHub auto-triggered) then
skipped the step entirely because `release_created` was no longer set
(the release already existed)
- Fix: add `GH_REPO: ${{ github.repository }}` so `gh` CLI knows the
target repo without needing a checkout

## Root cause trace

1. PR merged → release-please action ran, created the release, set
`release_created: true`
2. "Trigger publish" step executed `gh workflow run publish-sonatype.yml
--ref <tag>` → **failed** with `fatal: not a git repository`
3. Workflow auto-retried ~10.5 hours later → release-please found the
release already exists → `release_created` not set → "Trigger publish"
**skipped**

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 13:38:11 -07:00
stlc-bot 2f0ce40b86 chore(stainless): rename run models in runs resource
Stainless-Generated-From: e622f9e97b62df51f4dee52b289ed6857f615f26
2026-06-30 13:05:37 +00:00
Kiewan Villatel bbd15dab2c Build SDK
Stainless-Generated-From: 36442af6b17ea627fed196a08ea9ab79b9570abb
2026-06-30 14:27:33 +02:00
KiewanVillatel 2c08fe0ecb ci: prevent modification of Stainless-generated files on next branch (#23)
## Summary

- Adds a new GitHub Actions workflow
(`.github/workflows/check-stainless-file-modification.yml`) that runs on
every PR targeting the `next` branch.
- The workflow scans all changed files and fails if any contains the
header comment `File generated from our OpenAPI spec by Stainless.`
- When the check fails, it clearly guides the author toward one of three
remedies:
  1. Refactor to avoid touching the generated file
  2. Use Stainless configuration (OpenAPI extensions or transforms)
3. Add the label `skip-stainless-generated-file-modification-check` as a
last resort to bypass the check
- Re-triggers on label add/remove so adding the skip label immediately
clears the failure.
- Scoped to this repository only (won't run on forks).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 14:00:25 +02:00
sineha-mani 7d9a2fafd4 feat(evaluation): add core evaluate() param parity (#17)
**Description**
This change makes Java evaluate() faster and adds the main Python
evaluate() options that were still missing.

By default, nothing changes: examples run one at a time, in order, and
results upload to LangSmith as a new experiment.

What’s new

maxConcurrency — run multiple examples in parallel (default 0 =
sequential). Helps when each row is slow (LLM calls, network I/O).
numRepetitions — evaluate each row multiple times in one experiment.
Summary evaluators — dataset-level scores (e.g. precision/recall) after
all rows finish.
experimentId — add runs to an existing experiment instead of creating a
new one.
uploadResults(false) — local-only evaluation, no session or feedback
upload (dry runs, CI).
errorHandling — LOG (default) or IGNORE when the target fails on a row.
evaluateAsync() — non-blocking evaluation via CompletableFuture.


Feature | New in #17?
-- | --
maxConcurrency | 
summaryEvaluators | 
numRepetitions | 
experimentId (extend existing) | 
uploadResults | 
errorHandling | 
evaluateAsync() / blocking | 

<img width="1190" height="452" alt="Screenshot 2026-06-25 at 3 10 50 PM"
src="https://github.com/user-attachments/assets/1eb91d1d-abc9-4d9f-bb72-4f42e5aacef6"
/>

References
[LSDK-241](https://linear.app/langchain/issue/LSDK-241/add-evaluate-to-langsmith-java-sdk-example-release)

Sequential (max concurrency = 0)
<img width="1197" height="366" alt="Screenshot 2026-06-25 at 12 36
48 PM"
src="https://github.com/user-attachments/assets/f363b18e-059e-48da-8088-fe1d00e88d99"
/>

Parallel (max concurrency = 3)
<img width="1186" height="354" alt="Screenshot 2026-06-25 at 12 40
48 PM"
src="https://github.com/user-attachments/assets/05701745-27b2-4bd2-a2a5-5e30531b88c7"
/>

**Test Plan**
[X] ./gradlew :langsmith-java-core:test --tests
"com.langchain.smith.evaluation.EvaluateRunnerTest"
[X] Manual smoke: run evaluate() on a dataset with maxConcurrency(4) and
confirm runs appear in LangSmith with feedback scores

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 17:43:36 -05:00
github-actions[bot] ab6c153520 release: 0.1.0-beta.9 (#167)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-29 23:01:35 +02:00
KiewanVillatel 34f1c52fc1 Release SDK updates (#166)
fix: trigger publish-sonatype workflow after release-please creates a
release (#13)

feat(sandboxes): expose registry CRUD and generate Python/TypeScript SDK
clients

fix(backend)!: accept float value for percentile metric [LS-000]

feat(api)!: document v2 experiment-runs and de-publicize legacy dataset
endpoints

feat(sandbox-host): per-VM cgroups for CPU bursting (2x), fractional
vCPU, and sustained-burst limiting

feat: add GET endpoint to retrieve single workspace by id [closes
ENT-508]

feat(evaluation): add Java evaluate orchestrator (#8)
2026-06-29 22:47:46 +02:00
sineha-mani d1420efd7a feat(evaluation): add Java evaluate orchestrator (#8)
**Description**
Adds the Java evaluate() orchestrator on top of PR 1 (foundation). We
can now run an app against a dataset, trace each prediction, score it
with evaluators, and upload feedback as a LangSmith experiment. We are
aiming for parity with Python's client.evaluate().

End-to-end flow:

- Load test cases from a dataset (by name or ID), or pass in a list
directly
- Start an experiment in LangSmith to group the run
- For each test case: run your app, score the result, save feedback to
LangSmith
- Return a summary with each row’s run and scores


File | Purpose
-- | --
Evaluate.kt | Public entry point — Evaluation.evaluate(client, target,
params) (+ Java Function overload)
EvaluateParams.kt | Builder config: dataset (name/ID or examples),
evaluators, experiment name/prefix, description, metadata
EvaluateRunner.kt | Core loop: resolve dataset → list examples → create
session → trace app per row → run evaluators → collect results.
Resolves dataset name → UUID before listing examples (required by local
API)
EvaluateFeedback.kt | Maps EvaluationResult → FeedbackCreateSchema and
posts via client.feedback().create() with ModelFeedbackSource
ExperimentResults.kt | Return types: ExperimentResults (experiment
metadata + rows) and ExperimentResultRow (run, example, scores)
EvaluateRunnerTest.kt | Unit test with mocked client: tracing, scoring,
and feedback posting
AutoBatchQueueTest.kt (fix) | Fixes flaky CI test — waits on latch
instead of Thread.sleep(100) for async batch flush

**Follow Up** 
Support parallel

**Test Plan**
Verified locally against local LangSmith via the new example.


<img width="859" height="401" alt="Screenshot 2026-06-23 at 4 38 48 PM"
src="https://github.com/user-attachments/assets/862c5a9d-d198-4c83-a6b5-8c0751086ff1"
/>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 14:12:36 -05:00
stlc-bot cdbd36936f feat: add GET endpoint to retrieve single workspace by id [closes ENT-508]
Stainless-Generated-From: c6359ba3a207af8363ad5a4c2209270fccf84acb
2026-06-29 15:55:00 +00:00
stlc-bot 49bf68d747 feat(sandbox-host): per-VM cgroups for CPU bursting (2x), fractional vCPU, and sustained-burst limiting
Stainless-Generated-From: 81dece96e6afea57d658b0401e421c7bc3904003
2026-06-28 19:11:51 +00:00
stlc-bot 387526fd3a feat(api)!: document v2 experiment-runs and de-publicize legacy dataset endpoints
Stainless-Generated-From: 30e72d154a104230281840db94c0c4553ecdb293
2026-06-26 22:34:17 +00:00
stlc-bot e88560861c fix(backend)!: accept float value for percentile metric [LS-000]
Stainless-Generated-From: daeffb8cae1466669df7ea9d06949df815082dcf
2026-06-26 01:31:05 +00:00
stlc-bot cfed196979 feat(sandboxes): expose registry CRUD and generate Python/TypeScript SDK clients
Stainless-Generated-From: d36ac9040eeab46766b7560ab5afebcf988add42
2026-06-26 00:11:01 +00:00
KiewanVillatel 70edc4514c fix: trigger publish-sonatype workflow after release-please creates a release (#13)
## Problem

`publish-sonatype.yml` is triggered by `release: types: [published]`,
but the release created by the `release-please` action uses
`GITHUB_TOKEN` (`github-actions[bot]`). GitHub explicitly prevents
events created by `GITHUB_TOKEN` from triggering other workflows, so the
publish step was silently skipped for v0.1.0-beta.8.

Prior releases (beta.7 and earlier) worked because they were created by
`stainless-app[bot]` (a GitHub App token), which does not have this
restriction.

## Fix

Add a `gh workflow run` step inside the release-please job that fires
`publish-sonatype.yml` explicitly after the release is created,
conditioned on `steps.release.outputs.release_created`.

## Test plan

- [ ] Merge this PR and confirm the next release-please PR merge
triggers a `publish-sonatype.yml` run
- [ ] Manually trigger `publish-sonatype.yml` via `workflow_dispatch`
for v0.1.0-beta.8 to publish the missed release

🤖 Generated with [Claude Code](https://claude.ai/claude-code)
2026-06-25 11:47:21 +02:00
github-actions[bot] 6f1ae304fa release: 0.1.0-beta.8 (#165)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-25 11:17:49 +02:00
KiewanVillatel 84e02f96b4 Merge pull request #164 from langchain-ai/stainless/release
Release SDK updates
2026-06-25 10:57:06 +02:00
KiewanVillatel 65b5b9ae40 chore: add release-please workflow (#11)
## Description

Adds `.github/workflows/release-please.yml` to automate release PR
creation whenever commits land on `main`.

Closes:
https://linear.app/langchain/issue/LIN-400/chore-add-release-please-workflow-to-langsmith-java

- Adds the release-please workflow, guarded by `github.repository ==
'langchain-ai/langsmith-java'` so it only fires on the production repo.
- The existing `release-please-config.json` (release-type `simple`,
Gradle extra files) and `.release-please-manifest.json` (current version
`0.1.0-beta.7`) are already in place — no additional config needed.
- `publish-sonatype.yml` will continue to fire automatically via its
`release: published` trigger once release-please merges a release PR and
creates the GitHub Release.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 09:55:02 +02:00
sineha-mani a82b5c88fc feat(evaluation): Java evaluate() primitives (#7)
## Description

First PR in the Java `evaluate()` rollout based on the high priority
request by PayPal.

Python evaluate() needs three things: (1) run each dataset example
through a traced target, (2) score runs with evaluators, (3) post
feedback to LangSmith. This branch adds the shared primitives those
steps depend on — mirroring Python’s RunEvaluator, EvaluationResult,
evaluator coercion, and experiment/example linkage on runs.

We use generated types when possible. 

References
[LSDK-239](https://linear.app/langchain/issue/LSDK-239/add-evaluate-to-langsmith-java-sdk-tracing-types)

**_No breaking changes expected. All evaluate primitives are new public
surface (RunEvaluator, runEvaluator, EvaluationResult, etc.). The full
Client.evaluate() orchestrator is not included yet._**

What each file is doing:

**Evaluation types (com.langchain.smith.evaluation)**

EvaluationResult.kt — Defines what a single score looks like: name,
score, comment, and related fields. Uses generated
FeedbackCreateSchema.FeedbackConfig when a judge specifies how a score
should be interpreted.
EvaluationResults.kt — Holds multiple scores when one judge returns more
than one grade.
RunEvaluator.kt — The contract for something that judges one attempt
against the expected answer.
DynamicRunEvaluator.kt — Turns a custom judge function into something
the SDK can call consistently. Supports three input shapes (run +
example, outputs vs reference, inputs + outputs + reference) and
Java-friendly interfaces (RunExampleEvaluator,
OutputReferenceEvaluator).
EvaluatorUtils.kt — Unpacks run/example data into plain maps and
normalizes whatever a judge returns (bool, number, map, list, etc.) into
structured EvaluationResult(s).

**Tracing updates**

Traceable.kt — Lets traced runs remember which dataset row and
experiment they belong to (referenceExampleId, sessionId).
RunTree.kt — Carries that link through the run record sent to LangSmith.

**Tests**

EvaluationResultTest.kt — Verifies score objects and generated feedback
config are built correctly.
DynamicRunEvaluatorTest.kt — Verifies custom judges work in the
different ways users might write them.
TraceableEvaluateContextTest.kt — Verifies predictions are tagged with
the right example and experiment when traced.

## What's intentionally out of scope

- No `evaluate()` entry point yet (PR 2)
- No experiment session management or feedback upload loop
- No examples / release docs (PR 3)

## Test plan

- [X] `./gradlew :langsmith-java-core:test --tests
"com.langchain.smith.evaluation.*" --rerun`
- [X] `./gradlew :langsmith-java-core:test --tests
"com.langchain.smith.tracing.TraceableEvaluateContextTest" --rerun`
- [X] `./gradlew :langsmith-java-core:formatKotlin lintKotlin`

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 16:34:47 -07:00
KiewanVillatel e097147ff3 Merge pull request #163 from langchain-ai/stainless/release
Release SDK updates
2026-06-24 15:50:26 +02:00
KiewanVillatel ba419de13e fix(tests): remove duplicate retrieve() method in RunServiceTest (#10)
## Summary

- Removes a duplicate `fun retrieve()` method from `RunServiceTest.kt`
that caused a `Conflicting overloads` Kotlin compile error, breaking
`gradlew build`

## Root cause

**Stainless custom-code boundary mis-detection, not a codegen bug.**

Before commit `48c283c` (`feat(runs/v2): expose runs v2 endpoints
publicly`), the file already had one `fun retrieve()` at line ~453 — a
**previously generated** test that happened to sit immediately after the
hand-written batch tests.

When `48c283c` ran Stainless to add `retrieveV1`, `retrieveV2`, and the
`retrieve` alias, Stainless had to merge the new generated output with
the existing file. It:
1. Correctly preserved the hand-written batch tests as custom code
2. **Incorrectly** treated the old `retrieve()` test as custom code too
(it was adjacent to the hand-written section), freezing it in place
3. Then emitted the newly generated `retrieve()` alias test at its
correct config-order position → **duplicate**

Confirmed by running `stlc generate` without custom code: the raw
generated output has exactly one `retrieve()`, correctly positioned
after `retrieveV1`/`retrieveV2`.

The removed occurrence (line 454) was the stale generated test
mis-classified as custom code during the `48c283c` merge. The kept
occurrence (now ~line 700) is the freshly generated alias test at its
proper position.

## Test plan

- [x] CI passes on this branch

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:20:36 +02:00
stlc-bot 17bb73a006 chore(smith-sdks): update Java SDK stainless custom-code tracking file
Stainless-Generated-From: 2062f5beb2ee44bbc4337d84567cb8aafff7986a
2026-06-24 10:26:05 +00:00
Kiewan Villatel 48c283c7a7 feat(runs/v2): expose runs v2 endpoints publicly with stainless config
Stainless-Generated-From: 2698695f4f6a7b8162cd28e9066480ff3001ed79
2026-06-24 11:14:42 +02:00
KiewanVillatel 3c36fa9f58 Merge pull request #162 from langchain-ai/stainless/release
Release SDK updates
2026-06-24 10:48:33 +02:00
stlc-bot 49039f2770 feat(stats): add include_details param to /runs/stats for backwards compat
Stainless-Generated-From: ac7d88b132b0ec8c2fb75b773f5748a19a8ae478
2026-06-23 18:23:48 +00:00
stlc-bot 54d105ef91 fix(sandboxes)!: default S3 mount endpoint
Stainless-Generated-From: 3761a9c27af396f6b87676441431bbc645bfa0a1
2026-06-23 12:46:33 +00:00
stlc-bot b963cab6c6 refactor: rename field to match prev trace retention fields
Stainless-Generated-From: dd221815bcfc4a9bb610709484d60c2ca7c69e11
2026-06-22 19:45:43 +00:00
stlc-bot 734a8285cd feat(sandboxes): move mounts under mount_config
Stainless-Generated-From: 474ffec1d88a86c444dfcd8cd93176a196650e30
2026-06-19 11:12:28 +00:00
KiewanVillatel 152169f4e1 ci: add workflow to promote stlc/from-prod → next on approval (#6)
## Summary

- Adds `stlc-promote-from-prod-to-next.yml`: on an approving review of a
`stlc/from-prod → next` PR, dispatches `git-ff-only-merge.yml` in
langchainplus with `src_branch=stlc/from-prod` and `target_branch=next`
- Updates `stlc-promote-to-main.yml` to dispatch `git-ff-only-merge.yml`
(renamed from `stlc_promote.yml`) and use the new single-repo `repo`
field

Depends on: https://github.com/langchain-ai/langchainplus/pull/27932

## Test plan

- [ ] Approve the `stlc/from-prod → next` PR in this repo and verify the
workflow dispatches and fast-forwards `next` to `stlc/from-prod`
- [ ] Confirm existing `next → main` promotion still works via
`stlc-promote-to-main.yml`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 09:03:58 +02:00
stlc-bot ac8da0f77c fix(sandboxes): use built-in gcp proxy host matching
Stainless-Generated-From: 98e1d224d4e639c875df7395a46cdba8c84961f0
2026-06-18 13:41:33 +00:00
stlc-bot 7f24878418 feat(group-stats): back thread stats with SmithDB
Stainless-Generated-From: 8bed75a505269e08bfcbd524db27819bd139e83c
2026-06-16 18:58:33 +00:00
stlc-bot 3c218e98bc feat(run-rules): per-action trace-retention control for automations [LSO-2749]
Stainless-Generated-From: 820e434600b1e6b444e26fb94d7d35cd4ba52f25
2026-06-16 17:31:22 +00:00
stlc-bot 2ddc687610 feat(sandboxes)!: add ArtifactFS git mounts
Stainless-Generated-From: 699e98adc655572eead9632f161868b2d938f4e6
2026-06-16 12:13:59 +00:00
KiewanVillatel f00fef565e chore: add stlc sync and promotion workflows (#5)
Replicates the four STLC lifecycle workflows from
`langsmith-go-staging`, adapted for the Java SDK.

## Workflows added

| File | Trigger | Purpose |
|------|---------|---------|
| `stlc-sync-from-production.yml` | Cron (every 6h) + manual | Opens a
PR syncing `langsmith-java` (production) `main` back onto staging `next`
after a release |
| `stlc-promote-to-production.yml` | Push to `main` | Pushes staging
`main` to `stainless/release` on `langsmith-java` and opens/updates the
release PR |
| `stlc-promote-to-main.yml` | PR review submitted | When the `next →
main` PR is approved, dispatches `stlc_promote.yml` in `langchainplus` |
| `stlc-seal-custom-code.yml` | Push to `next` | Dispatches
`stlc_seal_custom_code.yml` in `langchainplus` scoped to
`langsmith-java-staging` |

All workflow filenames use hyphens and the `stlc: ...` name prefix
consistently. Logic is otherwise identical to the Go staging repo with
mechanical `go` → `java` substitutions.
2026-06-15 15:08:20 +02:00
stlc-bot 87e3acbb7b feat(sandboxes)!: add gcs bucket mounts
Stainless-Generated-From: d653643420667367faf788d3093269783eb0b9e4
2026-06-15 12:42:39 +00:00
stlc-bot 280a37489d feat(abac): add tag_value_ids to dataset creation endpoints
Stainless-Generated-From: 78633c683d6095befbc2e23f75c6c6e1aaf05084
2026-06-12 20:42:52 +00:00
stlc-bot 77f12e3a6e feat: end-to-end OAuth bearer for Playground [part of ENT-760]
Stainless-Generated-From: aae0770e95c27ab3ffcaae4e98706844a460bdc3
2026-06-12 17:31:19 +00:00
stlc-bot b86e854687 feat(backend): allow retrieving and storing V2 charts in database [LSO-2799]
Stainless-Generated-From: a6e52cd3ade297b9840fb679d25dc80e62a1eece
2026-06-12 16:15:49 +00:00
stlc-bot fd2980ce69 feat(sandboxes): add GCP proxy auth flow
Stainless-Generated-From: e19ec4f409a11b384e2bc5bdb260f025a53186b2
2026-06-12 11:36:58 +00:00
stlc-bot e480cd4104 feat: make online evaluator retention opt-in (backend)
Stainless-Generated-From: 833d850dc17eca03babbdf20bfd08e0b346084b8
2026-06-12 04:52:51 +00:00
stlc-bot 3f3a0c9c30 fix(evaluators): show code evaluator trace counts [LSE-2359]
Stainless-Generated-From: b73e13aeea10d57c3ea8fb62d922c34169736192
2026-06-11 22:17:29 +00:00
stlc-bot 77d19d3241 feat: add online evaluator endpoints to stainless config
Stainless-Generated-From: 6cc6df8d86158cf1cf863f95233a383508ee383f
2026-06-11 16:03:03 +00:00
stlc-bot 5a6bddd241 feat: adding time to first token for experiment metrics
Stainless-Generated-From: 53b0a374d8994730eeb629dd5b89d7e1dc0aa251
2026-06-10 18:07:42 +00:00
stlc-bot 10f1d65063 feat(hub): add include_owners to repos list for Fleet [closes AB-2537]
Stainless-Generated-From: d3f29792ae8ba2102d6583e32ddfe369c77beb43
2026-06-10 01:16:09 +00:00
stlc-bot 79647d1579 feat(sandboxes): snapshot memory from stopped boxes + honor restore_memory in v2 [INF-0000]
Stainless-Generated-From: 6bcb8e63cc367450c7ddf3dd646bf9932d7c49fb
2026-06-09 19:29:44 +00:00
stlc-bot f3ce273a02 feat(abac): add tag_value_ids to prompt creation endpoints [ENT-1176]
Stainless-Generated-From: b130dec878cfb300bf3156c9ef00f4fa57c3533b
2026-06-08 13:50:16 +00:00
stlc-bot bbbcf7bf40 feat(fleet): frontend passes typed fields (closes AB-000)
Stainless-Generated-From: d3df6f0341a9641e0c5353f5f3350b96b7038973
2026-06-08 09:22:40 +00:00
stlc-bot f859585592 feat(abac): add tag_value_ids to tracer session creation
Stainless-Generated-From: e9fdc72fa5c1fa28ec45a7fb08bd4078aaf732f3
2026-06-05 14:27:28 +00:00
stlc-bot 370b940a1c refactor(playground): remove legacy experiment endpoints [LSO-2230]
Stainless-Generated-From: 9953c40859655979614cd3c43c7a0755c45ed45e
2026-06-04 21:29:54 +00:00
Kiewan Villatel ad6daecac9 Revert "Build SDK"
This reverts commit 6b25ea38a8.
2026-06-03 21:20:47 +02:00
Kiewan Villatel 6b25ea38a8 Build SDK
Stainless-Generated-From: 3a0cd98377fb09f5d85dba3ea691f3849ba02263
2026-06-03 21:19:39 +02:00
stlc-bot 8b85e16472 chore(fleet): add param to list threads [closes AB-2522]
Stainless-Generated-From: c8d2efa256195ac10d872571d2e2139e1f7b0ff4
2026-06-02 18:50:54 +00:00
stlc-bot 7cb70fc3e3 feat(sandboxes): proxy runtime routes through platform backend [INF-0000]
Stainless-Generated-From: d30b7b81813354ec1ca1ae1094bbc1107017ac45
2026-06-02 17:23:16 +00:00
stlc-bot cb519f93d4 feat(sandboxes): resolve AWS proxy rule secrets
Stainless-Generated-From: cf35f3ba977200bfaef8361b423147be29b16c81
2026-06-02 12:55:51 +00:00
stlc-bot 6bbe522f0a feat(sandboxes): set AWS proxy compatibility env
Stainless-Generated-From: 9a9800e9de6b6498fe555f10a8961b322dd1ab1a
2026-06-01 14:43:32 +00:00
stlc-bot bb9f973f41 feat(sandboxes): filter lists by creator [INF-1492]
Stainless-Generated-From: 6521e9d00bd87c684594407f5a093ad54a2a0941
2026-06-01 12:11:47 +00:00
stlc-bot da0fc7b507 feat(sandboxes): add sandbox env var support
Stainless-Generated-From: e928201c44a02a9ced8002607677081d66ea4778
2026-06-01 10:17:32 +00:00
Kiewan Villatel 9c85b0cfd1 Build SDK
Stainless-Generated-From: 45836ee9e735d7588029f65d656d810f719b07f5
2026-05-30 14:28:28 +02:00
Kiewan Villatel 25b9aacfda Build SDK
Stainless-Generated-From: f669148e795626b9d422ea415951fdf934d9c9d5
2026-05-28 14:04:14 +02:00
Kiewan Villatel 3bd596cefa Resolve merge conflicts in ci, stats, and example build 2026-05-28 10:52:13 +02:00
Kiewan Villatel 334fe4ef56 Build SDK
Stainless-Generated-From: b3f3c130ed43c672c54694b4121e90305c6c58c7
2026-05-28 10:14:00 +02:00
stainless-app[bot] 744987978d release: 0.1.0-beta.7 (#156)
* chore(internal): renormalize gradlew.bat line endings

* fix(go): avoid panic when http.DefaultTransport is wrapped

defaultHTTPClient performed an unchecked type assertion on
http.DefaultTransport, which panicked for any caller that had wrapped
the global transport (e.g. otelhttp.NewTransport for distributed
tracing). When the assertion fails, fall back to the wrapped
RoundTripper as-is — preserving the caller's wrapping at the cost of
ResponseHeaderTimeout, which is strictly better than panicking.

* feat(api): api update

* docs: clarify forwards compat behavior

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* chore: remove duplicated dokka setup

* codegen metadata

* codegen metadata

* codegen metadata

* perf(client): create one json mapper

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(client): support proxy authentication

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(sdk): add hub directories endpoints to Stainless config

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): api update

* fix(go): avoid panic when http.DefaultTransport is wrapped

defaultHTTPClient performed an unchecked type assertion on
http.DefaultTransport, which panicked for any caller that had wrapped
the global transport (e.g. otelhttp.NewTransport for distributed
tracing). When the assertion fails, fall back to the wrapped
RoundTripper as-is — preserving the caller's wrapping at the cost of
ResponseHeaderTimeout, which is strictly better than panicking.

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* chore(internal): codegen related update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): api update

* feat(api): api update

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* fix: restore dokka-javadoc plugin application to subprojects (#157)

* fix: restore dokka-javadoc plugin application to subprojects

Commit b1af8d8 ("remove duplicated dokka setup") removed
`apply(plugin = "org.jetbrains.dokka-javadoc")` from the subprojects
block, thinking the root-level `plugins {}` declaration covered it.

The root `plugins {}` block applies the plugin to the root project only.
Each subproject needs its own application to register the
`dokkaGeneratePublicationJavadoc` task, which
`langchain.publish.gradle.kts` depends on via
`JavadocJar.Dokka("dokkaGeneratePublicationJavadoc")`. Without it,
configuring any subproject that applies `langchain.publish` fails with:

  Could not create task ':langsmith-java:dokkaJavadocJar'.
  > Task with name 'dokkaGeneratePublicationJavadoc' not found in
    project ':langsmith-java'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: fix double blank lines flagged by ktfmt lint

Remove spurious extra blank lines between `logLevel` and `apiKey` in
three Builder classes. ktfmt (--kotlinlang-style) disallows consecutive
blank lines inside a class body.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* release: 0.1.0-beta.7

---------

Co-authored-by: meorphis <eric@stainless.com>
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: Kiewan Villatel <kiewan@langchain.dev>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 14:32:52 +00:00
stainless-app[bot] c904c2d278 release: 0.1.0-beta.6 (#154)
* fix: patch security alerts — OTel, Tomcat, Jetty, Logback, Spring, SnakeYAML (#153)

Resolves open Dependabot security alerts across critical+high severity tiers.

Key change: opentelemetry-api/sdk/exporter-otlp 1.32.0 → 1.62.0 (CVE-2026-45292,
published dep). Semconv 1.23.1-alpha → 1.41.1 (stable GA); migrates
ResourceAttributes.SERVICE_* → ServiceAttributes.SERVICE_* (class removed in 1.30+).

Other fixes (test/example scope, non-published):
- Jetty 9.4.57 → 9.4.58.v20250814 (CVE-2025-5115 in http2-common)
- Logback 1.2.13 → 1.5.32 (CVE-2025-11226, CVE-2024-12798, CVE-2024-12801, CVE-2026-1225;
  the 1.2.13 pin was overly conservative — CVE-2023-6481/6378 are fixed since 1.3.12)
- Tomcat 9.0.115 → 9.0.118 in example module (14 alerts, 4 critical)
- Spring 5.3.34 → 5.3.39 + spring-expression constraint (CVE-2024-38809, CVE-2024-38808)
- SnakeYAML 1.31 → 1.32 (CVE-2022-38752, CVE-2022-41854)

Adds SECURITY_FIX_PLAN.md tracking open/stale/no-fix-available alert status.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* release: 0.1.0-beta.6

---------

Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
2026-05-20 11:19:28 -07:00
stainless-app[bot] 1540e83048 release: 0.1.0-beta.5 (#151)
* codegen metadata

* chore(deps): bump the minor-and-patch group across 1 directory with 3 updates (#145)

Bumps the minor-and-patch group with 3 updates in the / directory: [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin), [jvm](https://github.com/JetBrains/kotlin) and [gradle-wrapper](https://github.com/gradle/gradle).


Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 2.3.0 to 2.3.21
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.3.0...v2.3.21)

Updates `jvm` from 2.3.0 to 2.3.21
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.3.0...v2.3.21)

Updates `gradle-wrapper` from 9.4.1 to 9.5.0
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.4.1...v9.5.0)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: jvm
  dependency-version: 2.3.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-version: 2.3.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/github-script from 8 to 9 (#144)

Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump gradle/actions (#143)

Bumps the minor-and-patch group with 1 update in the / directory: [gradle/actions](https://github.com/gradle/actions).


Updates `gradle/actions` from 6.0.1 to 6.1.0
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f...50e97c2cd7a37755bbfafc9c5b7cafaece252f6e)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(sandbox): make snapshot optional (#152)

* feat(sandbox): default create to server runtime

* fix(sandbox): restore generated helper import

* release: 0.1.0-beta.5

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev>
2026-05-13 22:19:30 +00:00
stainless-app[bot] 513d207b80 release: 0.1.0-beta.4 (#148)
* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* Refresh profile auth before requests (#149)

* Refresh profile auth before requests

* Use profile URL for OAuth refresh

* release: 0.1.0-beta.4

---------

Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: Mukil Loganathan <mukil@langchain.dev>
2026-05-04 14:44:29 +00:00
stainless-app[bot] 48a0841016 release: 0.1.0-beta.3 (#147)
* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* Add profile loading (#146)

* codegen metadata

* release: 0.1.0-beta.3

---------

Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: Mukil Loganathan <mukil@langchain.dev>
2026-05-01 14:01:53 +00:00
stainless-app[bot] 090c06bbaa release: 0.1.0-beta.2 (#142)
* codegen metadata

* fix: Fix another build issue (#141)

* release: 0.1.0-beta.2

---------

Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: Jacob Lee <jacoblee93@gmail.com>
2026-04-30 19:00:56 +00:00
stainless-app[bot] ea5f34e155 release: 0.1.0-beta.1 (#140)
* chore: Fix build (#139)

* Fix build

* Comment

* release: 0.1.0-beta.1

---------

Co-authored-by: Jacob Lee <jacoblee93@gmail.com>
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
2026-04-30 18:07:06 +00:00
stainless-app[bot] 14634be733 release: 0.1.0-beta.0 (#132)
* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): sandbox apis

add sandbox apis

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* chore(deps): bump the minor-and-patch group across 1 directory with 5 updates (#123)

* chore(deps): bump the minor-and-patch group with 5 updates

Bumps the minor-and-patch group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [org.jetbrains.dokka](https://github.com/Kotlin/dokka) | `2.0.0` | `2.2.0` |
| [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) | `2.0.21` | `2.3.20` |
| [com.vanniktech:gradle-maven-publish-plugin](https://github.com/vanniktech/gradle-maven-publish-plugin) | `0.28.0` | `0.36.0` |
| [jvm](https://github.com/JetBrains/kotlin) | `2.0.21` | `2.3.20` |
| [com.vanniktech.maven.publish](https://github.com/vanniktech/gradle-maven-publish-plugin) | `0.28.0` | `0.36.0` |

Updates `org.jetbrains.dokka` from 2.0.0 to 2.2.0
- [Release notes](https://github.com/Kotlin/dokka/releases)
- [Commits](https://github.com/Kotlin/dokka/compare/v2.0.0...v2.2.0)

Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 2.0.21 to 2.3.20
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.0.21...v2.3.20)

Updates `com.vanniktech:gradle-maven-publish-plugin` from 0.28.0 to 0.36.0
- [Release notes](https://github.com/vanniktech/gradle-maven-publish-plugin/releases)
- [Changelog](https://github.com/vanniktech/gradle-maven-publish-plugin/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vanniktech/gradle-maven-publish-plugin/compare/0.28.0...0.36.0)

Updates `jvm` from 2.0.21 to 2.3.20
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.0.21...v2.3.20)

Updates `com.vanniktech.maven.publish` from 0.28.0 to 0.36.0
- [Release notes](https://github.com/vanniktech/gradle-maven-publish-plugin/releases)
- [Changelog](https://github.com/vanniktech/gradle-maven-publish-plugin/blob/main/CHANGELOG.md)
- [Commits](https://github.com/vanniktech/gradle-maven-publish-plugin/compare/0.28.0...0.36.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.dokka
  dependency-version: 2.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-version: 2.3.20
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: com.vanniktech:gradle-maven-publish-plugin
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: jvm
  dependency-version: 2.3.20
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: com.vanniktech.maven.publish
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix build failures from dependency bumps

Remove SonatypeHost reference removed in gradle-maven-publish-plugin
0.34.0+ (publishToMavenCentral() defaults to Central Portal).
Bump proguard-gradle 7.6.0 → 7.9.0 to support Kotlin 2.3.x metadata.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Revert Kotlin to 2.3.0 to match Gradle 9.4.1 embedded version

Kotlin 2.3.20 is not yet supported by CodeQL and conflicts with
the kotlin-dsl plugin's embedded Kotlin 2.3.0. Keeping the Kotlin
version aligned with the Gradle wrapper avoids both issues.

Bump proguard-gradle to 7.8.0 (from 7.6.0) to support Kotlin 2.3.0
metadata. 7.9.0 has a null pointer regression; 7.8.x is the sweet spot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(evaluators): add list evaluators (GET /api/v1/runs/rules)

Exposes `GET /api/v1/runs/rules`.

Will result in `client.evaluators.list()` in the Go SDK.

The endpoint returns online evaluators (internally called "run rules") — automated rules that trigger on incoming runs to apply LLM/code evaluators, add runs to annotation queues, trigger webhooks, etc. The `runs/rules` URL path is an implementation detail; `evaluators` matches the user-facing branding.

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* feat: support setting headers via env

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat: Add autobatch tracing mode (#134)

* Add autobatch mode

* Fix

* Update signature

* Add async impl

* Fix optin

* Fix

* Fix

* Fix tests

* Add test

* Refactor to be more idiomatic

* Feedback

* Use explicit lock, add to AGENTS.md

* Remove test script

* codegen metadata

* feat: Merge enqueued posts and patches to optimize batching (#135)

* Merge enqueued posts and patches to optimize batching

* Exclude patched inputs by default

* Logs

* Fix import order

* Feedback

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat: Make autobatch queue denote batches by payload size, add aggregation delay (#137)

* Make autobatch queue denote batches by payload size, add aggregation delay

* Fully drain queue when batch delay is hit

* Feedback on locks

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat: Add multipart batching (#138)

* Add multipart batching

* Handle 404s better

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): add workspaces

* feat(api): manual updates

* codegen metadata

* feat(api): manual updates

* codegen metadata

* codegen metadata

* codegen metadata

* feat: Add zstd compression to runs service (#136)

* Add zstd compression

* Merge

* Fix

* nits

* Feedback

* Fix build

* codegen metadata

* release: 0.1.0-beta.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Jacob Lee <jacoblee93@gmail.com>
2026-04-30 17:23:03 +00:00
stainless-app[bot] 3220bc56d5 release: 0.1.0-alpha.28 (#130)
* feat(api): api update

* codegen metadata

* Fix publish action (#131)

* release: 0.1.0-alpha.28

---------

Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: Jacob Lee <jacoblee93@gmail.com>
2026-04-14 18:11:47 +00:00
stainless-app[bot] 9ccfc6a350 release: 0.1.0-alpha.27 (#128)
* codegen metadata

* codegen metadata

* Edong/bump kotlin gradle ver (#127) (#129)

* chore: bumping ver

* chore: add back warning

Co-authored-by: ericdong-langchain <ericdong@langchain.dev>

* release: 0.1.0-alpha.27

---------

Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: Jacob Lee <jacoblee93@gmail.com>
Co-authored-by: ericdong-langchain <ericdong@langchain.dev>
2026-04-14 00:14:52 +00:00
ericdong-langchain 9f90d52312 Edong/bump kotlin gradle ver (#127)
* chore: bumping ver

* chore: add back warning
2026-04-13 18:30:19 -04:00
stainless-app[bot] 4e86f4ee9e release: 0.1.0-alpha.26 (#125)
* codegen metadata

* feat(api): api update

* chore(deps): bump gradle-wrapper from 8.12 to 9.4.1 (#124)

Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 8.12 to 9.4.1.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v8.12.0...v9.4.1)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.4.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump gradle/actions (#121)

Bumps [gradle/actions](https://github.com/gradle/actions) from 205054a7257716ec64af10a2e2ff1ac5d3b132db to 39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/205054a7257716ec64af10a2e2ff1ac5d3b132db...39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: 39e147cb9de83bb9910b8ef8bd7fff0ee20fcd6f
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump gradle/gradle-build-action (#122)

Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 12318b01111bfa6462c00534ffa998f8b397b979 to ac2d340dc04d9e1113182899e983b5400c17cda1.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/12318b01111bfa6462c00534ffa998f8b397b979...ac2d340dc04d9e1113182899e983b5400c17cda1)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-version: ac2d340dc04d9e1113182899e983b5400c17cda1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* docs: improve examples

* codegen metadata

* feat: Allow manually specifying parent run tree in traceable config (#126)

* Allow manually specifying parent run tree in traceable config

* Allow passing parent(null)

* Agents.md fixes

* Use singleton in both places

* release: 0.1.0-alpha.26

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jacob Lee <jacoblee93@gmail.com>
2026-04-13 17:59:16 +00:00
stainless-app[bot] f3d8fdeccf release: 0.1.0-alpha.25 (#109)
* Revert "main to next (#108)" (#110)

This reverts commit 5a8988d3f6.

* Fix/lint and remove method count (#111)

* release: 0.1.0-alpha.24

* fix: lint and remove outdated method count ci test

Made-with: Cursor

---------

Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>

* feat: Adds package version resource to build (#100)

* codegen metadata

* codegen metadata

* chore: make `Properties` more resilient to `null`

* chore: drop apache dependency

* codegen metadata

* codegen metadata

* codegen metadata

* chore(internal): expand imports

* feat(api): manual updates

* codegen metadata

* feat(api): manual updates

* codegen metadata

* feat(api): manual updates

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): api update

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* feat(api): manual updates

removed endpoints not in openAPI spec

* codegen metadata

* codegen metadata

* codegen metadata

* chore: align user agent format (#96)

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): api update

* chore(internal): codegen related update

* chore(internal): bump palantir-java-format

* chore(ci): skip uploading artifacts on stainless-internal branches

* chore: update placeholder string

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* fix(client): incorrect `Retry-After` parsing

* codegen metadata

* codegen metadata

* feat(api): api update

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* chore(internal): tweak CI branches

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* chore(internal): update retry delay tests

* fix(client): allow updating header/query affecting fields in `toBuilder()`

* codegen metadata

* codegen metadata

* feat(api): api update

* feat(api): api update

* codegen metadata

* chore(internal): bump ktfmt

* chore: remove old test (#97)

* codegen metadata

* feat(api): manual updates

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* chore(internal): update gitignore

* codegen metadata

* codegen metadata

* codegen metadata

* chore(ci): skip lint on metadata-only changes

Note that we still want to run tests, as these depend on the metadata.

* Fix error messages (#102)

* codegen metadata

* Merge

* Merge

* Lint

* Add debug log

* main to next (#108)

* codegen metadata

* codegen metadata

* chore: make `Properties` more resilient to `null`

* chore: drop apache dependency

* codegen metadata

* codegen metadata

* codegen metadata

* chore(internal): expand imports

* feat(api): manual updates

* codegen metadata

* feat(api): manual updates

* codegen metadata

* feat(api): manual updates

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): api update

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* feat(api): manual updates

removed endpoints not in openAPI spec

* codegen metadata

* codegen metadata

* codegen metadata

* chore: align user agent format (#96)

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): api update

* chore(internal): codegen related update

* chore(internal): bump palantir-java-format

* chore(ci): skip uploading artifacts on stainless-internal branches

* chore: update placeholder string

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* fix(client): incorrect `Retry-After` parsing

* codegen metadata

* codegen metadata

* feat(api): api update

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* chore(internal): tweak CI branches

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* chore(internal): update retry delay tests

* fix(client): allow updating header/query affecting fields in `toBuilder()`

* codegen metadata

* codegen metadata

* feat(api): api update

* feat(api): api update

* codegen metadata

* chore(internal): bump ktfmt

* chore: remove old test (#97)

* codegen metadata

* feat(api): manual updates

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* chore(internal): update gitignore

* codegen metadata

* codegen metadata

* codegen metadata

* chore(ci): skip lint on metadata-only changes

Note that we still want to run tests, as these depend on the metadata.

* Fix error messages (#102)

* codegen metadata

* release: 0.1.0-alpha.24

* fix: lint and remove outdated method count ci test (#103)

Made-with: Cursor

* chore(deps): bump gradle/actions from 5 to 6 in the all-actions group (#99)

Bumps the all-actions group with 1 update: [gradle/actions](https://github.com/gradle/actions).


Updates `gradle/actions` from 5 to 6
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v5...v6)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: Jacob Lee <jacoblee93@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Change to warning and add a test

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: ericdong-langchain <ericdong@langchain.dev>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Adds contributing guide (#112)

* ci: add minimum workflow permissions (#106)

- Add top-level `permissions: contents: read` to all 4 workflow files
- Change release-doctor.yml trigger from `pull_request` to `pull_request_target`
  to prevent secret exfiltration via PR-controlled workflow modifications

Co-authored-by: Posture Fix <posture-fix@langchain.ai>

* ci: SHA-pin third-party Gradle actions (#107)

Pin gradle/actions/setup-gradle and gradle/gradle-build-action to full
commit SHAs to prevent supply chain attacks via tag hijacking.

- gradle/actions/setup-gradle@v6  → @205054a...  (ci.yml ×2, codeql.yml)
- gradle/gradle-build-action@v3   → @12318b0...  (ci.yml, publish-sonatype.yml)

Co-authored-by: Posture Fix <posture-fix@langchain.ai>

* chore: fix dependabot.yml posture issues (#105)

* chore: update dependabot.yml to comply with posture checks

- Change schedule from weekly to monthly for all ecosystems
- Add update-types split (major vs minor-and-patch) for gradle and github-actions
- Add docker ecosystem entry for .devcontainer/Dockerfile

* chore: add target-branch next and fix docker group split for Stainless posture compliance

---------

Co-authored-by: Posture Fix <posture-fix@langchain.ai>

* feat(api): api update

* feat(api): api update

* feat: Add `traceable` function wrapper for LangSmith tracing (#101)

* Adds versioning resource to build

* Adds initial version of traceable

* Lint

* Deflake

* Progress

* Progress

* Fixes

* Fixes

* More refactor

* Small bug

* Refactor

* Fix

* Devin feedback

* Tests and feedback

* Remove redundant comment

* Docstring

* codegen metadata

* codegen metadata

* feat: Adds processInputs and processOutputs to traceable (#113)

* Adds processInputs and processOutputs to traceable

* Move generics into TraceProcessIO to avoid having them top level

* Fix docstring, nit

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* feat(api): manual updates

* codegen metadata

* fix: Fix format detection when pulling legacy prompts (#115)

* Fix format detection when pulling legacy prompts

* Feedback

* Update AGENTS.md (#116)

* feat: Rework wrapOpenAI (#114)

* Update wrapOpenAI

* Remove

* Fixes

* nits

* Feedback

* Cache

* Remove comment

* Fix

* feat(api): api update

* feat(api): api update

* fix: patch security alerts — bump Jackson and constrain vulnerable transitive deps (#119)

Bumps Jackson 2.18.2→2.18.6 in published api deps and adds version
constraints for vulnerable transitive dependencies in test and example
scopes (Tomcat 9.0.115, Jetty 9.4.57, logback 1.2.13,
commons-fileupload 1.6.0, commons-io 2.14.0, json-smart 2.4.9,
snakeyaml 1.31, spring-web 5.3.34).

Addresses alerts: #75 (jackson-core), #82/#81/#80/#79/#78/#72/#70/#69
/#68/#66/#65/#60/#59/#54/#52/#51/#48/#46/#45/#44/#43/#35/#34/#29/#31
(tomcat/jetty/logback/commons).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* codegen metadata

* codegen metadata

* feat: Adds support for tracing streams with traceable (#117)

* Adds support for tracing streams with traceable

* Polish

* Make stream tracing opt-in

* Rework to use a passthrough instead of a proxy

* Record stream cancellations as errors

* Feedback

* Format and add to AGENTS.md

* feat: Adds streaming support for wrapOpenAI (#118)

* Adds support for tracing streams with traceable

* Polish

* Make stream tracing opt-in

* Rework to use a passthrough instead of a proxy

* Adds streaming support for wrapOpenAI

* Record stream cancellations as errors

* Adds streaming example

* Allow empty config default, add example

* Fix

* release: 0.1.0-alpha.25

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ericdong-langchain <ericdong@langchain.dev>
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
Co-authored-by: Jacob Lee <jacoblee93@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com>
Co-authored-by: Posture Fix <posture-fix@langchain.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 10:46:03 -07:00
dependabot[bot] 4ba75e7776 chore(deps): bump gradle/actions from 5 to 6 in the all-actions group (#99)
Bumps the all-actions group with 1 update: [gradle/actions](https://github.com/gradle/actions).


Updates `gradle/actions` from 5 to 6
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v5...v6)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-25 12:46:52 -07:00
ericdong-langchain 35bbb77c96 fix: lint and remove outdated method count ci test (#103)
Made-with: Cursor
2026-03-25 17:36:20 +00:00
stainless-app[bot] ad80d1105b release: 0.1.0-alpha.24 2026-03-25 17:36:20 +00:00
stainless-app[bot] 12a841eeb4 codegen metadata 2026-03-25 17:36:20 +00:00
Jacob Lee 19c9b420ea Fix error messages (#102) 2026-03-25 17:36:20 +00:00
stainless-app[bot] 53f2092dcd chore(ci): skip lint on metadata-only changes
Note that we still want to run tests, as these depend on the metadata.
2026-03-25 17:36:20 +00:00
stainless-app[bot] 81f7359cac codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 5c4f4acb92 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 80dceff65a codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] b5b42f69a3 chore(internal): update gitignore 2026-03-25 17:36:20 +00:00
stainless-app[bot] c794cc215d codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] d07b7f3f40 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 305cae3bef codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] dd6d82830b feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] 9491540375 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 22ed526472 feat(api): manual updates 2026-03-25 17:36:20 +00:00
stainless-app[bot] f7f2672957 codegen metadata 2026-03-25 17:36:20 +00:00
ericdong-langchain eaf31118a5 chore: remove old test (#97) 2026-03-25 17:36:20 +00:00
stainless-app[bot] da3535ded5 chore(internal): bump ktfmt 2026-03-25 17:36:20 +00:00
stainless-app[bot] 2796973131 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] d977e55ba6 feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] 7b9a6f2b09 feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] 219e966da6 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] d8873a4cce codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 2ce327828b fix(client): allow updating header/query affecting fields in toBuilder() 2026-03-25 17:36:20 +00:00
stainless-app[bot] b0f3b5aab6 chore(internal): update retry delay tests 2026-03-25 17:36:20 +00:00
stainless-app[bot] a54a91735b codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 959d619ee0 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 960ee71498 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 26b3f9cb2f codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] d6e265a7df codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 315a4bd074 chore(internal): tweak CI branches 2026-03-25 17:36:20 +00:00
stainless-app[bot] bd1b0e8b59 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 57116f2104 feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] 554e497084 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] c8873ff34e codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 12e572bdd1 feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] a91eece7d1 feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] d3a153ef43 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 8705818e81 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] d272832132 fix(client): incorrect Retry-After parsing 2026-03-25 17:36:20 +00:00
stainless-app[bot] 0cf53d62a4 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 8336c6349a codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 382da9a537 feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] ad75c9789b codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] f6d0b66f60 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 6666c85031 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 8f1fc7bf69 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] f09f2c48b3 chore: update placeholder string 2026-03-25 17:36:20 +00:00
stainless-app[bot] 3517baad85 chore(ci): skip uploading artifacts on stainless-internal branches 2026-03-25 17:36:20 +00:00
stainless-app[bot] a42751fe92 chore(internal): bump palantir-java-format 2026-03-25 17:36:20 +00:00
stainless-app[bot] bca5f1858b chore(internal): codegen related update 2026-03-25 17:36:20 +00:00
stainless-app[bot] 43023605f0 feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] f247ab1bf3 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 4461ffbf99 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] bb060d0897 feat(api): api update 2026-03-25 17:36:20 +00:00
ericdong-langchain 3e731df52b chore: align user agent format (#96) 2026-03-25 17:36:20 +00:00
stainless-app[bot] b9ce5af2d4 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 3eed0e974f codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] c616f1a7af codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] c6e950f613 feat(api): manual updates
removed endpoints not in openAPI spec
2026-03-25 17:36:20 +00:00
stainless-app[bot] 2d81794530 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 33152dc37b feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] a1533b5161 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] c9238e7d8a codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 8c1f5f27ad codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 06771e7b9e codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 9b3cd93e74 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 0ff2f3175f codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] f06475795a codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 81ce4fe1f3 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 6c35351e46 feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] 7256a6ca4a codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] feb7baba86 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 4baa783b9b codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 97f40ac895 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 7d80a1978c feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] 92ce263397 feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] 4466c613fc codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 848c0a5751 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 1d2e391160 feat(api): api update 2026-03-25 17:36:20 +00:00
stainless-app[bot] dab7b73dc9 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 59ea961218 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 873396aceb feat(api): manual updates 2026-03-25 17:36:20 +00:00
stainless-app[bot] f12b234a80 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] a2520117a5 feat(api): manual updates 2026-03-25 17:36:20 +00:00
stainless-app[bot] 10946e186a codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 9bd1c8c310 feat(api): manual updates 2026-03-25 17:36:20 +00:00
stainless-app[bot] 44a2308df3 chore(internal): expand imports 2026-03-25 17:36:20 +00:00
stainless-app[bot] 765bad3142 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 41fcd7cec9 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] f102c9929d codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 6866c4fdfe chore: drop apache dependency 2026-03-25 17:36:20 +00:00
stainless-app[bot] 45dd9a2727 chore: make Properties more resilient to null 2026-03-25 17:36:20 +00:00
stainless-app[bot] 7512302dd4 codegen metadata 2026-03-25 17:36:20 +00:00
stainless-app[bot] 98e16a283e codegen metadata 2026-03-25 17:36:20 +00:00
Jacob Lee 8cdc61ab86 Merge pull request #98 from langchain-ai/jacob/prompts
feat: Adds prompt pulling helpers
2026-03-25 09:01:29 -07:00
dependabot[bot] 3a1a72f559 chore(deps): bump the all-actions group with 5 updates (#92)
Bumps the all-actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `4` | `6` |
| [actions/setup-java](https://github.com/actions/setup-java) | `4` | `5` |
| [gradle/actions](https://github.com/gradle/actions) | `4` | `5` |
| [actions/github-script](https://github.com/actions/github-script) | `6` | `8` |
| [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) | `2` | `3` |


Updates `actions/checkout` from 4 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

Updates `actions/setup-java` from 4 to 5
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

Updates `gradle/actions` from 4 to 5
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v4...v5)

Updates `actions/github-script` from 6 to 8
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v6...v8)

Updates `gradle/gradle-build-action` from 2 to 3
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-actions
- dependency-name: actions/setup-java
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-actions
- dependency-name: gradle/actions
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-actions
- dependency-name: actions/github-script
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-actions
- dependency-name: gradle/gradle-build-action
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-20 16:55:58 -04:00
ericdong-langchain 76da93f2c0 fix: fix streaming in java sdk (#95)
* fix: fix streaming in java sdk

* chore: cleanups

* chore: setting gen_ai.completion to json text

* chore: lint fix

* chore: comments

* chore: lint

* fix: type mismatch

* chore: lint

* chore: clean up building JSON
2026-03-09 16:07:54 -04:00
John Kennedy cf4696d51a Merge pull request #89 from langchain-ai/jk/deps
chore: dependabot
2026-02-24 18:54:28 -08:00
Eric Dong d99b42f8af fix: fix typing 2026-02-20 15:53:03 +00:00
stainless-app[bot] 19b171e07e release: 0.1.0-alpha.23 2026-02-20 15:53:03 +00:00
stainless-app[bot] d02088b5c9 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 8c1c7d1930 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] ee9eec9529 chore: update mock server docs 2026-02-20 15:53:03 +00:00
stainless-app[bot] 000f84af61 chore(internal): remove mock server code 2026-02-20 15:53:03 +00:00
stainless-app[bot] 115ecd9065 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] a5822f60d0 feat(client): add connection pooling option 2026-02-20 15:53:03 +00:00
stainless-app[bot] ea263dc48f feat(client): add more convenience service method overloads 2026-02-20 15:53:03 +00:00
stainless-app[bot] 2c88ad1bb9 chore(internal): make OkHttp constructor internal 2026-02-20 15:53:03 +00:00
stainless-app[bot] c4b69463ce codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] a797755dc6 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 750cb9727c chore(internal): update TestServerExtension comment 2026-02-20 15:53:03 +00:00
stainless-app[bot] 2e9af6f60c feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] ea568e078f codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 793ca57f56 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] f0673a8381 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] a3018d05fc feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] e11fa749b7 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 24bca7acbd feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] 3dfde0eac4 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 66495749d9 feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] cac6931b8f chore(api): minor updates 2026-02-20 15:53:03 +00:00
stainless-app[bot] 929a7d700f feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] fb2260d06f codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] a40f07edcd feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] 7068b7797c codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 379af11f42 feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] 974472d764 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] cd7a03e546 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] b528d8b51a codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 9a58e84cda codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] e6d4a0e83d codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 0871a9da0f chore(internal): upgrade AssertJ 2026-02-20 15:53:03 +00:00
stainless-app[bot] 86774475b2 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 7b84fdac3d codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 71cba9d0e2 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] bd733743ac codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] bc76417677 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 84e2de05ce codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 5b3bba1323 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 2b6c2ed358 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 88e87f3eae codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 40409eb155 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 4825017b86 chore(internal): allow passing args to ./scripts/test 2026-02-20 15:53:03 +00:00
stainless-app[bot] f1fa362930 feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] fdb1ee6552 feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] 0756d8db46 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] e4a386aa13 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 3b85a7e71f codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 021a770e89 feat(api): manual updates
added transformation for duplicate variant name
2026-02-20 15:53:03 +00:00
stainless-app[bot] c358fb4281 feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] 9c4625d82c feat(api): manual updates
Adding title to inline variant schema.
2026-02-20 15:53:03 +00:00
stainless-app[bot] e9125a7bdf codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] 74b82f7625 chore(ci): upgrade actions/github-script 2026-02-20 15:53:03 +00:00
stainless-app[bot] 449caf4826 codegen metadata 2026-02-20 15:53:03 +00:00
stainless-app[bot] e2583dfe8d feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] 52f37011ed feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] 4cde1b3db1 feat(api): manual updates
fix build error
2026-02-20 15:53:03 +00:00
stainless-app[bot] bb7ebc08af feat(api): manual updates 2026-02-20 15:53:03 +00:00
stainless-app[bot] 256dfab0e1 feat(api): api update 2026-02-20 15:53:03 +00:00
stainless-app[bot] 03270ff2a5 feat(api): manual updates
fix openapi spec
2026-02-20 15:53:03 +00:00
ericdong-langchain 1902c228dd chore: refactor (#90)
* chore: refactor

* fix: lint
2026-02-20 10:13:38 -05:00
John Kennedy 3c79eea5dd chore: dependabot 2026-02-06 16:42:56 -08:00
stainless-app[bot] cff199b8fa release: 0.1.0-alpha.22 2026-01-23 16:38:33 +00:00
stainless-app[bot] 88af6cd36e fix(client): preserve time zone in lenient date-time parsing 2026-01-23 16:38:33 +00:00
stainless-app[bot] 47f39a40e4 chore(internal): correct cache invalidation for SKIP_MOCK_TESTS 2026-01-23 16:38:33 +00:00
stainless-app[bot] 79f436a6a3 docs: add comment for arbitrary value fields 2026-01-23 16:38:33 +00:00
stainless-app[bot] 60e0ce825e codegen metadata 2026-01-23 16:38:33 +00:00
stainless-app[bot] 54d56e2915 codegen metadata 2026-01-23 16:38:33 +00:00
stainless-app[bot] 23597a4e3f feat(client): send X-Stainless-Kotlin-Version header 2026-01-23 16:38:33 +00:00
stainless-app[bot] e55e8c7fe2 chore(internal): update maven repo doc to include authentication 2026-01-23 16:38:33 +00:00
stainless-app[bot] 92d87eab22 codegen metadata 2026-01-23 16:38:33 +00:00
stainless-app[bot] d40dd2c26f codegen metadata 2026-01-23 16:38:33 +00:00
stainless-app[bot] f0f5ad3c41 feat(api): api update 2026-01-23 16:38:33 +00:00
stainless-app[bot] da14cc6869 chore(ci): upgrade actions/setup-java 2026-01-23 16:38:33 +00:00
stainless-app[bot] a2648390af fix(client): fully respect max retries
fix(client): send retry count header for max retries 0
chore(internal): depend on packages directly in example
2026-01-23 16:38:33 +00:00
stainless-app[bot] 43741790f7 fix: deserialization order 2026-01-23 16:38:33 +00:00
stainless-app[bot] 10e2af9aea chore(internal): update actions/checkout version 2026-01-23 16:38:33 +00:00
stainless-app[bot] 04508f89a2 fix(client): disallow coercion from float to int 2026-01-23 16:38:33 +00:00
stainless-app[bot] 4c2738351e chore(internal): improve maven repo docs 2026-01-23 16:38:33 +00:00
stainless-app[bot] b34611a2d6 chore: test on Jackson 2.14.0 to avoid encountering FasterXML/jackson-databind#3240 in tests
fix: date time deserialization leniency
2026-01-23 16:38:33 +00:00
stainless-app[bot] 0a7b44831a chore(internal): clean up maven repo artifact script and add html documentation to repo root 2026-01-23 16:38:33 +00:00
stainless-app[bot] 49918bb9ab codegen metadata 2026-01-23 16:38:33 +00:00
stainless-app[bot] 3c7a62f01e codegen metadata 2026-01-23 16:38:33 +00:00
stainless-app[bot] 501beab343 codegen metadata 2026-01-23 16:38:33 +00:00
stainless-app[bot] fff7a4663f release: 0.1.0-alpha.21 2026-01-14 21:28:19 +00:00
stainless-app[bot] 398c86771f feat(api): api update 2026-01-14 21:28:19 +00:00
stainless-app[bot] 2d298cd81f feat(api): manual updates
added title to duplicate schemas
2026-01-14 21:28:19 +00:00
stainless-app[bot] 7050baf4e1 codegen metadata 2026-01-14 21:28:19 +00:00
stainless-app[bot] f0b2abc659 feat(api): api update 2026-01-14 21:28:19 +00:00
stainless-app[bot] 7ef6b9f8c7 chore(internal): support uploading Maven repo artifacts to stainless package server 2026-01-14 21:28:19 +00:00
stainless-app[bot] 5c4cf4f0f7 feat(api): api update 2026-01-14 21:28:19 +00:00
stainless-app[bot] ded153ef51 release: 0.1.0-alpha.20 (#85)
* feat(api): api update

* codegen metadata

* codegen metadata

* feat(api): api update

* feat(api): api update

* feat(api): api update

* codegen metadata

* feat(api): api update

* feat(api): api update

* codegen metadata

* codegen metadata

* codegen metadata

* feat(api): api update

* codegen metadata

* codegen metadata

* feat(client): add `HttpRequest#url()` method

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* feat(client): allow configuring dispatcher executor service

* codegen metadata

* codegen metadata

* codegen metadata

* codegen metadata

* release: 0.1.0-alpha.20

---------

Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
2026-01-14 14:43:32 -05:00
Eric Dong 5572d09902 fix: fixed build and test 2025-12-10 22:13:47 +00:00
stainless-app[bot] 2f56b7d053 release: 0.1.0-alpha.19 2025-12-10 22:13:47 +00:00
stainless-app[bot] fe3381c85e feat(api): api update 2025-12-10 22:13:47 +00:00
stainless-app[bot] 4518614715 feat(api): api update 2025-12-10 22:13:47 +00:00
stainless-app[bot] b8f700dbc3 chore(api): delete index methods
Removed get and delete index methods
2025-12-10 22:13:47 +00:00
stainless-app[bot] 83aa1fe5b7 feat(api): api update 2025-12-10 22:13:47 +00:00
stainless-app[bot] 7e0b329e90 feat(api): manual updates
Initial release
2025-12-10 22:13:47 +00:00
stainless-app[bot] 070629d380 feat(api): api update 2025-12-10 22:13:47 +00:00
stainless-app[bot] 6207c8bf2d codegen metadata 2025-12-10 22:13:47 +00:00
stainless-app[bot] fbba779511 feat(api): api update 2025-12-10 22:13:47 +00:00
stainless-app[bot] b50bd7233c feat(api): api update 2025-12-10 22:13:47 +00:00
stainless-app[bot] 364329ec65 feat(api): manual updates 2025-12-10 22:13:47 +00:00
stainless-app[bot] 87d0bfb614 codegen metadata 2025-12-10 22:13:47 +00:00
stainless-app[bot] 7863108ae3 codegen metadata 2025-12-10 22:13:47 +00:00
stainless-app[bot] 1755b4ae62 codegen metadata 2025-12-10 22:13:47 +00:00
stainless-app[bot] 034daaf52e codegen metadata 2025-12-10 22:13:47 +00:00
stainless-app[bot] d267892e0d docs: remove $ for better copy-pasteabality 2025-12-10 22:13:47 +00:00
stainless-app[bot] 6bc095cc83 feat(api): api update 2025-12-10 22:13:47 +00:00
stainless-app[bot] 249b9c66c8 codegen metadata 2025-12-10 22:13:47 +00:00
stainless-app[bot] 737f8d2cf2 codegen metadata 2025-12-10 22:13:47 +00:00
stainless-app[bot] 4220932794 codegen metadata 2025-12-10 22:13:47 +00:00
stainless-app[bot] d4cab9eef6 codegen metadata 2025-12-10 22:13:47 +00:00
stainless-app[bot] 0a5f8a76be fix(client): cancel okhttp call when future cancelled 2025-12-10 22:13:47 +00:00
stainless-app[bot] 32eabcac91 feat(api): api update 2025-12-10 22:13:47 +00:00
ericdong-langchain 7a644105f6 fix: fix trace input and output (#83)
* fix: fix trace input and output

* fix: lint
2025-12-10 16:43:16 -05:00
stainless-app[bot] 9de590ad96 release: 0.1.0-alpha.18 (#79)
* chore: Configure workflow for CodeQL

* feat(api): pagination

added pagination to all endpoints that use pagination

* codegen metadata

* chore(internal): version bump

* codegen metadata

* feat(api): change paginated retrieve methods to list

change paginated retrieve methods to list

* feat(api): add go target

* codegen metadata

* feat(api): pagination read me

added pagination read me

* codegen metadata

* feat(api): readme example - change REPLACE_ME session_id to uuid

* feat: method counter in ci

* codegen metadata

* test

* feat(api): api update

* fix: formatting error in record experiment example (#81)

* release: 0.1.0-alpha.18

---------

Co-authored-by: xornivore <30147836+xornivore@users.noreply.github.com>
Co-authored-by: ericdong-langchain <ericdong@langchain.dev>
Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
2025-12-09 14:16:39 +00:00
stainless-app[bot] 2685825993 release: 0.1.0-alpha.17 2025-11-20 23:46:19 +00:00
xornivore cb8962fb25 chore: Configure workflow for CodeQL 2025-11-20 23:46:19 +00:00
stainless-app[bot] 4c0258d01f release: 0.1.0-alpha.16 2025-11-20 15:03:53 -05:00
stainless-app[bot] ded1673fa6 codegen metadata 2025-11-20 14:49:12 -05:00
xornivore 5597676151 chore: Update runner to ubuntu-latest-l-oss for Sonatype Publish 2025-11-20 14:49:12 -05:00
stainless-app[bot] dd880b1002 release: 0.1.0-alpha.15 2025-11-20 17:08:47 +00:00
Eric Dong 6eba79b3c5 chore: changed unit of time 2025-11-20 17:08:47 +00:00
stainless-app[bot] 299a93a87b release: 0.1.0-alpha.14 2025-11-20 16:40:26 +00:00
xornivore 5790fe6d94 chore: Update support portal link 2025-11-20 16:40:26 +00:00
stainless-app[bot] cda4be05e5 release: 0.1.0-alpha.13 2025-11-20 16:22:03 +00:00
stainless-app[bot] 4e90a43364 feat(api): Update contact email 2025-11-20 16:22:03 +00:00
stainless-app[bot] e81435a82f feat(api): Update docs to https://docs.smith.langchain.com/ 2025-11-20 16:22:03 +00:00
stainless-app[bot] dff44aec06 feat(api): add rate limiting
added limiting and timeout based on python SDK values
2025-11-20 16:22:03 +00:00
stainless-app[bot] b3499192a6 release: 0.1.0-alpha.12 2025-11-19 20:38:15 +00:00
stainless-app[bot] e2b33d224a feat(api): Set targets.java.publish.docs: true 2025-11-19 20:38:15 +00:00
xornivore c13b6cd619 chore: Move runner to ubuntu-latest-l to allow dokka to complete javadoc building 2025-11-19 20:38:15 +00:00
stainless-app[bot] 154488c893 release: 0.1.0-alpha.11 2025-11-19 19:00:48 +00:00
Eric Dong db74f4be82 fix: ls playground errors & commit builder class 2025-11-19 19:00:48 +00:00
xornivore 7dbd1c0053 chore: Update/simplify README leading example 2025-11-19 19:00:48 +00:00
stainless-app[bot] c7e00f585f feat(api): manual updates
revert is_public transformation
2025-11-19 19:00:48 +00:00
stainless-app[bot] 078bbb3740 codegen metadata 2025-11-19 19:00:48 +00:00
stainless-app[bot] b8579d5973 feat(api): manual updates 2025-11-19 19:00:48 +00:00
stainless-app[bot] 734fbb4396 feat(api): manual updates
revert transform change on repos
2025-11-19 19:00:48 +00:00
stainless-app[bot] 6cd87f48db feat(api): manual updates
boolean overload for isPublic API schema
2025-11-19 19:00:48 +00:00
stainless-app[bot] 2232c2d4a0 release: 0.1.0-alpha.10 2025-11-17 23:08:10 +00:00
stainless-app[bot] 77779ce4db codegen metadata 2025-11-17 23:08:10 +00:00
Eric Dong ae64e429ec chore: align env vars with other SDKs 2025-11-17 23:08:10 +00:00
stainless-app[bot] abce02c340 feat(api): switch license to MIT 2025-11-17 23:08:10 +00:00
stainless-app[bot] d0d7a93d4a codegen metadata 2025-11-17 23:08:10 +00:00
stainless-app[bot] b065e778cb codegen metadata 2025-11-17 23:08:10 +00:00
Eric Dong 802f5a118c chore: convert to kotlin 2025-11-17 23:08:10 +00:00
Eric Dong 3fdd586165 fix: lint 2025-11-17 23:08:10 +00:00
Eric Dong e020cc02cc added prompt example 2025-11-17 23:08:10 +00:00
stainless-app[bot] e9166fe90a feat(api): manual updates 2025-11-17 23:08:10 +00:00
stainless-app[bot] 9d34d35a47 codegen metadata 2025-11-17 23:08:10 +00:00
Quentin Brosse 2f488d90e1 lint 2025-11-17 23:08:10 +00:00
Quentin Brosse 205a6dd115 some fixes 2025-11-17 23:08:10 +00:00
Quentin Brosse f95d9c82fe add optional + lint 2025-11-17 23:08:10 +00:00
Quentin Brosse 73a79dd3c5 refactor: migrate to SLF4J logging and Builder-only API
Refactor OpenTelemetry configuration and logging to improve usability and follow industry standards.

API Changes:
  - Remove `configureForLangSmith()` static methods in favor of Builder pattern
  - Remove `configureFromEnv()` - use `builder().build()` instead
  - Add required field validation in `Builder.build()` with clear error messages
  - Builder constructor auto-reads environment variables (LANGSMITH_API_KEY, etc.)
  - Builder methods override environment variable values
2025-11-17 23:08:10 +00:00
Quentin Brosse e1e47762f1 refactor: ExperimentContext to use OpenTelemetry Context pattern 2025-11-17 23:08:10 +00:00
Quentin Brosse de912b531c improve 2025-11-17 23:08:10 +00:00
Quentin Brosse 217929c1f0 improve 2025-11-17 23:08:10 +00:00
Quentin Brosse fa00021a3b improve 2025-11-17 23:08:10 +00:00
Quentin Brosse b2488993e9 fix 2025-11-17 23:08:10 +00:00
Quentin Brosse a396ce53f3 doc: add eval examples 2025-11-17 23:08:10 +00:00
Quentin Brosse 05c6e74007 doc: RecordExperiment 2025-11-17 23:08:10 +00:00
stainless-app[bot] 1af52629ac fix(client): multi-value header serialization 2025-11-17 23:08:10 +00:00
stainless-app[bot] ac205104d1 feat(api): api update 2025-11-17 23:08:10 +00:00
stainless-app[bot] acafcfa885 codegen metadata 2025-11-17 23:08:10 +00:00
stainless-app[bot] 94b13b3709 codegen metadata 2025-11-17 23:08:10 +00:00
stainless-app[bot] 09c67642b9 codegen metadata 2025-11-17 23:08:10 +00:00
Eric Dong bf3497554e chore: combine otel examples 2025-11-17 23:08:10 +00:00
stainless-app[bot] 1f1c4fc8d3 feat(api): enable post /runs/batch 2025-11-17 23:08:10 +00:00
stainless-app[bot] 4382c85821 feat(api): api update 2025-11-17 23:08:10 +00:00
Quentin Brosse 2e14e46770 doc: some readme fixes 2025-11-17 23:08:10 +00:00
Quentin Brosse 86480af3e3 fix 2025-11-17 23:08:10 +00:00
Quentin Brosse 8afffa6275 doc: improve/fix DatasetExample 2025-11-17 23:08:10 +00:00
stainless-app[bot] ddd1ac02f2 codegen metadata 2025-11-17 23:08:10 +00:00
stainless-app[bot] 6865ca8717 codegen metadata 2025-11-17 23:08:10 +00:00
stainless-app[bot] 6fc4ddd1be feat(api): api update 2025-11-17 23:08:10 +00:00
Quentin Brosse 8c6eef991b Merge pull request #55 from langchain-ai/release-please--branches--main--changes--next
release: 0.1.0-alpha.9
2025-11-11 19:09:39 +01:00
stainless-app[bot] a3c380a7a6 release: 0.1.0-alpha.9 2025-11-11 11:02:54 +00:00
Quentin Brosse ea7d94b6d0 Merge pull request #8 from stainless-sdks/ericdong/fix-add-back-config 2025-11-11 12:02:24 +01:00
stainless-app[bot] 284d184316 feat(api): api update 2025-11-11 10:43:49 +00:00
stainless-app[bot] 21cf86157c codegen metadata 2025-11-11 00:21:59 +00:00
Eric Dong 866970e8e0 fix: added back the stdin build config 2025-11-10 16:55:45 -05:00
ericdong-langchain 63647df027 Merge pull request #7 from stainless-sdks/ericdong/feat-otel-ingestion-java
feat: added otel ingestion examples and code
2025-11-10 15:49:26 -05:00
Eric Dong 21969a61f9 fix: lint, build, test 2025-11-10 15:41:18 -05:00
stainless-app[bot] 3dcaaecdf1 codegen metadata 2025-11-10 20:22:00 +00:00
Eric Dong 16f01c8edb fix: lint fix 2025-11-10 15:19:05 -05:00
Eric Dong cd244b3fe9 fix: lint fix 2025-11-10 15:06:30 -05:00
ericdong-langchain cd54282023 Merge branch 'next' into ericdong/feat-otel-ingestion-java 2025-11-10 14:38:18 -05:00
Eric Dong 19c2cf595a feat: added otel ingestion examples and code 2025-11-10 14:26:14 -05:00
Quentin Brosse 6321686a8e doc: add dataset example
doc: add dataset example
2025-11-10 20:22:10 +01:00
Quentin Brosse e260744b87 fix 2025-11-10 20:21:41 +01:00
Quentin Brosse 431e6c6e4c example 2025-11-10 20:13:11 +01:00
Quentin Brosse f73ad91858 fix 2025-11-10 20:13:11 +01:00
Quentin Brosse 1e2825f8ae fix 2025-11-10 20:13:11 +01:00
Quentin Brosse fefc3d7a09 fix 2025-11-10 20:13:11 +01:00
Quentin Brosse c834f2571b get or create 2025-11-10 20:13:11 +01:00
Quentin Brosse 35c381c47b doc: add dataset example 2025-11-10 20:13:11 +01:00
Quentin Brosse e975a27fc9 Merge pull request #54 from langchain-ai/release-please--branches--main--changes--next
release: 0.1.0-alpha.8
2025-11-10 20:12:40 +01:00
stainless-app[bot] 4a481e4d21 release: 0.1.0-alpha.8 2025-11-10 17:45:57 +00:00
stainless-app[bot] ff1d89a0bb feat(api): manual updates 2025-11-10 17:45:36 +00:00
stainless-app[bot] 1c57a55b2e feat(api): manual updates 2025-11-10 17:43:36 +00:00
stainless-app[bot] 9b596ec763 fix: rename query param dataType to datatype 2025-11-10 17:07:12 +00:00
stainless-app[bot] e640c7f89a codegen metadata 2025-11-10 16:54:27 +00:00
stainless-app[bot] 43ffea37a2 fix: remove experiment_view_overrides 2025-11-10 16:49:24 +00:00
stainless-app[bot] 05b0e5757c codegen metadata 2025-11-10 14:50:30 +00:00
stainless-app[bot] b7182fe85e codegen metadata 2025-11-10 14:17:35 +00:00
stainless-app[bot] fac0d52aca codegen metadata 2025-11-10 14:13:51 +00:00
stainless-app[bot] 4d5b47e134 codegen metadata 2025-11-10 14:06:57 +00:00
stainless-app[bot] 9c71ed558a codegen metadata 2025-11-10 14:02:12 +00:00
stainless-app[bot] e24de3aae2 codegen metadata 2025-11-10 13:57:42 +00:00
stainless-app[bot] 1160c21dba fix: DatasetDataType name 2025-11-10 13:35:22 +00:00
stainless-app[bot] 494e91bba2 fix(api): rename DataType to DatasetDataType
To fix recursive type checking problems
2025-11-10 13:28:05 +00:00
stainless-app[bot] 1967627dd4 feat(api): enable /api/v1/runs/query 2025-11-10 13:01:27 +00:00
stainless-app[bot] 02d5b724de feat(api): enable /examples 2025-11-10 12:50:47 +00:00
Quentin Brosse 3a50ff913a Merge pull request #52 from langchain-ai/release-please--branches--main--changes--next
release: 0.1.0-alpha.7
2025-11-10 11:43:59 +01:00
stainless-app[bot] 5cc07fa08b release: 0.1.0-alpha.7 2025-11-10 10:29:42 +00:00
stainless-app[bot] 9d61b1d041 feat(api): manual updates 2025-11-10 10:29:18 +00:00
stainless-app[bot] 524a19f4de feat(api): api update 2025-11-08 01:21:59 +00:00
Ivan Ilichev 0620eb6e49 Merge pull request #6 from stainless-sdks/xornivore/chore-migrate-otel-wrappers-into-sdk
chore: Move LangSmith OTel Java wrappers PoC into SDK
2025-11-07 16:36:24 -05:00
xornivore d62a5ebc2a scripts/format 2025-11-07 16:27:09 -05:00
xornivore 24680beb2f chore: Move LangSmith OTel Java wrappers PoC into SDK 2025-11-07 15:13:27 -05:00
stainless-app[bot] 91e9267c8d feat(api): manual updates 2025-11-07 19:50:24 +00:00
stainless-app[bot] 5f71223bac feat(api): manual updates 2025-11-07 19:33:42 +00:00
Quentin Brosse f7fb8ad3a7 Merge pull request #50 from langchain-ai/release-please--branches--main--changes--next
release: 0.1.0-alpha.6
2025-11-07 20:22:25 +01:00
stainless-app[bot] bf6e6a11e0 release: 0.1.0-alpha.6 2025-11-07 19:21:02 +00:00
stainless-app[bot] 3da5f2870c feat(api): manual updates 2025-11-07 19:20:35 +00:00
stainless-app[bot] 3e0c4b89d9 feat(api): api update 2025-11-07 18:22:42 +00:00
stainless-app[bot] a5dc27ed22 feat(api): removing methods
Removing methods that are not in Python SDK
2025-11-07 15:51:05 +00:00
stainless-app[bot] b4e170da64 feat(api): remove some endpoints
Removed endpoints not in Python SDK
2025-11-07 15:13:29 +00:00
Daniel Kneipp 3394cf2248 Merge pull request #51 from langchain-ai/daniel/inf-1086-no-sf-runners-for-public-repos
chore: revert from SH to public GH runner
2025-11-07 16:13:21 +01:00
Daniel Kneipp 0d00830db9 chore: revert from SH to public GH runner 2025-11-07 15:57:27 +01:00
Daniel Kneipp 5e760c6964 Merge pull request #48 from langchain-ai/INF-1113/test-self-hosted/runners
chore: test self-hosted runners on Publish Sonatype workflow
2025-11-07 15:38:54 +01:00
Quentin Brosse adc0c4129e Merge pull request #49 from langchain-ai/release-please--branches--main--changes--next
release: 0.1.0-alpha.5
2025-11-07 13:19:30 +01:00
stainless-app[bot] 0b718da44a release: 0.1.0-alpha.5 2025-11-07 11:13:03 +00:00
stainless-app[bot] 77433e6705 fix(api): ignore /api/v1/runs/stats 2025-11-07 11:12:25 +00:00
Daniel Kneipp 603db8714f chore: test self-hosted runners 2025-11-07 09:55:09 +01:00
stainless-app[bot] a48888e46d release: 0.1.0-alpha.4 2025-11-06 12:09:50 -05:00
stainless-app[bot] 5ff053706d chore: disable javadocs 2025-11-06 12:09:50 -05:00
stainless-app[bot] f6922d4b42 fix(api): disable /api/v1/repos/optimize-job
Reason: generates Java/SchemaUnionDiscriminatorMissing warnings and not part of P0
2025-11-06 12:09:50 -05:00
stainless-app[bot] 40e12e4c9a feat(api): disabling charts and org-charts
Reason: generates misc warnings and not part of P0
2025-11-06 12:09:50 -05:00
stainless-app[bot] 3a370dc66e fix(api): disable optimization-jobs
Reason: generates lot of Java/SchemaUnionDiscriminatorMissing warnings and not part of P0
2025-11-06 12:09:50 -05:00
stainless-app[bot] 8b90997964 codegen metadata 2025-11-06 12:09:50 -05:00
stainless-app[bot] 05e6f61a16 fix(api): Rename RunRules*.transient for java target 2025-11-06 12:09:50 -05:00
stainless-app[bot] f513d26879 codegen metadata 2025-11-06 12:09:50 -05:00
stainless-app[bot] b780052150 fix(api): excluding post /api/v1/prompts*
They generate lots of `Schema/CannotInferName` warnings and not part of P0. Let's fix that later.
2025-11-06 12:09:50 -05:00
stainless-app[bot] 2c5e2fe014 feat(api): api update 2025-11-06 12:09:50 -05:00
Ivan Ilichev 83c1dd7caa Merge pull request #46 from langchain-ai/release-please--branches--main--changes--next
release: 0.1.0-alpha.3
2025-11-05 19:40:07 -05:00
stainless-app[bot] 440c7a2db9 release: 0.1.0-alpha.3 2025-11-05 23:59:56 +00:00
stainless-app[bot] 70bfdc0d47 feat(api): manual updates 2025-11-05 23:59:26 +00:00
stainless-app[bot] ba17bc4560 feat(api): manual updates 2025-11-05 22:58:32 +00:00
Ivan Ilichev a0975b8cca Merge pull request #45 from langchain-ai/release-please--branches--main--changes--next
release: 0.1.0
2025-11-05 16:01:53 -05:00
stainless-app[bot] 20f9cff6a4 release: 0.1.0 2025-11-05 20:39:22 +00:00
Samuel El-Borai 6225ccac5a fix: rename example directory directory 2025-11-05 21:38:24 +01:00
stainless-app[bot] 22881d97b2 chore: use langsmith-java as Maven artifact id 2025-11-05 20:33:19 +00:00
Samuel El-Borai e2ac9ba053 fix: remove outdated directories 2025-11-05 21:21:15 +01:00
Sam El-Borai e1bc4b0515 Merge pull request #3 from stainless-sdks/dgellow-patch-1
chore(docs): update readme headline
2025-11-05 21:05:44 +01:00
Sam El-Borai 7cc251affe chore(docs): update readme headline 2025-11-05 21:03:27 +01:00
stainless-app[bot] 61bfe11f7c chore: update SDK settings 2025-11-05 19:11:29 +00:00
stainless-app[bot] 9e876f623d chore: sync repo 2025-11-05 19:10:50 +00:00
Ivan Ilichev 1c60f27254 Merge pull request #44 from langchain-ai/xornivore/rm-rf-r-all-to-reset
chore: rm -rf all to reset publishing from stainless
2025-11-05 14:04:15 -05:00
xornivore aa5e0f7de3 chore: rm -rf all to reset publishing from stainless 2025-11-05 13:48:10 -05:00
Ankush Gola 922058acd6 Merge pull request #30 from langchain-ai/release-please--branches--main--changes--next
release: 0.1.0-alpha.2
2024-03-26 12:14:30 -07:00
stainless-app[bot] ee24bb3179 release: 0.1.0-alpha.2 2024-03-22 20:04:50 +00:00
Stainless Bot d3f8fb2c09 feat: Use new sonatype publishing plugin 2024-03-22 20:04:35 +00:00
William FH b189917b25 Merge pull request #32 from langchain-ai/dackerman/setup-new-plugin
Use new sonatype publishing plugin
2024-03-22 13:04:30 -07:00
David Ackerman 84ef64e15f Use new sonatype publishing plugin 2024-03-21 15:51:13 -04:00
Stainless Bot fa22fd702b feat: do not inline example 2024-03-21 01:08:45 +00:00
Stainless Bot 33019db58b feat: update builder patterns, use end options 2024-03-21 01:08:43 +00:00
Ankush Gola 29f6bb2b1e Merge pull request #31 from langchain-ai/ankush/stop-pass-nulls
Update builder patterns and don't pass `null`s
2024-03-20 18:08:38 -07:00
Ankush Gola a6af50c565 do not inline example 2024-03-19 18:35:11 -07:00
Ankush Gola 3a4ea2d150 update builder patterns, use end options 2024-03-19 18:31:21 -07:00
Stainless Bot c6b8b737b6 feat: fix link 2024-03-19 22:23:49 +00:00
Stainless Bot aa6b46d44f feat: update README 2024-03-19 22:23:48 +00:00
Stainless Bot eb0044a2a4 feat: updates 2024-03-19 22:23:47 +00:00
Ankush Gola 78c00fce7d Merge pull request #29 from langchain-ai/ankush/readme-updates-license
updates to README with RunTree examples
2024-03-19 15:23:42 -07:00
Ankush Gola c3f5b81742 fix link 2024-03-19 15:22:16 -07:00
Ankush Gola 9a2269151d update README 2024-03-19 15:20:36 -07:00
Ankush Gola 66929843d2 updates 2024-03-19 15:18:07 -07:00
William FH 14ee7a736b Merge pull request #27 from langchain-ai/release-please--branches--main--changes--next
release: 0.1.0-alpha.1
2024-03-19 12:54:22 -07:00
William Fu-Hinthorn 10345f26de Merge branch 'main' into release-please--branches--main--changes--next 2024-03-19 12:53:05 -07:00
stainless-app[bot] 2ffbd4ec60 release: 0.1.0-alpha.1 2024-03-19 19:42:58 +00:00
Stainless Bot 503e9927dc feat: Restore custom updates 2024-03-19 19:42:41 +00:00
William FH 8759286482 Merge pull request #28 from langchain-ai/dackerman/restore-custom-code
Restore custom code updates
2024-03-19 12:42:36 -07:00
David Ackerman f3f81cc6ff Restore custom updates 2024-03-19 15:20:08 -04:00
stainless-app[bot] a098ba3e9c feat(api): update via SDK Studio (#26) 2024-03-19 18:07:27 +00:00
stainless-app[bot] 18b32186ba feat(api): update via SDK Studio (#25) 2024-03-19 18:06:30 +00:00
stainless-app[bot] c34959c24e feat(api): update via SDK Studio (#24) 2024-03-19 18:05:30 +00:00
stainless-app[bot] bfc1bfe92b feat(api): update via SDK Studio (#23) 2024-03-19 18:04:34 +00:00
stainless-app[bot] 8842ad7d4c feat(api): update via SDK Studio (#22) 2024-03-19 18:03:37 +00:00
stainless-app[bot] d33b9aab8a feat(api): update via SDK Studio (#21) 2024-03-19 18:02:40 +00:00
stainless-app[bot] 6014dab0bb feat(api): update via SDK Studio (#20) 2024-03-19 18:01:42 +00:00
stainless-app[bot] 36e65eff30 feat(api): update via SDK Studio (#19) 2024-03-19 18:00:42 +00:00
stainless-app[bot] 0ffa490540 feat(api): update via SDK Studio (#18) 2024-03-19 17:59:44 +00:00
stainless-app[bot] 8b462e174e feat(api): update via SDK Studio (#17) 2024-03-19 17:58:46 +00:00
stainless-app[bot] 7953ae06de feat(api): update via SDK Studio (#16) 2024-03-19 17:57:48 +00:00
stainless-app[bot] 134c02266d feat(api): update via SDK Studio (#15) 2024-03-19 17:56:50 +00:00
stainless-app[bot] 79999bf542 feat(api): update via SDK Studio (#14) 2024-03-19 17:56:03 +00:00
William FH 2af08be726 Merge pull request #12 from langchain-ai/next
[Test] Release
2024-03-19 10:55:26 -07:00
stainless-app[bot] db651329e5 feat(api): update via SDK Studio (#13) 2024-03-19 17:54:56 +00:00
stainless-app[bot] 2a9fe2b42e feat(api): update via SDK Studio (#11) 2024-03-19 17:53:59 +00:00
1654 changed files with 374155 additions and 62047 deletions
+6 -2
View File
@@ -1,19 +1,23 @@
# syntax=docker/dockerfile:1
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y \
RUN apt-get update && apt-get install -y --no-install-recommends \
libxkbcommon0 \
ca-certificates \
ca-certificates-java \
make \
curl \
git \
openjdk-17-jdk \
openjdk-17-jdk-headless \
unzip \
libc++1 \
vim \
&& apt-get clean autoclean
# Ensure UTF-8 encoding
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
WORKDIR /workspace
COPY . /workspace
-5
View File
@@ -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
+12
View File
@@ -0,0 +1,12 @@
name: "CodeQL config"
queries:
- uses: security-extended
paths-ignore:
- '**/test/**'
- '**/tests/**'
- '**/*Test.java'
- '**/*Test.kt'
- '**/build/**'
- '.gradle/**'
+58
View File
@@ -0,0 +1,58 @@
version: 2
updates:
- package-ecosystem: gradle
directory: /
target-branch: "next"
schedule:
interval: monthly
groups:
minor-and-patch:
patterns:
- "*"
update-types:
- minor
- patch
major:
patterns:
- "*"
update-types:
- major
open-pull-requests-limit: 5
- package-ecosystem: docker
directory: /.devcontainer
target-branch: "next"
schedule:
interval: monthly
groups:
minor-and-patch:
patterns:
- "*"
update-types:
- minor
- patch
major:
patterns:
- "*"
update-types:
- major
open-pull-requests-limit: 5
- package-ecosystem: github-actions
directory: /
target-branch: "next"
schedule:
interval: monthly
groups:
minor-and-patch:
patterns:
- "*"
update-types:
- minor
- patch
major:
patterns:
- "*"
update-types:
- major
open-pull-requests-limit: 5
@@ -0,0 +1,72 @@
name: Check Stainless File Modification
on:
pull_request:
branches:
- main
types:
- opened
- synchronize
- reopened
- labeled
- unlabeled
permissions:
contents: read
pull-requests: read
jobs:
check-stainless-files:
name: Check for Stainless-generated file modifications
runs-on: ubuntu-latest
if: github.repository == 'langchain-ai/langsmith-java-staging'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for modifications to Stainless-generated files
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
SKIP_LABEL="skip-stainless-generated-file-modification-check"
# Check if the skip label is present
LABELS=$(gh api repos/"$REPO"/pulls/"$PR_NUMBER" --jq '[.labels[].name] | join(",")')
if echo "$LABELS" | grep -qF "$SKIP_LABEL"; then
echo "Skip label '$SKIP_LABEL' is present — skipping check."
exit 0
fi
# Get the list of files changed in this PR
CHANGED_FILES=$(git diff --name-only "origin/${{ github.base_ref }}"...HEAD)
STAINLESS_FILES=""
while IFS= read -r FILE; do
[ -z "$FILE" ] && continue
[ ! -f "$FILE" ] && continue
if grep -qF "File generated from our OpenAPI spec by Stainless." "$FILE"; then
STAINLESS_FILES="$STAINLESS_FILES\n - $FILE"
fi
done <<< "$CHANGED_FILES"
if [ -n "$STAINLESS_FILES" ]; then
echo "::error::This PR modifies Stainless-generated files, which should not be edited directly."
printf "Stainless-generated files modified:%b\n" "$STAINLESS_FILES"
echo ""
echo "Please choose one of the following options:"
echo ""
echo " 1. Refactor your change to avoid modifying the generated file directly."
echo ""
echo " 2. Use Stainless configuration instead:"
echo " - OpenAPI extensions: https://www.stainless.com/docs/openapi/extensions/"
echo " - Transforms: https://www.stainless.com/docs/openapi/transforms/reference/"
echo ""
echo " 3. If neither option above is feasible, add the label '$SKIP_LABEL' to this PR to bypass this check."
echo " Use this only as a last resort and document why it is necessary."
exit 1
fi
echo "No Stainless-generated files were modified."
+55 -12
View File
@@ -2,32 +2,75 @@ name: CI
on:
push:
branches:
- main
- '**'
- '!integrated/**'
- '!stl-preview-head/**'
- '!stl-preview-base/**'
- '!generated'
- '!codegen/**'
- 'codegen/stl/**'
pull_request:
branches:
- main
branches-ignore:
- 'stl-preview-head/**'
- 'stl-preview-base/**'
permissions:
contents: read
jobs:
lint:
timeout-minutes: 15
name: lint
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@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
uses: actions/setup-java@v4
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: |
8
17
21
cache: gradle
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
- name: Gradle build
run: |
./gradlew build testClasses -x test
- name: Run lints
run: ./scripts/lint
build:
timeout-minutes: 15
name: build
permissions:
contents: read
id-token: write
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: |
8
21
cache: gradle
- name: Set up Gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
- name: Build SDK
run: ./scripts/build
- name: Set up Gradle
uses: gradle/gradle-build-action@ac2d340dc04d9e1113182899e983b5400c17cda1 # v3
- name: Run tests
run: ./scripts/test
+44
View File
@@ -0,0 +1,44 @@
name: "CodeQL Advanced"
on:
push:
branches: [main, next]
pull_request:
branches: [main, next]
schedule:
- cron: "0 0 * * 0" # Weekly scan on Sunday at midnight
permissions:
contents: read
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: |
8
21
- name: Set up Gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: "java-kotlin"
config-file: ./.github/codeql/codeql-config.yml
- name: Build SDK
run: ./gradlew build testClasses -x test --no-daemon --no-build-cache clean build
+23 -15
View File
@@ -1,6 +1,5 @@
# This workflow is triggered when a GitHub release is created.
# It can also be run manually to re-publish to Sonatype in case it failed for some reason.
# You can run this workflow by navigating to https://www.github.com/langchain-ai/langsmith-java/actions/workflows/publish-sonatype.yml
# workflow for re-running publishing to Sonatype in case it fails for some reason
# you can run this workflow by navigating to https://www.github.com/langchain-ai/langsmith-java/actions/workflows/publish-sonatype.yml
name: Publish Sonatype
on:
workflow_dispatch:
@@ -8,32 +7,41 @@ on:
release:
types: [published]
permissions:
contents: read
jobs:
publish:
name: publish
runs-on: ubuntu-latest
runs-on: ubuntu-latest-l-oss
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
uses: actions/setup-java@v3
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: |
8
17
21
cache: gradle
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
uses: gradle/gradle-build-action@ac2d340dc04d9e1113182899e983b5400c17cda1 # v3
- name: Publish to Sonatype
run: |
./gradlew publish --stacktrace
run: |-
export -- GPG_SIGNING_KEY_ID
printenv -- GPG_SIGNING_KEY | gpg --batch --passphrase-fd 3 --import 3<<< "$GPG_SIGNING_PASSWORD"
GPG_SIGNING_KEY_ID="$(gpg --with-colons --list-keys | awk -F : -- '/^pub:/ { getline; print "0x" substr($10, length($10) - 7) }')"
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.LANG_SMITH_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.LANG_SMITH_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }}
GPG_SIGNING_KEY_ID: ${{ secrets.LANG_SMITH_SONATYPE_GPG_SIGNING_KEY_ID || secrets.GPG_SIGNING_KEY_ID }}
GPG_SIGNING_KEY: ${{ secrets.LANG_SMITH_SONATYPE_GPG_SIGNING_KEY || secrets.GPG_SIGNING_KEY }}
GPG_SIGNING_PASSWORD: ${{ secrets.LANG_SMITH_SONATYPE_GPG_SIGNING_PASSWORD || secrets.GPG_SIGNING_PASSWORD }}
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 }}
GPG_SIGNING_PASSWORD: ${{ secrets.LANGCHAIN_SONATYPE_GPG_SIGNING_PASSWORD || secrets.GPG_SIGNING_PASSWORD }}
+12 -7
View File
@@ -1,8 +1,13 @@
name: Release Doctor
on:
pull_request:
pull_request_target:
branches:
- main
workflow_dispatch:
permissions:
contents: read
jobs:
release_doctor:
name: release doctor
@@ -10,14 +15,14 @@ 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@v3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check release environment
run: |
bash ./bin/check-release-environment
env:
SONATYPE_USERNAME: ${{ secrets.LANG_SMITH_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.LANG_SMITH_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }}
GPG_SIGNING_KEY_ID: ${{ secrets.LANG_SMITH_SONATYPE_GPG_SIGNING_KEY_ID || secrets.GPG_SIGNING_KEY_ID }}
GPG_SIGNING_KEY: ${{ secrets.LANG_SMITH_SONATYPE_GPG_SIGNING_KEY || secrets.GPG_SIGNING_KEY }}
GPG_SIGNING_PASSWORD: ${{ secrets.LANG_SMITH_SONATYPE_GPG_SIGNING_PASSWORD || secrets.GPG_SIGNING_PASSWORD }}
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 }}
GPG_SIGNING_PASSWORD: ${{ secrets.LANGCHAIN_SONATYPE_GPG_SIGNING_PASSWORD || secrets.GPG_SIGNING_PASSWORD }}
+28
View File
@@ -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
+17 -1
View File
@@ -1,4 +1,20 @@
.prism.log
.stdy.log
.env
.env.*
*.pem
*.key
*.crt
credentials.json
node_modules/
__pycache__/
.venv/
.DS_Store
.gradle
.idea
build
.kotlin
**/bin/main/
**/bin/test/
build/
codegen.log
kls_database.db
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
View File
Binary file not shown.
@@ -1,2 +0,0 @@
#Thu Mar 07 21:29:16 EST 2024
gradle.version=8.0.2
Binary file not shown.
View File
+1 -1
View File
@@ -1,3 +1,3 @@
{
".": "0.0.1-alpha.0"
".": "0.1.0-beta.19"
}
+1 -1
View File
@@ -1 +1 @@
configured_endpoints: 82
configured_endpoints: 149
+266
View File
@@ -0,0 +1,266 @@
# Agent Guidelines
Code conventions and patterns for this project, learned from review feedback.
## Code structure
### Break up complex functions with helpers
When a function has deeply nested logic or multiple concerns, extract helpers. Use `flatMap` + small named functions instead of imperative loops with nested `when`/`if`:
```kotlin
// Good
fun format(variables: Map<String, Any>): PromptMessages {
val formatted = messages.flatMap { msg ->
if (msg.isPlaceholder()) expandPlaceholder(msg, variables)
else listOf(PromptMessage.withTemplate(msg, msg.format(variables)))
}
return PromptMessages(formatted, inputVariables, outputSchema)
}
private fun expandPlaceholder(msg: PromptMessage, variables: Map<String, Any>): List<PromptMessage> {
val items = variables[msg.template] as? List<*> ?: return emptyList()
return items.mapNotNull(::toPromptMessage)
}
// Bad — deeply nested imperative loop
fun format(variables: Map<String, Any>): PromptMessages {
val formatted = mutableListOf<PromptMessage>()
for (msg in messages) {
if (msg.isPlaceholder()) {
val value = variables[msg.template]
if (value is List<*>) {
for (item in value) {
when (item) {
is PromptMessage -> formatted.add(item)
is Map<*, *> -> { /* 15 more lines */ }
}
}
}
} else { ... }
}
}
```
## Kotlin idioms
### Prefer immutable collection transformations
Avoid mutable accumulators when `map`, `filter`, `partition`, `associate`, `buildMap`, or `buildList` express the same logic clearly. Use mutation only when it materially improves readability, performance, or is required by an API.
### Use `buildMap` / `buildList` instead of mutable + convert
```kotlin
// Good
val messages = items.map { msg ->
buildMap<String, String> {
put("role", msg.role)
put("content", msg.content)
msg.toolCallId?.let { put("tool_call_id", it) }
}
}
// Bad — unnecessary mutable/immutable conversion
val messages = items.map { msg ->
val base = mutableMapOf<String, String>(
"role" to msg.role,
"content" to msg.content,
)
if (msg.toolCallId != null) {
base["tool_call_id"] = msg.toolCallId
}
base.toMap()
}
```
### Use `buildList` for conditional `toString()` parts
```kotlin
// Good
override fun toString(): String {
val parts = buildList {
add("messages=[${messages.joinToString(", ")}]")
if (inputVariables.isNotEmpty()) add("inputVariables=$inputVariables")
commitHash?.let { add("commitHash=$it") }
outputSchema?.let { add("outputSchema=${it["title"] ?: "..."}") }
}
return "Prompt{${parts.joinToString(", ")}}"
}
// Bad — chained ternary string concatenation
override fun toString(): String =
"Prompt{messages=[...]" +
(if (commitHash != null) ", commitHash=$commitHash" else "") +
(if (hasOutputSchema()) ", outputSchema=..." else "") +
"}"
```
### Prefer extension functions over type casts
When adding behavior to a type you don't own, use a `private` extension function instead of casting to an implementation type:
```kotlin
// Good — extension function, no cast needed
private fun Stream<*>.withErrorTracking(
errorRef: AtomicReference<Throwable>,
exhaustedRef: AtomicBoolean,
): Stream<Any?> { ... }
val instrumented = result.withErrorTracking(iterationError, streamExhausted)
// Bad — casting to implementation type
val instrumented = wrapStreamWithErrorCapture(result, iterationError, streamExhausted)
// or worse:
(runs as? RunServiceImpl)?.flush()
```
### Use `partition` instead of double `filter`
```kotlin
// Good — single pass
val (systemMessages, nonSystemMessages) =
messages.partition { it.role == Role.SYSTEM }
// Bad — iterates the list twice
val systemMessages = messages.filter { it.role == Role.SYSTEM }
val nonSystemMessages = messages.filter { it.role != Role.SYSTEM }
```
### Use parameterized tests for table-driven cases
When multiple tests share the exact same structure (input → assert same fields), use `@ParameterizedTest` with `@MethodSource`:
```kotlin
data class Case(val input: String, val expected: String)
@ParameterizedTest(name = "{index}: \"{0}\"")
@MethodSource("cases")
fun myTest(case: Case) {
assertThat(transform(case.input)).isEqualTo(case.expected)
}
companion object {
@JvmStatic
fun cases(): Stream<Case> = Stream.of(
Case("input1", "expected1"),
Case("input2", "expected2"),
)
}
```
Only do this when every test has the same assertion shape. If tests have different setup or assertions, keep them as individual `@Test` methods.
### Extract test assertion helpers to reduce repetition
When the same assertion pattern appears across many tests, extract a helper:
```kotlin
// Good — readable, DRY
private fun assertMessage(msg: Map<String, String>, role: String, content: String) {
assertThat(msg["role"]).isEqualTo(role)
assertThat(msg["content"]).isEqualTo(content)
}
assertMessage(result.messages[0], "system", "You are helpful.")
assertMessage(result.messages[1], "user", "Hello")
// Bad — verbose, repetitive
assertThat(result.messages[0]).isEqualTo(mapOf("role" to "system", "content" to "You are helpful."))
assertThat(result.messages[1]).isEqualTo(mapOf("role" to "user", "content" to "Hello"))
```
## Formatting and linting
```bash
./gradlew :langsmith-java-core:formatKotlin
./gradlew lintKotlin
```
The project uses ktfmt with `--kotlinlang-style`.
## Testing
### Don't add comments that restate the test name
Test function names should be descriptive enough on their own. Don't add comments that repeat what the name already says:
```kotlin
// Good — name is self-documenting
@Test
fun parseLegacyPromptTemplateWithTemplateFormat() {
val manifest = ...
}
// Bad — comment restates the function name
@Test
fun parseLegacyPromptTemplateWithTemplateFormat() {
// Legacy PromptTemplate format with template_format field
val manifest = ...
}
```
### Running tests
Try to run targeted tests matching the code you changed:
```bash
# Run tests for the package you changed
./gradlew :langsmith-java-core:test --tests "com.langchain.smith.prompts.*" --rerun
# Run a single test class
./gradlew :langsmith-java-core:test --tests "com.langchain.smith.prompts.ManifestParserTest" --rerun
# Run a single test method
./gradlew :langsmith-java-core:test --tests "com.langchain.smith.prompts.ManifestParserTest.parsePromptTemplate" --rerun
# See println output
./gradlew :langsmith-java-core:test --tests "..." --rerun --info
```
Only run the full suite (`./gradlew :langsmith-java-core:test --rerun`) before finalizing a PR or after large cross-cutting changes.
### Integration tests
Integration tests require environment variables:
```bash
export LANGSMITH_API_KEY="lsv2_pt_..."
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
```
Tests skip gracefully via `assumeTrue` if keys are missing.
## Code style
- For cross-method concurrency coordination, prefer an explicit named `ReentrantLock` over `synchronized` when review clarity matters. Use Kotlin's `withLock { ... }` extension instead of manual `lock()` / `try` / `finally { unlock() }` unless explicit lock management is required. Keep the locked section minimal and do slow/blocking work outside the lock.
- Choose the simplest concurrency primitive that fits the state being protected:
- Use atomic types (`AtomicBoolean`, `AtomicInteger`, etc.) for simple flags, counters, and compare-and-set state.
- Use `synchronized` only for small, local critical sections where a named lock would not improve clarity.
- Use `ReentrantReadWriteLock` when reads are frequent, writes are infrequent, and concurrent reads materially help.
- Use coroutine `Mutex.withLock { ... }` for coroutine-based concurrency instead of blocking thread locks.
- `toString()` should be single-line, following the `ClassName{field=value, field=value}` convention used by the rest of the SDK.
- Avoid `@Suppress("UNCHECKED_CAST")` — restructure code to use safe patterns (`as? String`, `is Map<*, *>` with `entries.associate`, etc). When unavoidable (e.g. generic type erasure after an `is` check), add a comment explaining why the cast is safe.
- Use named arguments for constructor/function calls with 2+ parameters, especially when types could be confused:
```kotlin
// Good
PromptMessage(
role = PromptMessage.Role.HUMAN,
template = template,
templateFormat = templateFormat,
)
// Bad — positional args are ambiguous
PromptMessage(PromptMessage.Role.HUMAN, template, templateFormat = templateFormat)
```
- Name functions from the caller's perspective — describe what the caller gets, not what the function does internally. Prefer `stream.withErrorTracking()` over `wrapStreamWithErrorCapture(stream)`.
- When an `Optional` has a fallback default, use `orElse(default)` directly instead of `orElse(null) ?: default`:
```kotlin
// Good — default goes straight into orElse
kwargs["template_format"]?.asString()?.orElse("f-string") ?: "f-string"
// Bad — creates unnecessary null intermediary
kwargs["template_format"]?.asString()?.orElse(null) ?: "f-string"
```
The `?: "f-string"` is still needed to handle the case where the key is missing from the map (`null` from `kwargs["template_format"]`), but `orElse` should carry the default for when the key exists but isn't a string.
- Anthropic SDK is a `compileOnly` dependency — users must add it themselves. Methods that use Anthropic types should catch `NoClassDefFoundError` and throw `IllegalStateException` with a clear message.
+978
View File
@@ -0,0 +1,978 @@
# Changelog
## [0.1.0-beta.19](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.18...v0.1.0-beta.19) (2026-07-23)
### Features
* **backend:** Allow storing grid layout to custom dashboards ([8aded40](https://github.com/langchain-ai/langsmith-java/commit/8aded40800f6096880f43d2ef51a07963dec65e5))
* **backend:** update CRUD endpoint to allow retrieving / storing chart series metadata ([aa4be05](https://github.com/langchain-ai/langsmith-java/commit/aa4be05c111d702aa801cba4acbda69e0a5da7a6))
* **host:** p50/p99 run queue wait-time chart in project monitoring [LSD-1547] ([f660828](https://github.com/langchain-ai/langsmith-java/commit/f6608288d483e0041c6e0fe643f6881f3c1a3a68))
* **sandboxes:** free-form labels on sandboxes and snapshots ([6165434](https://github.com/langchain-ai/langsmith-java/commit/6165434d21025dfaeef22b4a63e31a0f0e4977a1))
* **sandboxes:** let proxy rules contribute sandbox env vars ([eace8c3](https://github.com/langchain-ai/langsmith-java/commit/eace8c3f9903e180fc7e9cfbaf0a13a7a91a5635))
### Bug Fixes
* **evaluators:** persist and wire through playground_settings_id for llm-as-judge oauth models [part of ENT-1402] ([b072b2e](https://github.com/langchain-ai/langsmith-java/commit/b072b2e2fcf6dafd32cb0213e02127a82ab0659a))
* **runs:** require session in POST /runs/stats ([056dbd3](https://github.com/langchain-ai/langsmith-java/commit/056dbd3673294fcd1fbc564d4332ba38c151ec6b))
* support last_queued_at in v2 runs query ([e7fe407](https://github.com/langchain-ai/langsmith-java/commit/e7fe407f74819ac775fcc092289d9dcfaafc036d))
### Reverts
* **runs:** require session in POST /runs/stats ([95da1e9](https://github.com/langchain-ai/langsmith-java/commit/95da1e9b944058f30b5274c882f4669933b8bbfd))
### Refactors
* **runs:** remove SmithDB v2 endpoint flag [LSO-3391] ([aece72d](https://github.com/langchain-ai/langsmith-java/commit/aece72d035ca24eb8ee94236d301f7610b18eefc))
## [0.1.0-beta.18](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.17...v0.1.0-beta.18) (2026-07-20)
### Features
* **runs:** add GET /v2/runs/{run_id}/url endpoint and SDK runs.get_url method ([57da309](https://github.com/langchain-ai/langsmith-java/commit/57da3096437fe210865bab3a7560eff50d20feef))
### Bug Fixes
* build, lint, and test failures (feat(gateway): rate limit policy CRUD, validation, and materialization [ENT-1138]) ([388984c](https://github.com/langchain-ai/langsmith-java/commit/388984c00503fe2dd39aacb93fe4053ae2030d43))
## [0.1.0-beta.17](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.16...v0.1.0-beta.17) (2026-07-17)
### Features
* **runs/v2:** make start_time optional in GET /v2/runs/:id ([101472f](https://github.com/langchain-ai/langsmith-java/commit/101472f4ce1210028bb37a577a3992a49511af40))
### Chores
* remove stlc-promote-to-main workflow ([#45](https://github.com/langchain-ai/langsmith-java/issues/45)) ([df8f95a](https://github.com/langchain-ai/langsmith-java/commit/df8f95af97e784a035da210551d5aa17c63608c3))
### Documentation
* minor rewording of List Commits endpoint description ([2f1ca87](https://github.com/langchain-ai/langsmith-java/commit/2f1ca870dd9a60016d2c0bad66b1ece2826d41da))
## [0.1.0-beta.16](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.15...v0.1.0-beta.16) (2026-07-17)
### Features
* add run rule evaluator tracing flag [LSO-3323] ([d53da42](https://github.com/langchain-ai/langsmith-java/commit/d53da42ffaa0d5072409f45d4398a03741013c6a))
* **engine:** issue lifecycle backend + webhook status filter (2/8) ([cbc0080](https://github.com/langchain-ai/langsmith-java/commit/cbc00807ee3991638b76430095c521243bcf5675))
* **feedback:** support GET /feedback for thread AQs ([5052542](https://github.com/langchain-ai/langsmith-java/commit/50525423cca7d2024be967a074267a97068c33ed))
### Bug Fixes
* **datasets:** serve public dataset sessions via Go, fixing SmithDB-only 501 ([8eb0670](https://github.com/langchain-ai/langsmith-java/commit/8eb067064eedc528a7e3381e9a84c9bceff0886f))
* patch 6 high-severity security alerts ([#42](https://github.com/langchain-ai/langsmith-java/issues/42)) ([8b1dff9](https://github.com/langchain-ai/langsmith-java/commit/8b1dff9a1ed8a7982278f393ca2859a3d96a97a0))
* remove July 2026 premier plan ([1409dd8](https://github.com/langchain-ai/langsmith-java/commit/1409dd89d8da75b5650ca15509c6e73d545364df))
### Chores
* new unshare endpoint that doesn't require lookup ([8391078](https://github.com/langchain-ai/langsmith-java/commit/839107858ca208c7238ef83e4fbd9379e31bf220))
* Stainless SDK methods for V2 public-run APIs ([306a079](https://github.com/langchain-ai/langsmith-java/commit/306a079479b914fc67a0d7627f29c8c95b6b5c4c))
## [0.1.0-beta.15](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.14...v0.1.0-beta.15) (2026-07-15)
### Features
* add July 2026 startup and premier plan tiers ([b2ed882](https://github.com/langchain-ai/langsmith-java/commit/b2ed882d0edd038733fcdeda5448519a5f4dfe9b))
* **context-hub:** fire commit webhook and deliver async ([2c9704f](https://github.com/langchain-ai/langsmith-java/commit/2c9704f3c0151bd51ff57ecc54edbcfb527e61d7))
* **sandboxes:** add preserve_memory_on_stop create attribute ([99fe7db](https://github.com/langchain-ai/langsmith-java/commit/99fe7dbe33f3a0bb8709577bcd08a3f24113d5e4))
### Chores
* update smithdb proto generated code ([d35ada2](https://github.com/langchain-ai/langsmith-java/commit/d35ada22c7e73d60b6fc44185d7a65a99a4255e1))
## [0.1.0-beta.14](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.13...v0.1.0-beta.14) (2026-07-14)
### Features
* **dashboards:** convert prebuilts to v2 charts on clone [LSO-3306, LSO-3322] ([0a29041](https://github.com/langchain-ai/langsmith-java/commit/0a2904189719cead7198fcc1765e2c44ab36dec9))
* **evaluators:** expose is_managed on Go evaluator reads [LSE-2532] ([17551de](https://github.com/langchain-ai/langsmith-java/commit/17551decf77e4e2141473fe9cdbf33ea4b4648b6))
* forward filter param to thread stats API ([12185be](https://github.com/langchain-ai/langsmith-java/commit/12185beb4cf348a4252af15257ae2b0133027a42))
* **sdk:** wire annotation-queue runs/by-key into stainless [LSAQ-141] ([18d7867](https://github.com/langchain-ai/langsmith-java/commit/18d7867206e7b66a91a3d4452811636aa173e55c))
### Bug Fixes
* patch remaining Java security alerts ([#38](https://github.com/langchain-ai/langsmith-java/issues/38)) ([e639046](https://github.com/langchain-ai/langsmith-java/commit/e639046090177450d005f9fa511d4c03a565a504))
* **smith-go:** default time bounds for POST /v2/threads/query ([63bde3c](https://github.com/langchain-ai/langsmith-java/commit/63bde3c6e4ef1c470a82a7814dd5f6083f98c110))
* **smith-sdks:** name thread stats response model ThreadStats ([1893f83](https://github.com/langchain-ai/langsmith-java/commit/1893f83cf27ed269a62fba637c829e31c1a94bf8))
### Refactors
* **sandboxes:** make context hub sync a generic mount ([9cfd26b](https://github.com/langchain-ai/langsmith-java/commit/9cfd26bef9d4efc3d66d25f6a54f2224393d935b))
## [0.1.0-beta.13](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.12...v0.1.0-beta.13) (2026-07-08)
### Features
* Adds attachment support to Java multipart requests ([#33](https://github.com/langchain-ai/langsmith-java/issues/33)) ([38646cb](https://github.com/langchain-ai/langsmith-java/commit/38646cb1fabc1155b36ea8db43c69f2dd38dede9))
* **backend:** extend_trace_retention field for annotation queue adds ([38646cb](https://github.com/langchain-ai/langsmith-java/commit/38646cb1fabc1155b36ea8db43c69f2dd38dede9))
### Bug Fixes
* re-emit commits missed by release-please due to timestamp ordering ([#181](https://github.com/langchain-ai/langsmith-java/issues/181)) ([38646cb](https://github.com/langchain-ai/langsmith-java/commit/38646cb1fabc1155b36ea8db43c69f2dd38dede9))
### Refactors
* **sandboxes:** resolve default sandbox via snapshot tag instead of blueprint ([38646cb](https://github.com/langchain-ai/langsmith-java/commit/38646cb1fabc1155b36ea8db43c69f2dd38dede9))
## [0.1.0-beta.12](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.11...v0.1.0-beta.12) (2026-07-08)
### ⚠ BREAKING CHANGES
* **stainless:** skip sessions resource for python and typescript
### Features
* **managed-eval:** expose is_managed_evaluator on run-rule read/write paths ([8e17124](https://github.com/langchain-ai/langsmith-java/commit/8e17124b681e504789b3bcb7b03653e327339d1a))
* **smith-sdks:** expose threads and traces v2 endpoints in public SDK ([3bc6173](https://github.com/langchain-ai/langsmith-java/commit/3bc61733286b179f5567439dd65cf94e6faff571))
* **stainless:** skip sessions resource for python and typescript ([d71cff9](https://github.com/langchain-ai/langsmith-java/commit/d71cff9f592c797a6b7383755ebfde6feea7e7c6))
### Bug Fixes
* remove duplicate threads/traces declarations in client impls ([#35](https://github.com/langchain-ai/langsmith-java/issues/35)) ([fdba8e6](https://github.com/langchain-ai/langsmith-java/commit/fdba8e6123bda467312acd260ef7cf20e21dba9b))
* **smith-sdks:** remove dead retrieve_thread_preview SDK mapping ([585b12d](https://github.com/langchain-ai/langsmith-java/commit/585b12dd2672808df97c6221c783342e17dd6755))
* **smith-sdks:** rename dataset runs POST methods from create to query ([4513b3f](https://github.com/langchain-ai/langsmith-java/commit/4513b3ffb05b51ec5be3e5f394d312726de1dd13))
### Chores
* **stainless:** skip all datasets endpoints in Python/TS SDKs except experiment-runs ([6ab6fa2](https://github.com/langchain-ai/langsmith-java/commit/6ab6fa281f3f9db790121a94d20a32323e56dc80))
## [0.1.0-beta.11](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.10...v0.1.0-beta.11) (2026-07-02)
### ⚠ BREAKING CHANGES
* disallow run_count session sort
### Bug Fixes
* restore title/additionalProperties on v2 RunResponse JSON fields for Stainless ([e3b4784](https://github.com/langchain-ai/langsmith-java/commit/e3b478437ef307d6895f0256b4945e68f5e32a23))
### Refactors
* disallow run_count session sort ([a00b1f1](https://github.com/langchain-ai/langsmith-java/commit/a00b1f18ca2c9b6048f4789df0da60704852a79a))
## [0.1.0-beta.10](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.9...v0.1.0-beta.10) (2026-07-01)
### Features
* **evaluation:** add core evaluate() param parity ([#17](https://github.com/langchain-ai/langsmith-java/issues/17)) ([7d9a2fa](https://github.com/langchain-ai/langsmith-java/commit/7d9a2fafd45c60dbe11852c1c4c74565c4517595))
### Bug Fixes
* **ci:** add GH_REPO to release-please trigger publish step ([#24](https://github.com/langchain-ai/langsmith-java/issues/24)) ([10a1f9b](https://github.com/langchain-ai/langsmith-java/commit/10a1f9bcbbf662e048b4e4f0a1bb4e9a29f18bc7))
* update hand-written code to use RunIngest after stainless rename ([#25](https://github.com/langchain-ai/langsmith-java/issues/25)) ([4d45358](https://github.com/langchain-ai/langsmith-java/commit/4d45358e0ef8c0df8905cc3f927a495af2bcbeca))
### Chores
* **examples:** dual-write source run lookup coordinates in Python ([2d4900b](https://github.com/langchain-ai/langsmith-java/commit/2d4900b16bdda9512fa71368dd03f4729f55620a))
* **stainless:** rename run models in runs resource ([2f0ce40](https://github.com/langchain-ai/langsmith-java/commit/2f0ce40b862dc33d52ff8cb0fb8d50fde790ef73))
## [0.1.0-beta.9](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.8...v0.1.0-beta.9) (2026-06-29)
### ⚠ BREAKING CHANGES
* **api:** document v2 experiment-runs and de-publicize legacy dataset endpoints
* **backend:** accept float value for percentile metric [LS-000]
### Features
* add GET endpoint to retrieve single workspace by id [closes ([34f1c52](https://github.com/langchain-ai/langsmith-java/commit/34f1c52fc1088310910de4f21670e2a4d41d196b))
* add GET endpoint to retrieve single workspace by id [closes ENT-508] ([cdbd369](https://github.com/langchain-ai/langsmith-java/commit/cdbd36936fde80c7c3dea773deefd2789d0feecf))
* **api:** document v2 experiment-runs and de-publicize legacy dataset endpoints ([387526f](https://github.com/langchain-ai/langsmith-java/commit/387526fd3ac9e892de0786be5b538c53d2f40049))
* **evaluation:** add Java evaluate orchestrator ([#8](https://github.com/langchain-ai/langsmith-java/issues/8)) ([34f1c52](https://github.com/langchain-ai/langsmith-java/commit/34f1c52fc1088310910de4f21670e2a4d41d196b))
* **evaluation:** add Java evaluate orchestrator ([#8](https://github.com/langchain-ai/langsmith-java/issues/8)) ([d1420ef](https://github.com/langchain-ai/langsmith-java/commit/d1420efd7a9c307d8b99d64ac57e3f0f26b1580b))
* **sandbox-host:** per-VM cgroups for CPU bursting (2x), fractional ([34f1c52](https://github.com/langchain-ai/langsmith-java/commit/34f1c52fc1088310910de4f21670e2a4d41d196b))
* **sandbox-host:** per-VM cgroups for CPU bursting (2x), fractional vCPU, and sustained-burst limiting ([49bf68d](https://github.com/langchain-ai/langsmith-java/commit/49bf68d74763138fa7894d0e04eb1b73e72e82b3))
* **sandboxes:** expose registry CRUD and generate Python/TypeScript SDK ([34f1c52](https://github.com/langchain-ai/langsmith-java/commit/34f1c52fc1088310910de4f21670e2a4d41d196b))
* **sandboxes:** expose registry CRUD and generate Python/TypeScript SDK clients ([cfed196](https://github.com/langchain-ai/langsmith-java/commit/cfed196979dce85b747a4171cb1dcacb38f88641))
### Bug Fixes
* **backend:** accept float value for percentile metric [LS-000] ([e885608](https://github.com/langchain-ai/langsmith-java/commit/e88560861c9f3de50e7448b97d2cde1cfdfbca46))
* trigger publish-sonatype workflow after release-please creates a ([34f1c52](https://github.com/langchain-ai/langsmith-java/commit/34f1c52fc1088310910de4f21670e2a4d41d196b))
* trigger publish-sonatype workflow after release-please creates a release ([#13](https://github.com/langchain-ai/langsmith-java/issues/13)) ([70edc45](https://github.com/langchain-ai/langsmith-java/commit/70edc4514cfc54a98fbf5605795a386b8149300d))
## [0.1.0-beta.8](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.7...v0.1.0-beta.8) (2026-06-25)
### ⚠ BREAKING CHANGES
* **sandboxes:** default S3 mount endpoint
* **sandboxes:** add ArtifactFS git mounts
* **sandboxes:** add gcs bucket mounts
### Features
* **abac:** add tag_value_ids to dataset creation endpoints ([280a374](https://github.com/langchain-ai/langsmith-java/commit/280a37489da492957d07773692e5ae62140d98d0))
* **abac:** add tag_value_ids to prompt creation endpoints [ENT-1176] ([f3ce273](https://github.com/langchain-ai/langsmith-java/commit/f3ce273a028c0de24171835d854edb4c69268d91))
* **abac:** add tag_value_ids to tracer session creation ([f859585](https://github.com/langchain-ai/langsmith-java/commit/f859585592c616310f02f91d8f0f6822ab9be17d))
* add online evaluator endpoints to stainless config ([77d19d3](https://github.com/langchain-ai/langsmith-java/commit/77d19d324195ca6a93b51ea74aaa4484fb7a2cef))
* adding time to first token for experiment metrics ([5a6bddd](https://github.com/langchain-ai/langsmith-java/commit/5a6bddd241bb95b0d176d70ae36c810a8157177d))
* **backend:** allow retrieving and storing V2 charts in database [LSO-2799] ([b86e854](https://github.com/langchain-ai/langsmith-java/commit/b86e854687089b26205de336c8c99d994fd57369))
* end-to-end OAuth bearer for Playground [part of ENT-760] ([77f12e3](https://github.com/langchain-ai/langsmith-java/commit/77f12e3a6e6d627a0e2309ecd35396abb01448ac))
* **evaluation:** Java evaluate() primitives ([#7](https://github.com/langchain-ai/langsmith-java/issues/7)) ([a82b5c8](https://github.com/langchain-ai/langsmith-java/commit/a82b5c88fc08de041e78657d368c2b0359196248))
* **fleet:** frontend passes typed fields (closes AB-000) ([bbbcf7b](https://github.com/langchain-ai/langsmith-java/commit/bbbcf7bf4027f95b13f2a0bffe18564da3fcfbe9))
* **group-stats:** back thread stats with SmithDB ([7f24878](https://github.com/langchain-ai/langsmith-java/commit/7f24878418347b187fbc2c7064c52725de775c8b))
* **hub:** add include_owners to repos list for Fleet [closes AB-2537] ([10f1d65](https://github.com/langchain-ai/langsmith-java/commit/10f1d6506332402672366470117774db374350a6))
* make online evaluator retention opt-in (backend) ([e480cd4](https://github.com/langchain-ai/langsmith-java/commit/e480cd4104a22b71607aca0f209b416320fe62e0))
* **run-rules:** per-action trace-retention control for automations [LSO-2749] ([3c218e9](https://github.com/langchain-ai/langsmith-java/commit/3c218e98bc255847a60b75499d9230372468adb6))
* **runs/v2:** expose runs v2 endpoints publicly with stainless config ([48c283c](https://github.com/langchain-ai/langsmith-java/commit/48c283c7a7448b03b52185053a578027005bbdbd))
* **sandboxes:** add ArtifactFS git mounts ([2ddc687](https://github.com/langchain-ai/langsmith-java/commit/2ddc687610df41bf15429b2492fd85cbcff86472))
* **sandboxes:** add GCP proxy auth flow ([fd2980c](https://github.com/langchain-ai/langsmith-java/commit/fd2980ce69f3be63a0fd44922f08f72a0a6d9ac2))
* **sandboxes:** add gcs bucket mounts ([87e3acb](https://github.com/langchain-ai/langsmith-java/commit/87e3acbb7b1d871a44e82472ff7fbce8669395de))
* **sandboxes:** add sandbox env var support ([da0fc7b](https://github.com/langchain-ai/langsmith-java/commit/da0fc7b507828609f0264e90a50f6f8910ce9567))
* **sandboxes:** filter lists by creator [INF-1492] ([bb9f973](https://github.com/langchain-ai/langsmith-java/commit/bb9f973f41a930ed23d33ee42deeaaaba4d07c35))
* **sandboxes:** move mounts under mount_config ([734a828](https://github.com/langchain-ai/langsmith-java/commit/734a8285cd37d10e9676e67ae8fa98b6420514b0))
* **sandboxes:** proxy runtime routes through platform backend [INF-0000] ([7cb70fc](https://github.com/langchain-ai/langsmith-java/commit/7cb70fc3e3dc71788c9ad66360dbcf4d577a098d))
* **sandboxes:** resolve AWS proxy rule secrets ([cb519f9](https://github.com/langchain-ai/langsmith-java/commit/cb519f93d44d0a34633cae88c0fa79a4c3db182b))
* **sandboxes:** set AWS proxy compatibility env ([6bbe522](https://github.com/langchain-ai/langsmith-java/commit/6bbe522f0a167e6671c5528eec2fa95dde0b680e))
* **sandboxes:** snapshot memory from stopped boxes + honor restore_memory in v2 [INF-0000] ([79647d1](https://github.com/langchain-ai/langsmith-java/commit/79647d1579da9cdc6027f7922075ddb489e9601a))
* **stats:** add include_details param to /runs/stats for backwards compat ([49039f2](https://github.com/langchain-ai/langsmith-java/commit/49039f277018a60f46af85108a30815609c8482c))
### Bug Fixes
* **evaluators:** show code evaluator trace counts [LSE-2359] ([3f3a0c9](https://github.com/langchain-ai/langsmith-java/commit/3f3a0c9c30fc61198a19b27c7b3c6b8e5aa444c6))
* **sandboxes:** default S3 mount endpoint ([54d105e](https://github.com/langchain-ai/langsmith-java/commit/54d105ef91e0c65de0f2a77ccc7e794570da15cb))
* **sandboxes:** use built-in gcp proxy host matching ([ac8da0f](https://github.com/langchain-ai/langsmith-java/commit/ac8da0f77c706c7fc072842d5ec4e95b4f82a2d7))
* **tests:** remove duplicate retrieve() method in RunServiceTest ([#10](https://github.com/langchain-ai/langsmith-java/issues/10)) ([ba419de](https://github.com/langchain-ai/langsmith-java/commit/ba419de13e4749f8427a98b1572bc1764404dd20))
### Chores
* add release-please workflow ([#11](https://github.com/langchain-ai/langsmith-java/issues/11)) ([65b5b9a](https://github.com/langchain-ai/langsmith-java/commit/65b5b9ae402be8b84253d5ca7c255bd188c0e415))
* add stlc sync and promotion workflows ([#5](https://github.com/langchain-ai/langsmith-java/issues/5)) ([f00fef5](https://github.com/langchain-ai/langsmith-java/commit/f00fef565e6c5c5edfa651223790e8e9cf90c317))
* **fleet:** add param to list threads [closes AB-2522] ([8b85e16](https://github.com/langchain-ai/langsmith-java/commit/8b85e16472982a2821e82a6677c843d0565d17f3))
* **smith-sdks:** update Java SDK stainless custom-code tracking file ([17bb73a](https://github.com/langchain-ai/langsmith-java/commit/17bb73a0061627a86d2ed1dba19316dcdfaf9c6d))
### Refactors
* **playground:** remove legacy experiment endpoints [LSO-2230] ([370b940](https://github.com/langchain-ai/langsmith-java/commit/370b940a1ca252f0198b49d315749dc5d9282714))
* rename field to match prev trace retention fields ([b963cab](https://github.com/langchain-ai/langsmith-java/commit/b963cab6c63f10bed9a7a1237b6df12c111be9d4))
## 0.1.0-beta.7 (2026-05-27)
Full Changelog: [v0.1.0-beta.6...v0.1.0-beta.7](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.6...v0.1.0-beta.7)
### Features
* **api:** api update ([cad4993](https://github.com/langchain-ai/langsmith-java/commit/cad499356e3a38d660814b7ceed92eafbcf2989d))
* **api:** api update ([84e287c](https://github.com/langchain-ai/langsmith-java/commit/84e287ca9be34e45b80fd0d33c3ebc1dd1ca0f32))
* **api:** api update ([ce44c3b](https://github.com/langchain-ai/langsmith-java/commit/ce44c3b540624598c9152b8d1d469bb28ec5c67e))
* **api:** api update ([81f7b39](https://github.com/langchain-ai/langsmith-java/commit/81f7b3937992f117bc573e968e9d5988c5bb5399))
* **api:** api update ([3d0c992](https://github.com/langchain-ai/langsmith-java/commit/3d0c992f4c4772d5a3e294ebeba2b28a641b21ed))
* **api:** api update ([b991c00](https://github.com/langchain-ai/langsmith-java/commit/b991c00c14ee68cffd2df17c6d6bdbc6c780cfd1))
* **api:** api update ([104d3c6](https://github.com/langchain-ai/langsmith-java/commit/104d3c65623d66f24f90589df3ee04586fa8d913))
* **api:** api update ([e01a424](https://github.com/langchain-ai/langsmith-java/commit/e01a42416bef9d17a897900e70cbef70e13ad552))
* **api:** api update ([e1443b6](https://github.com/langchain-ai/langsmith-java/commit/e1443b69ab955cbdcee618449f11bc3cdd41add4))
* **api:** api update ([9deafad](https://github.com/langchain-ai/langsmith-java/commit/9deafade612c0e7c163bf13fae4c2d27751122be))
* **api:** api update ([7e7c479](https://github.com/langchain-ai/langsmith-java/commit/7e7c479912229f27ae96878f2e939971b5fff829))
* **api:** api update ([b5a3200](https://github.com/langchain-ai/langsmith-java/commit/b5a32009fc34eb5a04d70205c19bf973414d800f))
* **api:** api update ([3ab0c6c](https://github.com/langchain-ai/langsmith-java/commit/3ab0c6c5482c2378fc1d2a2b8657248ec218a501))
* **client:** support proxy authentication ([d9e3ab5](https://github.com/langchain-ai/langsmith-java/commit/d9e3ab5b34a8fc2321fb1f9f86910517ec30dbdb))
* **sdk:** add hub directories endpoints to Stainless config ([b3dbb81](https://github.com/langchain-ai/langsmith-java/commit/b3dbb81bd63f8b21fce99d345cf9c73345626451))
### Bug Fixes
* **go:** avoid panic when http.DefaultTransport is wrapped ([aba9578](https://github.com/langchain-ai/langsmith-java/commit/aba9578496866a723d85540da2d9f9bc6418027a))
* **go:** avoid panic when http.DefaultTransport is wrapped ([9ea7653](https://github.com/langchain-ai/langsmith-java/commit/9ea76536a054185cbfdad5ddad7036b2e5d46137))
* restore dokka-javadoc plugin application to subprojects ([#157](https://github.com/langchain-ai/langsmith-java/issues/157)) ([1df1c4a](https://github.com/langchain-ai/langsmith-java/commit/1df1c4aacd6c3cc9477576403a1da641bd20302d))
### Performance Improvements
* **client:** create one json mapper ([e68229b](https://github.com/langchain-ai/langsmith-java/commit/e68229b7009315bf60149df92e4af5f0b083c012))
### Chores
* **internal:** codegen related update ([5e551de](https://github.com/langchain-ai/langsmith-java/commit/5e551de99e03fb34a737f3fcac3ee6a08fab8863))
* **internal:** renormalize gradlew.bat line endings ([4738606](https://github.com/langchain-ai/langsmith-java/commit/4738606db159ecc8ad5088714e3100c0e7972f52))
* remove duplicated dokka setup ([b1af8d8](https://github.com/langchain-ai/langsmith-java/commit/b1af8d842d8aa3b1a47cb00ffa22d71134b0d883))
### Documentation
* clarify forwards compat behavior ([d4b4164](https://github.com/langchain-ai/langsmith-java/commit/d4b41649cb18523851c353c6a47b83b492e0c8e5))
## 0.1.0-beta.6 (2026-05-20)
Full Changelog: [v0.1.0-beta.5...v0.1.0-beta.6](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.5...v0.1.0-beta.6)
### Bug Fixes
* patch security alerts — OTel, Tomcat, Jetty, Logback, Spring, SnakeYAML ([#153](https://github.com/langchain-ai/langsmith-java/issues/153)) ([dfa5469](https://github.com/langchain-ai/langsmith-java/commit/dfa54697292c14a987320ba776b580ee25c58f94))
## 0.1.0-beta.5 (2026-05-13)
Full Changelog: [v0.1.0-beta.4...v0.1.0-beta.5](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.4...v0.1.0-beta.5)
### Features
* **sandbox:** make snapshot optional ([#152](https://github.com/langchain-ai/langsmith-java/issues/152)) ([5849b16](https://github.com/langchain-ai/langsmith-java/commit/5849b1695ec4504460205c80cabf8d443fd8a5f0))
### Chores
* **deps:** bump actions/github-script from 8 to 9 ([#144](https://github.com/langchain-ai/langsmith-java/issues/144)) ([c3f8fbd](https://github.com/langchain-ai/langsmith-java/commit/c3f8fbd5449fb5f52529166fd9b2db5eb8dbe24a))
* **deps:** bump gradle/actions ([#143](https://github.com/langchain-ai/langsmith-java/issues/143)) ([fd8a8ae](https://github.com/langchain-ai/langsmith-java/commit/fd8a8aef399ba3784c6d459c758e7a06f6ee5e98))
* **deps:** bump the minor-and-patch group across 1 directory with 3 updates ([#145](https://github.com/langchain-ai/langsmith-java/issues/145)) ([c4a883d](https://github.com/langchain-ai/langsmith-java/commit/c4a883dfd2e89373180f88c7149c13ef426996ee))
## 0.1.0-beta.4 (2026-05-04)
Full Changelog: [v0.1.0-beta.3...v0.1.0-beta.4](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.3...v0.1.0-beta.4)
### Features
* **api:** api update ([4dc8a79](https://github.com/langchain-ai/langsmith-java/commit/4dc8a7912f8b1b449ad65c24bdf74b667dde30f4))
## 0.1.0-beta.3 (2026-05-01)
Full Changelog: [v0.1.0-beta.2...v0.1.0-beta.3](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.2...v0.1.0-beta.3)
## 0.1.0-beta.2 (2026-04-30)
Full Changelog: [v0.1.0-beta.1...v0.1.0-beta.2](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.1...v0.1.0-beta.2)
### Bug Fixes
* Fix another build issue ([#141](https://github.com/langchain-ai/langsmith-java/issues/141)) ([46baa8a](https://github.com/langchain-ai/langsmith-java/commit/46baa8a73e2770e190966d237996135b7fb6b0a3))
## 0.1.0-beta.1 (2026-04-30)
Full Changelog: [v0.1.0-beta.0...v0.1.0-beta.1](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-beta.0...v0.1.0-beta.1)
### Chores
* Fix build ([#139](https://github.com/langchain-ai/langsmith-java/issues/139)) ([cbaa348](https://github.com/langchain-ai/langsmith-java/commit/cbaa3480cd21c2f90a0056a575e977af1bf906b6))
## 0.1.0-beta.0 (2026-04-30)
Full Changelog: [v0.1.0-alpha.28...v0.1.0-beta.0](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.28...v0.1.0-beta.0)
### Features
* Add autobatch tracing mode ([#134](https://github.com/langchain-ai/langsmith-java/issues/134)) ([230959e](https://github.com/langchain-ai/langsmith-java/commit/230959e124ec705648c4cd1e48da915d945154b8))
* Add multipart batching ([#138](https://github.com/langchain-ai/langsmith-java/issues/138)) ([288221c](https://github.com/langchain-ai/langsmith-java/commit/288221c44835d472eb9f0ff59d1f22d0ec150ccb))
* Add zstd compression to runs service ([#136](https://github.com/langchain-ai/langsmith-java/issues/136)) ([e412191](https://github.com/langchain-ai/langsmith-java/commit/e4121913a5d5a8a6de2a16e272925c947694e7c0))
* **api:** add workspaces ([cc88cdf](https://github.com/langchain-ai/langsmith-java/commit/cc88cdf706b9f2ff7a1e66e00273af027c60f203))
* **api:** api update ([c3252b8](https://github.com/langchain-ai/langsmith-java/commit/c3252b8becd6ee89c95dc6ae39bf701d76629e18))
* **api:** api update ([af1ba52](https://github.com/langchain-ai/langsmith-java/commit/af1ba5242ec894847e0dc007674681858cb37a42))
* **api:** api update ([e1cca2b](https://github.com/langchain-ai/langsmith-java/commit/e1cca2b004858451074865be8b9fb4e532f2f5f5))
* **api:** api update ([fd1fc58](https://github.com/langchain-ai/langsmith-java/commit/fd1fc5830a739a88c221e682d1520612c0e45e66))
* **api:** api update ([b487509](https://github.com/langchain-ai/langsmith-java/commit/b4875093d10d752fcc8c3757e33d6c2bbe334c15))
* **api:** api update ([0758583](https://github.com/langchain-ai/langsmith-java/commit/0758583e5cc42b465c662a2897fff92a6fd61394))
* **api:** api update ([1310303](https://github.com/langchain-ai/langsmith-java/commit/131030331c0bab2568498a2bff96112f0fe46fee))
* **api:** manual updates ([0be4215](https://github.com/langchain-ai/langsmith-java/commit/0be4215e63ce63ee562ce1c48b001a6848cce704))
* **api:** manual updates ([f35b312](https://github.com/langchain-ai/langsmith-java/commit/f35b31249d59265e74d6d33f4e3298fe416757ae))
* **api:** sandbox apis ([bd21a1d](https://github.com/langchain-ai/langsmith-java/commit/bd21a1df96b708ac04712aaed7ef1a95505cf1f1))
* **evaluators:** add list evaluators (GET /api/v1/runs/rules) ([be32489](https://github.com/langchain-ai/langsmith-java/commit/be324893c09e4ab6e2176dda1d2f6a6cf15413e6))
* Make autobatch queue denote batches by payload size, add aggregation delay ([#137](https://github.com/langchain-ai/langsmith-java/issues/137)) ([2fb72ae](https://github.com/langchain-ai/langsmith-java/commit/2fb72ae9681964dd2e798642d88ded506c8189b5))
* Merge enqueued posts and patches to optimize batching ([#135](https://github.com/langchain-ai/langsmith-java/issues/135)) ([d65e301](https://github.com/langchain-ai/langsmith-java/commit/d65e3019910bb077ea59056aa37c9d0ef30bfb1a))
* support setting headers via env ([567af0c](https://github.com/langchain-ai/langsmith-java/commit/567af0c1eba8bf2dc6648529c11260f36e586bd3))
### Chores
* **deps:** bump the minor-and-patch group across 1 directory with 5 updates ([#123](https://github.com/langchain-ai/langsmith-java/issues/123)) ([1f88790](https://github.com/langchain-ai/langsmith-java/commit/1f887908d8d851dc641905b7beb8cf4f7cf935b1))
## 0.1.0-alpha.28 (2026-04-14)
Full Changelog: [v0.1.0-alpha.27...v0.1.0-alpha.28](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.27...v0.1.0-alpha.28)
### Features
* **api:** api update ([35484cf](https://github.com/langchain-ai/langsmith-java/commit/35484cf9b1f1ed6642ef857d3fb4aefdb37ae369))
## 0.1.0-alpha.27 (2026-04-14)
Full Changelog: [v0.1.0-alpha.26...v0.1.0-alpha.27](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.26...v0.1.0-alpha.27)
## 0.1.0-alpha.26 (2026-04-13)
Full Changelog: [v0.1.0-alpha.25...v0.1.0-alpha.26](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.25...v0.1.0-alpha.26)
### Features
* Allow manually specifying parent run tree in traceable config ([#126](https://github.com/langchain-ai/langsmith-java/issues/126)) ([a1409fb](https://github.com/langchain-ai/langsmith-java/commit/a1409fb0d7c0319d4129daea5300578e4371a858))
* **api:** api update ([8198ce7](https://github.com/langchain-ai/langsmith-java/commit/8198ce7001d0656bcb4807331d385b0eb8b81a75))
* **api:** api update ([286a62f](https://github.com/langchain-ai/langsmith-java/commit/286a62fba8b5bac8105c71dc52b4a36a37012eae))
### Chores
* **deps:** bump gradle-wrapper from 8.12 to 9.4.1 ([#124](https://github.com/langchain-ai/langsmith-java/issues/124)) ([78b7db0](https://github.com/langchain-ai/langsmith-java/commit/78b7db0e1f08c6de8c12af504dd098cf7c958faa))
* **deps:** bump gradle/actions ([#121](https://github.com/langchain-ai/langsmith-java/issues/121)) ([e3ca5d7](https://github.com/langchain-ai/langsmith-java/commit/e3ca5d7b712f92ce72586bfd7be208c73cec332f))
* **deps:** bump gradle/gradle-build-action ([#122](https://github.com/langchain-ai/langsmith-java/issues/122)) ([d91305f](https://github.com/langchain-ai/langsmith-java/commit/d91305f9157b8d73dfaf8a925c7b2b0e5743f4b2))
### Documentation
* improve examples ([1556365](https://github.com/langchain-ai/langsmith-java/commit/1556365dd6c04f3290b9a836b3c28c09056be5ad))
## 0.1.0-alpha.25 (2026-03-31)
Full Changelog: [v0.1.0-alpha.24...v0.1.0-alpha.25](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.24...v0.1.0-alpha.25)
### Features
* Add `traceable` function wrapper for LangSmith tracing ([#101](https://github.com/langchain-ai/langsmith-java/issues/101)) ([f3bf340](https://github.com/langchain-ai/langsmith-java/commit/f3bf3404fe56be9801806ea44f283eb586d96ce8))
* Adds package version resource to build ([#100](https://github.com/langchain-ai/langsmith-java/issues/100)) ([31cc003](https://github.com/langchain-ai/langsmith-java/commit/31cc0037be79a336cd86f6d53c7093f6c3fb7034))
* Adds processInputs and processOutputs to traceable ([#113](https://github.com/langchain-ai/langsmith-java/issues/113)) ([ee2991f](https://github.com/langchain-ai/langsmith-java/commit/ee2991f830703f29b3d0eddfd621109362a465e5))
* Adds streaming support for wrapOpenAI ([#118](https://github.com/langchain-ai/langsmith-java/issues/118)) ([aae9d2c](https://github.com/langchain-ai/langsmith-java/commit/aae9d2c0dc7f7dae40960113154d9ddff3bd0e06))
* Adds support for tracing streams with traceable ([#117](https://github.com/langchain-ai/langsmith-java/issues/117)) ([0217521](https://github.com/langchain-ai/langsmith-java/commit/0217521b852c70dba4795b145b3eb7b16fd2d10f))
* **api:** api update ([9182ef8](https://github.com/langchain-ai/langsmith-java/commit/9182ef84e304c602899c73b6865473d3ade4953f))
* **api:** api update ([0c91488](https://github.com/langchain-ai/langsmith-java/commit/0c914884473e1de2877708cd92db30fdc8fc10aa))
* **api:** api update ([efa16fc](https://github.com/langchain-ai/langsmith-java/commit/efa16fcb5974fbd5500714936cb52a6e930a6181))
* **api:** api update ([979e8af](https://github.com/langchain-ai/langsmith-java/commit/979e8af2b6f01444bcea33e1e09d1acbc986a6be))
* **api:** api update ([cd9f555](https://github.com/langchain-ai/langsmith-java/commit/cd9f555e525172d5ba0bcb0cd033dc045a9026bd))
* **api:** api update ([af93e09](https://github.com/langchain-ai/langsmith-java/commit/af93e09dc9dd8597454b2a02b1ed37de4e71fde4))
* **api:** manual updates ([493d5e9](https://github.com/langchain-ai/langsmith-java/commit/493d5e9b2844ff160598b87602c9f91733722fe4))
* Rework wrapOpenAI ([#114](https://github.com/langchain-ai/langsmith-java/issues/114)) ([63e0bdc](https://github.com/langchain-ai/langsmith-java/commit/63e0bdc5c893185399065c2c602ed0d5c46bbc68))
### Bug Fixes
* Fix format detection when pulling legacy prompts ([#115](https://github.com/langchain-ai/langsmith-java/issues/115)) ([ea6c3d2](https://github.com/langchain-ai/langsmith-java/commit/ea6c3d29d7c726b7b8455e6e93ec3322330ea5e7))
* patch security alerts — bump Jackson and constrain vulnerable transitive deps ([#119](https://github.com/langchain-ai/langsmith-java/issues/119)) ([dfe092c](https://github.com/langchain-ai/langsmith-java/commit/dfe092c51ab27c3131b81785321664a75d584764))
### Chores
* **deps:** bump gradle/actions from 5 to 6 in the all-actions group ([#99](https://github.com/langchain-ai/langsmith-java/issues/99)) ([4ba75e7](https://github.com/langchain-ai/langsmith-java/commit/4ba75e7776499833eddb1959b194b71eb847840f))
* fix dependabot.yml posture issues ([#105](https://github.com/langchain-ai/langsmith-java/issues/105)) ([a0e5441](https://github.com/langchain-ai/langsmith-java/commit/a0e544170d0c62e340a6250c32b6b521f818eaa4))
## 0.1.0-alpha.24 (2026-03-25)
Full Changelog: [v0.1.0-alpha.23...v0.1.0-alpha.24](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.23...v0.1.0-alpha.24)
### Features
* **api:** api update ([8e94d9d](https://github.com/langchain-ai/langsmith-java/commit/8e94d9dc65dfacd3f238e88631623a1338edec2e))
* **api:** api update ([3288052](https://github.com/langchain-ai/langsmith-java/commit/32880529fe17d465d20ee922894a9e1af3351edd))
* **api:** api update ([a4e3e5c](https://github.com/langchain-ai/langsmith-java/commit/a4e3e5cb858e66a9409ac162f04660c514cc24df))
* **api:** api update ([ff16250](https://github.com/langchain-ai/langsmith-java/commit/ff162506f81ad1616567c9119ded36c1a344c774))
* **api:** api update ([ef7c7a7](https://github.com/langchain-ai/langsmith-java/commit/ef7c7a798786b073e403d994ee2a9367d4db59ee))
* **api:** api update ([643c7b1](https://github.com/langchain-ai/langsmith-java/commit/643c7b121acd874a72c36e80fbd6d8d4aa772319))
* **api:** api update ([4b9c97d](https://github.com/langchain-ai/langsmith-java/commit/4b9c97d873492f919885fc1e394d1819fbedd09a))
* **api:** api update ([ea4e843](https://github.com/langchain-ai/langsmith-java/commit/ea4e843eea1709113fd259659c2cc85fd6317fa5))
* **api:** api update ([74d2131](https://github.com/langchain-ai/langsmith-java/commit/74d213181b7a2398fa8852235de5a1123ff0a7fe))
* **api:** api update ([2708533](https://github.com/langchain-ai/langsmith-java/commit/27085335b36f0ecb249a3bd079f96d8d9fad6c1f))
* **api:** api update ([8f48677](https://github.com/langchain-ai/langsmith-java/commit/8f48677e5ca0dfd9c9e9bc248c204863f233542a))
* **api:** api update ([c11821f](https://github.com/langchain-ai/langsmith-java/commit/c11821f56ba08939568332468a1e9376c78e6372))
* **api:** api update ([44fe8b3](https://github.com/langchain-ai/langsmith-java/commit/44fe8b31986af7ae7ac489c0a59335afb8021f9f))
* **api:** api update ([d11c759](https://github.com/langchain-ai/langsmith-java/commit/d11c759b1cf0d360f07aa0807f554c36f3262b14))
* **api:** manual updates ([09f1302](https://github.com/langchain-ai/langsmith-java/commit/09f1302329d0fcd9082bc52b2381271aabd71bab))
* **api:** manual updates ([dbed70a](https://github.com/langchain-ai/langsmith-java/commit/dbed70af760f0e807626650a2a7d6ecb8d30f0ad))
* **api:** manual updates ([48e2ca2](https://github.com/langchain-ai/langsmith-java/commit/48e2ca294bd412398290777db3ef14655ed47b1c))
* **api:** manual updates ([d8ac870](https://github.com/langchain-ai/langsmith-java/commit/d8ac87089db42004a53e880f419dcf6749c60817))
* **api:** manual updates ([bdb35f0](https://github.com/langchain-ai/langsmith-java/commit/bdb35f01f6374860fe6fa3c83f6f102f22590b23))
### Bug Fixes
* **client:** allow updating header/query affecting fields in `toBuilder()` ([5069881](https://github.com/langchain-ai/langsmith-java/commit/5069881aebcffc8ea2058de667a7af33efc15ec3))
* **client:** incorrect `Retry-After` parsing ([b607264](https://github.com/langchain-ai/langsmith-java/commit/b607264e2c7e10374f5d79d922c6850208b6f839))
* fix streaming in java sdk ([#95](https://github.com/langchain-ai/langsmith-java/issues/95)) ([76da93f](https://github.com/langchain-ai/langsmith-java/commit/76da93f2c0c3a0466feee196243a214e8642b303))
### Chores
* align user agent format ([#96](https://github.com/langchain-ai/langsmith-java/issues/96)) ([5cf06d6](https://github.com/langchain-ai/langsmith-java/commit/5cf06d612963f01b49d6f62117226d948e004046))
* **ci:** skip lint on metadata-only changes ([4c1197c](https://github.com/langchain-ai/langsmith-java/commit/4c1197ce483f317defed85f81d3ec3f5053f68d3))
* **ci:** skip uploading artifacts on stainless-internal branches ([c994122](https://github.com/langchain-ai/langsmith-java/commit/c994122d8c4eea78bb8a942b0ed1db58e6338e1e))
* **deps:** bump the all-actions group with 5 updates ([#92](https://github.com/langchain-ai/langsmith-java/issues/92)) ([3a1a72f](https://github.com/langchain-ai/langsmith-java/commit/3a1a72f5595393a437a8ddf1acc69bb44529b0e8))
* drop apache dependency ([75ca9d7](https://github.com/langchain-ai/langsmith-java/commit/75ca9d71b49bad02bdbd66141f693204b296a9ec))
* **internal:** bump ktfmt ([d9a4a39](https://github.com/langchain-ai/langsmith-java/commit/d9a4a396ad92c9f33c5922dabc00a8ba2451199b))
* **internal:** bump palantir-java-format ([576ebd2](https://github.com/langchain-ai/langsmith-java/commit/576ebd2a02613c632dd1e00ba9c606df99de8f37))
* **internal:** codegen related update ([29d55b0](https://github.com/langchain-ai/langsmith-java/commit/29d55b0ee9f40c2d327f1c08a714ccd9cad1384b))
* **internal:** expand imports ([4c7542f](https://github.com/langchain-ai/langsmith-java/commit/4c7542f4525b43d8fc2adcd7d084a080581b5c48))
* **internal:** tweak CI branches ([b7cecc1](https://github.com/langchain-ai/langsmith-java/commit/b7cecc1caa774426df72acd5734a2c29a96a3e60))
* **internal:** update gitignore ([d0df3fa](https://github.com/langchain-ai/langsmith-java/commit/d0df3fa13a923514d9d3c66d5d96b4fa1fea4a22))
* **internal:** update retry delay tests ([a9c7999](https://github.com/langchain-ai/langsmith-java/commit/a9c7999f55bc12c2d1bac268ce3a2ca5bbd58dc5))
* make `Properties` more resilient to `null` ([47c70f9](https://github.com/langchain-ai/langsmith-java/commit/47c70f91d5ec941fafc08a81baabf946602076c9))
* remove old test ([#97](https://github.com/langchain-ai/langsmith-java/issues/97)) ([79d708b](https://github.com/langchain-ai/langsmith-java/commit/79d708b935ba234c50d10980a7fef8ad12c58f61))
* update placeholder string ([be1baa7](https://github.com/langchain-ai/langsmith-java/commit/be1baa704ad8ebe899fbf8e4003f706fbc3d3f73))
## 0.1.0-alpha.23 (2026-02-20)
Full Changelog: [v0.1.0-alpha.22...v0.1.0-alpha.23](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.22...v0.1.0-alpha.23)
### Features
* **api:** api update ([9485112](https://github.com/langchain-ai/langsmith-java/commit/948511234707031e0e375ded4ed51cd142d0dae1))
* **api:** api update ([786edff](https://github.com/langchain-ai/langsmith-java/commit/786edfff5f8e200c20fb201d83daff8e0fe33b59))
* **api:** api update ([3a6c191](https://github.com/langchain-ai/langsmith-java/commit/3a6c1911a5127afe550cc6ff0cbc2182e7e9cce4))
* **api:** api update ([cd9f91d](https://github.com/langchain-ai/langsmith-java/commit/cd9f91d07e8e0c1c7d13f7a6a849856a9da19d90))
* **api:** api update ([b344828](https://github.com/langchain-ai/langsmith-java/commit/b3448285414b13fe8081c7b7eae023e033ad6ad2))
* **api:** api update ([2e0602a](https://github.com/langchain-ai/langsmith-java/commit/2e0602a9e218f3c9c435c7d1e254621d8f97f14b))
* **api:** api update ([3b1e4c6](https://github.com/langchain-ai/langsmith-java/commit/3b1e4c61e3b8ff44f0415aa808e6a7f550a7834b))
* **api:** api update ([703b77a](https://github.com/langchain-ai/langsmith-java/commit/703b77ac2c34ed600964d0c815b1fa759124756c))
* **api:** api update ([7d0f1d4](https://github.com/langchain-ai/langsmith-java/commit/7d0f1d4e07b0bd50a92947124e2bd1b83b840605))
* **api:** api update ([debadf0](https://github.com/langchain-ai/langsmith-java/commit/debadf0da6c073a569e1abea1c3b58fc8e33bcf4))
* **api:** api update ([9ebd84f](https://github.com/langchain-ai/langsmith-java/commit/9ebd84fe982b0a0304d1fcb7da168df0c97a043f))
* **api:** api update ([88bd850](https://github.com/langchain-ai/langsmith-java/commit/88bd85062b8c87a72f96d6fbac738963c1df562e))
* **api:** api update ([af7bf93](https://github.com/langchain-ai/langsmith-java/commit/af7bf93c5b285d509484c5f8cb838583dd0ed7e8))
* **api:** manual updates ([3f1d46e](https://github.com/langchain-ai/langsmith-java/commit/3f1d46e06a3616cb5fa22f3f89d13a0ff871c6c3))
* **api:** manual updates ([2ac51c9](https://github.com/langchain-ai/langsmith-java/commit/2ac51c9075348a4fd8e38dace4911d8a6bee2000))
* **api:** manual updates ([187c87f](https://github.com/langchain-ai/langsmith-java/commit/187c87f96a08b4c197b37232081c38797b8d59cc))
* **api:** manual updates ([5e0db72](https://github.com/langchain-ai/langsmith-java/commit/5e0db72d55d10c512dc0b0d4997968d2382dc32d))
* **api:** manual updates ([6bc9970](https://github.com/langchain-ai/langsmith-java/commit/6bc9970850b2ce9206eefa770c9e4e8164487745))
* **client:** add connection pooling option ([9acbf93](https://github.com/langchain-ai/langsmith-java/commit/9acbf9334972d4ff5d00caa346e3907ded7d3a72))
* **client:** add more convenience service method overloads ([fba86c8](https://github.com/langchain-ai/langsmith-java/commit/fba86c83fdbe3ad353ec9a5db065464ac0928e55))
### Chores
* **api:** minor updates ([5561ef0](https://github.com/langchain-ai/langsmith-java/commit/5561ef0a24492e78eab34609a48da09aa37496a1))
* **ci:** upgrade `actions/github-script` ([f758afb](https://github.com/langchain-ai/langsmith-java/commit/f758afb6d78e7ac01b085403f89ec33dbb0caa6c))
* **internal:** allow passing args to `./scripts/test` ([206af30](https://github.com/langchain-ai/langsmith-java/commit/206af3009b966f77ad4aec77a975b38ebafbbbde))
* **internal:** make `OkHttp` constructor internal ([b55c1e8](https://github.com/langchain-ai/langsmith-java/commit/b55c1e8e15bbb1c370ab4d8b4713d37965b2f4db))
* **internal:** remove mock server code ([f72c72d](https://github.com/langchain-ai/langsmith-java/commit/f72c72d36f8c390bfbcbd14cdf25cf25d8131a7f))
* **internal:** update `TestServerExtension` comment ([c4956f5](https://github.com/langchain-ai/langsmith-java/commit/c4956f59bae794a6da689eb891ed329ad6bac082))
* **internal:** upgrade AssertJ ([2535a3a](https://github.com/langchain-ai/langsmith-java/commit/2535a3ab58df98b740bee627f7fecd0f88adb05c))
* refactor ([#90](https://github.com/langchain-ai/langsmith-java/issues/90)) ([1902c22](https://github.com/langchain-ai/langsmith-java/commit/1902c228ddd2b4ddcc5b741bf7c67f250ae9da06))
* update mock server docs ([1704b40](https://github.com/langchain-ai/langsmith-java/commit/1704b408f7fe5af7ee8fa322f037a37749e22d05))
## 0.1.0-alpha.22 (2026-01-23)
Full Changelog: [v0.1.0-alpha.21...v0.1.0-alpha.22](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.21...v0.1.0-alpha.22)
### Features
* **api:** api update ([5fe36e6](https://github.com/langchain-ai/langsmith-java/commit/5fe36e66be0d8a4118d631794c5f70f443c88dac))
* **client:** send `X-Stainless-Kotlin-Version` header ([748e689](https://github.com/langchain-ai/langsmith-java/commit/748e6890d3560b3f373aa07031bc8b2e6fa288f8))
### Bug Fixes
* **client:** disallow coercion from float to int ([bc01e05](https://github.com/langchain-ai/langsmith-java/commit/bc01e05032fb90f0318bde2107978ccf93eaa330))
* **client:** fully respect max retries ([07ba637](https://github.com/langchain-ai/langsmith-java/commit/07ba63741949f3e159314c5556902e7a3fb93def))
* **client:** preserve time zone in lenient date-time parsing ([498395e](https://github.com/langchain-ai/langsmith-java/commit/498395ea719057dd6d5bbef7f38fc1206ffc6d04))
* **client:** send retry count header for max retries 0 ([07ba637](https://github.com/langchain-ai/langsmith-java/commit/07ba63741949f3e159314c5556902e7a3fb93def))
* date time deserialization leniency ([ac7a559](https://github.com/langchain-ai/langsmith-java/commit/ac7a559ad9704bac385a49c724b0d86704d0da4f))
* deserialization order ([71f380d](https://github.com/langchain-ai/langsmith-java/commit/71f380d5b09e54f6a0f0d2b703c2e3e2cc4e4766))
### Chores
* **ci:** upgrade `actions/setup-java` ([ee1ed58](https://github.com/langchain-ai/langsmith-java/commit/ee1ed58ada45c071662c61e700b473ee471660a1))
* **internal:** clean up maven repo artifact script and add html documentation to repo root ([85d8771](https://github.com/langchain-ai/langsmith-java/commit/85d877129ea7e78b889b4d28f6ea06bcefacfce1))
* **internal:** correct cache invalidation for `SKIP_MOCK_TESTS` ([1cd0cf2](https://github.com/langchain-ai/langsmith-java/commit/1cd0cf2d27d3c87c6196d1492a6c6a8b3562ba49))
* **internal:** depend on packages directly in example ([07ba637](https://github.com/langchain-ai/langsmith-java/commit/07ba63741949f3e159314c5556902e7a3fb93def))
* **internal:** improve maven repo docs ([d928145](https://github.com/langchain-ai/langsmith-java/commit/d92814500d00759eb188ab19bfc036b90022c782))
* **internal:** update `actions/checkout` version ([70785ca](https://github.com/langchain-ai/langsmith-java/commit/70785ca2b98445faebb66ef7a54223d5b3ea9e31))
* **internal:** update maven repo doc to include authentication ([2900175](https://github.com/langchain-ai/langsmith-java/commit/29001754d9b523a9bdb76a7c9650991284fbb761))
* test on Jackson 2.14.0 to avoid encountering FasterXML/jackson-databind[#3240](https://github.com/langchain-ai/langsmith-java/issues/3240) in tests ([ac7a559](https://github.com/langchain-ai/langsmith-java/commit/ac7a559ad9704bac385a49c724b0d86704d0da4f))
### Documentation
* add comment for arbitrary value fields ([642227b](https://github.com/langchain-ai/langsmith-java/commit/642227ba6b07a16d3f3287f585077afbc2684573))
## 0.1.0-alpha.21 (2026-01-14)
Full Changelog: [v0.1.0-alpha.20...v0.1.0-alpha.21](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.20...v0.1.0-alpha.21)
### Features
* **api:** api update ([d5e92e1](https://github.com/langchain-ai/langsmith-java/commit/d5e92e102d1dd770ae1205dc52d5d7f0a0692dcb))
* **api:** api update ([591546c](https://github.com/langchain-ai/langsmith-java/commit/591546c935e7e10e9521310870af83c8bd67ebd4))
* **api:** api update ([440ea8a](https://github.com/langchain-ai/langsmith-java/commit/440ea8abf0a22148c636f06f5a4c778e20b947e9))
* **api:** manual updates ([ee36c44](https://github.com/langchain-ai/langsmith-java/commit/ee36c44834da0d371168b876b7f9619c281f749e))
### Chores
* **internal:** support uploading Maven repo artifacts to stainless package server ([d1b01b9](https://github.com/langchain-ai/langsmith-java/commit/d1b01b9073fc2db7643fa07de2d421088218f4a3))
## 0.1.0-alpha.20 (2026-01-12)
Full Changelog: [v0.1.0-alpha.19...v0.1.0-alpha.20](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.19...v0.1.0-alpha.20)
### Features
* **api:** api update ([4b197f0](https://github.com/langchain-ai/langsmith-java/commit/4b197f03e8a2d7658d2e268d854114398429f7a9))
* **api:** api update ([5703f0f](https://github.com/langchain-ai/langsmith-java/commit/5703f0f838d16543a4c518dc108065d4e72e58b6))
* **api:** api update ([e2e98b7](https://github.com/langchain-ai/langsmith-java/commit/e2e98b7e9ab23ea0298e79cfc38a49797db0e978))
* **api:** api update ([96a6948](https://github.com/langchain-ai/langsmith-java/commit/96a69483bf3e39b21a65dfb1af1eea6914939dfa))
* **api:** api update ([8895f35](https://github.com/langchain-ai/langsmith-java/commit/8895f3571aafdffce373dac1052a4f26fd38347d))
* **api:** api update ([9c66288](https://github.com/langchain-ai/langsmith-java/commit/9c66288568aceb6d7b77b8742dc34e132c122b66))
* **api:** api update ([f73c866](https://github.com/langchain-ai/langsmith-java/commit/f73c86620f221064be73d86d9ce540f4e8aeb5c6))
* **client:** add `HttpRequest#url()` method ([7c51e6a](https://github.com/langchain-ai/langsmith-java/commit/7c51e6a7139f20c181bc4349c247da8aaa151a89))
* **client:** allow configuring dispatcher executor service ([4a50a22](https://github.com/langchain-ai/langsmith-java/commit/4a50a22f62527d90e5584b9c805b34cfa8c0a7c8))
## 0.1.0-alpha.19 (2025-12-10)
Full Changelog: [v0.1.0-alpha.18...v0.1.0-alpha.19](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.18...v0.1.0-alpha.19)
### Features
* **api:** api update ([c0d8bec](https://github.com/langchain-ai/langsmith-java/commit/c0d8bec69c14c9300a39737092b4a29210d4df59))
* **api:** api update ([9a9d362](https://github.com/langchain-ai/langsmith-java/commit/9a9d362b6e509abc8257c4ff8d5f11911f3ca4c0))
* **api:** api update ([e27bc6f](https://github.com/langchain-ai/langsmith-java/commit/e27bc6fd053032d0597a5dc4f4d636bada559748))
* **api:** api update ([ae353d7](https://github.com/langchain-ai/langsmith-java/commit/ae353d775aa03a2f820c9d065f79528401963c2c))
* **api:** api update ([fdb6021](https://github.com/langchain-ai/langsmith-java/commit/fdb6021eb3de21d7e3930dfe9f99a943100118b2))
* **api:** api update ([da999c0](https://github.com/langchain-ai/langsmith-java/commit/da999c02267725a5d5c4fdbf17f9482ad5df638e))
* **api:** api update ([daa7b07](https://github.com/langchain-ai/langsmith-java/commit/daa7b07fa755ad0d1feb6f6b3370c3bb5ff3c049))
* **api:** api update ([4985a65](https://github.com/langchain-ai/langsmith-java/commit/4985a651acb592b38f4d94afda202a4ecddb6be2))
* **api:** manual updates ([9ff3976](https://github.com/langchain-ai/langsmith-java/commit/9ff39760388f6cf4e68945ce536a0dd92d4f84bf))
* **api:** manual updates ([aa705c4](https://github.com/langchain-ai/langsmith-java/commit/aa705c489b4a64d2815c5ceacde5161c7518afac))
### Bug Fixes
* **client:** cancel okhttp call when future cancelled ([89ab779](https://github.com/langchain-ai/langsmith-java/commit/89ab7791a8e27347610bbecea44867c5b991073b))
* fix trace input and output ([#83](https://github.com/langchain-ai/langsmith-java/issues/83)) ([7a64410](https://github.com/langchain-ai/langsmith-java/commit/7a644105f69db9d10064ba38c18ee294fc62f360))
### Chores
* **api:** delete index methods ([7e90f5f](https://github.com/langchain-ai/langsmith-java/commit/7e90f5f70e263458f594ce92ae9a7b1a29e81f85))
### Documentation
* remove `$` for better copy-pasteabality ([4163726](https://github.com/langchain-ai/langsmith-java/commit/4163726d6d496289f025006b9a03363caeb879c7))
## 0.1.0-alpha.18 (2025-12-09)
Full Changelog: [v0.1.0-alpha.17...v0.1.0-alpha.18](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.17...v0.1.0-alpha.18)
### Features
* **api:** add go target ([ff3556a](https://github.com/langchain-ai/langsmith-java/commit/ff3556a80ff2fc128c17d06cc78b366e27865239))
* **api:** api update ([9a1ad24](https://github.com/langchain-ai/langsmith-java/commit/9a1ad247305770017095b760fa59c49883f6d204))
* **api:** change paginated retrieve methods to list ([229ed3e](https://github.com/langchain-ai/langsmith-java/commit/229ed3e0214e1d73cd81e05e118f5f6c7be670a7))
* **api:** pagination ([bd22dc6](https://github.com/langchain-ai/langsmith-java/commit/bd22dc6238f748edb0f0f70214f1a60b9087b525))
* **api:** pagination read me ([eccc183](https://github.com/langchain-ai/langsmith-java/commit/eccc183d0ec53b8b06122598daadafd707e28be3))
* **api:** readme example - change REPLACE_ME session_id to uuid ([a12609a](https://github.com/langchain-ai/langsmith-java/commit/a12609a69ae1d7d45a75f3032c74dc8a16c49242))
* method counter in ci ([8bb864e](https://github.com/langchain-ai/langsmith-java/commit/8bb864eaeccd46cb8153836e7b25ccfb1ba79838))
### Bug Fixes
* formatting error in record experiment example ([#81](https://github.com/langchain-ai/langsmith-java/issues/81)) ([243e318](https://github.com/langchain-ai/langsmith-java/commit/243e318f8defffda4b4116a8e2728c75f341dea7))
### Chores
* **internal:** version bump ([dcfaf78](https://github.com/langchain-ai/langsmith-java/commit/dcfaf785e20ada8e5c967a324aa23c9aa008372c))
## 0.1.0-alpha.17 (2025-11-20)
Full Changelog: [v0.1.0-alpha.16...v0.1.0-alpha.17](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.16...v0.1.0-alpha.17)
### Chores
* Configure workflow for CodeQL ([f0b024d](https://github.com/langchain-ai/langsmith-java/commit/f0b024dde59c5aab9c1da151061342d2586fe882))
## 0.1.0-alpha.16 (2025-11-20)
Full Changelog: [v0.1.0-alpha.15...v0.1.0-alpha.16](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.15...v0.1.0-alpha.16)
### Chores
* Update runner to ubuntu-latest-l-oss for Sonatype Publish ([5597676](https://github.com/langchain-ai/langsmith-java/commit/5597676151b5bd30087dcca93ae13f0e69714a16))
## 0.1.0-alpha.15 (2025-11-20)
Full Changelog: [v0.1.0-alpha.14...v0.1.0-alpha.15](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.14...v0.1.0-alpha.15)
## 0.1.0-alpha.14 (2025-11-20)
Full Changelog: [v0.1.0-alpha.13...v0.1.0-alpha.14](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.13...v0.1.0-alpha.14)
### Chores
* Update support portal link ([6a36fa7](https://github.com/langchain-ai/langsmith-java/commit/6a36fa7802d64d0fa0e3dc8f7f4958c2d9763f6c))
## 0.1.0-alpha.13 (2025-11-20)
Full Changelog: [v0.1.0-alpha.12...v0.1.0-alpha.13](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.12...v0.1.0-alpha.13)
### Features
* **api:** add rate limiting ([51fa6d7](https://github.com/langchain-ai/langsmith-java/commit/51fa6d7ec24088d1f5e4f7d64afa8b84ec0f32e1))
* **api:** Update contact email ([20a193d](https://github.com/langchain-ai/langsmith-java/commit/20a193d472fb6ba20d990852e7d116b7d688e9cb))
* **api:** Update docs to https://docs.smith.langchain.com/ ([f741f44](https://github.com/langchain-ai/langsmith-java/commit/f741f4487be9e79a4062bcb4e25f8605c2b6159c))
## 0.1.0-alpha.12 (2025-11-19)
Full Changelog: [v0.1.0-alpha.11...v0.1.0-alpha.12](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.11...v0.1.0-alpha.12)
### Features
* **api:** Set targets.java.publish.docs: true ([aec9261](https://github.com/langchain-ai/langsmith-java/commit/aec9261033ddb4a438e0bd409e96c2cb0c056471))
### Chores
* Move runner to ubuntu-latest-l to allow dokka to complete javadoc building ([05a9c63](https://github.com/langchain-ai/langsmith-java/commit/05a9c630d30918370524825769f9779a5925e9a3))
## 0.1.0-alpha.11 (2025-11-19)
Full Changelog: [v0.1.0-alpha.10...v0.1.0-alpha.11](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.10...v0.1.0-alpha.11)
### Features
* **api:** manual updates ([ceb4b70](https://github.com/langchain-ai/langsmith-java/commit/ceb4b70fb237b062479a82a33fd573282bc78ddd))
* **api:** manual updates ([5a26524](https://github.com/langchain-ai/langsmith-java/commit/5a265242c4eff81d3643a734694552a7002601c9))
* **api:** manual updates ([67d1fe5](https://github.com/langchain-ai/langsmith-java/commit/67d1fe57ad07a4501cc28d02e120e2cfaa571795))
* **api:** manual updates ([d0fe06d](https://github.com/langchain-ai/langsmith-java/commit/d0fe06d96b20eeb85102be4a27c3c4ff12d3f8bd))
### Bug Fixes
* ls playground errors & commit builder class ([3a6084e](https://github.com/langchain-ai/langsmith-java/commit/3a6084e3eb070030f42d5e7f255559c39f0c5c18))
### Chores
* Update/simplify README leading example ([1357414](https://github.com/langchain-ai/langsmith-java/commit/1357414326b7ff316364ebcfa4f03c6f07327c8a))
## 0.1.0-alpha.10 (2025-11-17)
Full Changelog: [v0.1.0-alpha.9...v0.1.0-alpha.10](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.9...v0.1.0-alpha.10)
### Features
* **api:** api update ([09fe76d](https://github.com/langchain-ai/langsmith-java/commit/09fe76d418f4d0adff2539db4e76abd950e1b1ba))
* **api:** api update ([3800b92](https://github.com/langchain-ai/langsmith-java/commit/3800b92761f1e238c9f76e8df2cabaed186a4eca))
* **api:** api update ([df4d45b](https://github.com/langchain-ai/langsmith-java/commit/df4d45b2319a20eef3938a6c065423a8d458c7df))
* **api:** enable post /runs/batch ([38a4268](https://github.com/langchain-ai/langsmith-java/commit/38a4268c565bc27b59b0a7bd7145888c1f7e51af))
* **api:** manual updates ([8e76b0d](https://github.com/langchain-ai/langsmith-java/commit/8e76b0d513e52007da90b9c8482203481b1753f6))
* **api:** switch license to MIT ([fb7fc1f](https://github.com/langchain-ai/langsmith-java/commit/fb7fc1f29d4bdd6e0a6b8fb743af3cd195c08fcc))
### Bug Fixes
* **client:** multi-value header serialization ([c477835](https://github.com/langchain-ai/langsmith-java/commit/c477835e95825176bc659da0c0a42714863fb308))
* lint ([91f5b8e](https://github.com/langchain-ai/langsmith-java/commit/91f5b8e78493a421a1c60351c7a6ef556f47fdcb))
### Chores
* align env vars with other SDKs ([ba82822](https://github.com/langchain-ai/langsmith-java/commit/ba8282230cf48fa04b9277d41128909d9c18cbc4))
* combine otel examples ([a2074ab](https://github.com/langchain-ai/langsmith-java/commit/a2074ab7b1346d8a9f0ccfae947ebeaf3b7bab55))
* convert to kotlin ([875050c](https://github.com/langchain-ai/langsmith-java/commit/875050c7524511a14a82f4e741625aeea5fe50a9))
### Refactors
* ExperimentContext to use OpenTelemetry Context pattern ([def5492](https://github.com/langchain-ai/langsmith-java/commit/def549215491cb5cf46db9f132c902388865f536))
* migrate to SLF4J logging and Builder-only API ([02cc2f3](https://github.com/langchain-ai/langsmith-java/commit/02cc2f35a2e12446a1f0874ed0d3053f755b506d))
## 0.1.0-alpha.9 (2025-11-11)
Full Changelog: [v0.1.0-alpha.8...v0.1.0-alpha.9](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.8...v0.1.0-alpha.9)
### Features
* added otel ingestion examples and code ([19c2cf5](https://github.com/langchain-ai/langsmith-java/commit/19c2cf595acfa19196b3ecf8726b1e888d5f6a17))
* **api:** api update ([284d184](https://github.com/langchain-ai/langsmith-java/commit/284d1843164d9803354e56220f436264743ea76d))
### Bug Fixes
* added back the stdin build config ([866970e](https://github.com/langchain-ai/langsmith-java/commit/866970e8e06c575564bfb0f33bd2ab9197ff16f0))
* lint fix ([16f01c8](https://github.com/langchain-ai/langsmith-java/commit/16f01c8edb0fcac9ab71882842daee252ae4198d))
* lint fix ([cd244b3](https://github.com/langchain-ai/langsmith-java/commit/cd244b3fe9fcc55ec53b4a66e8113cba77932212))
* lint, build, test ([21969a6](https://github.com/langchain-ai/langsmith-java/commit/21969a61f9e31dbd3b75e372795bc5960139486c))
## 0.1.0-alpha.8 (2025-11-10)
Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.7...v0.1.0-alpha.8)
### Features
* **api:** enable /api/v1/runs/query ([1967627](https://github.com/langchain-ai/langsmith-java/commit/1967627dd49bf1e4855c9b25a50ff8a40de65b61))
* **api:** enable /examples ([02d5b72](https://github.com/langchain-ai/langsmith-java/commit/02d5b724decd2cbbcc3e808f45b3fddbe3c6cce6))
* **api:** manual updates ([ff1d89a](https://github.com/langchain-ai/langsmith-java/commit/ff1d89a0bb9bbbf0096c8e75ca905a30a3e4324f))
* **api:** manual updates ([1c57a55](https://github.com/langchain-ai/langsmith-java/commit/1c57a55b2e0d0ead4bc48366478a933fb5e4c42c))
### Bug Fixes
* **api:** rename DataType to DatasetDataType ([494e91b](https://github.com/langchain-ai/langsmith-java/commit/494e91bba27677d2e19c70a96ee2cc69edf66539))
* DatasetDataType name ([1160c21](https://github.com/langchain-ai/langsmith-java/commit/1160c21dba6620792e25a83e6279583f5c1aed50))
* remove experiment_view_overrides ([43ffea3](https://github.com/langchain-ai/langsmith-java/commit/43ffea37a2b9051f136603dd8779d179439884e8))
* rename query param dataType to datatype ([9b596ec](https://github.com/langchain-ai/langsmith-java/commit/9b596ec763d1b3e93802a5579190a27c150944cd))
## 0.1.0-alpha.7 (2025-11-10)
Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.6...v0.1.0-alpha.7)
### Features
* **api:** api update ([524a19f](https://github.com/langchain-ai/langsmith-java/commit/524a19f4de68c8936cd5995a90491061710f2a5e))
* **api:** manual updates ([9d61b1d](https://github.com/langchain-ai/langsmith-java/commit/9d61b1d041a599d547bb734f63cd8021ed3f6154))
* **api:** manual updates ([91e9267](https://github.com/langchain-ai/langsmith-java/commit/91e9267c8dc7291ab5263b98c86df6467ca08b07))
* **api:** manual updates ([5f71223](https://github.com/langchain-ai/langsmith-java/commit/5f71223bac2a797dd1da9d3aaa8d6e94f3f6c685))
### Chores
* Move LangSmith OTel Java wrappers PoC into SDK ([24680be](https://github.com/langchain-ai/langsmith-java/commit/24680beb2f50ab99332fb8fcd55ca6e50890e8d1))
## 0.1.0-alpha.6 (2025-11-07)
Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.5...v0.1.0-alpha.6)
### Features
* **api:** api update ([3e0c4b8](https://github.com/langchain-ai/langsmith-java/commit/3e0c4b89d919ab48138fe8cc6dd3186fc543b1a1))
* **api:** manual updates ([3da5f28](https://github.com/langchain-ai/langsmith-java/commit/3da5f2870ce7a0aaab2bd8cd2363cdcbd3604fec))
* **api:** remove some endpoints ([b4e170d](https://github.com/langchain-ai/langsmith-java/commit/b4e170da648e42bcd936f315c21b7ca0739277a1))
* **api:** removing methods ([a5dc27e](https://github.com/langchain-ai/langsmith-java/commit/a5dc27ed22ec46dcfaa708318ba41bedb70742b2))
### Chores
* revert from SH to public GH runner ([0d00830](https://github.com/langchain-ai/langsmith-java/commit/0d00830db9c921154d9613b1a09e4324fe2a3795))
## 0.1.0-alpha.5 (2025-11-07)
Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.4...v0.1.0-alpha.5)
### Bug Fixes
* **api:** ignore /api/v1/runs/stats ([77433e6](https://github.com/langchain-ai/langsmith-java/commit/77433e67052b14b77d4f87dd3f2bf85d43625217))
## 0.1.0-alpha.4 (2025-11-06)
Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0-alpha.3...v0.1.0-alpha.4)
### Features
* **api:** api update ([1d38234](https://github.com/langchain-ai/langsmith-java/commit/1d382347b2bc40d0fc5c408a2d210d5a8b7b04ee))
* **api:** disabling charts and org-charts ([9513b86](https://github.com/langchain-ai/langsmith-java/commit/9513b866e94bcbc1feccbf993a7d858399b9836c))
### Bug Fixes
* **api:** disable /api/v1/repos/optimize-job ([48b96d6](https://github.com/langchain-ai/langsmith-java/commit/48b96d6df48665af0abba2480f0e91d5811c8267))
* **api:** disable optimization-jobs ([945edb4](https://github.com/langchain-ai/langsmith-java/commit/945edb47b4bc0fa82c2dbd7a629e51db43254579))
* **api:** excluding post /api/v1/prompts* ([e1a0439](https://github.com/langchain-ai/langsmith-java/commit/e1a0439908c777b281d2a6b91cbf8bfcb35afa4c))
* **api:** Rename RunRules*.transient for java target ([ea54d5a](https://github.com/langchain-ai/langsmith-java/commit/ea54d5a1a04e8b9ad6b1f80b18cb7e65a0dbc3e1))
### Chores
* disable javadocs ([0f8c477](https://github.com/langchain-ai/langsmith-java/commit/0f8c477a271c4e7771d56778f6aebcf7dc03d29e))
## 0.1.0-alpha.3 (2025-11-05)
Full Changelog: [v0.1.0...v0.1.0-alpha.3](https://github.com/langchain-ai/langsmith-java/compare/v0.1.0...v0.1.0-alpha.3)
### Features
* **api:** manual updates ([70bfdc0](https://github.com/langchain-ai/langsmith-java/commit/70bfdc0d4727e693fc60dc16b0b5c54f9ad0809a))
* **api:** manual updates ([ba17bc4](https://github.com/langchain-ai/langsmith-java/commit/ba17bc456030005106645000aaf3b6f69910587a))
## 0.1.0 (2025-11-05)
Full Changelog: [v0.0.1...v0.1.0](https://github.com/langchain-ai/langsmith-java/compare/v0.0.1...v0.1.0)
### Features
* **api:** update via SDK Studio ([d566db8](https://github.com/langchain-ai/langsmith-java/commit/d566db885648aceb59f329a8d0c9a55dffa4d56f))
* **api:** update via SDK Studio ([3b95796](https://github.com/langchain-ai/langsmith-java/commit/3b957961949fd4cafb9e300e59ad70c9323b5606))
* **api:** update via SDK Studio ([1121516](https://github.com/langchain-ai/langsmith-java/commit/1121516e4d26dc87b382b178bed2ad5a4e19c9bb))
* **api:** update via SDK Studio ([2443989](https://github.com/langchain-ai/langsmith-java/commit/2443989306c07abb715cfd56fa7233d990f65abe))
* **api:** update via SDK Studio ([154d8b6](https://github.com/langchain-ai/langsmith-java/commit/154d8b618d9a24d3244886c7a37eed727bc78056))
* **api:** update via SDK Studio ([9afbcab](https://github.com/langchain-ai/langsmith-java/commit/9afbcabf447707add64d587a7a25577570c6ea50))
* **api:** update via SDK Studio ([bad726a](https://github.com/langchain-ai/langsmith-java/commit/bad726a369361b69a24921fbee842e7c6449ff33))
* **api:** update via SDK Studio ([08ff898](https://github.com/langchain-ai/langsmith-java/commit/08ff89828ff62ed36e823cda514c96f56301f514))
* **api:** update via SDK Studio ([515bd12](https://github.com/langchain-ai/langsmith-java/commit/515bd1264635b7a6ff5fca43b4736d23a3243c00))
* **api:** update via SDK Studio ([ccaa939](https://github.com/langchain-ai/langsmith-java/commit/ccaa939ba9e47eccefbffb94a0a320c99a742e27))
* **api:** update via SDK Studio ([#10](https://github.com/langchain-ai/langsmith-java/issues/10)) ([8ce42d0](https://github.com/langchain-ai/langsmith-java/commit/8ce42d0d86b9eb2316323ecf5883060c3be6569b))
* **api:** update via SDK Studio ([#11](https://github.com/langchain-ai/langsmith-java/issues/11)) ([2a9fe2b](https://github.com/langchain-ai/langsmith-java/commit/2a9fe2b42e22ca023024900a3145c90f7f70876b))
* **api:** update via SDK Studio ([#13](https://github.com/langchain-ai/langsmith-java/issues/13)) ([db65132](https://github.com/langchain-ai/langsmith-java/commit/db651329e510afa7b4cbee0471d05f08bb7468c4))
* **api:** update via SDK Studio ([#14](https://github.com/langchain-ai/langsmith-java/issues/14)) ([79999bf](https://github.com/langchain-ai/langsmith-java/commit/79999bf54207e7c7ba190a30fee341c85f69e870))
* **api:** update via SDK Studio ([#15](https://github.com/langchain-ai/langsmith-java/issues/15)) ([134c022](https://github.com/langchain-ai/langsmith-java/commit/134c02266d7d774603ac604bb2e1138375a7679c))
* **api:** update via SDK Studio ([#16](https://github.com/langchain-ai/langsmith-java/issues/16)) ([7953ae0](https://github.com/langchain-ai/langsmith-java/commit/7953ae06de08e9362116f0c2d1a2daed89745a94))
* **api:** update via SDK Studio ([#17](https://github.com/langchain-ai/langsmith-java/issues/17)) ([8b462e1](https://github.com/langchain-ai/langsmith-java/commit/8b462e174e8bb482b7bfeecd3903b4caa0a76d97))
* **api:** update via SDK Studio ([#18](https://github.com/langchain-ai/langsmith-java/issues/18)) ([0ffa490](https://github.com/langchain-ai/langsmith-java/commit/0ffa490540523665467616d78ad0c11abcd596e3))
* **api:** update via SDK Studio ([#19](https://github.com/langchain-ai/langsmith-java/issues/19)) ([36e65ef](https://github.com/langchain-ai/langsmith-java/commit/36e65eff30b737df79f0309110cfb3fd31ad201e))
* **api:** update via SDK Studio ([#2](https://github.com/langchain-ai/langsmith-java/issues/2)) ([55c5994](https://github.com/langchain-ai/langsmith-java/commit/55c59946bba5240076888bc5dbc106b7c6e79e12))
* **api:** update via SDK Studio ([#20](https://github.com/langchain-ai/langsmith-java/issues/20)) ([6014dab](https://github.com/langchain-ai/langsmith-java/commit/6014dab0bb0cb252601045f886ab82f9ab486e8f))
* **api:** update via SDK Studio ([#21](https://github.com/langchain-ai/langsmith-java/issues/21)) ([d33b9aa](https://github.com/langchain-ai/langsmith-java/commit/d33b9aab8a15e89c204841d4bc2393a79821322b))
* **api:** update via SDK Studio ([#22](https://github.com/langchain-ai/langsmith-java/issues/22)) ([8842ad7](https://github.com/langchain-ai/langsmith-java/commit/8842ad7d4c77ba34732bc35a46b9691e4e96f27a))
* **api:** update via SDK Studio ([#23](https://github.com/langchain-ai/langsmith-java/issues/23)) ([bfc1bfe](https://github.com/langchain-ai/langsmith-java/commit/bfc1bfe92bb196d2dd144249ead8c630f5c22617))
* **api:** update via SDK Studio ([#24](https://github.com/langchain-ai/langsmith-java/issues/24)) ([c34959c](https://github.com/langchain-ai/langsmith-java/commit/c34959c24e720c97e7f217a809d8757722a29e0f))
* **api:** update via SDK Studio ([#25](https://github.com/langchain-ai/langsmith-java/issues/25)) ([18b3218](https://github.com/langchain-ai/langsmith-java/commit/18b32186ba51625597553c92acd28afa93d0daed))
* **api:** update via SDK Studio ([#26](https://github.com/langchain-ai/langsmith-java/issues/26)) ([a098ba3](https://github.com/langchain-ai/langsmith-java/commit/a098ba3e9cc1fe064c4d09d7d177814111d86668))
* **api:** update via SDK Studio ([#3](https://github.com/langchain-ai/langsmith-java/issues/3)) ([8f2c165](https://github.com/langchain-ai/langsmith-java/commit/8f2c1656243229dade53d1982b03776cd7dd1f54))
* **api:** update via SDK Studio ([#4](https://github.com/langchain-ai/langsmith-java/issues/4)) ([eb9f2ca](https://github.com/langchain-ai/langsmith-java/commit/eb9f2caa19bb047ad95d9735ce2978ed1b8de899))
* **api:** update via SDK Studio ([#5](https://github.com/langchain-ai/langsmith-java/issues/5)) ([6e2732b](https://github.com/langchain-ai/langsmith-java/commit/6e2732b4ddeefc1ef99e0b99632416e51407fa20))
* **api:** update via SDK Studio ([#6](https://github.com/langchain-ai/langsmith-java/issues/6)) ([66e3914](https://github.com/langchain-ai/langsmith-java/commit/66e39143b6a7dad335693155e059741e97f058d7))
* **api:** update via SDK Studio ([#7](https://github.com/langchain-ai/langsmith-java/issues/7)) ([42ad6b5](https://github.com/langchain-ai/langsmith-java/commit/42ad6b588e1fa6c628087a9efbe0671c715ce335))
* **api:** update via SDK Studio ([#8](https://github.com/langchain-ai/langsmith-java/issues/8)) ([1bfd5c1](https://github.com/langchain-ai/langsmith-java/commit/1bfd5c116543972511d514694a2b2b252e384739))
* **api:** update via SDK Studio ([#9](https://github.com/langchain-ai/langsmith-java/issues/9)) ([96d933d](https://github.com/langchain-ai/langsmith-java/commit/96d933d654b0b08bd091a5581b6b35dfa80fdc58))
* do not inline example ([fa22fd7](https://github.com/langchain-ai/langsmith-java/commit/fa22fd702baaaaa6c1062c73571360210a63de5c))
* fix link ([c6b8b73](https://github.com/langchain-ai/langsmith-java/commit/c6b8b737b6214b717f747002f4f5f314ebce7fbb))
* Restore custom updates ([503e992](https://github.com/langchain-ai/langsmith-java/commit/503e9927dc4d1bdabd9e0f48914d73c571719cd3))
* update builder patterns, use end options ([33019db](https://github.com/langchain-ai/langsmith-java/commit/33019db58bbbb63590dacbecd60548ea3f76ebd3))
* update README ([aa6b46d](https://github.com/langchain-ai/langsmith-java/commit/aa6b46d44f8f7b15d342d7bb4defc566fd6f5369))
* update via SDK Studio ([f66201a](https://github.com/langchain-ai/langsmith-java/commit/f66201ace3eadfb8f32d2e12eea8d83282713bbe))
* update via SDK Studio ([89fada3](https://github.com/langchain-ai/langsmith-java/commit/89fada3e45d37a8e81af6c2a5ef840bb7e67dead))
* update via SDK Studio ([df821a5](https://github.com/langchain-ai/langsmith-java/commit/df821a56cce19ffb53067585f482ccfb7ba8d437))
* update via SDK Studio ([ba80991](https://github.com/langchain-ai/langsmith-java/commit/ba809915e0ef257e4c06cdd81444cb5c93923cb5))
* update via SDK Studio ([146e79a](https://github.com/langchain-ai/langsmith-java/commit/146e79a492f1c6cd07fea0a4ef4ee9332384d8ad))
* update via SDK Studio ([0b6cc83](https://github.com/langchain-ai/langsmith-java/commit/0b6cc8326488e43db529266b8fa5183f4333e9fb))
* update via SDK Studio ([4f2d659](https://github.com/langchain-ai/langsmith-java/commit/4f2d659cf1a73ecc2539467fce4bd00930e59320))
* update via SDK Studio ([db11c5d](https://github.com/langchain-ai/langsmith-java/commit/db11c5dd9941ae2e1ff5e3620b325f5d9d18c1df))
* update via SDK Studio ([77b0683](https://github.com/langchain-ai/langsmith-java/commit/77b0683841856eddaeb433c948481809a60a1ea0))
* update via SDK Studio ([c447f29](https://github.com/langchain-ai/langsmith-java/commit/c447f2976db0d5deccce82a1fcccc91cc5072397))
* update via SDK Studio ([3e3cf2a](https://github.com/langchain-ai/langsmith-java/commit/3e3cf2aa5a0b014951c1c3c27da59f5e3414aa11))
* update via SDK Studio ([9ae290c](https://github.com/langchain-ai/langsmith-java/commit/9ae290cfef922c77d75a60aecd7270dee313aeea))
* update via SDK Studio ([ea990f2](https://github.com/langchain-ai/langsmith-java/commit/ea990f2c94faedb8241161907f6736b7eb96f553))
* update via SDK Studio ([5809aaf](https://github.com/langchain-ai/langsmith-java/commit/5809aaf7b39fd3fe6ccecb5ad21c38f89c2eed96))
* updates ([eb0044a](https://github.com/langchain-ai/langsmith-java/commit/eb0044a2a4b0d7f90e9842eb7fa53503d7c85232))
* Use new sonatype publishing plugin ([d3f8fb2](https://github.com/langchain-ai/langsmith-java/commit/d3f8fb2c0931c78f9a830b3fd4d0a398e5c481f1))
### Bug Fixes
* remove outdated directories ([e2ac9ba](https://github.com/langchain-ai/langsmith-java/commit/e2ac9ba0531d06420d3517e28aca84975446b222))
* rename example directory directory ([6225cca](https://github.com/langchain-ai/langsmith-java/commit/6225ccac5a6fac48c2fc4bb637a76b01579e4a6b))
### Chores
* **docs:** update readme headline ([7cc251a](https://github.com/langchain-ai/langsmith-java/commit/7cc251affecbc6c316d6526ccfc95bb6288a2c8e))
* go live ([#1](https://github.com/langchain-ai/langsmith-java/issues/1)) ([2c86176](https://github.com/langchain-ai/langsmith-java/commit/2c86176ee0910ea2deabf3f55ef1f5e2dd46b522))
* rm -rf all to reset publishing from stainless ([aa5e0f7](https://github.com/langchain-ai/langsmith-java/commit/aa5e0f7de3196ef63fd3a942b5d8dcfafb432d53))
* sync repo ([9e876f6](https://github.com/langchain-ai/langsmith-java/commit/9e876f623d7e925554baac296b27b0db7eb29d29))
* update SDK settings ([61bfe11](https://github.com/langchain-ai/langsmith-java/commit/61bfe11f7c82eb3bc848e218cb81b435f0455935))
* use langsmith-java as Maven artifact id ([22881d9](https://github.com/langchain-ai/langsmith-java/commit/22881d97b2e4944818e02d62c0a35f0874ab4131))
+122
View File
@@ -0,0 +1,122 @@
# Contributing
Thanks for your interest in contributing to the LangSmith Java SDK!
## Overview
This SDK is **auto-generated by [Stainless](https://www.stainless.com/)** from our OpenAPI spec. Custom code (prompts, OTel wrappers, etc.) lives alongside generated code — Stainless is configured to [preserve custom code](https://www.stainless.com/docs/sdks/configure/custom-code/) and never overwrite it.
> **Important:** Most files in the SDK are generated. If you need to change generated code, talk to @edongashi — there may be weird errors in the Stainless config, but they're generally fixable.
## Branching and workflow
| Branch | Purpose |
| ----------------------------------------------- | ----------------------------------------------------- |
| `next` | **Development branch.** All PRs should target `next`. |
| `main` | Release branch. Only updated via release-please. |
| `release-please--branches--main--changes--next` | Auto-generated release PR. Merges `next``main`. |
**Always work off `next`.** Do not merge directly to `main` — Dependabot or other bots occasionally violate this, which can cause headaches.
### Release process
1. Merge your PR into `next`.
2. A release-please PR is automatically created/updated (`release-please--branches--main--changes--next`).
3. Verify your change appears in the release PR diff and description.
4. Approve the release PR — it auto-merges on approval.
5. A GitHub Action publishes to Maven Central via `./gradlew publishAndReleaseToMavenCentral`.
6. Maven Central can be slow to index — verify on [repo1.maven.org](https://repo1.maven.org/maven2/com/langchain/smith/langsmith-java/) rather than [mvnrepository.com](https://mvnrepository.com/artifact/com.langchain.smith/langsmith-java), which lags behind.
> **Note:** If lint fixes or other changes are pushed to the release-please branch after a version bump, the publish action can fail. Always ensure CI is green before approving.
## Development setup
### Prerequisites
- Java 8+ (tested against 8 and 21)
- Gradle (uses the included `gradlew` wrapper)
### Building
```bash
./scripts/build
```
### Formatting
The project uses [ktfmt](https://github.com/facebook/ktfmt) (`--kotlinlang-style`) for Kotlin and [Palantir Java Format](https://github.com/palantir/palantir-java-format) for Java.
```bash
# Format all files
./scripts/format
# Format only Kotlin
./gradlew formatKotlin
# Format a single module
./gradlew :langsmith-java-core:formatKotlin
```
### Linting
```bash
# Lint all files
./scripts/lint
# Lint only Kotlin
./gradlew lintKotlin
```
CI blocks on lint failures. Always run `./scripts/format` before pushing.
### Testing
```bash
# Run all tests
./scripts/test
# Run specific tests
./gradlew :langsmith-java-core:test --tests "com.langchain.smith.prompts.*"
# Force re-run (skip Gradle cache)
./gradlew :langsmith-java-core:test --tests "..." --rerun
# See stdout output
./gradlew :langsmith-java-core:test --tests "..." --rerun --info
```
#### Integration tests
Integration tests require API keys and skip gracefully via `assumeTrue` if they're missing:
```bash
export LANGSMITH_API_KEY="lsv2_pt_..."
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
```
## CI
CI runs three jobs on every push and PR:
1. **lint**`./scripts/lint`
2. **build**`./scripts/build`
3. **test**`./scripts/test`
All three must pass before merging.
## Code style
See [AGENTS.md](AGENTS.md) for detailed Kotlin conventions, including:
- Prefer `buildMap`/`buildList` over mutable collections
- Use `partition` instead of double `filter`
- Use parameterized tests for table-driven cases
- Avoid `@Suppress("UNCHECKED_CAST")` — restructure with safe casts
- `toString()` should follow the `ClassName{field=value}` convention
## Dependencies
- **Anthropic SDK** is a `compileOnly` dependency — users must add it themselves. Methods using Anthropic types should catch `NoClassDefFoundError` and throw `IllegalStateException` with a clear message.
- **SLF4J** (`slf4j-api`) is an `api` dependency for logging. Tests use `slf4j-simple`.
- **Jackson** 2.22.0 is the default, but the SDK is compatible with 2.13.4+.
+4 -198
View File
@@ -1,201 +1,7 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright 2026 langChain
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
1. Definitions.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2024 Lang Smith
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+755 -121
View File
@@ -1,188 +1,507 @@
# Lang Smith Java API Library
# LangSmith Java SDK
<!-- x-release-please-start-version -->
[![Maven Central](https://img.shields.io/maven-central/v/com.langsmith.api/langsmith-java)](https://central.sonatype.com/artifact/com.langsmith.api/langsmith-java/0.0.1-alpha.0)
[![Maven Central](https://img.shields.io/maven-central/v/com.langchain.smith/langsmith-java)](https://central.sonatype.com/artifact/com.langchain.smith/langsmith-java/0.1.0-beta.19)
[![javadoc](https://javadoc.io/badge2/com.langchain.smith/langsmith-java/0.1.0-beta.19/javadoc.svg)](https://javadoc.io/doc/com.langchain.smith/langsmith-java/0.1.0-beta.18)
<!-- x-release-please-end -->
The Lang Smith Java SDK provides convenient access to the Lang Smith REST API from applications written in Java. It includes helper classes with helpful types and documentation for every request and response property.
The [LangSmith](https://www.langchain.com/langsmith/observability) Java SDK provides convenient access to the LangSmith [REST API](https://api.smith.langchain.com/docs) from applications written in Java.
The Lang Smith Java SDK is similar to the Lang Smith Kotlin SDK but with minor differences that make it more ergonomic for use in Java, such as `Optional` instead of nullable values, `Stream` instead of `Sequence`, and `CompletableFuture` instead of suspend functions.
## Documentation
The REST API documentation can be found [on docs.langsmith.com](https://docs.LangSmith.com).
---
## Getting started
### Install dependencies
#### Gradle
To learn more about LangSmith, check out the [docs](https://docs.smith.langchain.com/).
<!-- 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-beta.19).
<!-- x-release-please-end -->
## Installation
<!-- x-release-please-start-version -->
### Gradle
```kotlin
implementation("com.langsmith.api:langsmith-java:0.0.1-alpha.0")
implementation("com.langchain.smith:langsmith-java:0.1.0-beta.19")
```
#### Maven
### Maven
```xml
<dependency>
<groupId>com.langsmith.api</groupId>
<artifactId>langsmith-java</artifactId>
<version>0.0.1-alpha.0</version>
<groupId>com.langchain.smith</groupId>
<artifactId>langsmith-java</artifactId>
<version>0.1.0-beta.19</version>
</dependency>
```
<!-- x-release-please-end -->
### Configure the client
## Requirements
Use `LangSmithOkHttpClient.builder()` to configure the client. At a minimum you need to set `.apiKey()`:
This library requires Java 8 or later.
## Usage
```java
import com.langsmith.api.client.LangSmithClient;
import com.langsmith.api.client.okhttp.LangSmithOkHttpClient;
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
import com.langchain.smith.models.runs.RunQueryV2Page;
import com.langchain.smith.models.runs.RunQueryV2Params;
LangSmithClient client = LangSmithOkHttpClient.builder()
// 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();
RunQueryV2Params params = RunQueryV2Params.builder()
.addProjectId("00000000-0000-0000-0000-000000000000")
.build();
RunQueryV2Page page = client.runs().queryV2(params);
```
## Examples
This repository includes runnable examples in the `langsmith-java-example` module to help you get started.
### Running Examples
Examples can be run using Gradle:
```bash
# Set required environment variables
export LANGSMITH_API_KEY="your-api-key"
export LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
# Run a specific example
./gradlew :langsmith-java-example:run -Pexample=ListRuns
```
### Available Examples
```bash
export LANGSMITH_PROJECT_ID="your-project-id"
./gradlew :langsmith-java-example:run -Pexample=ListRuns
```
All examples are available in [`langsmith-java-example`](langsmith-java-example).
## Client configuration
Configure the client using system properties or environment variables:
```java
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
// 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();
```
Or manually:
```java
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
LangsmithClient client = LangsmithOkHttpClient.builder()
.apiKey("My API Key")
.tenantId("My Tenant ID")
.build();
```
Or using a combination of the two approaches:
```java
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
LangsmithClient client = LangsmithOkHttpClient.builder()
// 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();
```
Alternately, set the environment with `LANGCHAIN_API_KEY`, and use `LangSmithOkHttpClient.fromEnv()` to read from the environment.
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 | - |
| `baseUrl` | `langchain.baseUrl` | `LANGSMITH_ENDPOINT` | true | `"https://api.smith.langchain.com/"` |
System properties take precedence over environment variables.
> [!TIP]
> Don't create more than one client in the same application. Each client has a connection pool and
> thread pools, which are more efficient to share between requests.
### Modifying configuration
To temporarily use a modified client configuration, while reusing the same connection and thread pools, call `withOptions()` on any client or service:
```java
LangSmithClient client = LangSmithOkHttpClient.fromEnv();
import com.langchain.smith.client.LangsmithClient;
// Note: you can also call fromEnv() from the client builder, for example if you need to set additional properties
LangSmithClient client = LangSmithOkHttpClient.builder()
.fromEnv()
// ... set properties on the builder
LangsmithClient clientWithOptions = client.withOptions(optionsBuilder -> {
optionsBuilder.baseUrl("https://example.com");
optionsBuilder.maxRetries(42);
});
```
The `withOptions()` method does not affect the original client or service.
## Requests and responses
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.runs().queryV2(...)` should be called with an instance of `RunQueryV2Params`, and it will return an instance of `RunQueryV2Page`.
## Immutability
Each class in the SDK has an associated [builder](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java) or factory method for constructing it.
Each class is [immutable](https://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html) once constructed. If the class has an associated builder, then it has a `toBuilder()` method, which can be used to convert it back to a builder for making a modified copy.
Because each class is immutable, builder modification will _never_ affect already built class instances.
## Asynchronous execution
The default client is synchronous. To switch to asynchronous execution, call the `async()` method:
```java
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
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` and `langchain.baseUrl` system properties
// Or configures using the `LANGSMITH_API_KEY`, `LANGSMITH_TENANT_ID` and `LANGSMITH_ENDPOINT` environment variables
LangsmithClient client = LangsmithOkHttpClient.fromEnv();
RunQueryV2Params params = RunQueryV2Params.builder()
.addProjectId("00000000-0000-0000-0000-000000000000")
.build();
CompletableFuture<RunQueryV2PageAsync> page = client.async().runs().queryV2(params);
```
| Property | Environment variable | Required | Default value |
| -------- | -------------------- | -------- | ------------- |
| apiKey | `LANGCHAIN_API_KEY` | true | — |
Read the documentation for more configuration options.
---
### Example: creating a resource
To create a new run, first use the `RunBatchParams` builder to specify attributes,
then pass that to the `batch` method of the `runs` service.
Or create an asynchronous client from the beginning:
```java
import com.langsmith.api.models.RunBatchParams;
import com.langsmith.api.models.RunBatchResponse;
import com.langchain.smith.client.LangsmithClientAsync;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClientAsync;
import com.langchain.smith.models.runs.RunQueryV2PageAsync;
import com.langchain.smith.models.runs.RunQueryV2Params;
import java.util.concurrent.CompletableFuture;
RunBatchParams params = RunBatchParams.builder().build();
RunBatchResponse runBatchResponse = client.runs().batch(params);
```
// 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();
---
## Requests
### Parameters and bodies
To make a request to the Lang Smith API, you generally build an instance of the appropriate `Params` class.
In [Example: creating a resource](#example-creating-a-resource) above, we used the `SessionCreateParams.builder()` to pass to
the `create` method of the `sessions` service.
Sometimes, the API may support other properties that are not yet supported in the Java SDK types. In that case,
you can attach them using the `putAdditionalProperty` method.
```java
SessionCreateParams params = SessionCreateParams.builder()
// ... normal properties
.putAdditionalProperty("secret_param", "4242")
RunQueryV2Params params = RunQueryV2Params.builder()
.addProjectId("00000000-0000-0000-0000-000000000000")
.build();
CompletableFuture<RunQueryV2PageAsync> page = client.runs().queryV2(params);
```
## Responses
The asynchronous client supports the same options as the synchronous one, except most methods return `CompletableFuture`s.
### Response validation
## File uploads
When receiving a response, the Lang Smith Java SDK will deserialize it into instances of the typed model classes. In rare cases, the API may return a response property that doesn't match the expected Java type. If you directly access the mistaken property, the SDK will throw an unchecked `LangSmithInvalidDataException` at runtime. If you would prefer to check in advance that that response is completely well-typed, call `.validate()` on the returned model.
The SDK defines methods that accept files.
To upload a file, pass a [`Path`](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html):
```java
TracerSessionWithoutVirtualFields tracerSessionWithoutVirtualFields = client.sessions().create().validate();
import com.langchain.smith.models.examples.Example;
import com.langchain.smith.models.examples.ExampleUploadFromCsvParams;
import java.nio.file.Paths;
ExampleUploadFromCsvParams params = ExampleUploadFromCsvParams.builder()
.datasetId("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")
.addInputKey("string")
.file(Paths.get("/path/to/file"))
.build();
List<Example> examples = client.examples().uploadFromCsv(params);
```
### Response properties as JSON
In rare cases, you may want to access the underlying JSON value for a response property rather than using the typed version provided by
this SDK. Each model property has a corresponding JSON version, with an underscore before the method name, which returns a `JsonField` value.
Or an arbitrary [`InputStream`](https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html):
```java
JsonField field = responseObj._field();
import com.langchain.smith.models.examples.Example;
import com.langchain.smith.models.examples.ExampleUploadFromCsvParams;
import java.net.URL;
if (field.isMissing()) {
// Value was not specified in the JSON response
} else if (field.isNull()) {
// Value was provided as a literal null
} else {
// See if value was provided as a string
Optional<String> jsonString = field.asString();
// If the value given by the API did not match the shape that the SDK expects
// you can deserialise into a custom type
MyClass myObj = responseObj._field().asUnknown().orElseThrow().convert(MyClass.class);
}
ExampleUploadFromCsvParams params = ExampleUploadFromCsvParams.builder()
.datasetId("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")
.addInputKey("string")
.file(new URL("https://example.com//path/to/file").openStream())
.build();
List<Example> examples = client.examples().uploadFromCsv(params);
```
### Additional model properties
Sometimes, the server response may include additional properties that are not yet available in this library's types. You can access them using the model's `_additionalProperties` method:
Or a `byte[]` array:
```java
JsonValue secret = tracerSessionWithoutVirtualFields._additionalProperties().get("secret_field");
import com.langchain.smith.models.examples.Example;
import com.langchain.smith.models.examples.ExampleUploadFromCsvParams;
ExampleUploadFromCsvParams params = ExampleUploadFromCsvParams.builder()
.datasetId("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")
.addInputKey("string")
.file("content".getBytes())
.build();
List<Example> examples = client.examples().uploadFromCsv(params);
```
---
Note that when passing a non-`Path` its filename is unknown so it will not be included in the request. To manually set a filename, pass a [`MultipartField`](langsmith-java-core/src/main/kotlin/com/langchain/smith/core/Values.kt):
---
```java
import com.langchain.smith.core.MultipartField;
import com.langchain.smith.models.examples.Example;
import com.langchain.smith.models.examples.ExampleUploadFromCsvParams;
import java.io.InputStream;
import java.net.URL;
ExampleUploadFromCsvParams params = ExampleUploadFromCsvParams.builder()
.datasetId("182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e")
.addInputKey("string")
.file(MultipartField.<InputStream>builder()
.value(new URL("https://example.com//path/to/file").openStream())
.filename("/path/to/file")
.build())
.build();
List<Example> examples = client.examples().uploadFromCsv(params);
```
## Raw responses
The SDK defines methods that deserialize responses into instances of Java classes. However, these methods don't provide access to the response headers, status code, or the raw response body.
To access this data, prefix any HTTP method call on a client or service with `withRawResponse()`:
```java
import com.langchain.smith.core.http.Headers;
import com.langchain.smith.core.http.HttpResponseFor;
import com.langchain.smith.models.runs.RunQueryV2Page;
import com.langchain.smith.models.runs.RunQueryV2Params;
RunQueryV2Params params = RunQueryV2Params.builder()
.addProjectId("00000000-0000-0000-0000-000000000000")
.build();
HttpResponseFor<RunQueryV2Page> page = client.runs().withRawResponse().queryV2(params);
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.runs.RunQueryV2Page;
RunQueryV2Page parsedPage = page.parse();
```
## Error handling
This library throws exceptions in a single hierarchy for easy handling:
The SDK throws custom unchecked exception types:
- **`LangSmithException`** - Base exception for all exceptions
- [`LangChainServiceException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/LangChainServiceException.kt): Base class for HTTP errors. See this table for which exception subclass is thrown for each HTTP status code:
- **`LangSmithServiceException`** - HTTP errors with a well-formed response body we were able to parse. The exception message and the `.debuggingRequestId()` will be set by the server.
| Status | Exception |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| 400 | [`BadRequestException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/BadRequestException.kt) |
| 401 | [`UnauthorizedException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/UnauthorizedException.kt) |
| 403 | [`PermissionDeniedException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/PermissionDeniedException.kt) |
| 404 | [`NotFoundException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/NotFoundException.kt) |
| 422 | [`UnprocessableEntityException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/UnprocessableEntityException.kt) |
| 429 | [`RateLimitException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/RateLimitException.kt) |
| 5xx | [`InternalServerException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/InternalServerException.kt) |
| others | [`UnexpectedStatusCodeException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/UnexpectedStatusCodeException.kt) |
| 400 | BadRequestException |
| ------ | ----------------------------- |
| 401 | AuthenticationException |
| 403 | PermissionDeniedException |
| 404 | NotFoundException |
| 422 | UnprocessableEntityException |
| 429 | RateLimitException |
| 5xx | InternalServerException |
| others | UnexpectedStatusCodeException |
- [`LangChainIoException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/LangChainIoException.kt): I/O networking errors.
- **`LangSmithIoException`** - I/O networking errors
- **`LangSmithInvalidDataException`** - any other exceptions on the client side, e.g.:
- We failed to serialize the request body
- We failed to parse the response body (has access to response code and body)
- [`LangChainRetryableException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/LangChainRetryableException.kt): Generic error indicating a failure that could be retried by the client.
- [`LangChainInvalidDataException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/LangChainInvalidDataException.kt): Failure to interpret successfully parsed data. For example, when accessing a property that's supposed to be required, but the API unexpectedly omitted it from the response.
- [`LangChainException`](langsmith-java-core/src/main/kotlin/com/langchain/smith/errors/LangChainException.kt): Base class for all exceptions. Most errors will result in one of the previously mentioned ones, but completely generic errors may be thrown using the base class.
## Pagination
The SDK defines methods that return a paginated lists of results. It provides convenient ways to access the results either one page at a time or item-by-item across all pages.
### Auto-pagination
To iterate through all results across all pages, use the `autoPager()` method, which automatically fetches more pages as needed.
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.runs.Run;
import com.langchain.smith.models.runs.RunQueryV2Page;
RunQueryV2Page page = client.runs().queryV2();
// Process as an Iterable
for (Run run : page.autoPager()) {
System.out.println(run);
}
// Process as a Stream
page.autoPager()
.stream()
.limit(50)
.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.runs.Run;
import com.langchain.smith.models.runs.RunQueryV2PageAsync;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
CompletableFuture<RunQueryV2PageAsync> pageFuture = client.async().runs().queryV2();
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(Run run) {
System.out.println(run);
}
@Override
public void onComplete(Optional<Throwable> error) {
if (error.isPresent()) {
System.out.println("Something went wrong!");
throw new RuntimeException(error.get());
} else {
System.out.println("No more!");
}
}
}));
// Or use futures
pageFuture.thenRun(page -> page.autoPager()
.subscribe(run -> {
System.out.println(run);
})
.onCompleteFuture()
.whenComplete((unused, error) -> {
if (error != null) {
System.out.println("Something went wrong!");
throw new RuntimeException(error);
} else {
System.out.println("No more!");
}
}));
```
### Manual pagination
To access individual page items and manually request the next page, use the `items()`,
`hasNextPage()`, and `nextPage()` methods:
```java
import com.langchain.smith.models.runs.Run;
import com.langchain.smith.models.runs.RunQueryV2Page;
RunQueryV2Page page = client.runs().queryV2();
while (true) {
for (Run run : page.items()) {
System.out.println(run);
}
if (!page.hasNextPage()) {
break;
}
page = page.nextPage();
}
```
## Logging
Enable logging by setting the `LANGCHAIN_LOG` environment variable to `info`:
```sh
export LANGCHAIN_LOG=info
```
Or to `debug` for more verbose logging:
```sh
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).
ProGuard and R8 should automatically detect and use the published rules, but you can also manually copy the keep rules if necessary.
## 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.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).
If the SDK threw an exception, but you're _certain_ the version is compatible, then disable the version check using the `checkJacksonVersionCompatibility` on [`LangsmithOkHttpClient`](langsmith-java-client-okhttp/src/main/kotlin/com/langchain/smith/client/okhttp/LangsmithOkHttpClient.kt) or [`LangsmithOkHttpClientAsync`](langsmith-java-client-okhttp/src/main/kotlin/com/langchain/smith/client/okhttp/LangsmithOkHttpClientAsync.kt).
> [!CAUTION]
> We make no guarantee that the SDK works correctly when the Jackson version check is disabled.
Also note that there are bugs in older Jackson versions that can affect the SDK. We don't work around all Jackson bugs ([example](https://github.com/FasterXML/jackson-databind/issues/3240)) and expect users to upgrade Jackson for those instead.
## Network options
### Retries
Requests that experience certain errors are automatically retried 2 times by default, with a short exponential backoff. Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors will all be retried by default.
You can provide a `maxRetries` on the client builder to configure this:
The SDK automatically retries 2 times by default, with a short exponential backoff between requests.
Only the following error types are retried:
- Connection errors (for example, due to a network connectivity problem)
- 408 Request Timeout
- 409 Conflict
- 429 Rate Limit
- 5xx Internal
The API may also explicitly instruct the SDK to retry or not retry a request.
To set a custom number of retries, configure the client using the `maxRetries` method:
```java
LangSmithClient client = LangSmithOkHttpClient.builder()
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
LangsmithClient client = LangsmithOkHttpClient.builder()
.fromEnv()
.maxRetries(4)
.build();
@@ -190,10 +509,24 @@ LangSmithClient client = LangSmithOkHttpClient.builder()
### Timeouts
Requests time out after 1 minute by default. You can configure this on the client builder:
Requests time out after 90 seconds by default.
To set a custom timeout, configure the method call using the `timeout` method:
```java
LangSmithClient client = LangSmithOkHttpClient.builder()
import com.langchain.smith.models.runs.RunQueryV2Page;
RunQueryV2Page page = client.runs().queryV2(RequestOptions.builder().timeout(Duration.ofSeconds(30)).build());
```
Or configure the default for all method calls at the client level:
```java
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
import java.time.Duration;
LangsmithClient client = LangsmithOkHttpClient.builder()
.fromEnv()
.timeout(Duration.ofSeconds(30))
.build();
@@ -201,23 +534,324 @@ LangSmithClient client = LangSmithOkHttpClient.builder()
### Proxies
Requests can be routed through a proxy. You can configure this on the client builder:
To route requests through a proxy, configure the client using the `proxy` method:
```java
LangSmithClient client = LangSmithOkHttpClient.builder()
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
import java.net.InetSocketAddress;
import java.net.Proxy;
LangsmithClient client = LangsmithOkHttpClient.builder()
.fromEnv()
.proxy(new Proxy(
Type.HTTP,
new InetSocketAddress("proxy.com", 8080)
Proxy.Type.HTTP, new InetSocketAddress(
"https://example.com", 8080
)
))
.build();
```
## Semantic Versioning
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:
```java
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
import java.time.Duration;
LangsmithClient client = LangsmithOkHttpClient.builder()
.fromEnv()
// If `maxIdleConnections` is set, then `keepAliveDuration` must be set, and vice versa.
.maxIdleConnections(10)
.keepAliveDuration(Duration.ofMinutes(2))
.build();
```
If both options are unset, OkHttp's default connection pool settings are used.
### HTTPS
> [!NOTE]
> Most applications should not call these methods, and instead use the system defaults. The defaults include
> special optimizations that can be lost if the implementations are modified.
To configure how HTTPS connections are secured, configure the client using the `sslSocketFactory`, `trustManager`, and `hostnameVerifier` methods:
```java
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
LangsmithClient client = LangsmithOkHttpClient.builder()
.fromEnv()
// If `sslSocketFactory` is set, then `trustManager` must be set, and vice versa.
.sslSocketFactory(yourSSLSocketFactory)
.trustManager(yourTrustManager)
.hostnameVerifier(yourHostnameVerifier)
.build();
```
### Custom HTTP client
The SDK consists of three artifacts:
- `langsmith-java-core`
- Contains core SDK logic
- Does not depend on [OkHttp](https://square.github.io/okhttp)
- Exposes [`LangsmithClient`](langsmith-java-core/src/main/kotlin/com/langchain/smith/client/LangsmithClient.kt), [`LangsmithClientAsync`](langsmith-java-core/src/main/kotlin/com/langchain/smith/client/LangsmithClientAsync.kt), [`LangsmithClientImpl`](langsmith-java-core/src/main/kotlin/com/langchain/smith/client/LangsmithClientImpl.kt), and [`LangsmithClientAsyncImpl`](langsmith-java-core/src/main/kotlin/com/langchain/smith/client/LangsmithClientAsyncImpl.kt), all of which can work with any HTTP client
- `langsmith-java-client-okhttp`
- Depends on [OkHttp](https://square.github.io/okhttp)
- Exposes [`LangsmithOkHttpClient`](langsmith-java-client-okhttp/src/main/kotlin/com/langchain/smith/client/okhttp/LangsmithOkHttpClient.kt) and [`LangsmithOkHttpClientAsync`](langsmith-java-client-okhttp/src/main/kotlin/com/langchain/smith/client/okhttp/LangsmithOkHttpClientAsync.kt), which provide a way to construct [`LangsmithClientImpl`](langsmith-java-core/src/main/kotlin/com/langchain/smith/client/LangsmithClientImpl.kt) and [`LangsmithClientAsyncImpl`](langsmith-java-core/src/main/kotlin/com/langchain/smith/client/LangsmithClientAsyncImpl.kt), respectively, using OkHttp
- `langsmith-java`
- Depends on and exposes the APIs of both `langsmith-java-core` and `langsmith-java-client-okhttp`
- Does not have its own logic
This structure allows replacing the SDK's default HTTP client without pulling in unnecessary dependencies.
#### Customized [`OkHttpClient`](https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.html)
> [!TIP]
> Try the available [network options](#network-options) before replacing the default client.
To use a customized `OkHttpClient`:
1. Replace your [`langsmith-java` dependency](#installation) with `langsmith-java-core`
2. Copy `langsmith-java-client-okhttp`'s [`OkHttpClient`](langsmith-java-client-okhttp/src/main/kotlin/com/langchain/smith/client/okhttp/OkHttpClient.kt) class into your code and customize it
3. Construct [`LangsmithClientImpl`](langsmith-java-core/src/main/kotlin/com/langchain/smith/client/LangsmithClientImpl.kt) or [`LangsmithClientAsyncImpl`](langsmith-java-core/src/main/kotlin/com/langchain/smith/client/LangsmithClientAsyncImpl.kt), similarly to [`LangsmithOkHttpClient`](langsmith-java-client-okhttp/src/main/kotlin/com/langchain/smith/client/okhttp/LangsmithOkHttpClient.kt) or [`LangsmithOkHttpClientAsync`](langsmith-java-client-okhttp/src/main/kotlin/com/langchain/smith/client/okhttp/LangsmithOkHttpClientAsync.kt), using your customized client
### Completely custom HTTP client
To use a completely custom HTTP client:
1. Replace your [`langsmith-java` dependency](#installation) with `langsmith-java-core`
2. Write a class that implements the [`HttpClient`](langsmith-java-core/src/main/kotlin/com/langchain/smith/core/http/HttpClient.kt) interface
3. Construct [`LangsmithClientImpl`](langsmith-java-core/src/main/kotlin/com/langchain/smith/client/LangsmithClientImpl.kt) or [`LangsmithClientAsyncImpl`](langsmith-java-core/src/main/kotlin/com/langchain/smith/client/LangsmithClientAsyncImpl.kt), similarly to [`LangsmithOkHttpClient`](langsmith-java-client-okhttp/src/main/kotlin/com/langchain/smith/client/okhttp/LangsmithOkHttpClient.kt) or [`LangsmithOkHttpClientAsync`](langsmith-java-client-okhttp/src/main/kotlin/com/langchain/smith/client/okhttp/LangsmithOkHttpClientAsync.kt), using your new client class
## Undocumented API functionality
The SDK is typed for convenient usage of the documented API. However, it also supports working with undocumented or not yet supported parts of the API.
### Parameters
To set undocumented parameters, call the `putAdditionalHeader`, `putAdditionalQueryParam`, or `putAdditionalBodyProperty` methods on any `Params` class:
```java
import com.langchain.smith.core.JsonValue;
import com.langchain.smith.models.runs.RunQueryV2Params;
RunQueryV2Params params = RunQueryV2Params.builder()
.putAdditionalHeader("Secret-Header", "42")
.putAdditionalQueryParam("secret_query_param", "42")
.putAdditionalBodyProperty("secretProperty", JsonValue.from("42"))
.build();
```
These can be accessed on the built object later using the `_additionalHeaders()`, `_additionalQueryParams()`, and `_additionalBodyProperties()` methods.
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.core.JsonValue;
import com.langchain.smith.models.runs.RunQueryV2Params;
RunQueryV2Params params = RunQueryV2Params.builder()
.projectIds(JsonValue.from(42))
.build();
```
The most straightforward way to create a [`JsonValue`](langsmith-java-core/src/main/kotlin/com/langchain/smith/core/Values.kt) is using its `from(...)` method:
```java
import com.langchain.smith.core.JsonValue;
import java.util.List;
import java.util.Map;
// Create primitive JSON values
JsonValue nullValue = JsonValue.from(null);
JsonValue booleanValue = JsonValue.from(true);
JsonValue numberValue = JsonValue.from(42);
JsonValue stringValue = JsonValue.from("Hello World!");
// Create a JSON array value equivalent to `["Hello", "World"]`
JsonValue arrayValue = JsonValue.from(List.of(
"Hello", "World"
));
// Create a JSON object value equivalent to `{ "a": 1, "b": 2 }`
JsonValue objectValue = JsonValue.from(Map.of(
"a", 1,
"b", 2
));
// Create an arbitrarily nested JSON equivalent to:
// {
// "a": [1, 2],
// "b": [3, 4]
// }
JsonValue complexValue = JsonValue.from(Map.of(
"a", List.of(
1, 2
),
"b", List.of(
3, 4
)
));
```
Normally a `Builder` class's `build` method will throw [`IllegalStateException`](https://docs.oracle.com/javase/8/docs/api/java/lang/IllegalStateException.html) if any required parameter or property is unset.
To forcibly omit a required parameter or property, pass [`JsonMissing`](langsmith-java-core/src/main/kotlin/com/langchain/smith/core/Values.kt):
```java
import com.langchain.smith.core.JsonMissing;
import com.langchain.smith.models.runs.RunQueryV2Params;
import com.langchain.smith.models.sessions.SessionRetrieveParams;
RunQueryV2Params params = SessionRetrieveParams.builder()
.sessionId(JsonMissing.of())
.build();
```
### Response properties
To access undocumented response properties, call the `_additionalProperties()` method:
```java
import com.langchain.smith.core.JsonValue;
import java.util.Map;
Map<String, JsonValue> additionalProperties = client.sessions().create(params)._additionalProperties();
JsonValue secretPropertyValue = additionalProperties.get("secretProperty");
String result = secretPropertyValue.accept(new JsonValue.Visitor<>() {
@Override
public String visitNull() {
return "It's null!";
}
@Override
public String visitBoolean(boolean value) {
return "It's a boolean!";
}
@Override
public String visitNumber(Number value) {
return "It's a number!";
}
// Other methods include `visitMissing`, `visitString`, `visitArray`, and `visitObject`
// The default implementation of each unimplemented method delegates to `visitDefault`, which throws by default, but can also be overridden
});
```
To access a property's raw JSON value, which may be undocumented, call its `_` prefixed method:
```java
import com.langchain.smith.core.JsonField;
import java.util.Optional;
JsonField<String> id = client.sessions().create(params)._id();
if (id.isMissing()) {
// The property is absent from the JSON response
} 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 = id.asString();
// Try to deserialize into a custom type
MyClass myObject = id.asUnknown().orElseThrow().convert(MyClass.class);
}
```
### Response validation
In rare cases, the API may return a response that doesn't match the expected type. For example, the SDK may expect a property to contain a `String`, but the API could return something else.
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.
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.TracerSessionWithoutVirtualFields;
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.runs.RunQueryV2Page;
RunQueryV2Page page = client.runs().queryV2(RequestOptions.builder().responseValidation(true).build());
```
Or configure the default for all method calls at the client level:
```java
import com.langchain.smith.client.LangsmithClient;
import com.langchain.smith.client.okhttp.LangsmithOkHttpClient;
LangsmithClient client = LangsmithOkHttpClient.builder()
.fromEnv()
.responseValidation(true)
.build();
```
## FAQ
### Why don't you use plain `enum` classes?
Java `enum` classes are not trivially [forwards compatible](https://www.stainless.com/blog/making-java-enums-forwards-compatible). Using them in the SDK could cause runtime exceptions if the API is updated to respond with a new enum value.
### Why do you represent fields using `JsonField<T>` instead of just plain `T`?
Using `JsonField<T>` enables a few features:
- Allowing usage of [undocumented API functionality](#undocumented-api-functionality)
- Lazily [validating the API response against the expected shape](#response-validation)
- Representing absent vs explicitly null values
### Why don't you use [`data` classes](https://kotlinlang.org/docs/data-classes.html)?
It is not [backwards compatible to add new fields to a data class](https://kotlinlang.org/docs/api-guidelines-backward-compatibility.html#avoid-using-data-classes-in-your-api) and we don't want to introduce a breaking change every time we add a field to a class.
### Why don't you use checked exceptions?
Checked exceptions are widely considered a mistake in the Java programming language. In fact, they were omitted from Kotlin for this reason.
Checked exceptions:
- Are verbose to handle
- Encourage error handling at the wrong level of abstraction, where nothing can be done about the error
- Are tedious to propagate due to the [function coloring problem](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function)
- Don't play well with lambdas (also due to the function coloring problem)
## Semantic versioning
This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
1. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals)_.
1. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
2. Changes that we do not expect to impact the vast majority of users in practice.
We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
+27
View File
@@ -0,0 +1,27 @@
# Security Policy
## Reporting Security Issues
This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
To report a security issue, please contact the Stainless team at security@stainless.com.
## Responsible Disclosure
We appreciate the efforts of security researchers and individuals who help us maintain the security of
SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible
disclosure practices by allowing us a reasonable amount of time to investigate and address the issue
before making any information public.
## Reporting Non-SDK Related Security Issues
If you encounter security issues that are not directly related to SDKs but pertain to the services
or products provided by LangChain, please follow the respective company's security reporting guidelines.
### LangChain Terms and Policies
Please contact [support.langchain.com](http://support.langchain.com/) for any questions or concerns regarding the security of our services.
---
Thank you for helping us keep the SDKs and systems they interact with secure.
+30
View File
@@ -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.
+8 -15
View File
@@ -1,32 +1,25 @@
#!/usr/bin/env bash
warnings=()
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
warnings+=("The LANG_SMITH_SONATYPE_USERNAME secret has not been set. Please set it in either this repository's secrets or your organization secrets")
errors+=("The SONATYPE_USERNAME secret has not been set. Please set it in either this repository's secrets or your organization secrets")
fi
if [ -z "${SONATYPE_PASSWORD}" ]; then
warnings+=("The LANG_SMITH_SONATYPE_PASSWORD secret has not been set. Please set it in either this repository's secrets or your organization secrets")
errors+=("The SONATYPE_PASSWORD secret has not been set. Please set it in either this repository's secrets or your organization secrets")
fi
if [ -z "${GPG_SIGNING_KEY}" ]; then
warnings+=("The LANG_SMITH_SONATYPE_GPG_SIGNING_KEY secret has not been set. Please set it in either this repository's secrets or your organization secrets")
errors+=("The GPG_SIGNING_KEY secret has not been set. Please set it in either this repository's secrets or your organization secrets")
fi
if [ -z "${GPG_SIGNING_PASSWORD}" ]; then
warnings+=("The LANG_SMITH_SONATYPE_GPG_SIGNING_PASSWORD secret has not been set. Please set it in either this repository's secrets or your organization secrets")
fi
lenWarnings=${#warnings[@]}
if [[ lenWarnings -gt 0 ]]; then
echo -e "Found the following warnings in the release environment:\n"
for warning in "${warnings[@]}"; do
echo -e "- $warning\n"
done
errors+=("The GPG_SIGNING_PASSWORD secret has not been set. Please set it in either this repository's secrets or your organization secrets")
fi
lenErrors=${#errors[@]}
+58 -17
View File
@@ -1,20 +1,61 @@
plugins {
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
}
allprojects {
group = "com.langsmith.api"
version = "0.0.1-alpha.0" // x-release-please-version
}
nexusPublishing {
repositories {
sonatype {
nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
username.set(System.getenv("SONATYPE_USERNAME"))
password.set(System.getenv("SONATYPE_PASSWORD"))
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.2.0"
id("org.jetbrains.dokka-javadoc") version "2.2.0"
}
repositories {
mavenCentral()
}
allprojects {
group = "com.langchain.smith"
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 {
// These are populated with dependencies by `buildSrc` scripts.
tasks.register("format") {
group = "Verification"
description = "Formats all source files."
}
tasks.register("lint") {
group = "Verification"
description = "Verifies all source files are formatted."
}
}
subprojects {
apply(plugin = "org.jetbrains.dokka")
apply(plugin = "org.jetbrains.dokka-javadoc")
}
// Avoid race conditions between `dokkaJavadocCollector` and `dokkaJavadocJar` tasks
tasks.named("dokkaJavadocCollector").configure {
subprojects.flatMap { it.tasks }
.filter { it.project.name != "langsmith-java" && it.name == "dokkaJavadocJar" }
.forEach { mustRunAfter(it) }
}
@@ -1,2 +0,0 @@
#Thu Mar 07 21:29:16 EST 2024
gradle.version=8.0.2
Binary file not shown.
Binary file not shown.
+11 -2
View File
@@ -1,12 +1,21 @@
plugins {
`kotlin-dsl`
kotlin("jvm") version "2.3.21"
id("com.vanniktech.maven.publish") version "0.36.0"
}
repositories {
gradlePluginPortal()
mavenCentral()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
dependencies {
implementation("com.diffplug.spotless:spotless-plugin-gradle:6.25.0")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.21")
implementation("com.vanniktech:gradle-maven-publish-plugin:0.36.0")
}
@@ -0,0 +1,133 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
`java-library`
}
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("-Werror")
options.compilerArgs.add("-Xlint:-options") // Suppress obsolete Java 8 warnings
options.release.set(8)
}
tasks.named<Jar>("jar") {
manifest {
attributes(mapOf(
"Implementation-Title" to project.name,
"Implementation-Version" to project.version
))
}
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
// Run tests in parallel to some degree.
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
forkEvery = 100
testLogging {
exceptionFormat = TestExceptionFormat.FULL
}
}
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.93.0")
}
fun registerPalantir(
name: String,
description: String,
) {
val javaName = "${name}Java"
tasks.register<JavaExec>(javaName) {
group = "Verification"
this.description = description
classpath = palantir
mainClass = "com.palantir.javaformat.java.Main"
// Avoid an `IllegalAccessError` on Java 9+.
jvmArgs(
"--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
"--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
"--add-exports", "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
"--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
"--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
)
// Use paths relative to the current module.
val argumentFile =
project.layout.buildDirectory.file("palantir-$name-args.txt").get().asFile
val lastRunTimeFile =
project.layout.buildDirectory.file("palantir-$name-last-run.txt").get().asFile
// Read the time when this task was last executed for this module (if ever).
val lastRunTime = lastRunTimeFile.takeIf { it.exists() }?.readText()?.toLongOrNull() ?: 0L
// Use a `fileTree` relative to the module's source directory.
val javaFiles = project.fileTree("src") { include("**/*.java") }
// Determine if any files need to be formatted or linted and continue only if there is at least
// one file.
onlyIf { javaFiles.any { it.lastModified() > lastRunTime } }
inputs.files(javaFiles)
doFirst {
// Create the argument file and set the preferred formatting style.
argumentFile.parentFile.mkdirs()
argumentFile.writeText("--palantir\n")
if (name == "lint") {
// For lint, do a dry run, so no files are modified. Set the exit code to 1 (instead of
// the default 0) if any files need to be formatted, indicating that linting has failed.
argumentFile.appendText("--dry-run\n")
argumentFile.appendText("--set-exit-if-changed\n")
} else {
// `--dry-run` and `--replace` (for in-place formatting) are mutually exclusive.
argumentFile.appendText("--replace\n")
}
// Write the modified files to the argument file.
javaFiles.filter { it.lastModified() > lastRunTime }
.forEach { argumentFile.appendText("${it.absolutePath}\n") }
}
doLast {
// Record the last execution time for later up-to-date checking.
lastRunTimeFile.writeText(System.currentTimeMillis().toString())
}
// Pass the argument file using the @ symbol
args = listOf("@${argumentFile.absolutePath}")
outputs.upToDateWhen { javaFiles.none { it.lastModified() > lastRunTime } }
}
tasks.named(name) {
dependsOn(tasks.named(javaName))
}
}
registerPalantir(name = "format", description = "Formats all Java source files.")
registerPalantir(name = "lint", description = "Verifies all Java source files are formatted.")
@@ -0,0 +1,112 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins {
id("langchain.java")
kotlin("jvm")
}
repositories {
mavenCentral()
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
compilerOptions {
freeCompilerArgs = listOf(
"-Xjvm-default=all",
"-Xjdk-release=1.8",
// Suppress deprecation warnings because we may still reference and test deprecated members.
// TODO: Replace with `-Xsuppress-warning=DEPRECATION` once we use Kotlin compiler 2.1.0+.
"-nowarn",
)
jvmTarget.set(JvmTarget.JVM_1_8)
languageVersion.set(KotlinVersion.KOTLIN_2_1)
apiVersion.set(KotlinVersion.KOTLIN_2_1)
}
}
tasks.withType<Test>().configureEach {
systemProperty("junit.jupiter.execution.parallel.enabled", true)
systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent")
// Suppress JVM warnings about dynamically loaded agents (e.g. byte-buddy used by Mockito).
// Without this, agent warnings are written to stderr and break tests that capture stderr output.
jvmArgs("-XX:+EnableDynamicAgentLoading")
// `SKIP_MOCK_TESTS` affects which tests run so it must be added as input for proper cache invalidation.
inputs.property("skipMockTests", System.getenv("SKIP_MOCK_TESTS")).optional(true)
}
val ktfmt by configurations.creating
dependencies {
ktfmt("com.facebook:ktfmt:0.61")
}
fun registerKtfmt(
name: String,
description: String,
) {
val kotlinName = "${name}Kotlin"
tasks.register<JavaExec>(kotlinName) {
group = "Verification"
this.description = description
classpath = ktfmt
mainClass = "com.facebook.ktfmt.cli.Main"
// Use paths relative to the current module.
val argumentFile = project.layout.buildDirectory.file("ktfmt-$name-args.txt").get().asFile
val lastRunTimeFile =
project.layout.buildDirectory.file("ktfmt-$name-last-run.txt").get().asFile
// Read the time when this task was last executed for this module (if ever).
val lastRunTime = lastRunTimeFile.takeIf { it.exists() }?.readText()?.toLongOrNull() ?: 0L
// Use a `fileTree` relative to the module's source directory.
val kotlinFiles = project.fileTree("src") { include("**/*.kt") }
// Determine if any files need to be formatted or linted and continue only if there is at least
// one file (otherwise Ktfmt will fail).
onlyIf { kotlinFiles.any { it.lastModified() > lastRunTime } }
inputs.files(kotlinFiles)
doFirst {
// Create the argument file and set the preferred formatting style.
argumentFile.parentFile.mkdirs()
argumentFile.writeText("--kotlinlang-style\n")
if (name == "lint") {
// For lint, do a dry run, so no files are modified. Set the exit code to 1 (instead of
// the default 0) if any files need to be formatted, indicating that linting has failed.
argumentFile.appendText("--dry-run\n")
argumentFile.appendText("--set-exit-if-changed\n")
}
// Write the modified files to the argument file.
kotlinFiles.filter { it.lastModified() > lastRunTime }
.forEach { argumentFile.appendText("${it.absolutePath}\n") }
}
doLast {
// Record the last execution time for later up-to-date checking.
lastRunTimeFile.writeText(System.currentTimeMillis().toString())
}
// Pass the argument file using the @ symbol
args = listOf("@${argumentFile.absolutePath}")
outputs.upToDateWhen { kotlinFiles.none { it.lastModified() > lastRunTime } }
}
tasks.named(name) {
dependsOn(tasks.named(kotlinName))
}
}
registerKtfmt(name = "format", description = "Formats all Kotlin source files.")
registerKtfmt(name = "lint", description = "Verifies all Kotlin source files are formatted.")
@@ -0,0 +1,67 @@
import com.vanniktech.maven.publish.JavadocJar
import com.vanniktech.maven.publish.KotlinJvm
import com.vanniktech.maven.publish.MavenPublishBaseExtension
import com.vanniktech.maven.publish.SourcesJar
plugins {
id("com.vanniktech.maven.publish")
}
publishing {
repositories {
if (project.hasProperty("publishLocal")) {
maven {
name = "LocalFileSystem"
url = uri("${rootProject.layout.buildDirectory.get()}/local-maven-repo")
}
}
}
}
repositories {
gradlePluginPortal()
mavenCentral()
}
configure<MavenPublishBaseExtension> {
if (!project.hasProperty("publishLocal")) {
signAllPublications()
publishToMavenCentral()
}
coordinates(project.group.toString(), project.name, project.version.toString())
configure(
KotlinJvm(
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\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 {
license {
name.set("MIT")
}
}
developers {
developer {
name.set("LangChain")
}
}
scm {
connection.set("scm:git:git://github.com/langchain-ai/langsmith-java.git")
developerConnection.set("scm:git:git://github.com/langchain-ai/langsmith-java.git")
url.set("https://github.com/langchain-ai/langsmith-java")
}
}
}
tasks.withType<Zip>().configureEach {
isZip64 = true
}
@@ -1,57 +0,0 @@
import com.diffplug.gradle.spotless.SpotlessExtension
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import java.util.Locale
plugins {
`java-library`
id("com.diffplug.spotless")
}
repositories {
mavenCentral()
}
configure<JavaPluginExtension> {
withJavadocJar()
withSourcesJar()
}
configure<SpotlessExtension> {
java {
importOrder()
removeUnusedImports()
palantirJavaFormat()
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
tasks.withType<JavaCompile> {
options.compilerArgs.add("-Werror")
options.release.set(8)
}
tasks.named<Jar>("javadocJar") {
setZip64(true)
}
tasks.jar {
manifest {
attributes(mapOf(
"Implementation-Title" to project.name,
"Implementation-Version" to project.version
))
}
}
tasks.test {
useJUnitPlatform()
testLogging {
exceptionFormat = TestExceptionFormat.FULL
}
}
@@ -1,27 +0,0 @@
import com.diffplug.gradle.spotless.SpotlessExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("langsmith.java")
kotlin("jvm")
}
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
configure<SpotlessExtension> {
kotlin {
ktfmt().kotlinlangStyle()
}
}
tasks.withType<KotlinCompile> {
kotlinOptions {
allWarningsAsErrors = true
freeCompilerArgs = listOf("-Xjvm-default=all", "-Xjdk-release=1.8")
jvmTarget = "1.8"
}
}
@@ -1,67 +0,0 @@
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.get
plugins {
`maven-publish`
`signing`
}
configure<PublishingExtension> {
publications {
create<MavenPublication>("maven") {
from(components["java"])
pom {
name.set("LangSmith")
description.set("An SDK library for LangSmith")
url.set("https://docs.LangSmith.com")
licenses {
license {
name.set("Apache-2.0")
}
}
developers {
developer {
name.set("Lang Smith")
email.set("dev-feedback@LangSmith.com")
}
}
scm {
connection.set("scm:git:git://github.com/langchain-ai/langsmith-java.git")
developerConnection.set("scm:git:git://github.com/langchain-ai/langsmith-java.git")
url.set("https://github.com/langchain-ai/langsmith-java")
}
versionMapping {
allVariants {
fromResolutionResult()
}
}
}
}
}
}
signing {
val signingKeyId = System.getenv("GPG_SIGNING_KEY_ID")?.ifBlank { null }
val signingKey = System.getenv("GPG_SIGNING_KEY")?.ifBlank { null }
val signingPassword = System.getenv("GPG_SIGNING_PASSWORD")?.ifBlank { null }
if (signingKey != null && signingPassword != null) {
useInMemoryPgpKeys(
signingKeyId,
signingKey,
signingPassword,
)
sign(publishing.publications["maven"])
}
}
tasks.publish {
dependsOn(":closeAndReleaseSonatypeStagingRepository")
}
-4
View File
@@ -1,4 +0,0 @@
File generated from our OpenAPI spec by Stainless.
This directory can be used to store example files demonstrating usage of this SDK.
It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.
-44
View File
@@ -1,44 +0,0 @@
plugins {
id("org.jetbrains.kotlin.jvm")
}
repositories {
mavenCentral()
}
dependencies {
// Assuming langsmith-java-core and langsmith-java-client-okhttp are needed for the examples
implementation(project(":langsmith-java-core"))
implementation(project(":langsmith-java-client-okhttp"))
implementation(project(":langsmith-java-trace"))
// Add any additional dependencies your examples need
implementation("com.google.guava:guava:33.0-jre")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
implementation("com.theokanning.openai-gpt3-java:service:0.18.2")
// Test dependencies if needed
testImplementation(kotlin("test"))
}
tasks.withType<Jar> {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
// Dynamically create a task for each example
val examples = listOf("RunTreeExample", "OpenAiExample") // Add the names of your example classes here
examples.forEach { example ->
tasks.create("run$example", org.gradle.api.tasks.JavaExec::class.java) {
group = "Examples"
description = "Run $example"
classpath = sourceSets.getByName("main").runtimeClasspath
mainClass.set("com.langsmith.example.$example")
}
}
@@ -1,60 +0,0 @@
package com.langsmith.example;
import com.langsmith.runtree.RunTree;
import com.langsmith.runtree.RunTreeConfigBuilder;
import com.theokanning.openai.service.OpenAiService;
import com.theokanning.openai.completion.chat.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class OpenAiExample {
public static void main(String[] args) {
String apiKey = System.getenv("OPENAI_API_KEY");
OpenAiService service = new OpenAiService(apiKey);
String question = "Can you summarize this morning's meetings?";
// Create a top-level run
RunTreeConfigBuilder pipelineConfigBuilder = new RunTreeConfigBuilder()
.setName("Chat Pipeline")
.setRunType("chain")
.setInputs(Collections.singletonMap("question", question));
RunTree pipeline = new RunTree(pipelineConfigBuilder.build());
var context = "During this morning's meeting, we solved all world conflict.";
List<ChatMessage> messages = new ArrayList<>();
var systemMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), "You are a helpful assistant. Please respond to the user's request only based on the given context.");
messages.add(systemMessage);
var userMessage = new ChatMessage(ChatMessageRole.USER.value(), "Question: " + question + "\nContext: " + context);
messages.add(userMessage);
var completionRequest = ChatCompletionRequest
.builder()
.model("gpt-3.5-turbo")
.messages(messages)
.build();
// Create a child run
RunTreeConfigBuilder childConfigBuilder = new RunTreeConfigBuilder()
.setName("OpenAI Call")
.setRunType("llm")
.setInputs(Collections.singletonMap("messages", messages))
.setParentRun(pipeline);
RunTree childTree = pipeline.createChild(childConfigBuilder.build());
ChatMessage responseMessage = service.createChatCompletion(completionRequest).getChoices().get(0).getMessage();
System.out.println("Response: " + responseMessage.getContent());
// End the child run
childTree.end(Collections.singletonMap("response", responseMessage), null, null);
// End the parent run
pipeline.end(Collections.singletonMap("response", responseMessage.getContent()), null, null);
childTree.postRunAsync().join();
pipeline.postRunAsync().join();
}
}
@@ -1,106 +0,0 @@
package com.langsmith.example;
import com.langsmith.api.client.LangSmithClientAsync;
import com.langsmith.api.client.okhttp.LangSmithOkHttpClientAsync;
import com.langsmith.api.models.DatasetListParams;
import com.langsmith.api.models.ExampleListParams;
import com.langsmith.api.models.Example;
import com.langsmith.api.models.SessionCreateParams;
import com.langsmith.traceable.TraceConfig;
import com.langsmith.traceable.Traceable;
import com.langsmith.traceable.LangSmithExtra;
import com.langsmith.api.core.JsonValue;
import java.util.Map;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Optional;
import java.util.UUID;
public class RunOnDataset {
public static void main(String[] args) {
// The fake inputs
Function<JsonValue, Object> originalChain = (inputMap) -> {
// Perform operations on the input map and generate the output map
Map<String, Object> outputMap = new HashMap<>();
outputMap.put("output", "foo");
return outputMap;
};
var datasetName = "foo";
// *****************/
LangSmithClientAsync client = LangSmithOkHttpClientAsync.fromEnv();
// 1. List the examples
// a. And record the max example time as the version ID
// Just get the examples
var dsListParams = DatasetListParams.builder().name(datasetName).build();
var datasets = client.datasets().list(dsListParams).join();
var dataset = datasets.get(0);
var datasetId = dataset.id();
System.out.println(datasetId);
var exampleParams = ExampleListParams.builder()
.dataset(datasetId)
.build();
List<Example> examples = client.examples().list(exampleParams).join();
var maxModifiedAt = examples.stream()
.map(Example::modifiedAt)
.max(Comparator.comparing(Optional::get));
String datasetVersion = maxModifiedAt.map(date -> date.toString()).orElseGet(() -> "");
System.out.println(datasetVersion);
// 2. Create project
// b. TODO: With project metadata like git version, etc
var projectName = "Project_" + UUID.randomUUID().toString();
var projectParams = SessionCreateParams.builder()
.name(projectName)
.referenceDatasetId(datasetId)
.build();
client.sessions().create(projectParams).join();
// 3. Wrap the chain as a traceable object
TraceConfig config = new TraceConfig();
config.setName("Wrapped");
config.setProjectName(projectName);
var wrapped = Traceable.traceWithConfig(originalChain, config);
var uuid = UUID.randomUUID().toString();
var extras = examples.stream()
.map(example -> new LangSmithExtra(UUID.randomUUID().toString(), example.id()))
.collect(Collectors.toList());
// 4. Run chain over dataset
List<CompletableFuture<Object>> futures = IntStream.range(0, examples.size())
.mapToObj(index -> {
var example = examples.get(index);
var traceableInput = Traceable.withExtra(example._inputs(), extras.get(index));
return CompletableFuture.supplyAsync(() -> wrapped.apply(traceableInput))
.exceptionally(ex -> {
System.err.println("Error processing example: " + example.id());
ex.printStackTrace();
return null;
});
})
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
List<Object> results = futures.stream()
.map(future -> future.join())
.collect(Collectors.toList());
System.out.println("Results: " + results);
// 5. Run evaluators over the outputs
// 6. Assign feedback to the runs
// 7. End the project (update with end time)
// ```
}
}
@@ -1,69 +0,0 @@
package com.langsmith.example;
import com.langsmith.api.client.LangSmithClientAsync;
import com.langsmith.api.client.okhttp.LangSmithOkHttpClientAsync;
import com.langsmith.runtree.RunTree;
import com.langsmith.runtree.RunTreeConfigBuilder;
import java.util.Map;
public class RunTreeExample {
public static void main(String[] args) {
System.out.println("Hello, World!");
LangSmithClientAsync client = LangSmithOkHttpClientAsync.fromEnv();
// Create a RunTreeConfig object
var builder = new RunTreeConfigBuilder();
builder.setName("Test Run");
builder.setRunType("llm");
builder.setInputs(Map.of("input", "Testing!"));
var config = builder.build();
// Create a RunTree object
RunTree runTree = new RunTree(config);
var childBuilder = new RunTreeConfigBuilder();
childBuilder.setName("Test Child Run");
childBuilder.setInputs(Map.of("input", "Test2!"));
var childConfig = childBuilder.build();
// Time it
var startTime = System.currentTimeMillis();
// Using TLS
RunTree childTree = RunTree.fromCurrentSpan(childConfig);
var parentFuture = runTree.postRunAsync();
parentFuture.join();
// var endTime = System.currentTimeMillis();
// var elapsedTime = endTime - startTime;
// System.out.println("Post1 elapsed time: " + elapsedTime + " milliseconds");
// var childFuture = childTree.postRunAsync();
// endTime = System.currentTimeMillis();
// elapsedTime = endTime - startTime;
// System.out.println("Post2 time: " + elapsedTime + " milliseconds");
// var childEndFuture = childTree.endAsync(Map.of("output", "Test Output"), null, null);
// endTime = System.currentTimeMillis();
// elapsedTime = endTime - startTime;
// var endTime = System.currentTimeMillis();
// var elapsedTime = endTime - startTime;
// System.out.println("Post1 elapsed time: " + elapsedTime + " milliseconds");
// var childFuture = childTree.postRunAsync(true);
// endTime = System.currentTimeMillis();
// elapsedTime = endTime - startTime;
// System.out.println("Post2 time: " + elapsedTime + " milliseconds");
// var childEndFuture = childTree.endAsync(Map.of("output", "Test Output"), null, null);
// endTime = System.currentTimeMillis();
// elapsedTime = endTime - startTime;
// System.out.println("End1 elapsed time: " + elapsedTime + " milliseconds");
// var parentEndFuture = runTree.endAsync(Map.of("output", "Test Output Parent"), null, null);
// endTime = System.currentTimeMillis();
// elapsedTime = endTime - startTime;
// System.out.println("End parent elapsed time: " + elapsedTime + " milliseconds");
//
// // Calculate elapsed time
// endTime = System.currentTimeMillis();
// elapsedTime = endTime - startTime;
// System.out.println("Total lapsed time: " + elapsedTime + " milliseconds");
}
}
@@ -1,68 +0,0 @@
package com.langsmith.example;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.langsmith.api.client.LangSmithClient;
import com.langsmith.api.client.okhttp.LangSmithOkHttpClient;
import com.langsmith.api.models.RunCreateParams;
import com.langsmith.api.models.RunUpdateParams;
import com.langsmith.api.core.JsonValue;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
public class TestClient {
private static final ObjectMapper objectMapper = new ObjectMapper();
public static void main(String[] args) {
System.out.println("Hello, World!");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
String startTime = LocalDateTime.now(ZoneOffset.UTC).format(formatter);
String endTime = LocalDateTime.now(ZoneOffset.UTC).plusMinutes(1).format(formatter);
LangSmithClient client = LangSmithOkHttpClient.fromEnv();
// Retrieve works (phew
var runId = UUID.randomUUID().toString();
System.out.println("Run ID: " + runId);
Map<String, Object> additionalProperties = Map.of(
"run_type", "chain",
"name", "Java Test",
"inputs", Map.of(
"input", "Hello, World!"),
"start_time", startTime,
"end_time", endTime,
"id", runId);
Map<String, JsonValue> additionalPropertiesMap = additionalProperties.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> JsonValue.from(objectMapper.valueToTree(entry.getValue()))));
var params = RunCreateParams.builder()
.additionalBodyProperties(additionalPropertiesMap)
.build();
var result = client.runs().create(params);
// Now update
var additionalUpdateProperties = Map.of(
"end_time", endTime,
"outputs", Map.of(
"output", "Been a great day!"));
var runUpdateParams = RunUpdateParams.builder()
.runId(runId)
.additionalBodyProperties(
additionalUpdateProperties.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> JsonValue.from(objectMapper
.valueToTree(entry
.getValue())))))
.build();
var updateResult = client.runs().update(runUpdateParams);
System.out.println("Update Result: " + updateResult.toString());
}
}
+16 -2
View File
@@ -1,4 +1,18 @@
org.gradle.caching=true
org.gradle.jvmargs=-Xmx4g
org.gradle.configuration-cache=true
org.gradle.parallel=true
kotlin.daemon.jvmargs=-Xmx4g
org.gradle.daemon=false
# These options improve our compilation and test performance. They are inherited by the Kotlin daemon.
org.gradle.jvmargs=\
-Xms2g \
-Xmx8g \
-XX:+UseParallelGC \
-XX:InitialCodeCacheSize=256m \
-XX:ReservedCodeCacheSize=1G \
-XX:MetaspaceSize=512m \
-XX:MaxMetaspaceSize=2G \
-XX:TieredStopAtLevel=1 \
-XX:GCTimeRatio=4 \
-XX:CICompilerCount=4 \
-XX:+OptimizeStringConcat \
-XX:+UseStringDeduplication
Binary file not shown.
+5 -1
View File
@@ -1,5 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-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
Vendored
+32 -18
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/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/.
@@ -80,13 +82,11 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -133,22 +132,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -165,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -193,18 +198,27 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
Vendored
+82 -89
View File
@@ -1,89 +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
@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=.
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%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="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!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
: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%
+27 -5
View File
@@ -1,15 +1,37 @@
plugins {
id("langsmith.kotlin")
id("langsmith.publish")
id("langchain.kotlin")
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.google.guava:guava:33.0.0-jre")
implementation("com.squareup.okhttp3:okhttp: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.25.3")
testImplementation("org.slf4j:slf4j-simple:2.0.12")
testImplementation("org.assertj:assertj-core:3.27.7")
testImplementation("org.wiremock:wiremock:4.0.0-beta.37")
testImplementation("org.wiremock:wiremock-junit5:4.0.0-beta.37")
}
@@ -0,0 +1,434 @@
// File generated from our OpenAPI spec by Stainless.
package com.langchain.smith.client.okhttp
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
import java.time.Clock
import java.time.Duration
import java.util.Optional
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
import kotlin.jvm.optionals.getOrNull
/**
* A class that allows building an instance of [LangsmithClient] with [OkHttpClient] as the
* underlying [HttpClient].
*/
class LangsmithOkHttpClient private constructor() {
companion object {
/** Returns a mutable builder for constructing an instance of [LangsmithClient]. */
@JvmStatic fun builder() = Builder()
/**
* Returns a client configured using system properties and environment variables.
*
* @see ClientOptions.Builder.fromEnv
*/
@JvmStatic fun fromEnv(): LangsmithClient = builder().fromEnv().build()
}
/** A builder for [LangsmithOkHttpClient]. */
class Builder internal 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
private var trustManager: X509TrustManager? = null
private var hostnameVerifier: HostnameVerifier? = null
/**
* The executor service to use for running HTTP requests.
*
* Defaults to OkHttp's
* [default executor service](https://github.com/square/okhttp/blob/ace792f443b2ffb17974f5c0d1cecdf589309f26/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dispatcher.kt#L98-L104).
*
* This class takes ownership of the executor service and shuts it down when closed.
*/
fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply {
this.dispatcherExecutorService = dispatcherExecutorService
}
/**
* Alias for calling [Builder.dispatcherExecutorService] with
* `dispatcherExecutorService.orElse(null)`.
*/
fun dispatcherExecutorService(dispatcherExecutorService: Optional<ExecutorService>) =
dispatcherExecutorService(dispatcherExecutorService.getOrNull())
fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
/** 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.
*
* If this is set, then [keepAliveDuration] must also be set.
*
* If unset, then OkHttp's default is used.
*/
fun maxIdleConnections(maxIdleConnections: Int?) = apply {
this.maxIdleConnections = maxIdleConnections
}
/**
* Alias for [Builder.maxIdleConnections].
*
* This unboxed primitive overload exists for backwards compatibility.
*/
fun maxIdleConnections(maxIdleConnections: Int) =
maxIdleConnections(maxIdleConnections as Int?)
/**
* Alias for calling [Builder.maxIdleConnections] with `maxIdleConnections.orElse(null)`.
*/
fun maxIdleConnections(maxIdleConnections: Optional<Int>) =
maxIdleConnections(maxIdleConnections.getOrNull())
/**
* The keep-alive duration for idle connections in the underlying OkHttp connection pool.
*
* If this is set, then [maxIdleConnections] must also be set.
*
* If unset, then OkHttp's default is used.
*/
fun keepAliveDuration(keepAliveDuration: Duration?) = apply {
this.keepAliveDuration = keepAliveDuration
}
/** Alias for calling [Builder.keepAliveDuration] with `keepAliveDuration.orElse(null)`. */
fun keepAliveDuration(keepAliveDuration: Optional<Duration>) =
keepAliveDuration(keepAliveDuration.getOrNull())
/**
* The socket factory used to secure HTTPS connections.
*
* If this is set, then [trustManager] must also be set.
*
* If unset, then the system default is used. Most applications should not call this method,
* and instead use the system default. The default include special optimizations that can be
* lost if the implementation is modified.
*/
fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply {
this.sslSocketFactory = sslSocketFactory
}
/** Alias for calling [Builder.sslSocketFactory] with `sslSocketFactory.orElse(null)`. */
fun sslSocketFactory(sslSocketFactory: Optional<SSLSocketFactory>) =
sslSocketFactory(sslSocketFactory.getOrNull())
/**
* The trust manager used to secure HTTPS connections.
*
* If this is set, then [sslSocketFactory] must also be set.
*
* If unset, then the system default is used. Most applications should not call this method,
* and instead use the system default. The default include special optimizations that can be
* lost if the implementation is modified.
*/
fun trustManager(trustManager: X509TrustManager?) = apply {
this.trustManager = trustManager
}
/** Alias for calling [Builder.trustManager] with `trustManager.orElse(null)`. */
fun trustManager(trustManager: Optional<X509TrustManager>) =
trustManager(trustManager.getOrNull())
/**
* The verifier used to confirm that response certificates apply to requested hostnames for
* HTTPS connections.
*
* If unset, then a default hostname verifier is used.
*/
fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply {
this.hostnameVerifier = hostnameVerifier
}
/** Alias for calling [Builder.hostnameVerifier] with `hostnameVerifier.orElse(null)`. */
fun hostnameVerifier(hostnameVerifier: Optional<HostnameVerifier>) =
hostnameVerifier(hostnameVerifier.getOrNull())
/**
* Whether to throw an exception if any of the Jackson versions detected at runtime are
* incompatible with the SDK's minimum supported Jackson version (2.13.4).
*
* Defaults to true. Use extreme caution when disabling this option. There is no guarantee
* that the SDK will work correctly when using an incompatible Jackson version.
*/
fun checkJacksonVersionCompatibility(checkJacksonVersionCompatibility: Boolean) = apply {
clientOptions.checkJacksonVersionCompatibility(checkJacksonVersionCompatibility)
}
/**
* The Jackson JSON mapper to use for serializing and deserializing JSON.
*
* Defaults to [com.langchain.smith.core.jsonMapper]. The default is usually sufficient and
* rarely needs to be overridden.
*/
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
/**
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
*
* Defaults to a dedicated cached thread pool.
*
* This class takes ownership of the executor and shuts it down, if possible, when closed.
*/
fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
clientOptions.streamHandlerExecutor(streamHandlerExecutor)
}
/**
* The interface to use for delaying execution, like during retries.
*
* This is primarily useful for using fake delays in tests.
*
* Defaults to real execution delays.
*
* This class takes ownership of the sleeper and closes it when closed.
*/
fun sleeper(sleeper: Sleeper) = apply { clientOptions.sleeper(sleeper) }
/**
* The clock to use for operations that require timing, like retries.
*
* This is primarily useful for using a fake clock in tests.
*
* Defaults to [Clock.systemUTC].
*/
fun clock(clock: Clock) = apply { clientOptions.clock(clock) }
/**
* The base URL to use for every request.
*
* Defaults to the production environment: `https://api.smith.langchain.com/`.
*/
fun baseUrl(baseUrl: String?) = apply { clientOptions.baseUrl(baseUrl) }
/** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */
fun baseUrl(baseUrl: Optional<String>) = baseUrl(baseUrl.getOrNull())
/**
* 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.
*/
fun responseValidation(responseValidation: Boolean) = apply {
clientOptions.responseValidation(responseValidation)
}
/**
* Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
* retries.
*
* Defaults to [Timeout.default].
*/
fun timeout(timeout: Timeout) = apply { clientOptions.timeout(timeout) }
/**
* Sets the maximum time allowed for a complete HTTP call, not including retries.
*
* See [Timeout.request] for more details.
*
* For fine-grained control, pass a [Timeout] object.
*/
fun timeout(timeout: Duration) = apply { clientOptions.timeout(timeout) }
/**
* The maximum number of times to retry failed requests, with a short exponential backoff
* between requests.
*
* Only the following error types are retried:
* - Connection errors (for example, due to a network connectivity problem)
* - 408 Request Timeout
* - 409 Conflict
* - 429 Rate Limit
* - 5xx Internal
*
* The API may also explicitly instruct the SDK to retry or not retry a request.
*
* Defaults to 2.
*/
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)`. */
fun apiKey(apiKey: Optional<String>) = apiKey(apiKey.getOrNull())
fun tenantId(tenantId: String?) = apply { clientOptions.tenantId(tenantId) }
/** Alias for calling [Builder.tenantId] with `tenantId.orElse(null)`. */
fun tenantId(tenantId: Optional<String>) = tenantId(tenantId.getOrNull())
fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
fun headers(headers: Map<String, Iterable<String>>) = apply {
clientOptions.headers(headers)
}
fun putHeader(name: String, value: String) = apply { clientOptions.putHeader(name, value) }
fun putHeaders(name: String, values: Iterable<String>) = apply {
clientOptions.putHeaders(name, values)
}
fun putAllHeaders(headers: Headers) = apply { clientOptions.putAllHeaders(headers) }
fun putAllHeaders(headers: Map<String, Iterable<String>>) = apply {
clientOptions.putAllHeaders(headers)
}
fun replaceHeaders(name: String, value: String) = apply {
clientOptions.replaceHeaders(name, value)
}
fun replaceHeaders(name: String, values: Iterable<String>) = apply {
clientOptions.replaceHeaders(name, values)
}
fun replaceAllHeaders(headers: Headers) = apply { clientOptions.replaceAllHeaders(headers) }
fun replaceAllHeaders(headers: Map<String, Iterable<String>>) = apply {
clientOptions.replaceAllHeaders(headers)
}
fun removeHeaders(name: String) = apply { clientOptions.removeHeaders(name) }
fun removeAllHeaders(names: Set<String>) = apply { clientOptions.removeAllHeaders(names) }
fun queryParams(queryParams: QueryParams) = apply { clientOptions.queryParams(queryParams) }
fun queryParams(queryParams: Map<String, Iterable<String>>) = apply {
clientOptions.queryParams(queryParams)
}
fun putQueryParam(key: String, value: String) = apply {
clientOptions.putQueryParam(key, value)
}
fun putQueryParams(key: String, values: Iterable<String>) = apply {
clientOptions.putQueryParams(key, values)
}
fun putAllQueryParams(queryParams: QueryParams) = apply {
clientOptions.putAllQueryParams(queryParams)
}
fun putAllQueryParams(queryParams: Map<String, Iterable<String>>) = apply {
clientOptions.putAllQueryParams(queryParams)
}
fun replaceQueryParams(key: String, value: String) = apply {
clientOptions.replaceQueryParams(key, value)
}
fun replaceQueryParams(key: String, values: Iterable<String>) = apply {
clientOptions.replaceQueryParams(key, values)
}
fun replaceAllQueryParams(queryParams: QueryParams) = apply {
clientOptions.replaceAllQueryParams(queryParams)
}
fun replaceAllQueryParams(queryParams: Map<String, Iterable<String>>) = apply {
clientOptions.replaceAllQueryParams(queryParams)
}
fun removeQueryParams(key: String) = apply { clientOptions.removeQueryParams(key) }
fun removeAllQueryParams(keys: Set<String>) = apply {
clientOptions.removeAllQueryParams(keys)
}
/**
* Updates configuration using system properties and environment variables.
*
* @see ClientOptions.Builder.fromEnv
*/
fun fromEnv() = apply { clientOptions.fromEnv() }
/**
* Returns an immutable instance of [LangsmithClient].
*
* Further updates to this [Builder] will not mutate the returned instance.
*/
fun build(): LangsmithClient =
LangsmithClientImpl(
clientOptions
.httpClient(
OkHttpClient.builder()
.timeout(clientOptions.timeout())
.proxy(proxy)
.proxyAuthenticator(proxyAuthenticator)
.maxIdleConnections(maxIdleConnections)
.keepAliveDuration(keepAliveDuration)
.dispatcherExecutorService(dispatcherExecutorService)
.sslSocketFactory(sslSocketFactory)
.trustManager(trustManager)
.hostnameVerifier(hostnameVerifier)
.build()
)
.build()
)
}
}
@@ -0,0 +1,434 @@
// File generated from our OpenAPI spec by Stainless.
package com.langchain.smith.client.okhttp
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
import java.time.Clock
import java.time.Duration
import java.util.Optional
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager
import kotlin.jvm.optionals.getOrNull
/**
* A class that allows building an instance of [LangsmithClientAsync] with [OkHttpClient] as the
* underlying [HttpClient].
*/
class LangsmithOkHttpClientAsync private constructor() {
companion object {
/** Returns a mutable builder for constructing an instance of [LangsmithClientAsync]. */
@JvmStatic fun builder() = Builder()
/**
* Returns a client configured using system properties and environment variables.
*
* @see ClientOptions.Builder.fromEnv
*/
@JvmStatic fun fromEnv(): LangsmithClientAsync = builder().fromEnv().build()
}
/** A builder for [LangsmithOkHttpClientAsync]. */
class Builder internal 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
private var trustManager: X509TrustManager? = null
private var hostnameVerifier: HostnameVerifier? = null
/**
* The executor service to use for running HTTP requests.
*
* Defaults to OkHttp's
* [default executor service](https://github.com/square/okhttp/blob/ace792f443b2ffb17974f5c0d1cecdf589309f26/okhttp/src/commonJvmAndroid/kotlin/okhttp3/Dispatcher.kt#L98-L104).
*
* This class takes ownership of the executor service and shuts it down when closed.
*/
fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply {
this.dispatcherExecutorService = dispatcherExecutorService
}
/**
* Alias for calling [Builder.dispatcherExecutorService] with
* `dispatcherExecutorService.orElse(null)`.
*/
fun dispatcherExecutorService(dispatcherExecutorService: Optional<ExecutorService>) =
dispatcherExecutorService(dispatcherExecutorService.getOrNull())
fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
/** 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.
*
* If this is set, then [keepAliveDuration] must also be set.
*
* If unset, then OkHttp's default is used.
*/
fun maxIdleConnections(maxIdleConnections: Int?) = apply {
this.maxIdleConnections = maxIdleConnections
}
/**
* Alias for [Builder.maxIdleConnections].
*
* This unboxed primitive overload exists for backwards compatibility.
*/
fun maxIdleConnections(maxIdleConnections: Int) =
maxIdleConnections(maxIdleConnections as Int?)
/**
* Alias for calling [Builder.maxIdleConnections] with `maxIdleConnections.orElse(null)`.
*/
fun maxIdleConnections(maxIdleConnections: Optional<Int>) =
maxIdleConnections(maxIdleConnections.getOrNull())
/**
* The keep-alive duration for idle connections in the underlying OkHttp connection pool.
*
* If this is set, then [maxIdleConnections] must also be set.
*
* If unset, then OkHttp's default is used.
*/
fun keepAliveDuration(keepAliveDuration: Duration?) = apply {
this.keepAliveDuration = keepAliveDuration
}
/** Alias for calling [Builder.keepAliveDuration] with `keepAliveDuration.orElse(null)`. */
fun keepAliveDuration(keepAliveDuration: Optional<Duration>) =
keepAliveDuration(keepAliveDuration.getOrNull())
/**
* The socket factory used to secure HTTPS connections.
*
* If this is set, then [trustManager] must also be set.
*
* If unset, then the system default is used. Most applications should not call this method,
* and instead use the system default. The default include special optimizations that can be
* lost if the implementation is modified.
*/
fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply {
this.sslSocketFactory = sslSocketFactory
}
/** Alias for calling [Builder.sslSocketFactory] with `sslSocketFactory.orElse(null)`. */
fun sslSocketFactory(sslSocketFactory: Optional<SSLSocketFactory>) =
sslSocketFactory(sslSocketFactory.getOrNull())
/**
* The trust manager used to secure HTTPS connections.
*
* If this is set, then [sslSocketFactory] must also be set.
*
* If unset, then the system default is used. Most applications should not call this method,
* and instead use the system default. The default include special optimizations that can be
* lost if the implementation is modified.
*/
fun trustManager(trustManager: X509TrustManager?) = apply {
this.trustManager = trustManager
}
/** Alias for calling [Builder.trustManager] with `trustManager.orElse(null)`. */
fun trustManager(trustManager: Optional<X509TrustManager>) =
trustManager(trustManager.getOrNull())
/**
* The verifier used to confirm that response certificates apply to requested hostnames for
* HTTPS connections.
*
* If unset, then a default hostname verifier is used.
*/
fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply {
this.hostnameVerifier = hostnameVerifier
}
/** Alias for calling [Builder.hostnameVerifier] with `hostnameVerifier.orElse(null)`. */
fun hostnameVerifier(hostnameVerifier: Optional<HostnameVerifier>) =
hostnameVerifier(hostnameVerifier.getOrNull())
/**
* Whether to throw an exception if any of the Jackson versions detected at runtime are
* incompatible with the SDK's minimum supported Jackson version (2.13.4).
*
* Defaults to true. Use extreme caution when disabling this option. There is no guarantee
* that the SDK will work correctly when using an incompatible Jackson version.
*/
fun checkJacksonVersionCompatibility(checkJacksonVersionCompatibility: Boolean) = apply {
clientOptions.checkJacksonVersionCompatibility(checkJacksonVersionCompatibility)
}
/**
* The Jackson JSON mapper to use for serializing and deserializing JSON.
*
* Defaults to [com.langchain.smith.core.jsonMapper]. The default is usually sufficient and
* rarely needs to be overridden.
*/
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
/**
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
*
* Defaults to a dedicated cached thread pool.
*
* This class takes ownership of the executor and shuts it down, if possible, when closed.
*/
fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
clientOptions.streamHandlerExecutor(streamHandlerExecutor)
}
/**
* The interface to use for delaying execution, like during retries.
*
* This is primarily useful for using fake delays in tests.
*
* Defaults to real execution delays.
*
* This class takes ownership of the sleeper and closes it when closed.
*/
fun sleeper(sleeper: Sleeper) = apply { clientOptions.sleeper(sleeper) }
/**
* The clock to use for operations that require timing, like retries.
*
* This is primarily useful for using a fake clock in tests.
*
* Defaults to [Clock.systemUTC].
*/
fun clock(clock: Clock) = apply { clientOptions.clock(clock) }
/**
* The base URL to use for every request.
*
* Defaults to the production environment: `https://api.smith.langchain.com/`.
*/
fun baseUrl(baseUrl: String?) = apply { clientOptions.baseUrl(baseUrl) }
/** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */
fun baseUrl(baseUrl: Optional<String>) = baseUrl(baseUrl.getOrNull())
/**
* 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.
*/
fun responseValidation(responseValidation: Boolean) = apply {
clientOptions.responseValidation(responseValidation)
}
/**
* Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
* retries.
*
* Defaults to [Timeout.default].
*/
fun timeout(timeout: Timeout) = apply { clientOptions.timeout(timeout) }
/**
* Sets the maximum time allowed for a complete HTTP call, not including retries.
*
* See [Timeout.request] for more details.
*
* For fine-grained control, pass a [Timeout] object.
*/
fun timeout(timeout: Duration) = apply { clientOptions.timeout(timeout) }
/**
* The maximum number of times to retry failed requests, with a short exponential backoff
* between requests.
*
* Only the following error types are retried:
* - Connection errors (for example, due to a network connectivity problem)
* - 408 Request Timeout
* - 409 Conflict
* - 429 Rate Limit
* - 5xx Internal
*
* The API may also explicitly instruct the SDK to retry or not retry a request.
*
* Defaults to 2.
*/
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)`. */
fun apiKey(apiKey: Optional<String>) = apiKey(apiKey.getOrNull())
fun tenantId(tenantId: String?) = apply { clientOptions.tenantId(tenantId) }
/** Alias for calling [Builder.tenantId] with `tenantId.orElse(null)`. */
fun tenantId(tenantId: Optional<String>) = tenantId(tenantId.getOrNull())
fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
fun headers(headers: Map<String, Iterable<String>>) = apply {
clientOptions.headers(headers)
}
fun putHeader(name: String, value: String) = apply { clientOptions.putHeader(name, value) }
fun putHeaders(name: String, values: Iterable<String>) = apply {
clientOptions.putHeaders(name, values)
}
fun putAllHeaders(headers: Headers) = apply { clientOptions.putAllHeaders(headers) }
fun putAllHeaders(headers: Map<String, Iterable<String>>) = apply {
clientOptions.putAllHeaders(headers)
}
fun replaceHeaders(name: String, value: String) = apply {
clientOptions.replaceHeaders(name, value)
}
fun replaceHeaders(name: String, values: Iterable<String>) = apply {
clientOptions.replaceHeaders(name, values)
}
fun replaceAllHeaders(headers: Headers) = apply { clientOptions.replaceAllHeaders(headers) }
fun replaceAllHeaders(headers: Map<String, Iterable<String>>) = apply {
clientOptions.replaceAllHeaders(headers)
}
fun removeHeaders(name: String) = apply { clientOptions.removeHeaders(name) }
fun removeAllHeaders(names: Set<String>) = apply { clientOptions.removeAllHeaders(names) }
fun queryParams(queryParams: QueryParams) = apply { clientOptions.queryParams(queryParams) }
fun queryParams(queryParams: Map<String, Iterable<String>>) = apply {
clientOptions.queryParams(queryParams)
}
fun putQueryParam(key: String, value: String) = apply {
clientOptions.putQueryParam(key, value)
}
fun putQueryParams(key: String, values: Iterable<String>) = apply {
clientOptions.putQueryParams(key, values)
}
fun putAllQueryParams(queryParams: QueryParams) = apply {
clientOptions.putAllQueryParams(queryParams)
}
fun putAllQueryParams(queryParams: Map<String, Iterable<String>>) = apply {
clientOptions.putAllQueryParams(queryParams)
}
fun replaceQueryParams(key: String, value: String) = apply {
clientOptions.replaceQueryParams(key, value)
}
fun replaceQueryParams(key: String, values: Iterable<String>) = apply {
clientOptions.replaceQueryParams(key, values)
}
fun replaceAllQueryParams(queryParams: QueryParams) = apply {
clientOptions.replaceAllQueryParams(queryParams)
}
fun replaceAllQueryParams(queryParams: Map<String, Iterable<String>>) = apply {
clientOptions.replaceAllQueryParams(queryParams)
}
fun removeQueryParams(key: String) = apply { clientOptions.removeQueryParams(key) }
fun removeAllQueryParams(keys: Set<String>) = apply {
clientOptions.removeAllQueryParams(keys)
}
/**
* Updates configuration using system properties and environment variables.
*
* @see ClientOptions.Builder.fromEnv
*/
fun fromEnv() = apply { clientOptions.fromEnv() }
/**
* Returns an immutable instance of [LangsmithClientAsync].
*
* Further updates to this [Builder] will not mutate the returned instance.
*/
fun build(): LangsmithClientAsync =
LangsmithClientAsyncImpl(
clientOptions
.httpClient(
OkHttpClient.builder()
.timeout(clientOptions.timeout())
.proxy(proxy)
.proxyAuthenticator(proxyAuthenticator)
.maxIdleConnections(maxIdleConnections)
.keepAliveDuration(keepAliveDuration)
.dispatcherExecutorService(dispatcherExecutorService)
.sslSocketFactory(sslSocketFactory)
.trustManager(trustManager)
.hostnameVerifier(hostnameVerifier)
.build()
)
.build()
)
}
}
@@ -0,0 +1,356 @@
package com.langchain.smith.client.okhttp
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.HttpClient
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
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutorService
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
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okio.BufferedSink
import okio.buffer
import okio.sink
class OkHttpClient
internal constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClient) : HttpClient {
override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse {
val call = newCall(request, requestOptions)
return try {
call.execute().toHttpResponse()
} catch (e: IOException) {
throw LangChainIoException("Request failed", e)
} finally {
request.body?.close()
}
}
override fun executeAsync(
request: HttpRequest,
requestOptions: RequestOptions,
): CompletableFuture<HttpResponse> {
val future = CompletableFuture<HttpResponse>()
val call = newCall(request, requestOptions)
call.enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
future.complete(response.toHttpResponse())
}
override fun onFailure(call: Call, e: IOException) {
future.completeExceptionally(LangChainIoException("Request failed", e))
}
}
)
future.whenComplete { _, e ->
if (e is CancellationException) {
call.cancel()
}
request.body?.close()
}
return future
}
override fun close() {
okHttpClient.dispatcher.executorService.shutdown()
okHttpClient.connectionPool.evictAll()
okHttpClient.cache?.close()
}
private fun newCall(request: HttpRequest, requestOptions: RequestOptions): Call {
val clientBuilder = okHttpClient.newBuilder()
requestOptions.timeout?.let {
clientBuilder
.connectTimeout(it.connect())
.readTimeout(it.read())
.writeTimeout(it.write())
.callTimeout(it.request())
}
val client = clientBuilder.build()
return client.newCall(request.toRequest(client))
}
companion object {
@JvmStatic fun builder() = Builder()
}
class Builder internal constructor() {
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
private var sslSocketFactory: SSLSocketFactory? = null
private var trustManager: X509TrustManager? = null
private var hostnameVerifier: HostnameVerifier? = null
fun timeout(timeout: Timeout) = apply { this.timeout = timeout }
fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build())
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].
*
* If this is set, then [keepAliveDuration] must also be set.
*
* If unset, then OkHttp's default is used.
*/
fun maxIdleConnections(maxIdleConnections: Int?) = apply {
this.maxIdleConnections = maxIdleConnections
}
/**
* Sets the keep-alive duration for idle connections in the underlying [ConnectionPool].
*
* If this is set, then [maxIdleConnections] must also be set.
*
* If unset, then OkHttp's default is used.
*/
fun keepAliveDuration(keepAliveDuration: Duration?) = apply {
this.keepAliveDuration = keepAliveDuration
}
fun dispatcherExecutorService(dispatcherExecutorService: ExecutorService?) = apply {
this.dispatcherExecutorService = dispatcherExecutorService
}
fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply {
this.sslSocketFactory = sslSocketFactory
}
fun trustManager(trustManager: X509TrustManager?) = apply {
this.trustManager = trustManager
}
fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply {
this.hostnameVerifier = hostnameVerifier
}
fun build(): OkHttpClient =
OkHttpClient(
okhttp3.OkHttpClient.Builder()
// `RetryingHttpClient` handles retries if the user enabled them.
.retryOnConnectionFailure(false)
.connectTimeout(timeout.connect())
.readTimeout(timeout.read())
.writeTimeout(timeout.write())
.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
val keepAliveDuration = keepAliveDuration
if (maxIdleConnections != null && keepAliveDuration != null) {
connectionPool(
ConnectionPool(
maxIdleConnections,
keepAliveDuration.toNanos(),
TimeUnit.NANOSECONDS,
)
)
} else {
check((maxIdleConnections != null) == (keepAliveDuration != null)) {
"Both or none of `maxIdleConnections` and `keepAliveDuration` must be set, but only one was set"
}
}
val sslSocketFactory = sslSocketFactory
val trustManager = trustManager
if (sslSocketFactory != null && trustManager != null) {
sslSocketFactory(sslSocketFactory, trustManager)
} else {
check((sslSocketFactory != null) == (trustManager != null)) {
"Both or none of `sslSocketFactory` and `trustManager` must be set, but only one was set"
}
}
hostnameVerifier?.let(::hostnameVerifier)
}
.build()
.apply {
// We usually make all our requests to the same host so it makes sense to
// raise the per-host limit to the overall limit.
dispatcher.maxRequestsPerHost = dispatcher.maxRequests
}
)
}
}
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()
}
@@ -0,0 +1,9 @@
package com.langchain.smith.client.okhttp
import com.langchain.smith.client.LangsmithClient
import com.langchain.smith.tracing.LangsmithClientProvider
/** [LangsmithClientProvider] backed by [LangsmithOkHttpClient]. */
class OkHttpLangsmithClientProvider : LangsmithClientProvider {
override fun createClient(): LangsmithClient = LangsmithOkHttpClient.fromEnv()
}
@@ -1,83 +0,0 @@
// File generated from our OpenAPI spec by Stainless.
package com.langsmith.api.client.okhttp
import com.fasterxml.jackson.databind.json.JsonMapper
import com.langsmith.api.client.LangSmithClient
import com.langsmith.api.client.LangSmithClientImpl
import com.langsmith.api.core.ClientOptions
import java.net.Proxy
import java.time.Clock
import java.time.Duration
class LangSmithOkHttpClient private constructor() {
companion object {
@JvmStatic fun builder() = Builder()
@JvmStatic fun fromEnv(): LangSmithClient = builder().fromEnv().build()
}
class Builder {
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
private var baseUrl: String = ClientOptions.PRODUCTION_URL
// default timeout for client is 1 minute
private var timeout: Duration = Duration.ofSeconds(60)
private var proxy: Proxy? = null
fun baseUrl(baseUrl: String) = apply {
clientOptions.baseUrl(baseUrl)
this.baseUrl = baseUrl
}
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
fun clock(clock: Clock) = apply { clientOptions.clock(clock) }
fun headers(headers: Map<String, Iterable<String>>) = apply {
clientOptions.headers(headers)
}
fun putHeader(name: String, value: String) = apply { clientOptions.putHeader(name, value) }
fun putHeaders(name: String, values: Iterable<String>) = apply {
clientOptions.putHeaders(name, values)
}
fun putAllHeaders(headers: Map<String, Iterable<String>>) = apply {
clientOptions.putAllHeaders(headers)
}
fun removeHeader(name: String) = apply { clientOptions.removeHeader(name) }
fun timeout(timeout: Duration) = apply { this.timeout = timeout }
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
fun proxy(proxy: Proxy) = apply { this.proxy = proxy }
fun responseValidation(responseValidation: Boolean) = apply {
clientOptions.responseValidation(responseValidation)
}
fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) }
fun fromEnv() = apply { clientOptions.fromEnv() }
fun build(): LangSmithClient {
return LangSmithClientImpl(
clientOptions
.httpClient(
OkHttpClient.builder()
.baseUrl(baseUrl)
.timeout(timeout)
.proxy(proxy)
.build()
)
.build()
)
}
}
}
@@ -1,83 +0,0 @@
// File generated from our OpenAPI spec by Stainless.
package com.langsmith.api.client.okhttp
import com.fasterxml.jackson.databind.json.JsonMapper
import com.langsmith.api.client.LangSmithClientAsync
import com.langsmith.api.client.LangSmithClientAsyncImpl
import com.langsmith.api.core.ClientOptions
import java.net.Proxy
import java.time.Clock
import java.time.Duration
class LangSmithOkHttpClientAsync private constructor() {
companion object {
@JvmStatic fun builder() = Builder()
@JvmStatic fun fromEnv(): LangSmithClientAsync = builder().fromEnv().build()
}
class Builder {
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
private var baseUrl: String = ClientOptions.PRODUCTION_URL
// default timeout for client is 1 minute
private var timeout: Duration = Duration.ofSeconds(60)
private var proxy: Proxy? = null
fun baseUrl(baseUrl: String) = apply {
clientOptions.baseUrl(baseUrl)
this.baseUrl = baseUrl
}
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
fun clock(clock: Clock) = apply { clientOptions.clock(clock) }
fun headers(headers: Map<String, Iterable<String>>) = apply {
clientOptions.headers(headers)
}
fun putHeader(name: String, value: String) = apply { clientOptions.putHeader(name, value) }
fun putHeaders(name: String, values: Iterable<String>) = apply {
clientOptions.putHeaders(name, values)
}
fun putAllHeaders(headers: Map<String, Iterable<String>>) = apply {
clientOptions.putAllHeaders(headers)
}
fun removeHeader(name: String) = apply { clientOptions.removeHeader(name) }
fun timeout(timeout: Duration) = apply { this.timeout = timeout }
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
fun proxy(proxy: Proxy) = apply { this.proxy = proxy }
fun responseValidation(responseValidation: Boolean) = apply {
clientOptions.responseValidation(responseValidation)
}
fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) }
fun fromEnv() = apply { clientOptions.fromEnv() }
fun build(): LangSmithClientAsync {
return LangSmithClientAsyncImpl(
clientOptions
.httpClient(
OkHttpClient.builder()
.baseUrl(baseUrl)
.timeout(timeout)
.proxy(proxy)
.build()
)
.build()
)
}
}
}
@@ -1,202 +0,0 @@
package com.langsmith.api.client.okhttp
import com.google.common.collect.ListMultimap
import com.google.common.collect.MultimapBuilder
import com.langsmith.api.core.RequestOptions
import com.langsmith.api.core.http.HttpClient
import com.langsmith.api.core.http.HttpMethod
import com.langsmith.api.core.http.HttpRequest
import com.langsmith.api.core.http.HttpRequestBody
import com.langsmith.api.core.http.HttpResponse
import com.langsmith.api.errors.LangSmithIoException
import java.io.IOException
import java.io.InputStream
import java.net.Proxy
import java.time.Duration
import java.util.concurrent.CompletableFuture
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okio.BufferedSink
class OkHttpClient
private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val baseUrl: HttpUrl) :
HttpClient {
private fun getClient(requestOptions: RequestOptions): okhttp3.OkHttpClient {
val timeout = requestOptions.timeout ?: return okHttpClient
return okHttpClient
.newBuilder()
.connectTimeout(timeout)
.readTimeout(timeout)
.writeTimeout(timeout)
.callTimeout(if (timeout.seconds == 0L) timeout else timeout.plusSeconds(30))
.build()
}
override fun execute(
request: HttpRequest,
requestOptions: RequestOptions,
): HttpResponse {
val call = getClient(requestOptions).newCall(request.toRequest())
return try {
call.execute().toResponse()
} catch (e: IOException) {
throw LangSmithIoException("Request failed", e)
} finally {
request.body?.close()
}
}
override fun executeAsync(
request: HttpRequest,
requestOptions: RequestOptions,
): CompletableFuture<HttpResponse> {
val future = CompletableFuture<HttpResponse>()
request.body?.run { future.whenComplete { _, _ -> close() } }
val call = getClient(requestOptions).newCall(request.toRequest())
call.enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
future.complete(response.toResponse())
}
override fun onFailure(call: Call, e: IOException) {
future.completeExceptionally(LangSmithIoException("Request failed", e))
}
}
)
return future
}
override fun close() {
okHttpClient.dispatcher.executorService.shutdown()
okHttpClient.connectionPool.evictAll()
okHttpClient.cache?.close()
}
private fun HttpRequest.toRequest(): Request {
var body: RequestBody? = body?.toRequestBody()
// OkHttpClient always requires a request body for PUT and POST methods
if (body == null && (method == HttpMethod.PUT || method == HttpMethod.POST)) {
body = "".toRequestBody()
}
val builder = Request.Builder().url(toUrl()).method(method.name, body)
headers.forEach(builder::header)
return builder.build()
}
private fun HttpRequest.toUrl(): String {
url?.let {
return it
}
val builder = baseUrl.newBuilder()
pathSegments.forEach(builder::addPathSegment)
queryParams.forEach(builder::addQueryParameter)
return builder.toString()
}
private fun HttpRequestBody.toRequestBody(): RequestBody {
val mediaType = contentType()?.toMediaType()
val length = contentLength()
return object : RequestBody() {
override fun contentType(): MediaType? {
return mediaType
}
override fun contentLength(): Long {
return length
}
override fun isOneShot(): Boolean {
return !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 {
return code
}
override fun headers(): ListMultimap<String, String> {
return headers
}
override fun body(): InputStream {
return body!!.byteStream()
}
override fun close() {
body!!.close()
}
}
}
private fun Headers.toHeaders(): ListMultimap<String, String> {
val headers =
MultimapBuilder.treeKeys(String.CASE_INSENSITIVE_ORDER)
.arrayListValues()
.build<String, String>()
forEach { pair -> headers.put(pair.first, pair.second) }
return headers
}
companion object {
@JvmStatic fun builder() = Builder()
}
class Builder {
private var baseUrl: HttpUrl? = null
// default timeout is 1 minute
private var timeout: Duration = Duration.ofSeconds(60)
private var proxy: Proxy? = null
fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl.toHttpUrl() }
fun timeout(timeout: Duration) = apply { this.timeout = timeout }
fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
fun build(): OkHttpClient {
return OkHttpClient(
okhttp3.OkHttpClient.Builder()
.connectTimeout(timeout)
.readTimeout(timeout)
.writeTimeout(timeout)
.callTimeout(if (timeout.seconds == 0L) timeout else timeout.plusSeconds(30))
.proxy(proxy)
.build(),
checkNotNull(baseUrl) { "`baseUrl` is required but was not set" },
)
}
}
}
@@ -0,0 +1 @@
com.langchain.smith.client.okhttp.OkHttpLangsmithClientProvider
@@ -0,0 +1,44 @@
package com.langchain.smith.client.okhttp
import com.github.tomakehurst.wiremock.client.WireMock.*
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo
import com.github.tomakehurst.wiremock.junit5.WireMockTest
import com.langchain.smith.core.http.HttpMethod
import com.langchain.smith.core.http.HttpRequest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.parallel.ResourceLock
@WireMockTest
@ResourceLock("https://github.com/wiremock/wiremock/issues/169")
internal class OkHttpClientTest {
private lateinit var baseUrl: String
private lateinit var httpClient: OkHttpClient
@BeforeEach
fun beforeEach(wmRuntimeInfo: WireMockRuntimeInfo) {
baseUrl = wmRuntimeInfo.httpBaseUrl
httpClient = OkHttpClient.builder().build()
}
@Test
fun executeAsync_whenFutureCancelled_cancelsUnderlyingCall() {
stubFor(post(urlPathEqualTo("/something")).willReturn(ok()))
val responseFuture =
httpClient.executeAsync(
HttpRequest.builder()
.method(HttpMethod.POST)
.baseUrl(baseUrl)
.addPathSegment("something")
.build()
)
val call = httpClient.okHttpClient.dispatcher.runningCalls().single()
responseFuture.cancel(false)
// Should have cancelled the underlying call
assertThat(call.isCanceled()).isTrue()
}
}
+109 -15
View File
@@ -1,26 +1,120 @@
plugins {
id("langsmith.kotlin")
id("langsmith.publish")
id("langchain.kotlin")
id("langchain.publish")
}
val generateVersionProperties by tasks.registering {
val outputDir = layout.buildDirectory.dir("generated/resources/version")
val projectVersion = provider { project.version.toString() }
outputs.dir(outputDir)
inputs.property("version", projectVersion)
doLast {
val dir = outputDir.get().asFile.resolve("com/langchain/smith")
dir.mkdirs()
dir.resolve("version.properties").writeText("version=${projectVersion.get()}\n")
}
}
sourceSets.main {
resources.srcDir(generateVersionProperties)
}
val jacksonVersion = "2.22.1"
val jacksonAnnotationsVersion = "2.22"
configurations.matching { it.name in setOf("testCompileClasspath", "testRuntimeClasspath") }.configureEach {
// Test-only WireMock 4 requires Java 17+ dependencies. Published artifacts still target Java 8.
attributes.attribute(org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
}
configurations.all {
resolutionStrategy {
// 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.14.1")
api("com.fasterxml.jackson.core:jackson-databind:2.14.1")
api("com.google.guava:guava:33.0.0-jre")
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.14.1")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.1")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.1")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.1")
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.62.0")
api("io.opentelemetry:opentelemetry-sdk:1.62.0")
api("io.opentelemetry:opentelemetry-exporter-otlp:1.62.0")
api("io.opentelemetry.semconv:opentelemetry-semconv:1.41.1")
// OpenAI SDK (for wrapOpenAI tracing wrapper)
api("com.openai:openai-java:4.30.0")
// Mustache template engine (for prompt template formatting)
implementation("com.samskivert:jmustache:1.16")
// SLF4J for logging (API only - consumers choose implementation)
api("org.slf4j:slf4j-api:2.0.17")
// Anthropic SDK — optional peer dependency for prompt conversion.
// Users who call AnthropicPayload.toAnthropicParams() must add this to their own dependencies.
compileOnly("com.anthropic:anthropic-java:2.18.0")
testImplementation("com.anthropic:anthropic-java:2.18.0")
// Security: constrain vulnerable transitive test dependencies.
// These constraints apply to the test scope only and do not affect published artifacts.
constraints {
testImplementation("org.eclipse.jetty:jetty-server") { version { require("12.1.10") } }
testImplementation("org.eclipse.jetty:jetty-http") { version { require("12.1.10") } }
testImplementation("org.eclipse.jetty:jetty-servlets") { version { require("12.1.10") } }
testImplementation("org.eclipse.jetty:jetty-xml") { version { require("12.1.10") } }
testImplementation("org.eclipse.jetty.http2:http2-common") { version { require("12.1.10") } }
testImplementation("org.eclipse.jetty.http2:http2-hpack") { version { require("12.1.10") } }
testImplementation("org.eclipse.jetty.http2:http2-server") { version { require("12.1.10") } }
testImplementation("org.bouncycastle:bcpg-jdk18on:1.84")
testImplementation("org.bouncycastle:bcpkix-jdk18on:1.84")
testImplementation("org.bouncycastle:bcprov-jdk18on:1.84")
testImplementation("org.apache.logging.log4j:log4j-core:2.25.4")
testImplementation("org.apache.opennlp:opennlp-tools") { version { require("2.5.9") } }
testImplementation("com.github.jknack:handlebars") { version { require("4.5.2") } }
testImplementation("org.codehaus.plexus:plexus-utils:4.0.3")
testImplementation("org.apache.commons:commons-lang3:3.18.0")
testImplementation("org.apache.commons:commons-compress") { version { require("1.28.0") } }
testImplementation("commons-fileupload:commons-fileupload") { version { require("1.6.0") } }
testImplementation("commons-io:commons-io") { version { require("2.22.0") } }
testImplementation("com.google.guava:guava") { version { require("33.6.0-jre") } }
testImplementation("com.jayway.jsonpath:json-path") { version { require("2.10.0") } }
testImplementation("org.xmlunit:xmlunit-core") { version { require("2.12.0") } }
testImplementation("net.minidev:json-smart") { version { require("2.6.0") } }
}
testImplementation(kotlin("test"))
// Simple logging for tests only
testImplementation("org.slf4j:slf4j-simple:2.0.17")
testImplementation(project(":langsmith-java-client-okhttp"))
testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2")
testImplementation("org.assertj:assertj-core:3.25.3")
testImplementation("org.assertj:assertj-guava:3.25.3")
testImplementation("org.slf4j:slf4j-simple:2.0.12")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.1")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.1")
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")
testImplementation("org.junit-pioneer:junit-pioneer:1.9.1")
testImplementation("org.mockito:mockito-core:5.14.2")
testImplementation("org.mockito:mockito-junit-jupiter:5.14.2")
testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0")
// LangChain4j for testing parallel tool execution context propagation
testImplementation("dev.langchain4j:langchain4j:1.12.2")
testImplementation("dev.langchain4j:langchain4j-core:1.12.2")
}
@@ -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
}
}
@@ -0,0 +1,157 @@
// File generated from our OpenAPI spec by Stainless.
package com.langchain.smith.client
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
/**
* A client for interacting with the LangChain REST API synchronously. You can also switch to
* asynchronous execution via the [async] method.
*
* This client performs best when you create a single instance and reuse it for all interactions
* with the REST API. This is because each client holds its own connection pool and thread pools.
* Reusing connections and threads reduces latency and saves memory. The client also handles rate
* limiting per client. This means that creating and using multiple instances at the same time will
* not respect rate limits.
*
* The threads and connections that are held will be released automatically if they remain idle. But
* if you are writing an application that needs to aggressively release unused resources, then you
* may call [close].
*/
interface LangsmithClient {
/**
* Returns a version of this client that uses asynchronous execution.
*
* The returned client shares its resources, like its connection pool and thread pools, with
* this client.
*/
fun async(): LangsmithClientAsync
/**
* Returns a view of this service that provides access to raw HTTP responses for each method.
*/
fun withRawResponse(): WithRawResponse
/**
* Returns a view of this service with the given option modifications applied.
*
* The original service is not modified.
*/
fun withOptions(modifier: Consumer<ClientOptions.Builder>): LangsmithClient
fun sessions(): SessionService
fun examples(): ExampleService
fun datasets(): DatasetService
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.
*
* This is purposefully not inherited from [AutoCloseable] because the client is long-lived and
* usually should not be synchronously closed via try-with-resources.
*
* It's also usually not necessary to call this method at all. the default HTTP client
* automatically releases threads and connections if they remain idle, but if you are writing an
* application that needs to aggressively release unused resources, then you may call this
* method.
*/
fun close()
/** A view of [LangsmithClient] that provides access to raw HTTP responses for each method. */
interface WithRawResponse {
/**
* Returns a view of this service with the given option modifications applied.
*
* The original service is not modified.
*/
fun withOptions(modifier: Consumer<ClientOptions.Builder>): LangsmithClient.WithRawResponse
fun sessions(): SessionService.WithRawResponse
fun examples(): ExampleService.WithRawResponse
fun datasets(): DatasetService.WithRawResponse
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
}
}
@@ -0,0 +1,161 @@
// File generated from our OpenAPI spec by Stainless.
package com.langchain.smith.client
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
/**
* A client for interacting with the LangChain REST API asynchronously. You can also switch to
* synchronous execution via the [sync] method.
*
* This client performs best when you create a single instance and reuse it for all interactions
* with the REST API. This is because each client holds its own connection pool and thread pools.
* Reusing connections and threads reduces latency and saves memory. The client also handles rate
* limiting per client. This means that creating and using multiple instances at the same time will
* not respect rate limits.
*
* The threads and connections that are held will be released automatically if they remain idle. But
* if you are writing an application that needs to aggressively release unused resources, then you
* may call [close].
*/
interface LangsmithClientAsync {
/**
* Returns a version of this client that uses synchronous execution.
*
* The returned client shares its resources, like its connection pool and thread pools, with
* this client.
*/
fun sync(): LangsmithClient
/**
* Returns a view of this service that provides access to raw HTTP responses for each method.
*/
fun withRawResponse(): WithRawResponse
/**
* Returns a view of this service with the given option modifications applied.
*
* The original service is not modified.
*/
fun withOptions(modifier: Consumer<ClientOptions.Builder>): LangsmithClientAsync
fun sessions(): SessionServiceAsync
fun examples(): ExampleServiceAsync
fun datasets(): DatasetServiceAsync
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.
*
* This is purposefully not inherited from [AutoCloseable] because the client is long-lived and
* usually should not be synchronously closed via try-with-resources.
*
* It's also usually not necessary to call this method at all. the default HTTP client
* automatically releases threads and connections if they remain idle, but if you are writing an
* application that needs to aggressively release unused resources, then you may call this
* method.
*/
fun close()
/**
* A view of [LangsmithClientAsync] that provides access to raw HTTP responses for each method.
*/
interface WithRawResponse {
/**
* Returns a view of this service with the given option modifications applied.
*
* The original service is not modified.
*/
fun withOptions(
modifier: Consumer<ClientOptions.Builder>
): LangsmithClientAsync.WithRawResponse
fun sessions(): SessionServiceAsync.WithRawResponse
fun examples(): ExampleServiceAsync.WithRawResponse
fun datasets(): DatasetServiceAsync.WithRawResponse
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
}
}
@@ -0,0 +1,302 @@
// File generated from our OpenAPI spec by Stainless.
package com.langchain.smith.client
import com.langchain.smith.core.ClientOptions
import com.langchain.smith.core.getPackageVersion
import com.langchain.smith.services.async.AnnotationQueueServiceAsync
import com.langchain.smith.services.async.AnnotationQueueServiceAsyncImpl
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 {
private val clientOptionsWithUserAgent =
if (clientOptions.headers.names().contains("User-Agent")) clientOptions
else
clientOptions
.toBuilder()
.putHeader("User-Agent", "langsmith-java/${getPackageVersion()}")
.build()
// Pass the original clientOptions so that this client sets its own User-Agent.
private val sync = lazy { LangsmithClientImpl(clientOptions) }
private val withRawResponse: LangsmithClientAsync.WithRawResponse by lazy {
WithRawResponseImpl(clientOptions)
}
private val sessions: SessionServiceAsync by lazy {
SessionServiceAsyncImpl(clientOptionsWithUserAgent)
}
private val examples: ExampleServiceAsync by lazy {
ExampleServiceAsyncImpl(clientOptionsWithUserAgent)
}
private val datasets: DatasetServiceAsync by lazy {
DatasetServiceAsyncImpl(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)
}
private val public_: PublicServiceAsync by lazy {
PublicServiceAsyncImpl(clientOptionsWithUserAgent)
}
private val annotationQueues: AnnotationQueueServiceAsync by lazy {
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 {
CommitServiceAsyncImpl(clientOptionsWithUserAgent)
}
private val settings: SettingServiceAsync by lazy {
SettingServiceAsyncImpl(clientOptionsWithUserAgent)
}
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
override fun withOptions(modifier: Consumer<ClientOptions.Builder>): LangsmithClientAsync =
LangsmithClientAsyncImpl(clientOptions.toBuilder().apply(modifier::accept).build())
override fun sessions(): SessionServiceAsync = sessions
override fun examples(): ExampleServiceAsync = examples
override fun datasets(): DatasetServiceAsync = datasets
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
override fun public_(): PublicServiceAsync = public_
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 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 {
private val sessions: SessionServiceAsync.WithRawResponse by lazy {
SessionServiceAsyncImpl.WithRawResponseImpl(clientOptions)
}
private val examples: ExampleServiceAsync.WithRawResponse by lazy {
ExampleServiceAsyncImpl.WithRawResponseImpl(clientOptions)
}
private val datasets: DatasetServiceAsync.WithRawResponse by lazy {
DatasetServiceAsyncImpl.WithRawResponseImpl(clientOptions)
}
private val runs: RunServiceAsync.WithRawResponse by lazy {
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)
}
private val public_: PublicServiceAsync.WithRawResponse by lazy {
PublicServiceAsyncImpl.WithRawResponseImpl(clientOptions)
}
private val annotationQueues: AnnotationQueueServiceAsync.WithRawResponse by lazy {
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)
}
private val commits: CommitServiceAsync.WithRawResponse by lazy {
CommitServiceAsyncImpl.WithRawResponseImpl(clientOptions)
}
private val settings: SettingServiceAsync.WithRawResponse by lazy {
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 =
LangsmithClientAsyncImpl.WithRawResponseImpl(
clientOptions.toBuilder().apply(modifier::accept).build()
)
override fun sessions(): SessionServiceAsync.WithRawResponse = sessions
override fun examples(): ExampleServiceAsync.WithRawResponse = examples
override fun datasets(): DatasetServiceAsync.WithRawResponse = datasets
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_
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
}
}
@@ -0,0 +1,276 @@
// File generated from our OpenAPI spec by Stainless.
package com.langchain.smith.client
import com.langchain.smith.core.ClientOptions
import com.langchain.smith.core.getPackageVersion
import com.langchain.smith.services.blocking.AnnotationQueueService
import com.langchain.smith.services.blocking.AnnotationQueueServiceImpl
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 {
private val clientOptionsWithUserAgent =
if (clientOptions.headers.names().contains("User-Agent")) clientOptions
else
clientOptions
.toBuilder()
.putHeader("User-Agent", "langsmith-java/${getPackageVersion()}")
.build()
// Pass the original clientOptions so that this client sets its own User-Agent.
private val async: LangsmithClientAsync by lazy { LangsmithClientAsyncImpl(clientOptions) }
private val withRawResponse: LangsmithClient.WithRawResponse by lazy {
WithRawResponseImpl(clientOptions)
}
private val sessions: SessionService by lazy { SessionServiceImpl(clientOptionsWithUserAgent) }
private val examples: ExampleService by lazy { ExampleServiceImpl(clientOptionsWithUserAgent) }
private val datasets: DatasetService by lazy { DatasetServiceImpl(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)
}
private val public_: PublicService by lazy { PublicServiceImpl(clientOptionsWithUserAgent) }
private val annotationQueues: AnnotationQueueService by lazy {
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
override fun withOptions(modifier: Consumer<ClientOptions.Builder>): LangsmithClient =
LangsmithClientImpl(clientOptions.toBuilder().apply(modifier::accept).build())
override fun sessions(): SessionService = sessions
override fun examples(): ExampleService = examples
override fun datasets(): DatasetService = datasets
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
override fun public_(): PublicService = public_
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 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 {
private val sessions: SessionService.WithRawResponse by lazy {
SessionServiceImpl.WithRawResponseImpl(clientOptions)
}
private val examples: ExampleService.WithRawResponse by lazy {
ExampleServiceImpl.WithRawResponseImpl(clientOptions)
}
private val datasets: DatasetService.WithRawResponse by lazy {
DatasetServiceImpl.WithRawResponseImpl(clientOptions)
}
private val runs: RunService.WithRawResponse by lazy {
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)
}
private val public_: PublicService.WithRawResponse by lazy {
PublicServiceImpl.WithRawResponseImpl(clientOptions)
}
private val annotationQueues: AnnotationQueueService.WithRawResponse by lazy {
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)
}
private val commits: CommitService.WithRawResponse by lazy {
CommitServiceImpl.WithRawResponseImpl(clientOptions)
}
private val settings: SettingService.WithRawResponse by lazy {
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 =
LangsmithClientImpl.WithRawResponseImpl(
clientOptions.toBuilder().apply(modifier::accept).build()
)
override fun sessions(): SessionService.WithRawResponse = sessions
override fun examples(): ExampleService.WithRawResponse = examples
override fun datasets(): DatasetService.WithRawResponse = datasets
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")
@@ -0,0 +1,21 @@
// File generated from our OpenAPI spec by Stainless.
package com.langchain.smith.core
import java.util.stream.Stream
import java.util.stream.StreamSupport
class AutoPager<T> private constructor(private val firstPage: Page<T>) : Iterable<T> {
companion object {
fun <T> from(firstPage: Page<T>): AutoPager<T> = AutoPager(firstPage)
}
override fun iterator(): Iterator<T> =
generateSequence(firstPage) { if (it.hasNextPage()) it.nextPage() else null }
.flatMap { it.items() }
.iterator()
fun stream(): Stream<T> = StreamSupport.stream(spliterator(), false)
}
@@ -0,0 +1,88 @@
// File generated from our OpenAPI spec by Stainless.
package com.langchain.smith.core
import com.langchain.smith.core.http.AsyncStreamResponse
import java.util.Optional
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicReference
class AutoPagerAsync<T>
private constructor(private val firstPage: PageAsync<T>, private val defaultExecutor: Executor) :
AsyncStreamResponse<T> {
companion object {
fun <T> from(firstPage: PageAsync<T>, defaultExecutor: Executor): AutoPagerAsync<T> =
AutoPagerAsync(firstPage, defaultExecutor)
}
private val onCompleteFuture = CompletableFuture<Void?>()
private val state = AtomicReference(State.NEW)
override fun subscribe(handler: AsyncStreamResponse.Handler<T>): AsyncStreamResponse<T> =
subscribe(handler, defaultExecutor)
override fun subscribe(
handler: AsyncStreamResponse.Handler<T>,
executor: Executor,
): AsyncStreamResponse<T> = apply {
// TODO(JDK): Use `compareAndExchange` once targeting JDK 9.
check(state.compareAndSet(State.NEW, State.SUBSCRIBED)) {
if (state.get() == State.SUBSCRIBED) "Cannot subscribe more than once"
else "Cannot subscribe after the response is closed"
}
fun PageAsync<T>.handle(): CompletableFuture<Void?> {
if (state.get() == State.CLOSED) {
return CompletableFuture.completedFuture(null)
}
items().forEach { handler.onNext(it) }
return if (hasNextPage()) nextPage().thenCompose { it.handle() }
else CompletableFuture.completedFuture(null)
}
executor.execute {
firstPage.handle().whenComplete { _, error ->
val actualError =
if (error is CompletionException && error.cause != null) error.cause else error
try {
handler.onComplete(Optional.ofNullable(actualError))
} finally {
try {
if (actualError == null) {
onCompleteFuture.complete(null)
} else {
onCompleteFuture.completeExceptionally(actualError)
}
} finally {
close()
}
}
}
}
}
override fun onCompleteFuture(): CompletableFuture<Void?> = onCompleteFuture
override fun close() {
val previousState = state.getAndSet(State.CLOSED)
if (previousState == State.CLOSED) {
return
}
// When the stream is closed, we should always consider it closed. If it closed due
// to an error, then we will have already completed the future earlier, and this
// will be a no-op.
onCompleteFuture.complete(null)
}
}
private enum class State {
NEW,
SUBSCRIBED,
CLOSED,
}
@@ -1,4 +1,4 @@
package com.langsmith.api.core
package com.langchain.smith.core
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.ObjectCodec
@@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.BeanProperty
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonMappingException
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.deser.ContextualDeserializer
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
@@ -18,7 +17,7 @@ abstract class BaseDeserializer<T : Any>(type: KClass<T>) :
override fun createContextual(
context: DeserializationContext,
property: BeanProperty?
property: BeanProperty?,
): JsonDeserializer<T> {
return this
}
@@ -29,31 +28,17 @@ abstract class BaseDeserializer<T : Any>(type: KClass<T>) :
protected abstract fun ObjectCodec.deserialize(node: JsonNode): T
protected fun <T> ObjectCodec.tryDeserialize(
node: JsonNode,
type: TypeReference<T>,
validate: (T) -> Unit = {}
): T? {
return try {
readValue(treeAsTokens(node), type).apply(validate)
} catch (e: JsonMappingException) {
null
} catch (e: RuntimeException) {
protected fun <T> ObjectCodec.tryDeserialize(node: JsonNode, type: TypeReference<T>): T? =
try {
readValue(treeAsTokens(node), type)
} catch (e: Exception) {
null
}
}
protected fun <T> ObjectCodec.tryDeserialize(
node: JsonNode,
type: JavaType,
validate: (T) -> Unit = {}
): T? {
return try {
readValue<T>(treeAsTokens(node), type).apply(validate)
} catch (e: JsonMappingException) {
null
} catch (e: RuntimeException) {
protected fun <T> ObjectCodec.tryDeserialize(node: JsonNode, type: JavaType): T? =
try {
readValue(treeAsTokens(node), type)
} catch (e: Exception) {
null
}
}
}
@@ -1,4 +1,4 @@
package com.langsmith.api.core
package com.langchain.smith.core
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import kotlin.reflect.KClass
@@ -0,0 +1,96 @@
@file:JvmName("Check")
package com.langchain.smith.core
import com.fasterxml.jackson.core.Version
import com.fasterxml.jackson.core.util.VersionUtil
fun checkRequired(name: String, condition: Boolean) =
check(condition) { "`$name` is required, but was not set" }
fun <T : Any> checkRequired(name: String, value: T?): T =
checkNotNull(value) { "`$name` is required, but was not set" }
@JvmSynthetic
internal fun <T : Any> checkKnown(name: String, value: JsonField<T>): T =
value.asKnown().orElseThrow {
IllegalStateException("`$name` is not a known type: ${value.javaClass.simpleName}")
}
@JvmSynthetic
internal fun <T : Any> checkKnown(name: String, value: MultipartField<T>): T =
value.value.asKnown().orElseThrow {
IllegalStateException("`$name` is not a known type: ${value.javaClass.simpleName}")
}
@JvmSynthetic
internal fun checkLength(name: String, value: String, length: Int): String =
value.also {
check(it.length == length) { "`$name` must have length $length, but was ${it.length}" }
}
@JvmSynthetic
internal fun checkMinLength(name: String, value: String, minLength: Int): String =
value.also {
check(it.length >= minLength) {
if (minLength == 1) "`$name` must be non-empty, but was empty"
else "`$name` must have at least length $minLength, but was ${it.length}"
}
}
@JvmSynthetic
internal fun checkMaxLength(name: String, value: String, maxLength: Int): String =
value.also {
check(it.length <= maxLength) {
"`$name` must have at most length $maxLength, but was ${it.length}"
}
}
@JvmSynthetic
internal fun checkJacksonVersionCompatibility() {
val incompatibleJacksonVersions =
RUNTIME_JACKSON_VERSIONS.mapNotNull {
val badVersionReason = BAD_JACKSON_VERSIONS[it.toString()]
when {
it.majorVersion != MINIMUM_JACKSON_VERSION.majorVersion ->
it to "incompatible major version"
it.minorVersion < MINIMUM_JACKSON_VERSION.minorVersion ->
it to "minor version too low"
it.minorVersion == MINIMUM_JACKSON_VERSION.minorVersion &&
it.patchLevel < MINIMUM_JACKSON_VERSION.patchLevel ->
it to "patch version too low"
badVersionReason != null -> it to badVersionReason
else -> null
}
}
check(incompatibleJacksonVersions.isEmpty()) {
"""
This SDK requires a minimum Jackson version of $MINIMUM_JACKSON_VERSION, but the following incompatible Jackson versions were detected at runtime:
${incompatibleJacksonVersions.asSequence().map { (version, incompatibilityReason) ->
"- `${version.toFullString().replace("/", ":")}` ($incompatibilityReason)"
}.joinToString("\n")}
This can happen if you are either:
1. Directly depending on different Jackson versions
2. Depending on some library that depends on different Jackson versions, potentially transitively
Double-check that you are depending on compatible Jackson versions.
See https://www.github.com/langchain-ai/langsmith-java#jackson for more information.
"""
.trimIndent()
}
}
private val MINIMUM_JACKSON_VERSION: Version = VersionUtil.parseVersion("2.13.4", null, null)
private val BAD_JACKSON_VERSIONS: Map<String, String> =
mapOf("2.18.1" to "due to https://github.com/FasterXML/jackson-databind/issues/4639")
private val RUNTIME_JACKSON_VERSIONS: List<Version> =
listOf(
com.fasterxml.jackson.core.json.PackageVersion.VERSION,
com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION,
com.fasterxml.jackson.datatype.jdk8.PackageVersion.VERSION,
com.fasterxml.jackson.datatype.jsr310.PackageVersion.VERSION,
com.fasterxml.jackson.module.kotlin.PackageVersion.VERSION,
)
@@ -0,0 +1,642 @@
// File generated from our OpenAPI spec by Stainless.
package com.langchain.smith.core
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
import java.time.Clock
import java.time.Duration
import java.util.Optional
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicLong
import kotlin.jvm.optionals.getOrNull
/** A class representing the SDK client configuration. */
class ClientOptions
private constructor(
private val originalHttpClient: HttpClient,
/**
* The HTTP client to use in the SDK.
*
* Use the one published in `langsmith-java-client-okhttp` or implement your own.
*
* This class takes ownership of the client and closes it when closed.
*/
@get:JvmName("httpClient") val httpClient: HttpClient,
/**
* Whether to throw an exception if any of the Jackson versions detected at runtime are
* incompatible with the SDK's minimum supported Jackson version (2.13.4).
*
* Defaults to true. Use extreme caution when disabling this option. There is no guarantee that
* the SDK will work correctly when using an incompatible Jackson version.
*/
@get:JvmName("checkJacksonVersionCompatibility") val checkJacksonVersionCompatibility: Boolean,
/**
* The Jackson JSON mapper to use for serializing and deserializing JSON.
*
* Defaults to [com.langchain.smith.core.jsonMapper]. The default is usually sufficient and
* rarely needs to be overridden.
*/
@get:JvmName("jsonMapper") val jsonMapper: JsonMapper,
/**
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
*
* Defaults to a dedicated cached thread pool.
*
* This class takes ownership of the executor and shuts it down, if possible, when closed.
*/
@get:JvmName("streamHandlerExecutor") val streamHandlerExecutor: Executor,
/**
* The interface to use for delaying execution, like during retries.
*
* This is primarily useful for using fake delays in tests.
*
* Defaults to real execution delays.
*
* This class takes ownership of the sleeper and closes it when closed.
*/
@get:JvmName("sleeper") val sleeper: Sleeper,
/**
* The clock to use for operations that require timing, like retries.
*
* This is primarily useful for using a fake clock in tests.
*
* Defaults to [Clock.systemUTC].
*/
@get:JvmName("clock") val clock: Clock,
private val baseUrl: String?,
/** Headers to send with the request. */
@get:JvmName("headers") val headers: Headers,
/** Query params to send with the request. */
@get:JvmName("queryParams") val queryParams: QueryParams,
/**
* 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.
*/
@get:JvmName("responseValidation") val responseValidation: Boolean,
/**
* Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
* retries.
*
* Defaults to [Timeout.default].
*/
@get:JvmName("timeout") val timeout: Timeout,
/**
* The maximum number of times to retry failed requests, with a short exponential backoff
* between requests.
*
* Only the following error types are retried:
* - Connection errors (for example, due to a network connectivity problem)
* - 408 Request Timeout
* - 409 Conflict
* - 429 Rate Limit
* - 5xx Internal
*
* The API may also explicitly instruct the SDK to retry or not retry a request.
*
* 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 oauthAccessToken: String?,
private val profileAuth: ProfileAuth?,
) {
init {
if (checkJacksonVersionCompatibility) {
checkJacksonVersionCompatibility()
}
}
/**
* The base URL to use for every request.
*
* Defaults to the production environment: `https://api.smith.langchain.com/`.
*/
fun baseUrl(): String = baseUrl ?: PRODUCTION_URL
fun apiKey(): Optional<String> = Optional.ofNullable(apiKey)
fun tenantId(): Optional<String> = Optional.ofNullable(tenantId)
fun toBuilder() = Builder().from(this)
companion object {
const val PRODUCTION_URL = "https://api.smith.langchain.com/"
/**
* Returns a mutable builder for constructing an instance of [ClientOptions].
*
* The following fields are required:
* ```java
* .httpClient()
* ```
*/
@JvmStatic fun builder() = Builder()
/**
* Returns options configured using system properties and environment variables.
*
* @see Builder.fromEnv
*/
@JvmStatic fun fromEnv(): ClientOptions = builder().fromEnv().build()
}
/** A builder for [ClientOptions]. */
class Builder internal constructor() {
private var httpClient: HttpClient? = null
private var checkJacksonVersionCompatibility: Boolean = true
private var jsonMapper: JsonMapper = jsonMapper()
private var streamHandlerExecutor: Executor? = null
private var sleeper: Sleeper? = null
private var clock: Clock = Clock.systemUTC()
private var baseUrl: String? = null
private var headers: Headers.Builder = Headers.builder()
private var queryParams: QueryParams.Builder = QueryParams.builder()
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 oauthAccessToken: String? = null
private var profileAuth: ProfileAuth? = null
@JvmSynthetic
internal fun from(clientOptions: ClientOptions) = apply {
httpClient = clientOptions.originalHttpClient
checkJacksonVersionCompatibility = clientOptions.checkJacksonVersionCompatibility
jsonMapper = clientOptions.jsonMapper
streamHandlerExecutor = clientOptions.streamHandlerExecutor
sleeper = clientOptions.sleeper
clock = clientOptions.clock
baseUrl = clientOptions.baseUrl
headers = clientOptions.headers.toBuilder()
queryParams = clientOptions.queryParams.toBuilder()
responseValidation = clientOptions.responseValidation
timeout = clientOptions.timeout
maxRetries = clientOptions.maxRetries
autoBatchTracing = clientOptions.autoBatchTracing
logLevel = clientOptions.logLevel
apiKey = clientOptions.apiKey
tenantId = clientOptions.tenantId
oauthAccessToken = clientOptions.oauthAccessToken
profileAuth = clientOptions.profileAuth
}
/**
* The HTTP client to use in the SDK.
*
* Use the one published in `langsmith-java-client-okhttp` or implement your own.
*
* This class takes ownership of the client and closes it when closed.
*/
fun httpClient(httpClient: HttpClient) = apply {
this.httpClient = PhantomReachableClosingHttpClient(httpClient)
}
/**
* Whether to throw an exception if any of the Jackson versions detected at runtime are
* incompatible with the SDK's minimum supported Jackson version (2.13.4).
*
* Defaults to true. Use extreme caution when disabling this option. There is no guarantee
* that the SDK will work correctly when using an incompatible Jackson version.
*/
fun checkJacksonVersionCompatibility(checkJacksonVersionCompatibility: Boolean) = apply {
this.checkJacksonVersionCompatibility = checkJacksonVersionCompatibility
}
/**
* The Jackson JSON mapper to use for serializing and deserializing JSON.
*
* Defaults to [com.langchain.smith.core.jsonMapper]. The default is usually sufficient and
* rarely needs to be overridden.
*/
fun jsonMapper(jsonMapper: JsonMapper) = apply { this.jsonMapper = jsonMapper }
/**
* The executor to use for running [AsyncStreamResponse.Handler] callbacks.
*
* Defaults to a dedicated cached thread pool.
*
* This class takes ownership of the executor and shuts it down, if possible, when closed.
*/
fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
this.streamHandlerExecutor =
if (streamHandlerExecutor is ExecutorService)
PhantomReachableExecutorService(streamHandlerExecutor)
else streamHandlerExecutor
}
/**
* The interface to use for delaying execution, like during retries.
*
* This is primarily useful for using fake delays in tests.
*
* Defaults to real execution delays.
*
* This class takes ownership of the sleeper and closes it when closed.
*/
fun sleeper(sleeper: Sleeper) = apply { this.sleeper = PhantomReachableSleeper(sleeper) }
/**
* The clock to use for operations that require timing, like retries.
*
* This is primarily useful for using a fake clock in tests.
*
* Defaults to [Clock.systemUTC].
*/
fun clock(clock: Clock) = apply { this.clock = clock }
/**
* The base URL to use for every request.
*
* Defaults to the production environment: `https://api.smith.langchain.com/`.
*/
fun baseUrl(baseUrl: String?) = apply { this.baseUrl = baseUrl }
/** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */
fun baseUrl(baseUrl: Optional<String>) = baseUrl(baseUrl.getOrNull())
/**
* 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.
*/
fun responseValidation(responseValidation: Boolean) = apply {
this.responseValidation = responseValidation
}
/**
* Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
* retries.
*
* Defaults to [Timeout.default].
*/
fun timeout(timeout: Timeout) = apply { this.timeout = timeout }
/**
* Sets the maximum time allowed for a complete HTTP call, not including retries.
*
* See [Timeout.request] for more details.
*
* For fine-grained control, pass a [Timeout] object.
*/
fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build())
/**
* The maximum number of times to retry failed requests, with a short exponential backoff
* between requests.
*
* Only the following error types are retried:
* - Connection errors (for example, due to a network connectivity problem)
* - 408 Request Timeout
* - 409 Conflict
* - 429 Rate Limit
* - 5xx Internal
*
* The API may also explicitly instruct the SDK to retry or not retry a request.
*
* Defaults to 2.
*/
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)`. */
fun apiKey(apiKey: Optional<String>) = apiKey(apiKey.getOrNull())
fun tenantId(tenantId: String?) = apply { this.tenantId = tenantId }
/** Alias for calling [Builder.tenantId] with `tenantId.orElse(null)`. */
fun tenantId(tenantId: Optional<String>) = tenantId(tenantId.getOrNull())
fun headers(headers: Headers) = apply {
this.headers.clear()
putAllHeaders(headers)
}
fun headers(headers: Map<String, Iterable<String>>) = apply {
this.headers.clear()
putAllHeaders(headers)
}
fun putHeader(name: String, value: String) = apply { headers.put(name, value) }
fun putHeaders(name: String, values: Iterable<String>) = apply { headers.put(name, values) }
fun putAllHeaders(headers: Headers) = apply { this.headers.putAll(headers) }
fun putAllHeaders(headers: Map<String, Iterable<String>>) = apply {
this.headers.putAll(headers)
}
fun replaceHeaders(name: String, value: String) = apply { headers.replace(name, value) }
fun replaceHeaders(name: String, values: Iterable<String>) = apply {
headers.replace(name, values)
}
fun replaceAllHeaders(headers: Headers) = apply { this.headers.replaceAll(headers) }
fun replaceAllHeaders(headers: Map<String, Iterable<String>>) = apply {
this.headers.replaceAll(headers)
}
fun removeHeaders(name: String) = apply { headers.remove(name) }
fun removeAllHeaders(names: Set<String>) = apply { headers.removeAll(names) }
fun queryParams(queryParams: QueryParams) = apply {
this.queryParams.clear()
putAllQueryParams(queryParams)
}
fun queryParams(queryParams: Map<String, Iterable<String>>) = apply {
this.queryParams.clear()
putAllQueryParams(queryParams)
}
fun putQueryParam(key: String, value: String) = apply { queryParams.put(key, value) }
fun putQueryParams(key: String, values: Iterable<String>) = apply {
queryParams.put(key, values)
}
fun putAllQueryParams(queryParams: QueryParams) = apply {
this.queryParams.putAll(queryParams)
}
fun putAllQueryParams(queryParams: Map<String, Iterable<String>>) = apply {
this.queryParams.putAll(queryParams)
}
fun replaceQueryParams(key: String, value: String) = apply {
queryParams.replace(key, value)
}
fun replaceQueryParams(key: String, values: Iterable<String>) = apply {
queryParams.replace(key, values)
}
fun replaceAllQueryParams(queryParams: QueryParams) = apply {
this.queryParams.replaceAll(queryParams)
}
fun replaceAllQueryParams(queryParams: Map<String, Iterable<String>>) = apply {
this.queryParams.replaceAll(queryParams)
}
fun removeQueryParams(key: String) = apply { queryParams.remove(key) }
fun removeAllQueryParams(keys: Set<String>) = apply { queryParams.removeAll(keys) }
fun timeout(): Timeout = timeout
/**
* Updates configuration using system properties and environment variables.
*
* 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 |- |
* |`baseUrl` |`langchain.baseUrl` |`LANGSMITH_ENDPOINT` |true |`"https://api.smith.langchain.com/"`|
*
* System properties take precedence over environment variables.
*/
fun fromEnv() = apply {
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)
}
}
/**
* Returns an immutable instance of [ClientOptions].
*
* Further updates to this [Builder] will not mutate the returned instance.
*
* The following fields are required:
* ```java
* .httpClient()
* ```
*
* @throws IllegalStateException if any required field is unset.
*/
fun build(): ClientOptions {
val httpClient = checkRequired("httpClient", httpClient)
val streamHandlerExecutor =
streamHandlerExecutor
?: PhantomReachableExecutorService(
Executors.newCachedThreadPool(
object : ThreadFactory {
private val threadFactory: ThreadFactory =
Executors.defaultThreadFactory()
private val count = AtomicLong(0)
override fun newThread(runnable: Runnable): Thread =
threadFactory.newThread(runnable).also {
it.name =
"langchain-stream-handler-thread-${count.getAndIncrement()}"
}
}
)
)
val sleeper = sleeper ?: PhantomReachableSleeper(DefaultSleeper())
val headers = Headers.builder()
val queryParams = QueryParams.builder()
headers.put("X-Stainless-Lang", "java")
headers.put("X-Stainless-Arch", getOsArch())
headers.put("X-Stainless-OS", getOsName())
headers.put("X-Stainless-OS-Version", getOsVersion())
headers.put("X-Stainless-Package-Version", getPackageVersion())
headers.put("X-Stainless-Runtime", "JRE")
headers.put("X-Stainless-Runtime-Version", getJavaVersion())
headers.put("X-Stainless-Kotlin-Version", KotlinVersion.CURRENT.toString())
// We replace after all the default headers to allow end-users to overwrite them.
headers.replaceAll(this.headers.build())
queryParams.replaceAll(this.queryParams.build())
apiKey
?.takeIf { it.isNotEmpty() }
?.let {
headers.remove("Authorization")
headers.replace("X-API-Key", it)
}
if (apiKey.isNullOrEmpty()) {
oauthAccessToken
?.takeIf { it.isNotEmpty() }
?.let {
val currentHeaders = headers.build()
if (
currentHeaders.values("X-API-Key").all(String::isBlank) &&
currentHeaders.values("Authorization").all(String::isBlank)
) {
headers.replace("Authorization", "Bearer $it")
}
}
}
tenantId?.takeIf { it.isNotEmpty() }?.let { headers.replace("X-Tenant-Id", it) }
val retryingHttpClient =
RetryingHttpClient.builder()
.httpClient(
LoggingHttpClient.builder()
.httpClient(httpClient)
.clock(clock)
.level(logLevel)
.build()
)
.sleeper(sleeper)
.clock(clock)
.maxRetries(maxRetries)
.build()
return ClientOptions(
httpClient,
profileAuth?.let { ProfileAuthHttpClient(retryingHttpClient, it) }
?: retryingHttpClient,
checkJacksonVersionCompatibility,
jsonMapper,
streamHandlerExecutor,
sleeper,
clock,
baseUrl,
headers.build(),
queryParams.build(),
responseValidation,
timeout,
maxRetries,
autoBatchTracing,
logLevel,
apiKey,
tenantId,
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")
}
/**
* Closes these client options, relinquishing any underlying resources.
*
* This is purposefully not inherited from [AutoCloseable] because the client options are
* long-lived and usually should not be synchronously closed via try-with-resources.
*
* It's also usually not necessary to call this method at all. the default client automatically
* releases threads and connections if they remain idle, but if you are writing an application
* that needs to aggressively release unused resources, then you may call this method.
*/
fun close() {
httpClient.close()
(streamHandlerExecutor as? ExecutorService)?.shutdown()
sleeper.close()
}
}
@@ -0,0 +1,28 @@
package com.langchain.smith.core
import java.time.Duration
import java.util.Timer
import java.util.TimerTask
import java.util.concurrent.CompletableFuture
class DefaultSleeper : Sleeper {
private val timer = Timer("DefaultSleeper", true)
override fun sleep(duration: Duration) = Thread.sleep(duration.toMillis())
override fun sleepAsync(duration: Duration): CompletableFuture<Void> {
val future = CompletableFuture<Void>()
timer.schedule(
object : TimerTask() {
override fun run() {
future.complete(null)
}
},
duration.toMillis(),
)
return future
}
override fun close() = timer.cancel()
}
@@ -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
}
}
}
@@ -0,0 +1,180 @@
@file:JvmName("ObjectMappers")
package com.langchain.smith.core
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.cfg.CoercionAction
import com.fasterxml.jackson.databind.cfg.CoercionInputShape
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.type.LogicalType
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.kotlinModule
import java.io.InputStream
import java.time.DateTimeException
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoField
fun jsonMapper(): JsonMapper = JSON_MAPPER
private val JSON_MAPPER: JsonMapper =
JsonMapper.builder()
.addModule(kotlinModule())
.addModule(Jdk8Module())
.addModule(JavaTimeModule())
.addModule(
SimpleModule()
.addSerializer(InputStreamSerializer)
.addDeserializer(OffsetDateTime::class.java, LenientOffsetDateTimeDeserializer())
)
.withCoercionConfig(LogicalType.Boolean) {
it.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
.setCoercion(CoercionInputShape.String, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
}
.withCoercionConfig(LogicalType.Integer) {
it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
.setCoercion(CoercionInputShape.String, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
}
.withCoercionConfig(LogicalType.Float) {
it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
.setCoercion(CoercionInputShape.String, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
}
.withCoercionConfig(LogicalType.Textual) {
it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
}
.withCoercionConfig(LogicalType.DateTime) {
it.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
}
.withCoercionConfig(LogicalType.Array) {
it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
.setCoercion(CoercionInputShape.String, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
}
.withCoercionConfig(LogicalType.Collection) {
it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
.setCoercion(CoercionInputShape.String, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
}
.withCoercionConfig(LogicalType.Map) {
it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
.setCoercion(CoercionInputShape.String, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
}
.withCoercionConfig(LogicalType.POJO) {
it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
.setCoercion(CoercionInputShape.String, CoercionAction.Fail)
.setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
}
.serializationInclusion(JsonInclude.Include.NON_ABSENT)
.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.disable(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
.disable(MapperFeature.ALLOW_COERCION_OF_SCALARS)
.disable(MapperFeature.AUTO_DETECT_CREATORS)
.disable(MapperFeature.AUTO_DETECT_FIELDS)
.disable(MapperFeature.AUTO_DETECT_GETTERS)
.disable(MapperFeature.AUTO_DETECT_IS_GETTERS)
.disable(MapperFeature.AUTO_DETECT_SETTERS)
.build()
/** A serializer that serializes [InputStream] to bytes. */
private object InputStreamSerializer : BaseSerializer<InputStream>(InputStream::class) {
private fun readResolve(): Any = InputStreamSerializer
override fun serialize(
value: InputStream?,
gen: JsonGenerator?,
serializers: SerializerProvider?,
) {
if (value == null) {
gen?.writeNull()
} else {
value.use { gen?.writeBinary(it.readBytes()) }
}
}
}
/**
* A deserializer that can deserialize [OffsetDateTime] from datetimes, dates, and zoned datetimes.
*/
private class LenientOffsetDateTimeDeserializer :
StdDeserializer<OffsetDateTime>(OffsetDateTime::class.java) {
companion object {
private val DATE_TIME_FORMATTERS =
listOf(
DateTimeFormatter.ISO_LOCAL_DATE_TIME,
DateTimeFormatter.ISO_LOCAL_DATE,
DateTimeFormatter.ISO_ZONED_DATE_TIME,
)
}
override fun logicalType(): LogicalType = LogicalType.DateTime
override fun deserialize(p: JsonParser, context: DeserializationContext): OffsetDateTime {
val exceptions = mutableListOf<Exception>()
for (formatter in DATE_TIME_FORMATTERS) {
try {
val temporal = formatter.parse(p.text)
return when {
!temporal.isSupported(ChronoField.HOUR_OF_DAY) ->
LocalDate.from(temporal)
.atStartOfDay()
.atZone(ZoneId.of("UTC"))
.toOffsetDateTime()
!temporal.isSupported(ChronoField.OFFSET_SECONDS) ->
LocalDateTime.from(temporal).atZone(ZoneId.of("UTC")).toOffsetDateTime()
else -> OffsetDateTime.from(temporal)
}
} catch (e: DateTimeException) {
exceptions.add(e)
}
}
throw JsonParseException(p, "Cannot parse `OffsetDateTime` from value: ${p.text}").apply {
exceptions.forEach { addSuppressed(it) }
}
}
}
@@ -0,0 +1,33 @@
// File generated from our OpenAPI spec by Stainless.
package com.langchain.smith.core
/**
* An interface representing a single page, with items of type [T], from a paginated endpoint
* response.
*
* Implementations of this interface are expected to request additional pages synchronously. For
* asynchronous pagination, see the [PageAsync] interface.
*/
interface Page<T> {
/**
* Returns whether there's another page after this one.
*
* The method generally doesn't make requests so the result depends entirely on the data in this
* page. If a significant amount of time has passed between requesting this page and calling
* this method, then the result could be stale.
*/
fun hasNextPage(): Boolean
/**
* Returns the page after this one by making another request.
*
* @throws IllegalStateException if it's impossible to get the next page. This exception is
* avoidable by calling [hasNextPage] first.
*/
fun nextPage(): Page<T>
/** Returns the items in this page. */
fun items(): List<T>
}
@@ -0,0 +1,35 @@
// File generated from our OpenAPI spec by Stainless.
package com.langchain.smith.core
import java.util.concurrent.CompletableFuture
/**
* An interface representing a single page, with items of type [T], from a paginated endpoint
* response.
*
* Implementations of this interface are expected to request additional pages asynchronously. For
* synchronous pagination, see the [Page] interface.
*/
interface PageAsync<T> {
/**
* Returns whether there's another page after this one.
*
* The method generally doesn't make requests so the result depends entirely on the data in this
* page. If a significant amount of time has passed between requesting this page and calling
* this method, then the result could be stale.
*/
fun hasNextPage(): Boolean
/**
* Returns the page after this one by making another request.
*
* @throws IllegalStateException if it's impossible to get the next page. This exception is
* avoidable by calling [hasNextPage] first.
*/
fun nextPage(): CompletableFuture<out PageAsync<T>>
/** Returns the items in this page. */
fun items(): List<T>
}
@@ -0,0 +1,16 @@
package com.langchain.smith.core
import com.langchain.smith.core.http.Headers
import com.langchain.smith.core.http.QueryParams
/** An interface representing parameters passed to a service method. */
interface Params {
/** The full set of headers in the parameters, including both fixed and additional headers. */
fun _headers(): Headers
/**
* The full set of query params in the parameters, including both fixed and additional query
* params.
*/
fun _queryParams(): QueryParams
}
@@ -0,0 +1,56 @@
@file:JvmName("PhantomReachable")
package com.langchain.smith.core
import com.langchain.smith.errors.LangChainException
import java.lang.reflect.InvocationTargetException
/**
* Closes [closeable] when [observed] becomes only phantom reachable.
*
* This is a wrapper around a Java 9+ [java.lang.ref.Cleaner], or a no-op in older Java versions.
*/
@JvmSynthetic
internal fun closeWhenPhantomReachable(observed: Any, closeable: AutoCloseable) {
check(observed !== closeable) {
"`observed` cannot be the same object as `closeable` because it would never become phantom reachable"
}
closeWhenPhantomReachable(observed, closeable::close)
}
/**
* Calls [close] when [observed] becomes only phantom reachable.
*
* This is a wrapper around a Java 9+ [java.lang.ref.Cleaner], or a no-op in older Java versions.
*/
@JvmSynthetic
internal fun closeWhenPhantomReachable(observed: Any, close: () -> Unit) {
closeWhenPhantomReachable?.let { it(observed, close) }
}
private val closeWhenPhantomReachable: ((Any, () -> Unit) -> Unit)? by lazy {
try {
val cleanerClass = Class.forName("java.lang.ref.Cleaner")
val cleanerCreate = cleanerClass.getMethod("create")
val cleanerRegister =
cleanerClass.getMethod("register", Any::class.java, Runnable::class.java)
val cleanerObject = cleanerCreate.invoke(null);
{ observed, close ->
try {
cleanerRegister.invoke(cleanerObject, observed, Runnable { close() })
} catch (e: ReflectiveOperationException) {
if (e is InvocationTargetException) {
when (val cause = e.cause) {
is RuntimeException,
is Error -> throw cause
}
}
throw LangChainException("Unexpected reflective invocation failure", e)
}
}
} catch (e: ReflectiveOperationException) {
// We're running Java 8, which has no Cleaner.
null
}
}
@@ -0,0 +1,58 @@
package com.langchain.smith.core
import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
/**
* A delegating wrapper around an [ExecutorService] that shuts it down once it's only phantom
* reachable.
*
* This class ensures the [ExecutorService] is shut down even if the user forgets to do it.
*/
internal class PhantomReachableExecutorService(private val executorService: ExecutorService) :
ExecutorService {
init {
closeWhenPhantomReachable(this) { executorService.shutdown() }
}
override fun execute(command: Runnable) = executorService.execute(command)
override fun shutdown() = executorService.shutdown()
override fun shutdownNow(): MutableList<Runnable> = executorService.shutdownNow()
override fun isShutdown(): Boolean = executorService.isShutdown
override fun isTerminated(): Boolean = executorService.isTerminated
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean =
executorService.awaitTermination(timeout, unit)
override fun <T : Any?> submit(task: Callable<T>): Future<T> = executorService.submit(task)
override fun <T : Any?> submit(task: Runnable, result: T): Future<T> =
executorService.submit(task, result)
override fun submit(task: Runnable): Future<*> = executorService.submit(task)
override fun <T : Any?> invokeAll(
tasks: MutableCollection<out Callable<T>>
): MutableList<Future<T>> = executorService.invokeAll(tasks)
override fun <T : Any?> invokeAll(
tasks: MutableCollection<out Callable<T>>,
timeout: Long,
unit: TimeUnit,
): MutableList<Future<T>> = executorService.invokeAll(tasks, timeout, unit)
override fun <T : Any?> invokeAny(tasks: MutableCollection<out Callable<T>>): T =
executorService.invokeAny(tasks)
override fun <T : Any?> invokeAny(
tasks: MutableCollection<out Callable<T>>,
timeout: Long,
unit: TimeUnit,
): T = executorService.invokeAny(tasks, timeout, unit)
}
@@ -0,0 +1,23 @@
package com.langchain.smith.core
import java.time.Duration
import java.util.concurrent.CompletableFuture
/**
* A delegating wrapper around a [Sleeper] that closes it once it's only phantom reachable.
*
* This class ensures the [Sleeper] is closed even if the user forgets to do it.
*/
internal class PhantomReachableSleeper(private val sleeper: Sleeper) : Sleeper {
init {
closeWhenPhantomReachable(this, sleeper)
}
override fun sleep(duration: Duration) = sleeper.sleep(duration)
override fun sleepAsync(duration: Duration): CompletableFuture<Void> =
sleeper.sleepAsync(duration)
override fun close() = sleeper.close()
}
@@ -0,0 +1,24 @@
@file:JvmName("PrepareRequest")
package com.langchain.smith.core
import com.langchain.smith.core.http.HttpRequest
import java.util.concurrent.CompletableFuture
@JvmSynthetic
internal fun HttpRequest.prepare(clientOptions: ClientOptions, params: Params): HttpRequest =
toBuilder()
.putAllQueryParams(clientOptions.queryParams)
.replaceAllQueryParams(params._queryParams())
.putAllHeaders(clientOptions.headers)
.replaceAllHeaders(params._headers())
.build()
@JvmSynthetic
internal fun HttpRequest.prepareAsync(
clientOptions: ClientOptions,
params: Params,
): CompletableFuture<HttpRequest> =
// This async version exists to make it easier to add async specific preparation logic in the
// future.
CompletableFuture.completedFuture(prepare(clientOptions, params))
@@ -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?,
)
@@ -0,0 +1,61 @@
@file:JvmName("Properties")
package com.langchain.smith.core
import com.langchain.smith.client.LangsmithClient
import org.slf4j.LoggerFactory
private val logger = LoggerFactory.getLogger("com.langchain.smith.core.Properties")
fun getOsArch(): String {
val osArch = System.getProperty("os.arch")
return when (osArch) {
null -> "unknown"
"i386",
"x32",
"x86" -> "x32"
"amd64",
"x86_64" -> "x64"
"arm" -> "arm"
"aarch64" -> "arm64"
else -> "other:$osArch"
}
}
fun getOsName(): String {
val osName = System.getProperty("os.name")
val vendorUrl = System.getProperty("java.vendor.url")
return when {
osName == null -> "Unknown"
osName.startsWith("Linux") && vendorUrl == "http://www.android.com/" -> "Android"
osName.startsWith("Linux") -> "Linux"
osName.startsWith("Mac OS") -> "MacOS"
osName.startsWith("Windows") -> "Windows"
else -> "Other:$osName"
}
}
fun getOsVersion(): String = System.getProperty("os.version", "unknown") ?: "unknown"
fun getPackageVersion(): String {
try {
val props = java.util.Properties()
LangsmithClient::class
.java
.classLoader
.getResourceAsStream("com/langchain/smith/version.properties")
?.use { props.load(it) }
props.getProperty("version")?.let {
return it
}
} catch (e: Exception) {
logger.warn("Failed to read version from version.properties", e)
}
return LangsmithClient::class.java.`package`.implementationVersion ?: "unknown"
}
fun getJavaVersion(): String = System.getProperty("java.version", "unknown") ?: "unknown"

Some files were not shown because too many files have changed in this diff Show More