mirror of
https://github.com/langchain-ai/langsmith-java.git
synced 2026-07-25 21:25:45 -04:00
Compare commits
573 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2775de9d25 | |||
| 2ab482c2d3 | |||
| b1649d975e | |||
| aece72d035 | |||
| aa4be05c11 | |||
| 8aded40800 | |||
| 6165434d21 | |||
| 95da1e9b94 | |||
| eace8c3f99 | |||
| 056dbd3673 | |||
| e7fe407f74 | |||
| f6608288d4 | |||
| b072b2e2fc | |||
| 391b75ca3b | |||
| 42326ef486 | |||
| b6d079b991 | |||
| 804022edf1 | |||
| feb140e343 | |||
| 0d0a8f5ffb | |||
| 57da309643 | |||
| 388984c005 | |||
| 283c4c2bcf | |||
| b8f1caebe1 | |||
| b64cfc2ca8 | |||
| 101472f4ce | |||
| df8f95af97 | |||
| 6720c1f0e4 | |||
| 2f1ca870dd | |||
| 7ba0bad56f | |||
| 68707629d2 | |||
| 3880cb324a | |||
| 37d650a362 | |||
| cff262179d | |||
| a19f9aae0b | |||
| 1409dd89d8 | |||
| 839107858c | |||
| 8eb067064e | |||
| 306a079479 | |||
| 8b1dff9a1e | |||
| 50525423cc | |||
| d53da42ffa | |||
| cbc00807ee | |||
| 2cf0ff6e08 | |||
| 8e2a0583bf | |||
| 63ca829e51 | |||
| d35ada22c7 | |||
| 2c9704f3c0 | |||
| b2ed882d0e | |||
| 99fe7dbe33 | |||
| c3764a059c | |||
| 3f086d2302 | |||
| ca210b2c66 | |||
| e639046090 | |||
| 1893f83cf2 | |||
| 63bde3c6e4 | |||
| 9cfd26bef9 | |||
| 17551decf7 | |||
| 0a29041897 | |||
| 12185beb4c | |||
| 18d7867206 | |||
| a80e55d510 | |||
| b8f033e3d9 | |||
| 38646cb1fa | |||
| 59d3e33e71 | |||
| c35c92289b | |||
| c22f69aac3 | |||
| 57f86d2e60 | |||
| 141e3e5603 | |||
| 5d5112753c | |||
| 13b36e3f11 | |||
| fcc7ab733b | |||
| a07babf48b | |||
| fdba8e6123 | |||
| bf2443da84 | |||
| 3bc6173328 | |||
| d71cff9f59 | |||
| e2fc8a84dd | |||
| 8e17124b68 | |||
| 6ab6fa281f | |||
| 585b12dd26 | |||
| 4513b3ffb0 | |||
| b00f843bc8 | |||
| 5b3f2edfce | |||
| f6ea8673f6 | |||
| 539f2893ea | |||
| e3b478437e | |||
| c01f895e57 | |||
| 6b52583c7c | |||
| 384c001825 | |||
| a00b1f18ca | |||
| 48b3b08984 | |||
| 7a1922239b | |||
| 8b752b109b | |||
| 636b67ce06 | |||
| 2d4900b16b | |||
| 4d45358e0e | |||
| 10a1f9bcbb | |||
| 2f0ce40b86 | |||
| bbd15dab2c | |||
| 2c08fe0ecb | |||
| 7d9a2fafd4 | |||
| ab6c153520 | |||
| 34f1c52fc1 | |||
| d1420efd7a | |||
| cdbd36936f | |||
| 49bf68d747 | |||
| 387526fd3a | |||
| e88560861c | |||
| cfed196979 | |||
| 70edc4514c | |||
| 6f1ae304fa | |||
| 84e02f96b4 | |||
| 65b5b9ae40 | |||
| a82b5c88fc | |||
| e097147ff3 | |||
| ba419de13e | |||
| 17bb73a006 | |||
| 48c283c7a7 | |||
| 3c36fa9f58 | |||
| 49039f2770 | |||
| 54d105ef91 | |||
| b963cab6c6 | |||
| 734a8285cd | |||
| 152169f4e1 | |||
| ac8da0f77c | |||
| 7f24878418 | |||
| 3c218e98bc | |||
| 2ddc687610 | |||
| f00fef565e | |||
| 87e3acbb7b | |||
| 280a37489d | |||
| 77f12e3a6e | |||
| b86e854687 | |||
| fd2980ce69 | |||
| e480cd4104 | |||
| 3f3a0c9c30 | |||
| 77d19d3241 | |||
| 5a6bddd241 | |||
| 10f1d65063 | |||
| 79647d1579 | |||
| f3ce273a02 | |||
| bbbcf7bf40 | |||
| f859585592 | |||
| 370b940a1c | |||
| ad6daecac9 | |||
| 6b25ea38a8 | |||
| 8b85e16472 | |||
| 7cb70fc3e3 | |||
| cb519f93d4 | |||
| 6bbe522f0a | |||
| bb9f973f41 | |||
| da0fc7b507 | |||
| 9c85b0cfd1 | |||
| 25b9aacfda | |||
| 3bd596cefa | |||
| 334fe4ef56 | |||
| 744987978d | |||
| c904c2d278 | |||
| 1540e83048 | |||
| 513d207b80 | |||
| 48a0841016 | |||
| 090c06bbaa | |||
| ea5f34e155 | |||
| 14634be733 | |||
| 3220bc56d5 | |||
| 9ccfc6a350 | |||
| 9f90d52312 | |||
| 4e86f4ee9e | |||
| f3d8fdeccf | |||
| 4ba75e7776 | |||
| 35bbb77c96 | |||
| ad80d1105b | |||
| 12a841eeb4 | |||
| 19c9b420ea | |||
| 53f2092dcd | |||
| 81f7359cac | |||
| 5c4f4acb92 | |||
| 80dceff65a | |||
| b5b42f69a3 | |||
| c794cc215d | |||
| d07b7f3f40 | |||
| 305cae3bef | |||
| dd6d82830b | |||
| 9491540375 | |||
| 22ed526472 | |||
| f7f2672957 | |||
| eaf31118a5 | |||
| da3535ded5 | |||
| 2796973131 | |||
| d977e55ba6 | |||
| 7b9a6f2b09 | |||
| 219e966da6 | |||
| d8873a4cce | |||
| 2ce327828b | |||
| b0f3b5aab6 | |||
| a54a91735b | |||
| 959d619ee0 | |||
| 960ee71498 | |||
| 26b3f9cb2f | |||
| d6e265a7df | |||
| 315a4bd074 | |||
| bd1b0e8b59 | |||
| 57116f2104 | |||
| 554e497084 | |||
| c8873ff34e | |||
| 12e572bdd1 | |||
| a91eece7d1 | |||
| d3a153ef43 | |||
| 8705818e81 | |||
| d272832132 | |||
| 0cf53d62a4 | |||
| 8336c6349a | |||
| 382da9a537 | |||
| ad75c9789b | |||
| f6d0b66f60 | |||
| 6666c85031 | |||
| 8f1fc7bf69 | |||
| f09f2c48b3 | |||
| 3517baad85 | |||
| a42751fe92 | |||
| bca5f1858b | |||
| 43023605f0 | |||
| f247ab1bf3 | |||
| 4461ffbf99 | |||
| bb060d0897 | |||
| 3e731df52b | |||
| b9ce5af2d4 | |||
| 3eed0e974f | |||
| c616f1a7af | |||
| c6e950f613 | |||
| 2d81794530 | |||
| 33152dc37b | |||
| a1533b5161 | |||
| c9238e7d8a | |||
| 8c1f5f27ad | |||
| 06771e7b9e | |||
| 9b3cd93e74 | |||
| 0ff2f3175f | |||
| f06475795a | |||
| 81ce4fe1f3 | |||
| 6c35351e46 | |||
| 7256a6ca4a | |||
| feb7baba86 | |||
| 4baa783b9b | |||
| 97f40ac895 | |||
| 7d80a1978c | |||
| 92ce263397 | |||
| 4466c613fc | |||
| 848c0a5751 | |||
| 1d2e391160 | |||
| dab7b73dc9 | |||
| 59ea961218 | |||
| 873396aceb | |||
| f12b234a80 | |||
| a2520117a5 | |||
| 10946e186a | |||
| 9bd1c8c310 | |||
| 44a2308df3 | |||
| 765bad3142 | |||
| 41fcd7cec9 | |||
| f102c9929d | |||
| 6866c4fdfe | |||
| 45dd9a2727 | |||
| 7512302dd4 | |||
| 98e16a283e | |||
| 8cdc61ab86 | |||
| 3a1a72f559 | |||
| 76da93f2c0 | |||
| cf4696d51a | |||
| d99b42f8af | |||
| 19b171e07e | |||
| d02088b5c9 | |||
| 8c1c7d1930 | |||
| ee9eec9529 | |||
| 000f84af61 | |||
| 115ecd9065 | |||
| a5822f60d0 | |||
| ea263dc48f | |||
| 2c88ad1bb9 | |||
| c4b69463ce | |||
| a797755dc6 | |||
| 750cb9727c | |||
| 2e9af6f60c | |||
| ea568e078f | |||
| 793ca57f56 | |||
| f0673a8381 | |||
| a3018d05fc | |||
| e11fa749b7 | |||
| 24bca7acbd | |||
| 3dfde0eac4 | |||
| 66495749d9 | |||
| cac6931b8f | |||
| 929a7d700f | |||
| fb2260d06f | |||
| a40f07edcd | |||
| 7068b7797c | |||
| 379af11f42 | |||
| 974472d764 | |||
| cd7a03e546 | |||
| b528d8b51a | |||
| 9a58e84cda | |||
| e6d4a0e83d | |||
| 0871a9da0f | |||
| 86774475b2 | |||
| 7b84fdac3d | |||
| 71cba9d0e2 | |||
| bd733743ac | |||
| bc76417677 | |||
| 84e2de05ce | |||
| 5b3bba1323 | |||
| 2b6c2ed358 | |||
| 88e87f3eae | |||
| 40409eb155 | |||
| 4825017b86 | |||
| f1fa362930 | |||
| fdb1ee6552 | |||
| 0756d8db46 | |||
| e4a386aa13 | |||
| 3b85a7e71f | |||
| 021a770e89 | |||
| c358fb4281 | |||
| 9c4625d82c | |||
| e9125a7bdf | |||
| 74b82f7625 | |||
| 449caf4826 | |||
| e2583dfe8d | |||
| 52f37011ed | |||
| 4cde1b3db1 | |||
| bb7ebc08af | |||
| 256dfab0e1 | |||
| 03270ff2a5 | |||
| 1902c228dd | |||
| 3c79eea5dd | |||
| cff199b8fa | |||
| 88af6cd36e | |||
| 47f39a40e4 | |||
| 79f436a6a3 | |||
| 60e0ce825e | |||
| 54d56e2915 | |||
| 23597a4e3f | |||
| e55e8c7fe2 | |||
| 92d87eab22 | |||
| d40dd2c26f | |||
| f0f5ad3c41 | |||
| da14cc6869 | |||
| a2648390af | |||
| 43741790f7 | |||
| 10e2af9aea | |||
| 04508f89a2 | |||
| 4c2738351e | |||
| b34611a2d6 | |||
| 0a7b44831a | |||
| 49918bb9ab | |||
| 3c7a62f01e | |||
| 501beab343 | |||
| fff7a4663f | |||
| 398c86771f | |||
| 2d298cd81f | |||
| 7050baf4e1 | |||
| f0b2abc659 | |||
| 7ef6b9f8c7 | |||
| 5c4cf4f0f7 | |||
| ded153ef51 | |||
| 5572d09902 | |||
| 2f56b7d053 | |||
| fe3381c85e | |||
| 4518614715 | |||
| b8f700dbc3 | |||
| 83aa1fe5b7 | |||
| 7e0b329e90 | |||
| 070629d380 | |||
| 6207c8bf2d | |||
| fbba779511 | |||
| b50bd7233c | |||
| 364329ec65 | |||
| 87d0bfb614 | |||
| 7863108ae3 | |||
| 1755b4ae62 | |||
| 034daaf52e | |||
| d267892e0d | |||
| 6bc095cc83 | |||
| 249b9c66c8 | |||
| 737f8d2cf2 | |||
| 4220932794 | |||
| d4cab9eef6 | |||
| 0a5f8a76be | |||
| 32eabcac91 | |||
| 7a644105f6 | |||
| 9de590ad96 | |||
| 2685825993 | |||
| cb8962fb25 | |||
| 4c0258d01f | |||
| ded1673fa6 | |||
| 5597676151 | |||
| dd880b1002 | |||
| 6eba79b3c5 | |||
| 299a93a87b | |||
| 5790fe6d94 | |||
| cda4be05e5 | |||
| 4e90a43364 | |||
| e81435a82f | |||
| dff44aec06 | |||
| b3499192a6 | |||
| e2b33d224a | |||
| c13b6cd619 | |||
| 154488c893 | |||
| db74f4be82 | |||
| 7dbd1c0053 | |||
| c7e00f585f | |||
| 078bbb3740 | |||
| b8579d5973 | |||
| 734fbb4396 | |||
| 6cd87f48db | |||
| 2232c2d4a0 | |||
| 77779ce4db | |||
| ae64e429ec | |||
| abce02c340 | |||
| d0d7a93d4a | |||
| b065e778cb | |||
| 802f5a118c | |||
| 3fdd586165 | |||
| e020cc02cc | |||
| e9166fe90a | |||
| 9d34d35a47 | |||
| 2f488d90e1 | |||
| 205a6dd115 | |||
| f95d9c82fe | |||
| 73a79dd3c5 | |||
| e1e47762f1 | |||
| de912b531c | |||
| 217929c1f0 | |||
| fa00021a3b | |||
| b2488993e9 | |||
| a396ce53f3 | |||
| 05c6e74007 | |||
| 1af52629ac | |||
| ac205104d1 | |||
| acafcfa885 | |||
| 94b13b3709 | |||
| 09c67642b9 | |||
| bf3497554e | |||
| 1f1c4fc8d3 | |||
| 4382c85821 | |||
| 2e14e46770 | |||
| 86480af3e3 | |||
| 8afffa6275 | |||
| ddd1ac02f2 | |||
| 6865ca8717 | |||
| 6fc4ddd1be | |||
| 8c6eef991b | |||
| a3c380a7a6 | |||
| ea7d94b6d0 | |||
| 284d184316 | |||
| 21cf86157c | |||
| 866970e8e0 | |||
| 63647df027 | |||
| 21969a61f9 | |||
| 3dcaaecdf1 | |||
| 16f01c8edb | |||
| cd244b3fe9 | |||
| cd54282023 | |||
| 19c2cf595a | |||
| 6321686a8e | |||
| e260744b87 | |||
| 431e6c6e4c | |||
| f73ad91858 | |||
| 1e2825f8ae | |||
| fefc3d7a09 | |||
| c834f2571b | |||
| 35c381c47b | |||
| e975a27fc9 | |||
| 4a481e4d21 | |||
| ff1d89a0bb | |||
| 1c57a55b2e | |||
| 9b596ec763 | |||
| e640c7f89a | |||
| 43ffea37a2 | |||
| 05b0e5757c | |||
| b7182fe85e | |||
| fac0d52aca | |||
| 4d5b47e134 | |||
| 9c71ed558a | |||
| e24de3aae2 | |||
| 1160c21dba | |||
| 494e91bba2 | |||
| 1967627dd4 | |||
| 02d5b724de | |||
| 3a50ff913a | |||
| 5cc07fa08b | |||
| 9d61b1d041 | |||
| 524a19f4de | |||
| 0620eb6e49 | |||
| d62a5ebc2a | |||
| 24680beb2f | |||
| 91e9267c8d | |||
| 5f71223bac | |||
| f7fb8ad3a7 | |||
| bf6e6a11e0 | |||
| 3da5f2870c | |||
| 3e0c4b89d9 | |||
| a5dc27ed22 | |||
| b4e170da64 | |||
| 3394cf2248 | |||
| 0d00830db9 | |||
| 5e760c6964 | |||
| adc0c4129e | |||
| 0b718da44a | |||
| 77433e6705 | |||
| 603db8714f | |||
| a48888e46d | |||
| 5ff053706d | |||
| f6922d4b42 | |||
| 40e12e4c9a | |||
| 3a370dc66e | |||
| 8b90997964 | |||
| 05e6f61a16 | |||
| f513d26879 | |||
| b780052150 | |||
| 2c5e2fe014 | |||
| 83c1dd7caa | |||
| 440c7a2db9 | |||
| 70bfdc0d47 | |||
| ba17bc4560 | |||
| a0975b8cca | |||
| 20f9cff6a4 | |||
| 6225ccac5a | |||
| 22881d97b2 | |||
| e2ac9ba053 | |||
| e1bc4b0515 | |||
| 7cc251affe | |||
| 61bfe11f7c | |||
| 9e876f623d | |||
| 1c60f27254 | |||
| aa5e0f7de3 | |||
| 922058acd6 | |||
| ee24bb3179 | |||
| d3f8fb2c09 | |||
| b189917b25 | |||
| 84ef64e15f | |||
| fa22fd702b | |||
| 33019db58b | |||
| 29f6bb2b1e | |||
| a6af50c565 | |||
| 3a4ea2d150 | |||
| c6b8b737b6 | |||
| aa6b46d44f | |||
| eb0044a2a4 | |||
| 78c00fce7d | |||
| c3f5b81742 | |||
| 9a2269151d | |||
| 66929843d2 | |||
| 14ee7a736b | |||
| 10345f26de | |||
| 2ffbd4ec60 | |||
| 503e9927dc | |||
| 8759286482 | |||
| f3f81cc6ff | |||
| a098ba3e9c | |||
| 18b32186ba | |||
| c34959c24e | |||
| bfc1bfe92b | |||
| 8842ad7d4c | |||
| d33b9aab8a | |||
| 6014dab0bb | |||
| 36e65eff30 | |||
| 0ffa490540 | |||
| 8b462e174e | |||
| 7953ae06de | |||
| 134c02266d | |||
| 79999bf542 | |||
| 2af08be726 | |||
| db651329e5 | |||
| 2a9fe2b42e |
@@ -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
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
#
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
@@ -0,0 +1,12 @@
|
||||
name: "CodeQL config"
|
||||
|
||||
queries:
|
||||
- uses: security-extended
|
||||
|
||||
paths-ignore:
|
||||
- '**/test/**'
|
||||
- '**/tests/**'
|
||||
- '**/*Test.java'
|
||||
- '**/*Test.kt'
|
||||
- '**/build/**'
|
||||
- '.gradle/**'
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
@@ -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.
Binary file not shown.
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.
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.0.1-alpha.0"
|
||||
".": "0.1.0-beta.19"
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
configured_endpoints: 82
|
||||
configured_endpoints: 149
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
# Agent Guidelines
|
||||
|
||||
Code conventions and patterns for this project, learned from review feedback.
|
||||
|
||||
## Code structure
|
||||
|
||||
### Break up complex functions with helpers
|
||||
|
||||
When a function has deeply nested logic or multiple concerns, extract helpers. Use `flatMap` + small named functions instead of imperative loops with nested `when`/`if`:
|
||||
|
||||
```kotlin
|
||||
// Good
|
||||
fun format(variables: Map<String, Any>): PromptMessages {
|
||||
val formatted = messages.flatMap { msg ->
|
||||
if (msg.isPlaceholder()) expandPlaceholder(msg, variables)
|
||||
else listOf(PromptMessage.withTemplate(msg, msg.format(variables)))
|
||||
}
|
||||
return PromptMessages(formatted, inputVariables, outputSchema)
|
||||
}
|
||||
|
||||
private fun expandPlaceholder(msg: PromptMessage, variables: Map<String, Any>): List<PromptMessage> {
|
||||
val items = variables[msg.template] as? List<*> ?: return emptyList()
|
||||
return items.mapNotNull(::toPromptMessage)
|
||||
}
|
||||
|
||||
// Bad — deeply nested imperative loop
|
||||
fun format(variables: Map<String, Any>): PromptMessages {
|
||||
val formatted = mutableListOf<PromptMessage>()
|
||||
for (msg in messages) {
|
||||
if (msg.isPlaceholder()) {
|
||||
val value = variables[msg.template]
|
||||
if (value is List<*>) {
|
||||
for (item in value) {
|
||||
when (item) {
|
||||
is PromptMessage -> formatted.add(item)
|
||||
is Map<*, *> -> { /* 15 more lines */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Kotlin idioms
|
||||
|
||||
### Prefer immutable collection transformations
|
||||
|
||||
Avoid mutable accumulators when `map`, `filter`, `partition`, `associate`, `buildMap`, or `buildList` express the same logic clearly. Use mutation only when it materially improves readability, performance, or is required by an API.
|
||||
|
||||
### Use `buildMap` / `buildList` instead of mutable + convert
|
||||
|
||||
```kotlin
|
||||
// Good
|
||||
val messages = items.map { msg ->
|
||||
buildMap<String, String> {
|
||||
put("role", msg.role)
|
||||
put("content", msg.content)
|
||||
msg.toolCallId?.let { put("tool_call_id", it) }
|
||||
}
|
||||
}
|
||||
|
||||
// Bad — unnecessary mutable/immutable conversion
|
||||
val messages = items.map { msg ->
|
||||
val base = mutableMapOf<String, String>(
|
||||
"role" to msg.role,
|
||||
"content" to msg.content,
|
||||
)
|
||||
if (msg.toolCallId != null) {
|
||||
base["tool_call_id"] = msg.toolCallId
|
||||
}
|
||||
base.toMap()
|
||||
}
|
||||
```
|
||||
|
||||
### Use `buildList` for conditional `toString()` parts
|
||||
|
||||
```kotlin
|
||||
// Good
|
||||
override fun toString(): String {
|
||||
val parts = buildList {
|
||||
add("messages=[${messages.joinToString(", ")}]")
|
||||
if (inputVariables.isNotEmpty()) add("inputVariables=$inputVariables")
|
||||
commitHash?.let { add("commitHash=$it") }
|
||||
outputSchema?.let { add("outputSchema=${it["title"] ?: "..."}") }
|
||||
}
|
||||
return "Prompt{${parts.joinToString(", ")}}"
|
||||
}
|
||||
|
||||
// Bad — chained ternary string concatenation
|
||||
override fun toString(): String =
|
||||
"Prompt{messages=[...]" +
|
||||
(if (commitHash != null) ", commitHash=$commitHash" else "") +
|
||||
(if (hasOutputSchema()) ", outputSchema=..." else "") +
|
||||
"}"
|
||||
```
|
||||
|
||||
### Prefer extension functions over type casts
|
||||
|
||||
When adding behavior to a type you don't own, use a `private` extension function instead of casting to an implementation type:
|
||||
|
||||
```kotlin
|
||||
// Good — extension function, no cast needed
|
||||
private fun Stream<*>.withErrorTracking(
|
||||
errorRef: AtomicReference<Throwable>,
|
||||
exhaustedRef: AtomicBoolean,
|
||||
): Stream<Any?> { ... }
|
||||
|
||||
val instrumented = result.withErrorTracking(iterationError, streamExhausted)
|
||||
|
||||
// Bad — casting to implementation type
|
||||
val instrumented = wrapStreamWithErrorCapture(result, iterationError, streamExhausted)
|
||||
// or worse:
|
||||
(runs as? RunServiceImpl)?.flush()
|
||||
```
|
||||
|
||||
### Use `partition` instead of double `filter`
|
||||
|
||||
```kotlin
|
||||
// Good — single pass
|
||||
val (systemMessages, nonSystemMessages) =
|
||||
messages.partition { it.role == Role.SYSTEM }
|
||||
|
||||
// Bad — iterates the list twice
|
||||
val systemMessages = messages.filter { it.role == Role.SYSTEM }
|
||||
val nonSystemMessages = messages.filter { it.role != Role.SYSTEM }
|
||||
```
|
||||
|
||||
### Use parameterized tests for table-driven cases
|
||||
|
||||
When multiple tests share the exact same structure (input → assert same fields), use `@ParameterizedTest` with `@MethodSource`:
|
||||
|
||||
```kotlin
|
||||
data class Case(val input: String, val expected: String)
|
||||
|
||||
@ParameterizedTest(name = "{index}: \"{0}\"")
|
||||
@MethodSource("cases")
|
||||
fun myTest(case: Case) {
|
||||
assertThat(transform(case.input)).isEqualTo(case.expected)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun cases(): Stream<Case> = Stream.of(
|
||||
Case("input1", "expected1"),
|
||||
Case("input2", "expected2"),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Only do this when every test has the same assertion shape. If tests have different setup or assertions, keep them as individual `@Test` methods.
|
||||
|
||||
### Extract test assertion helpers to reduce repetition
|
||||
|
||||
When the same assertion pattern appears across many tests, extract a helper:
|
||||
|
||||
```kotlin
|
||||
// Good — readable, DRY
|
||||
private fun assertMessage(msg: Map<String, String>, role: String, content: String) {
|
||||
assertThat(msg["role"]).isEqualTo(role)
|
||||
assertThat(msg["content"]).isEqualTo(content)
|
||||
}
|
||||
|
||||
assertMessage(result.messages[0], "system", "You are helpful.")
|
||||
assertMessage(result.messages[1], "user", "Hello")
|
||||
|
||||
// Bad — verbose, repetitive
|
||||
assertThat(result.messages[0]).isEqualTo(mapOf("role" to "system", "content" to "You are helpful."))
|
||||
assertThat(result.messages[1]).isEqualTo(mapOf("role" to "user", "content" to "Hello"))
|
||||
```
|
||||
|
||||
## Formatting and linting
|
||||
|
||||
```bash
|
||||
./gradlew :langsmith-java-core:formatKotlin
|
||||
./gradlew lintKotlin
|
||||
```
|
||||
|
||||
The project uses ktfmt with `--kotlinlang-style`.
|
||||
|
||||
## Testing
|
||||
|
||||
### Don't add comments that restate the test name
|
||||
|
||||
Test function names should be descriptive enough on their own. Don't add comments that repeat what the name already says:
|
||||
|
||||
```kotlin
|
||||
// Good — name is self-documenting
|
||||
@Test
|
||||
fun parseLegacyPromptTemplateWithTemplateFormat() {
|
||||
val manifest = ...
|
||||
}
|
||||
|
||||
// Bad — comment restates the function name
|
||||
@Test
|
||||
fun parseLegacyPromptTemplateWithTemplateFormat() {
|
||||
// Legacy PromptTemplate format with template_format field
|
||||
val manifest = ...
|
||||
}
|
||||
```
|
||||
|
||||
### Running tests
|
||||
|
||||
Try to run targeted tests matching the code you changed:
|
||||
|
||||
```bash
|
||||
# Run tests for the package you changed
|
||||
./gradlew :langsmith-java-core:test --tests "com.langchain.smith.prompts.*" --rerun
|
||||
|
||||
# Run a single test class
|
||||
./gradlew :langsmith-java-core:test --tests "com.langchain.smith.prompts.ManifestParserTest" --rerun
|
||||
|
||||
# Run a single test method
|
||||
./gradlew :langsmith-java-core:test --tests "com.langchain.smith.prompts.ManifestParserTest.parsePromptTemplate" --rerun
|
||||
|
||||
# See println output
|
||||
./gradlew :langsmith-java-core:test --tests "..." --rerun --info
|
||||
```
|
||||
|
||||
Only run the full suite (`./gradlew :langsmith-java-core:test --rerun`) before finalizing a PR or after large cross-cutting changes.
|
||||
|
||||
### Integration tests
|
||||
|
||||
Integration tests require environment variables:
|
||||
|
||||
```bash
|
||||
export LANGSMITH_API_KEY="lsv2_pt_..."
|
||||
export OPENAI_API_KEY="sk-..."
|
||||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||||
```
|
||||
|
||||
Tests skip gracefully via `assumeTrue` if keys are missing.
|
||||
|
||||
## Code style
|
||||
|
||||
- For cross-method concurrency coordination, prefer an explicit named `ReentrantLock` over `synchronized` when review clarity matters. Use Kotlin's `withLock { ... }` extension instead of manual `lock()` / `try` / `finally { unlock() }` unless explicit lock management is required. Keep the locked section minimal and do slow/blocking work outside the lock.
|
||||
- Choose the simplest concurrency primitive that fits the state being protected:
|
||||
- Use atomic types (`AtomicBoolean`, `AtomicInteger`, etc.) for simple flags, counters, and compare-and-set state.
|
||||
- Use `synchronized` only for small, local critical sections where a named lock would not improve clarity.
|
||||
- Use `ReentrantReadWriteLock` when reads are frequent, writes are infrequent, and concurrent reads materially help.
|
||||
- Use coroutine `Mutex.withLock { ... }` for coroutine-based concurrency instead of blocking thread locks.
|
||||
- `toString()` should be single-line, following the `ClassName{field=value, field=value}` convention used by the rest of the SDK.
|
||||
- Avoid `@Suppress("UNCHECKED_CAST")` — restructure code to use safe patterns (`as? String`, `is Map<*, *>` with `entries.associate`, etc). When unavoidable (e.g. generic type erasure after an `is` check), add a comment explaining why the cast is safe.
|
||||
- Use named arguments for constructor/function calls with 2+ parameters, especially when types could be confused:
|
||||
```kotlin
|
||||
// Good
|
||||
PromptMessage(
|
||||
role = PromptMessage.Role.HUMAN,
|
||||
template = template,
|
||||
templateFormat = templateFormat,
|
||||
)
|
||||
|
||||
// Bad — positional args are ambiguous
|
||||
PromptMessage(PromptMessage.Role.HUMAN, template, templateFormat = templateFormat)
|
||||
```
|
||||
- Name functions from the caller's perspective — describe what the caller gets, not what the function does internally. Prefer `stream.withErrorTracking()` over `wrapStreamWithErrorCapture(stream)`.
|
||||
- When an `Optional` has a fallback default, use `orElse(default)` directly instead of `orElse(null) ?: default`:
|
||||
```kotlin
|
||||
// Good — default goes straight into orElse
|
||||
kwargs["template_format"]?.asString()?.orElse("f-string") ?: "f-string"
|
||||
|
||||
// Bad — creates unnecessary null intermediary
|
||||
kwargs["template_format"]?.asString()?.orElse(null) ?: "f-string"
|
||||
```
|
||||
The `?: "f-string"` is still needed to handle the case where the key is missing from the map (`null` from `kwargs["template_format"]`), but `orElse` should carry the default for when the key exists but isn't a string.
|
||||
- Anthropic SDK is a `compileOnly` dependency — users must add it themselves. Methods that use Anthropic types should catch `NoClassDefFoundError` and throw `IllegalStateException` with a clear message.
|
||||
+978
@@ -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
@@ -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+.
|
||||
@@ -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.
|
||||
|
||||
@@ -1,188 +1,507 @@
|
||||
# Lang Smith Java API Library
|
||||
# LangSmith Java SDK
|
||||
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
[](https://central.sonatype.com/artifact/com.langsmith.api/langsmith-java/0.0.1-alpha.0)
|
||||
[](https://central.sonatype.com/artifact/com.langchain.smith/langsmith-java/0.1.0-beta.19)
|
||||
[](https://javadoc.io/doc/com.langchain.smith/langsmith-java/0.1.0-beta.18)
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
||||
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
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
@@ -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) }
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
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.
Binary file not shown.
@@ -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")
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+5
-1
@@ -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
|
||||
|
||||
@@ -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
@@ -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%
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
+434
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
+434
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
+356
@@ -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()
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.langchain.smith.client.okhttp
|
||||
|
||||
import com.langchain.smith.client.LangsmithClient
|
||||
import com.langchain.smith.tracing.LangsmithClientProvider
|
||||
|
||||
/** [LangsmithClientProvider] backed by [LangsmithOkHttpClient]. */
|
||||
class OkHttpLangsmithClientProvider : LangsmithClientProvider {
|
||||
override fun createClient(): LangsmithClient = LangsmithOkHttpClient.fromEnv()
|
||||
}
|
||||
-83
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-83
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-202
@@ -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" },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
com.langchain.smith.client.okhttp.OkHttpLangsmithClientProvider
|
||||
+44
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.langchain.smith.client
|
||||
|
||||
import com.langchain.smith.models.info.InfoListResponse.BatchIngestConfig
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
internal data class AutoBatchIngestLimits(
|
||||
val batchSizeLimit: Int = AutoBatchQueue.DEFAULT_BATCH_SIZE_LIMIT,
|
||||
val batchSizeLimitBytes: Int = AutoBatchQueue.DEFAULT_BATCH_SIZE_LIMIT_BYTES,
|
||||
val useMultipartEndpoint: Boolean = true,
|
||||
)
|
||||
|
||||
internal fun BatchIngestConfig?.toAutoBatchIngestLimits(): AutoBatchIngestLimits =
|
||||
AutoBatchIngestLimits(
|
||||
batchSizeLimit =
|
||||
this?.sizeLimit()?.getOrNull()?.takeIf { it > 0 && it <= Int.MAX_VALUE }?.toInt()
|
||||
?: AutoBatchQueue.DEFAULT_BATCH_SIZE_LIMIT,
|
||||
batchSizeLimitBytes =
|
||||
this?.sizeLimitBytes()?.getOrNull()?.takeIf { it > 0 && it <= Int.MAX_VALUE }?.toInt()
|
||||
?: AutoBatchQueue.DEFAULT_BATCH_SIZE_LIMIT_BYTES,
|
||||
useMultipartEndpoint = this?.useMultipartEndpoint()?.getOrNull() ?: true,
|
||||
)
|
||||
@@ -0,0 +1,519 @@
|
||||
package com.langchain.smith.client
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.langchain.smith.core.RequestOptions
|
||||
import com.langchain.smith.core.Timeout
|
||||
import com.langchain.smith.core.http.Headers
|
||||
import com.langchain.smith.core.http.QueryParams
|
||||
import com.langchain.smith.core.jsonMapper
|
||||
import com.langchain.smith.models.runs.RunIngest
|
||||
import com.langchain.smith.models.runs.RunIngestBatchParams
|
||||
import java.util.concurrent.CompletionException
|
||||
import java.util.concurrent.CompletionStage
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Phaser
|
||||
import java.util.concurrent.RejectedExecutionException
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
* Batches run create/update operations and sends them to LangSmith in a single `ingestBatch`
|
||||
* request, reducing HTTP overhead.
|
||||
*
|
||||
* Operations are buffered and flushed either:
|
||||
* - When the buffer reaches [batchSizeLimit] operations
|
||||
* - After [aggregationDelayMs] milliseconds of inactivity (timer-based drain)
|
||||
* - When [flush] is called explicitly
|
||||
*
|
||||
* @param sendBatch sends a batch and completes when the send has finished
|
||||
* @param batchSizeLimit max operations before auto-flush (default 100)
|
||||
* @param aggregationDelayMs inactivity delay before timer-based flush (default 250ms, matching JS
|
||||
* auto-batch aggregation and Python's initial queue wait)
|
||||
* @param sendParallelism max number of batch requests to send concurrently (default 4)
|
||||
* @param maxAggregationDelayMs max delay before timer-based flush after first item in a burst
|
||||
* (default 500ms, matching Python's background flush interval)
|
||||
* @param batchSizeLimitBytes max serialized JSON body size per batch (default 20 MiB)
|
||||
*/
|
||||
class AutoBatchQueue(
|
||||
private val sendBatch: (RunIngestBatchParams, RequestOptions) -> CompletionStage<Void?>,
|
||||
private val batchSizeLimit: Int = DEFAULT_BATCH_SIZE_LIMIT,
|
||||
private val aggregationDelayMs: Long = DEFAULT_AGGREGATION_DELAY_MS,
|
||||
private val sendParallelism: Int = DEFAULT_SEND_PARALLELISM,
|
||||
private val maxAggregationDelayMs: Long = DEFAULT_MAX_AGGREGATION_DELAY_MS,
|
||||
private val batchSizeLimitBytes: Int,
|
||||
) {
|
||||
private val items = ConcurrentLinkedQueue<BatchItem>()
|
||||
private val queuedCount = AtomicInteger(0)
|
||||
private val shutdown = AtomicBoolean(false)
|
||||
private val enqueueShutdownLock = ReentrantLock()
|
||||
private val delayedFlushLock = ReentrantLock()
|
||||
private var delayedFlushFuture: ScheduledFuture<*>? = null
|
||||
private var firstQueuedAtNanos: Long? = null
|
||||
private val activeSends =
|
||||
object : Phaser(0) {
|
||||
override fun onAdvance(phase: Int, registeredParties: Int): Boolean = false
|
||||
}
|
||||
|
||||
private val coordinator: ScheduledExecutorService =
|
||||
Executors.newSingleThreadScheduledExecutor { r ->
|
||||
Thread(r, "langsmith-batch-coordinator").apply { isDaemon = true }
|
||||
}
|
||||
|
||||
private val sendExecutor: ExecutorService =
|
||||
Executors.newFixedThreadPool(sendParallelism) { r ->
|
||||
Thread(r, "langsmith-batch-sender").apply { isDaemon = true }
|
||||
}
|
||||
|
||||
/** Enqueues a run create operation. */
|
||||
fun post(
|
||||
run: RunIngest,
|
||||
headers: Headers = Headers.builder().build(),
|
||||
queryParams: QueryParams = QueryParams.builder().build(),
|
||||
requestOptions: RequestOptions = RequestOptions.none(),
|
||||
) {
|
||||
enqueue(BatchOp.Post, run, headers, queryParams, requestOptions)
|
||||
}
|
||||
|
||||
/** Enqueues a run update (patch) operation. */
|
||||
fun patch(
|
||||
run: RunIngest,
|
||||
headers: Headers = Headers.builder().build(),
|
||||
queryParams: QueryParams = QueryParams.builder().build(),
|
||||
requestOptions: RequestOptions = RequestOptions.none(),
|
||||
) {
|
||||
enqueue(BatchOp.Patch, run, headers, queryParams, requestOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes all queued operations immediately, blocking until batch requests that were queued or
|
||||
* already in-flight have completed.
|
||||
*
|
||||
* Safe to call from any thread. No-op if the queue is empty.
|
||||
*/
|
||||
fun flush() {
|
||||
cancelDelayedFlush()
|
||||
while (true) {
|
||||
if (queuedCount.get() > 0 && !drainOnCoordinator()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!waitForActiveSends()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (queuedCount.get() == 0 && !hasActiveSends()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes remaining operations and shuts down the background executors.
|
||||
*
|
||||
* After calling this, the queue will no longer accept new operations.
|
||||
*/
|
||||
fun shutdown() {
|
||||
val startedShutdown =
|
||||
enqueueShutdownLock.withLock {
|
||||
// Serialize with enqueue's check-and-add so flush cannot miss an item that observed
|
||||
// shutdown=false but has not yet been queued.
|
||||
shutdown.compareAndSet(false, true)
|
||||
}
|
||||
if (!startedShutdown) return
|
||||
|
||||
flush()
|
||||
coordinator.shutdown()
|
||||
sendExecutor.shutdown()
|
||||
|
||||
try {
|
||||
if (!coordinator.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
coordinator.shutdownNow()
|
||||
}
|
||||
if (!sendExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
sendExecutor.shutdownNow()
|
||||
}
|
||||
} catch (_: InterruptedException) {
|
||||
coordinator.shutdownNow()
|
||||
sendExecutor.shutdownNow()
|
||||
Thread.currentThread().interrupt()
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
sendBatch: (RunIngestBatchParams, RequestOptions) -> CompletionStage<Void?>,
|
||||
batchSizeLimit: Int = DEFAULT_BATCH_SIZE_LIMIT,
|
||||
aggregationDelayMs: Long = DEFAULT_AGGREGATION_DELAY_MS,
|
||||
sendParallelism: Int = DEFAULT_SEND_PARALLELISM,
|
||||
) : this(
|
||||
sendBatch = sendBatch,
|
||||
batchSizeLimit = batchSizeLimit,
|
||||
aggregationDelayMs = aggregationDelayMs,
|
||||
sendParallelism = sendParallelism,
|
||||
maxAggregationDelayMs = DEFAULT_MAX_AGGREGATION_DELAY_MS,
|
||||
batchSizeLimitBytes = DEFAULT_BATCH_SIZE_LIMIT_BYTES,
|
||||
)
|
||||
|
||||
/** Returns the number of queued operations (for testing). */
|
||||
internal fun size(): Int = queuedCount.get()
|
||||
|
||||
private fun enqueue(
|
||||
op: BatchOp,
|
||||
run: RunIngest,
|
||||
headers: Headers,
|
||||
queryParams: QueryParams,
|
||||
requestOptions: RequestOptions,
|
||||
) {
|
||||
val count =
|
||||
enqueueShutdownLock.withLock {
|
||||
check(!shutdown.get()) { "AutoBatchQueue is shut down" }
|
||||
items.add(
|
||||
BatchItem(
|
||||
op = op,
|
||||
run = run,
|
||||
headers = headers,
|
||||
queryParams = queryParams,
|
||||
requestOptions = requestOptions,
|
||||
)
|
||||
)
|
||||
queuedCount.incrementAndGet()
|
||||
}
|
||||
|
||||
afterEnqueue(count)
|
||||
}
|
||||
|
||||
private fun afterEnqueue(count: Int) {
|
||||
if (count >= batchSizeLimit) {
|
||||
triggerFlush()
|
||||
} else {
|
||||
scheduleFlush()
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleFlush() {
|
||||
delayedFlushLock.withLock {
|
||||
try {
|
||||
val now = System.nanoTime()
|
||||
val firstQueuedAt = firstQueuedAtNanos ?: now.also { firstQueuedAtNanos = it }
|
||||
val elapsedMs = TimeUnit.NANOSECONDS.toMillis(now - firstQueuedAt)
|
||||
val remainingMaxDelayMs = (maxAggregationDelayMs - elapsedMs).coerceAtLeast(0)
|
||||
val delayMs = minOf(aggregationDelayMs, remainingMaxDelayMs)
|
||||
|
||||
delayedFlushFuture?.cancel(false)
|
||||
delayedFlushFuture =
|
||||
coordinator.schedule(
|
||||
{
|
||||
clearDelayedFlushState()
|
||||
drainAndSubmitSends()
|
||||
},
|
||||
delayMs,
|
||||
TimeUnit.MILLISECONDS,
|
||||
)
|
||||
} catch (e: RejectedExecutionException) {
|
||||
delayedFlushFuture = null
|
||||
firstQueuedAtNanos = null
|
||||
logger.warn("Batch queue coordinator rejected delayed flush", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearDelayedFlushState() {
|
||||
delayedFlushLock.withLock {
|
||||
delayedFlushFuture = null
|
||||
firstQueuedAtNanos = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun triggerFlush() {
|
||||
cancelDelayedFlush()
|
||||
try {
|
||||
coordinator.execute { drainAndSubmitSends() }
|
||||
} catch (e: RejectedExecutionException) {
|
||||
logger.warn("Batch queue coordinator rejected flush", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelDelayedFlush() {
|
||||
delayedFlushLock.withLock {
|
||||
delayedFlushFuture?.cancel(false)
|
||||
delayedFlushFuture = null
|
||||
firstQueuedAtNanos = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun drainOnCoordinator(): Boolean {
|
||||
val drainFuture =
|
||||
try {
|
||||
coordinator.submit { drainAndSubmitSends() }
|
||||
} catch (e: RejectedExecutionException) {
|
||||
throw IllegalStateException("Batch queue coordinator rejected flush", e)
|
||||
}
|
||||
|
||||
try {
|
||||
drainFuture.get()
|
||||
return true
|
||||
} catch (_: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
return false
|
||||
} catch (e: ExecutionException) {
|
||||
throw RuntimeException("Failed to flush batch queue", e.cause)
|
||||
}
|
||||
}
|
||||
|
||||
private fun drainAndSubmitSends() {
|
||||
var remainingToDrain = queuedCount.get()
|
||||
while (remainingToDrain > 0) {
|
||||
val drainResult = drainUpTo(minOf(batchSizeLimit, remainingToDrain))
|
||||
if (drainResult.itemCount == 0) break
|
||||
|
||||
drainResult.batches.forEach(::submitBatch)
|
||||
remainingToDrain -= drainResult.itemCount
|
||||
}
|
||||
|
||||
when {
|
||||
queuedCount.get() >= batchSizeLimit -> triggerFlush()
|
||||
queuedCount.get() > 0 && !shutdown.get() -> scheduleFlush()
|
||||
queuedCount.get() > 0 && shutdown.get() -> triggerFlush()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drains up to [maxItems] queued operations and returns batch params grouped by request
|
||||
* options.
|
||||
*
|
||||
* Multipart ingest compression is applied at send time; legacy `/runs/batch` remains
|
||||
* uncompressed.
|
||||
*
|
||||
* TODO: Support attachment parts in multipart ingest.
|
||||
*/
|
||||
private fun drainUpTo(maxItems: Int): DrainResult {
|
||||
val openGroups = linkedMapOf<RequestOptionsKey, BatchGroup>()
|
||||
var drained = 0
|
||||
|
||||
val batches = buildList {
|
||||
while (drained < maxItems) {
|
||||
val item = items.poll() ?: break
|
||||
queuedCount.decrementAndGet()
|
||||
drained++
|
||||
|
||||
val key = item.requestOptions.key()
|
||||
val itemSerializedSize = objectMapper.writeValueAsBytes(item.run).size
|
||||
var group = openGroups.getOrPut(key) { BatchGroup(item.requestOptions) }
|
||||
if (
|
||||
group.isNotEmpty() &&
|
||||
group.serializedSizeWith(item, itemSerializedSize) > batchSizeLimitBytes
|
||||
) {
|
||||
add(group.toBatch())
|
||||
group = BatchGroup(item.requestOptions)
|
||||
openGroups[key] = group
|
||||
}
|
||||
group.add(item, itemSerializedSize)
|
||||
}
|
||||
|
||||
addAll(openGroups.values.map { it.toBatch() })
|
||||
}
|
||||
return DrainResult(batches = batches, itemCount = drained)
|
||||
}
|
||||
|
||||
private fun submitBatch(batch: Batch) {
|
||||
activeSends.register()
|
||||
try {
|
||||
sendExecutor.execute {
|
||||
try {
|
||||
sendBatch(batch.params, batch.requestOptions).toCompletableFuture().join()
|
||||
} catch (e: CompletionException) {
|
||||
logger.warn("Failed to send batch of runs", e.cause ?: e)
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Failed to send batch of runs", e)
|
||||
} finally {
|
||||
activeSends.arriveAndDeregister()
|
||||
}
|
||||
}
|
||||
} catch (e: RejectedExecutionException) {
|
||||
activeSends.arriveAndDeregister()
|
||||
logger.warn(
|
||||
"Batch queue sender rejected a batch; dropping {} run operations",
|
||||
operationCount(batch.params),
|
||||
e,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun waitForActiveSends(): Boolean {
|
||||
while (hasActiveSends()) {
|
||||
val phase = activeSends.phase
|
||||
try {
|
||||
activeSends.awaitAdvanceInterruptibly(phase)
|
||||
} catch (_: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun hasActiveSends(): Boolean = activeSends.registeredParties > 0
|
||||
|
||||
private fun operationCount(params: RunIngestBatchParams): Int =
|
||||
params.post().orElse(emptyList()).size + params.patch().orElse(emptyList()).size
|
||||
|
||||
private enum class BatchOp {
|
||||
Post,
|
||||
Patch,
|
||||
}
|
||||
|
||||
private data class DrainResult(val batches: List<Batch>, val itemCount: Int)
|
||||
|
||||
private data class Batch(val params: RunIngestBatchParams, val requestOptions: RequestOptions)
|
||||
|
||||
private class BatchGroup(
|
||||
val requestOptions: RequestOptions,
|
||||
val posts: MutableList<RunIngest> = mutableListOf(),
|
||||
val patches: MutableList<RunIngest> = mutableListOf(),
|
||||
val headers: Headers.Builder = Headers.builder(),
|
||||
val queryParams: QueryParams.Builder = QueryParams.builder(),
|
||||
) {
|
||||
private var postSerializedSizeBytes = 0
|
||||
private var patchSerializedSizeBytes = 0
|
||||
|
||||
fun add(item: BatchItem, serializedSizeBytes: Int) {
|
||||
when (item.op) {
|
||||
BatchOp.Post -> {
|
||||
posts.add(item.run)
|
||||
postSerializedSizeBytes += serializedSizeBytes
|
||||
}
|
||||
BatchOp.Patch -> {
|
||||
patches.add(item.run)
|
||||
patchSerializedSizeBytes += serializedSizeBytes
|
||||
}
|
||||
}
|
||||
headers.putAll(item.headers)
|
||||
queryParams.putAll(item.queryParams)
|
||||
}
|
||||
|
||||
fun isNotEmpty(): Boolean = posts.isNotEmpty() || patches.isNotEmpty()
|
||||
|
||||
fun serializedSizeWith(item: BatchItem, itemSerializedSizeBytes: Int): Int =
|
||||
estimatedBodySizeBytes(
|
||||
postCount = posts.size + if (item.op == BatchOp.Post) 1 else 0,
|
||||
postItemsSizeBytes =
|
||||
postSerializedSizeBytes +
|
||||
if (item.op == BatchOp.Post) itemSerializedSizeBytes else 0,
|
||||
patchCount = patches.size + if (item.op == BatchOp.Patch) 1 else 0,
|
||||
patchItemsSizeBytes =
|
||||
patchSerializedSizeBytes +
|
||||
if (item.op == BatchOp.Patch) itemSerializedSizeBytes else 0,
|
||||
)
|
||||
|
||||
private fun estimatedBodySizeBytes(
|
||||
postCount: Int,
|
||||
postItemsSizeBytes: Int,
|
||||
patchCount: Int,
|
||||
patchItemsSizeBytes: Int,
|
||||
): Int {
|
||||
var size = 2 // Opening and closing braces.
|
||||
var fieldCount = 0
|
||||
if (patchCount > 0) {
|
||||
size += fieldSizeBytes("patch", patchCount, patchItemsSizeBytes)
|
||||
fieldCount++
|
||||
}
|
||||
if (postCount > 0) {
|
||||
if (fieldCount > 0) size++ // Comma between top-level fields.
|
||||
size += fieldSizeBytes("post", postCount, postItemsSizeBytes)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
private fun fieldSizeBytes(name: String, count: Int, itemsSizeBytes: Int): Int =
|
||||
name.length + 5 + itemsSizeBytes + (count - 1) // `"name":[]` + item commas.
|
||||
|
||||
fun toBatch(): Batch {
|
||||
val mergeResult = mergePostsAndPatches()
|
||||
val builder = RunIngestBatchParams.builder()
|
||||
if (mergeResult.posts.isNotEmpty()) builder.post(mergeResult.posts)
|
||||
if (mergeResult.patches.isNotEmpty()) builder.patch(mergeResult.patches)
|
||||
builder.additionalHeaders(headers.build())
|
||||
builder.additionalQueryParams(queryParams.build())
|
||||
return Batch(params = builder.build(), requestOptions = requestOptions)
|
||||
}
|
||||
|
||||
private fun mergePostsAndPatches(): MergeResult {
|
||||
if (posts.isEmpty() || patches.isEmpty()) {
|
||||
return MergeResult(posts = posts, patches = patches)
|
||||
}
|
||||
|
||||
val postsById =
|
||||
posts.mapNotNull { post -> post.id().getOrNull()?.let { it to post } }.toMap()
|
||||
val postsWithoutId = posts.filter { it.id().getOrNull() == null }
|
||||
val patchesByPostId =
|
||||
patches.mapNotNull { patch ->
|
||||
patch.id().getOrNull()?.takeIf(postsById::containsKey)?.let { it to patch }
|
||||
}
|
||||
val patchesByPostIdMap = patchesByPostId.toMap()
|
||||
val standalonePatches =
|
||||
patches.filter { patch ->
|
||||
patch.id().getOrNull()?.let(postsById::containsKey) != true
|
||||
}
|
||||
|
||||
return MergeResult(
|
||||
posts =
|
||||
postsWithoutId +
|
||||
postsById.map { (id, post) ->
|
||||
patchesByPostIdMap[id]?.let { mergePostAndPatch(post, it) } ?: post
|
||||
},
|
||||
patches = standalonePatches,
|
||||
mergedRunIds = patchesByPostId.map { it.first },
|
||||
)
|
||||
}
|
||||
|
||||
private fun mergePostAndPatch(post: RunIngest, patch: RunIngest): RunIngest {
|
||||
val merged = objectMapper.valueToTree<ObjectNode>(post)
|
||||
val patchFields = objectMapper.valueToTree<ObjectNode>(patch)
|
||||
patchFields.fields().forEach { (field, value) -> merged.set<ObjectNode>(field, value) }
|
||||
val mergedRun = objectMapper.treeToValue(merged, RunIngest::class.java)
|
||||
return mergedRun
|
||||
.toBuilder()
|
||||
.attachments(post.attachments() + patch.attachments())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private data class MergeResult(
|
||||
val posts: List<RunIngest>,
|
||||
val patches: List<RunIngest>,
|
||||
val mergedRunIds: List<String> = emptyList(),
|
||||
)
|
||||
|
||||
private data class BatchItem(
|
||||
val op: BatchOp,
|
||||
val run: RunIngest,
|
||||
val headers: Headers,
|
||||
val queryParams: QueryParams,
|
||||
val requestOptions: RequestOptions,
|
||||
)
|
||||
|
||||
private data class RequestOptionsKey(val responseValidation: Boolean?, val timeout: Timeout?)
|
||||
|
||||
private fun RequestOptions.key(): RequestOptionsKey =
|
||||
RequestOptionsKey(responseValidation = responseValidation, timeout = timeout)
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(AutoBatchQueue::class.java)
|
||||
private val objectMapper = jsonMapper()
|
||||
|
||||
const val DEFAULT_BATCH_SIZE_LIMIT = 100
|
||||
const val DEFAULT_BATCH_SIZE_LIMIT_BYTES = 20_971_520
|
||||
const val DEFAULT_AGGREGATION_DELAY_MS = 250L
|
||||
const val DEFAULT_MAX_AGGREGATION_DELAY_MS = 500L
|
||||
const val DEFAULT_SEND_PARALLELISM = 4
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+161
@@ -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
|
||||
}
|
||||
}
|
||||
+302
@@ -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
|
||||
}
|
||||
}
|
||||
+276
@@ -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,
|
||||
}
|
||||
+10
-25
@@ -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
-1
@@ -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
|
||||
}
|
||||
}
|
||||
+58
@@ -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)
|
||||
}
|
||||
+23
@@ -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
Reference in New Issue
Block a user