client.js 137 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388
  1. import * as uuid from "uuid";
  2. import { AsyncCaller } from "./utils/async_caller.js";
  3. import { convertLangChainMessageToExample, isLangChainMessage, } from "./utils/messages.js";
  4. import { getEnvironmentVariable, getLangChainEnvVarsMetadata, getLangSmithEnvironmentVariable, getRuntimeEnvironment, } from "./utils/env.js";
  5. import { __version__ } from "./index.js";
  6. import { assertUuid } from "./utils/_uuid.js";
  7. import { warnOnce } from "./utils/warn.js";
  8. import { parsePromptIdentifier } from "./utils/prompts.js";
  9. import { raiseForStatus } from "./utils/error.js";
  10. import { _globalFetchImplementationIsNodeFetch, _getFetchImplementation, } from "./singletons/fetch.js";
  11. import { serialize as serializePayloadForTracing } from "./utils/fast-safe-stringify/index.js";
  12. export function mergeRuntimeEnvIntoRunCreate(run) {
  13. const runtimeEnv = getRuntimeEnvironment();
  14. const envVars = getLangChainEnvVarsMetadata();
  15. const extra = run.extra ?? {};
  16. const metadata = extra.metadata;
  17. run.extra = {
  18. ...extra,
  19. runtime: {
  20. ...runtimeEnv,
  21. ...extra?.runtime,
  22. },
  23. metadata: {
  24. ...envVars,
  25. ...(envVars.revision_id || run.revision_id
  26. ? { revision_id: run.revision_id ?? envVars.revision_id }
  27. : {}),
  28. ...metadata,
  29. },
  30. };
  31. return run;
  32. }
  33. const getTracingSamplingRate = (configRate) => {
  34. const samplingRateStr = configRate?.toString() ??
  35. getLangSmithEnvironmentVariable("TRACING_SAMPLING_RATE");
  36. if (samplingRateStr === undefined) {
  37. return undefined;
  38. }
  39. const samplingRate = parseFloat(samplingRateStr);
  40. if (samplingRate < 0 || samplingRate > 1) {
  41. throw new Error(`LANGSMITH_TRACING_SAMPLING_RATE must be between 0 and 1 if set. Got: ${samplingRate}`);
  42. }
  43. return samplingRate;
  44. };
  45. // utility functions
  46. const isLocalhost = (url) => {
  47. const strippedUrl = url.replace("http://", "").replace("https://", "");
  48. const hostname = strippedUrl.split("/")[0].split(":")[0];
  49. return (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1");
  50. };
  51. async function toArray(iterable) {
  52. const result = [];
  53. for await (const item of iterable) {
  54. result.push(item);
  55. }
  56. return result;
  57. }
  58. function trimQuotes(str) {
  59. if (str === undefined) {
  60. return undefined;
  61. }
  62. return str
  63. .trim()
  64. .replace(/^"(.*)"$/, "$1")
  65. .replace(/^'(.*)'$/, "$1");
  66. }
  67. const handle429 = async (response) => {
  68. if (response?.status === 429) {
  69. const retryAfter = parseInt(response.headers.get("retry-after") ?? "30", 10) * 1000;
  70. if (retryAfter > 0) {
  71. await new Promise((resolve) => setTimeout(resolve, retryAfter));
  72. // Return directly after calling this check
  73. return true;
  74. }
  75. }
  76. // Fall back to existing status checks
  77. return false;
  78. };
  79. function _formatFeedbackScore(score) {
  80. if (typeof score === "number") {
  81. // Truncate at 4 decimal places
  82. return Number(score.toFixed(4));
  83. }
  84. return score;
  85. }
  86. export class AutoBatchQueue {
  87. constructor() {
  88. Object.defineProperty(this, "items", {
  89. enumerable: true,
  90. configurable: true,
  91. writable: true,
  92. value: []
  93. });
  94. Object.defineProperty(this, "sizeBytes", {
  95. enumerable: true,
  96. configurable: true,
  97. writable: true,
  98. value: 0
  99. });
  100. }
  101. peek() {
  102. return this.items[0];
  103. }
  104. push(item) {
  105. let itemPromiseResolve;
  106. const itemPromise = new Promise((resolve) => {
  107. // Setting itemPromiseResolve is synchronous with promise creation:
  108. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
  109. itemPromiseResolve = resolve;
  110. });
  111. const size = serializePayloadForTracing(item.item, `Serializing run with id: ${item.item.id}`).length;
  112. this.items.push({
  113. action: item.action,
  114. payload: item.item,
  115. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  116. itemPromiseResolve: itemPromiseResolve,
  117. itemPromise,
  118. size,
  119. });
  120. this.sizeBytes += size;
  121. return itemPromise;
  122. }
  123. pop(upToSizeBytes) {
  124. if (upToSizeBytes < 1) {
  125. throw new Error("Number of bytes to pop off may not be less than 1.");
  126. }
  127. const popped = [];
  128. let poppedSizeBytes = 0;
  129. // Pop items until we reach or exceed the size limit
  130. while (poppedSizeBytes + (this.peek()?.size ?? 0) < upToSizeBytes &&
  131. this.items.length > 0) {
  132. const item = this.items.shift();
  133. if (item) {
  134. popped.push(item);
  135. poppedSizeBytes += item.size;
  136. this.sizeBytes -= item.size;
  137. }
  138. }
  139. // If there is an item on the queue we were unable to pop,
  140. // just return it as a single batch.
  141. if (popped.length === 0 && this.items.length > 0) {
  142. const item = this.items.shift();
  143. popped.push(item);
  144. poppedSizeBytes += item.size;
  145. this.sizeBytes -= item.size;
  146. }
  147. return [
  148. popped.map((it) => ({ action: it.action, item: it.payload })),
  149. () => popped.forEach((it) => it.itemPromiseResolve()),
  150. ];
  151. }
  152. }
  153. // 20 MB
  154. export const DEFAULT_BATCH_SIZE_LIMIT_BYTES = 20_971_520;
  155. const SERVER_INFO_REQUEST_TIMEOUT = 2500;
  156. export class Client {
  157. constructor(config = {}) {
  158. Object.defineProperty(this, "apiKey", {
  159. enumerable: true,
  160. configurable: true,
  161. writable: true,
  162. value: void 0
  163. });
  164. Object.defineProperty(this, "apiUrl", {
  165. enumerable: true,
  166. configurable: true,
  167. writable: true,
  168. value: void 0
  169. });
  170. Object.defineProperty(this, "webUrl", {
  171. enumerable: true,
  172. configurable: true,
  173. writable: true,
  174. value: void 0
  175. });
  176. Object.defineProperty(this, "caller", {
  177. enumerable: true,
  178. configurable: true,
  179. writable: true,
  180. value: void 0
  181. });
  182. Object.defineProperty(this, "batchIngestCaller", {
  183. enumerable: true,
  184. configurable: true,
  185. writable: true,
  186. value: void 0
  187. });
  188. Object.defineProperty(this, "timeout_ms", {
  189. enumerable: true,
  190. configurable: true,
  191. writable: true,
  192. value: void 0
  193. });
  194. Object.defineProperty(this, "_tenantId", {
  195. enumerable: true,
  196. configurable: true,
  197. writable: true,
  198. value: null
  199. });
  200. Object.defineProperty(this, "hideInputs", {
  201. enumerable: true,
  202. configurable: true,
  203. writable: true,
  204. value: void 0
  205. });
  206. Object.defineProperty(this, "hideOutputs", {
  207. enumerable: true,
  208. configurable: true,
  209. writable: true,
  210. value: void 0
  211. });
  212. Object.defineProperty(this, "tracingSampleRate", {
  213. enumerable: true,
  214. configurable: true,
  215. writable: true,
  216. value: void 0
  217. });
  218. Object.defineProperty(this, "filteredPostUuids", {
  219. enumerable: true,
  220. configurable: true,
  221. writable: true,
  222. value: new Set()
  223. });
  224. Object.defineProperty(this, "autoBatchTracing", {
  225. enumerable: true,
  226. configurable: true,
  227. writable: true,
  228. value: true
  229. });
  230. Object.defineProperty(this, "autoBatchQueue", {
  231. enumerable: true,
  232. configurable: true,
  233. writable: true,
  234. value: new AutoBatchQueue()
  235. });
  236. Object.defineProperty(this, "autoBatchTimeout", {
  237. enumerable: true,
  238. configurable: true,
  239. writable: true,
  240. value: void 0
  241. });
  242. Object.defineProperty(this, "autoBatchAggregationDelayMs", {
  243. enumerable: true,
  244. configurable: true,
  245. writable: true,
  246. value: 250
  247. });
  248. Object.defineProperty(this, "batchSizeBytesLimit", {
  249. enumerable: true,
  250. configurable: true,
  251. writable: true,
  252. value: void 0
  253. });
  254. Object.defineProperty(this, "fetchOptions", {
  255. enumerable: true,
  256. configurable: true,
  257. writable: true,
  258. value: void 0
  259. });
  260. Object.defineProperty(this, "settings", {
  261. enumerable: true,
  262. configurable: true,
  263. writable: true,
  264. value: void 0
  265. });
  266. Object.defineProperty(this, "blockOnRootRunFinalization", {
  267. enumerable: true,
  268. configurable: true,
  269. writable: true,
  270. value: getEnvironmentVariable("LANGSMITH_TRACING_BACKGROUND") === "false"
  271. });
  272. Object.defineProperty(this, "traceBatchConcurrency", {
  273. enumerable: true,
  274. configurable: true,
  275. writable: true,
  276. value: 5
  277. });
  278. Object.defineProperty(this, "_serverInfo", {
  279. enumerable: true,
  280. configurable: true,
  281. writable: true,
  282. value: void 0
  283. });
  284. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  285. Object.defineProperty(this, "_getServerInfoPromise", {
  286. enumerable: true,
  287. configurable: true,
  288. writable: true,
  289. value: void 0
  290. });
  291. Object.defineProperty(this, "manualFlushMode", {
  292. enumerable: true,
  293. configurable: true,
  294. writable: true,
  295. value: false
  296. });
  297. Object.defineProperty(this, "debug", {
  298. enumerable: true,
  299. configurable: true,
  300. writable: true,
  301. value: getEnvironmentVariable("LANGSMITH_DEBUG") === "true"
  302. });
  303. const defaultConfig = Client.getDefaultClientConfig();
  304. this.tracingSampleRate = getTracingSamplingRate(config.tracingSamplingRate);
  305. this.apiUrl = trimQuotes(config.apiUrl ?? defaultConfig.apiUrl) ?? "";
  306. if (this.apiUrl.endsWith("/")) {
  307. this.apiUrl = this.apiUrl.slice(0, -1);
  308. }
  309. this.apiKey = trimQuotes(config.apiKey ?? defaultConfig.apiKey);
  310. this.webUrl = trimQuotes(config.webUrl ?? defaultConfig.webUrl);
  311. if (this.webUrl?.endsWith("/")) {
  312. this.webUrl = this.webUrl.slice(0, -1);
  313. }
  314. this.timeout_ms = config.timeout_ms ?? 90_000;
  315. this.caller = new AsyncCaller({
  316. ...(config.callerOptions ?? {}),
  317. debug: config.debug ?? this.debug,
  318. });
  319. this.traceBatchConcurrency =
  320. config.traceBatchConcurrency ?? this.traceBatchConcurrency;
  321. if (this.traceBatchConcurrency < 1) {
  322. throw new Error("Trace batch concurrency must be positive.");
  323. }
  324. this.debug = config.debug ?? this.debug;
  325. this.batchIngestCaller = new AsyncCaller({
  326. maxRetries: 2,
  327. maxConcurrency: this.traceBatchConcurrency,
  328. ...(config.callerOptions ?? {}),
  329. onFailedResponseHook: handle429,
  330. debug: config.debug ?? this.debug,
  331. });
  332. this.hideInputs =
  333. config.hideInputs ?? config.anonymizer ?? defaultConfig.hideInputs;
  334. this.hideOutputs =
  335. config.hideOutputs ?? config.anonymizer ?? defaultConfig.hideOutputs;
  336. this.autoBatchTracing = config.autoBatchTracing ?? this.autoBatchTracing;
  337. this.blockOnRootRunFinalization =
  338. config.blockOnRootRunFinalization ?? this.blockOnRootRunFinalization;
  339. this.batchSizeBytesLimit = config.batchSizeBytesLimit;
  340. this.fetchOptions = config.fetchOptions || {};
  341. this.manualFlushMode = config.manualFlushMode ?? this.manualFlushMode;
  342. }
  343. static getDefaultClientConfig() {
  344. const apiKey = getLangSmithEnvironmentVariable("API_KEY");
  345. const apiUrl = getLangSmithEnvironmentVariable("ENDPOINT") ??
  346. "https://api.smith.langchain.com";
  347. const hideInputs = getLangSmithEnvironmentVariable("HIDE_INPUTS") === "true";
  348. const hideOutputs = getLangSmithEnvironmentVariable("HIDE_OUTPUTS") === "true";
  349. return {
  350. apiUrl: apiUrl,
  351. apiKey: apiKey,
  352. webUrl: undefined,
  353. hideInputs: hideInputs,
  354. hideOutputs: hideOutputs,
  355. };
  356. }
  357. getHostUrl() {
  358. if (this.webUrl) {
  359. return this.webUrl;
  360. }
  361. else if (isLocalhost(this.apiUrl)) {
  362. this.webUrl = "http://localhost:3000";
  363. return this.webUrl;
  364. }
  365. else if (this.apiUrl.endsWith("/api/v1")) {
  366. this.webUrl = this.apiUrl.replace("/api/v1", "");
  367. return this.webUrl;
  368. }
  369. else if (this.apiUrl.includes("/api") &&
  370. !this.apiUrl.split(".", 1)[0].endsWith("api")) {
  371. this.webUrl = this.apiUrl.replace("/api", "");
  372. return this.webUrl;
  373. }
  374. else if (this.apiUrl.split(".", 1)[0].includes("dev")) {
  375. this.webUrl = "https://dev.smith.langchain.com";
  376. return this.webUrl;
  377. }
  378. else if (this.apiUrl.split(".", 1)[0].includes("eu")) {
  379. this.webUrl = "https://eu.smith.langchain.com";
  380. return this.webUrl;
  381. }
  382. else if (this.apiUrl.split(".", 1)[0].includes("beta")) {
  383. this.webUrl = "https://beta.smith.langchain.com";
  384. return this.webUrl;
  385. }
  386. else {
  387. this.webUrl = "https://smith.langchain.com";
  388. return this.webUrl;
  389. }
  390. }
  391. get headers() {
  392. const headers = {
  393. "User-Agent": `langsmith-js/${__version__}`,
  394. };
  395. if (this.apiKey) {
  396. headers["x-api-key"] = `${this.apiKey}`;
  397. }
  398. return headers;
  399. }
  400. async processInputs(inputs) {
  401. if (this.hideInputs === false) {
  402. return inputs;
  403. }
  404. if (this.hideInputs === true) {
  405. return {};
  406. }
  407. if (typeof this.hideInputs === "function") {
  408. return this.hideInputs(inputs);
  409. }
  410. return inputs;
  411. }
  412. async processOutputs(outputs) {
  413. if (this.hideOutputs === false) {
  414. return outputs;
  415. }
  416. if (this.hideOutputs === true) {
  417. return {};
  418. }
  419. if (typeof this.hideOutputs === "function") {
  420. return this.hideOutputs(outputs);
  421. }
  422. return outputs;
  423. }
  424. async prepareRunCreateOrUpdateInputs(run) {
  425. const runParams = { ...run };
  426. if (runParams.inputs !== undefined) {
  427. runParams.inputs = await this.processInputs(runParams.inputs);
  428. }
  429. if (runParams.outputs !== undefined) {
  430. runParams.outputs = await this.processOutputs(runParams.outputs);
  431. }
  432. return runParams;
  433. }
  434. async _getResponse(path, queryParams) {
  435. const paramsString = queryParams?.toString() ?? "";
  436. const url = `${this.apiUrl}${path}?${paramsString}`;
  437. const response = await this.caller.call(_getFetchImplementation(this.debug), url, {
  438. method: "GET",
  439. headers: this.headers,
  440. signal: AbortSignal.timeout(this.timeout_ms),
  441. ...this.fetchOptions,
  442. });
  443. await raiseForStatus(response, `Failed to fetch ${path}`);
  444. return response;
  445. }
  446. async _get(path, queryParams) {
  447. const response = await this._getResponse(path, queryParams);
  448. return response.json();
  449. }
  450. async *_getPaginated(path, queryParams = new URLSearchParams(), transform) {
  451. let offset = Number(queryParams.get("offset")) || 0;
  452. const limit = Number(queryParams.get("limit")) || 100;
  453. while (true) {
  454. queryParams.set("offset", String(offset));
  455. queryParams.set("limit", String(limit));
  456. const url = `${this.apiUrl}${path}?${queryParams}`;
  457. const response = await this.caller.call(_getFetchImplementation(this.debug), url, {
  458. method: "GET",
  459. headers: this.headers,
  460. signal: AbortSignal.timeout(this.timeout_ms),
  461. ...this.fetchOptions,
  462. });
  463. await raiseForStatus(response, `Failed to fetch ${path}`);
  464. const items = transform
  465. ? transform(await response.json())
  466. : await response.json();
  467. if (items.length === 0) {
  468. break;
  469. }
  470. yield items;
  471. if (items.length < limit) {
  472. break;
  473. }
  474. offset += items.length;
  475. }
  476. }
  477. async *_getCursorPaginatedList(path, body = null, requestMethod = "POST", dataKey = "runs") {
  478. const bodyParams = body ? { ...body } : {};
  479. while (true) {
  480. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}${path}`, {
  481. method: requestMethod,
  482. headers: { ...this.headers, "Content-Type": "application/json" },
  483. signal: AbortSignal.timeout(this.timeout_ms),
  484. ...this.fetchOptions,
  485. body: JSON.stringify(bodyParams),
  486. });
  487. const responseBody = await response.json();
  488. if (!responseBody) {
  489. break;
  490. }
  491. if (!responseBody[dataKey]) {
  492. break;
  493. }
  494. yield responseBody[dataKey];
  495. const cursors = responseBody.cursors;
  496. if (!cursors) {
  497. break;
  498. }
  499. if (!cursors.next) {
  500. break;
  501. }
  502. bodyParams.cursor = cursors.next;
  503. }
  504. }
  505. // Allows mocking for tests
  506. _shouldSample() {
  507. if (this.tracingSampleRate === undefined) {
  508. return true;
  509. }
  510. return Math.random() < this.tracingSampleRate;
  511. }
  512. _filterForSampling(runs, patch = false) {
  513. if (this.tracingSampleRate === undefined) {
  514. return runs;
  515. }
  516. if (patch) {
  517. const sampled = [];
  518. for (const run of runs) {
  519. if (!this.filteredPostUuids.has(run.id)) {
  520. sampled.push(run);
  521. }
  522. else {
  523. this.filteredPostUuids.delete(run.id);
  524. }
  525. }
  526. return sampled;
  527. }
  528. else {
  529. // For new runs, sample at trace level to maintain consistency
  530. const sampled = [];
  531. for (const run of runs) {
  532. const traceId = run.trace_id ?? run.id;
  533. // If we've already made a decision about this trace, follow it
  534. if (this.filteredPostUuids.has(traceId)) {
  535. continue;
  536. }
  537. // For new traces, apply sampling
  538. if (run.id === traceId) {
  539. if (this._shouldSample()) {
  540. sampled.push(run);
  541. }
  542. else {
  543. this.filteredPostUuids.add(traceId);
  544. }
  545. }
  546. else {
  547. // Child runs follow their trace's sampling decision
  548. sampled.push(run);
  549. }
  550. }
  551. return sampled;
  552. }
  553. }
  554. async _getBatchSizeLimitBytes() {
  555. const serverInfo = await this._ensureServerInfo();
  556. return (this.batchSizeBytesLimit ??
  557. serverInfo.batch_ingest_config?.size_limit_bytes ??
  558. DEFAULT_BATCH_SIZE_LIMIT_BYTES);
  559. }
  560. async _getMultiPartSupport() {
  561. const serverInfo = await this._ensureServerInfo();
  562. return (serverInfo.instance_flags?.dataset_examples_multipart_enabled ?? false);
  563. }
  564. drainAutoBatchQueue(batchSizeLimit) {
  565. const promises = [];
  566. while (this.autoBatchQueue.items.length > 0) {
  567. const [batch, done] = this.autoBatchQueue.pop(batchSizeLimit);
  568. if (!batch.length) {
  569. done();
  570. break;
  571. }
  572. const batchPromise = this._processBatch(batch, done).catch(console.error);
  573. promises.push(batchPromise);
  574. }
  575. return Promise.all(promises);
  576. }
  577. async _processBatch(batch, done) {
  578. if (!batch.length) {
  579. done();
  580. return;
  581. }
  582. try {
  583. const ingestParams = {
  584. runCreates: batch
  585. .filter((item) => item.action === "create")
  586. .map((item) => item.item),
  587. runUpdates: batch
  588. .filter((item) => item.action === "update")
  589. .map((item) => item.item),
  590. };
  591. const serverInfo = await this._ensureServerInfo();
  592. if (serverInfo?.batch_ingest_config?.use_multipart_endpoint) {
  593. await this.multipartIngestRuns(ingestParams);
  594. }
  595. else {
  596. await this.batchIngestRuns(ingestParams);
  597. }
  598. }
  599. finally {
  600. done();
  601. }
  602. }
  603. async processRunOperation(item) {
  604. clearTimeout(this.autoBatchTimeout);
  605. this.autoBatchTimeout = undefined;
  606. if (item.action === "create") {
  607. item.item = mergeRuntimeEnvIntoRunCreate(item.item);
  608. }
  609. const itemPromise = this.autoBatchQueue.push(item);
  610. if (this.manualFlushMode) {
  611. // Rely on manual flushing in serverless environments
  612. return itemPromise;
  613. }
  614. const sizeLimitBytes = await this._getBatchSizeLimitBytes();
  615. if (this.autoBatchQueue.sizeBytes > sizeLimitBytes) {
  616. void this.drainAutoBatchQueue(sizeLimitBytes);
  617. }
  618. if (this.autoBatchQueue.items.length > 0) {
  619. this.autoBatchTimeout = setTimeout(() => {
  620. this.autoBatchTimeout = undefined;
  621. void this.drainAutoBatchQueue(sizeLimitBytes);
  622. }, this.autoBatchAggregationDelayMs);
  623. }
  624. return itemPromise;
  625. }
  626. async _getServerInfo() {
  627. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/info`, {
  628. method: "GET",
  629. headers: { Accept: "application/json" },
  630. signal: AbortSignal.timeout(SERVER_INFO_REQUEST_TIMEOUT),
  631. ...this.fetchOptions,
  632. });
  633. await raiseForStatus(response, "get server info");
  634. const json = await response.json();
  635. if (this.debug) {
  636. console.log("\n=== LangSmith Server Configuration ===\n" +
  637. JSON.stringify(json, null, 2) +
  638. "\n");
  639. }
  640. return json;
  641. }
  642. async _ensureServerInfo() {
  643. if (this._getServerInfoPromise === undefined) {
  644. this._getServerInfoPromise = (async () => {
  645. if (this._serverInfo === undefined) {
  646. try {
  647. this._serverInfo = await this._getServerInfo();
  648. }
  649. catch (e) {
  650. console.warn(`[WARNING]: LangSmith failed to fetch info on supported operations with status code ${e.status}. Falling back to batch operations and default limits.`);
  651. }
  652. }
  653. return this._serverInfo ?? {};
  654. })();
  655. }
  656. return this._getServerInfoPromise.then((serverInfo) => {
  657. if (this._serverInfo === undefined) {
  658. this._getServerInfoPromise = undefined;
  659. }
  660. return serverInfo;
  661. });
  662. }
  663. async _getSettings() {
  664. if (!this.settings) {
  665. this.settings = this._get("/settings");
  666. }
  667. return await this.settings;
  668. }
  669. /**
  670. * Flushes current queued traces.
  671. */
  672. async flush() {
  673. const sizeLimitBytes = await this._getBatchSizeLimitBytes();
  674. await this.drainAutoBatchQueue(sizeLimitBytes);
  675. }
  676. async createRun(run) {
  677. if (!this._filterForSampling([run]).length) {
  678. return;
  679. }
  680. const headers = { ...this.headers, "Content-Type": "application/json" };
  681. const session_name = run.project_name;
  682. delete run.project_name;
  683. const runCreate = await this.prepareRunCreateOrUpdateInputs({
  684. session_name,
  685. ...run,
  686. start_time: run.start_time ?? Date.now(),
  687. });
  688. if (this.autoBatchTracing &&
  689. runCreate.trace_id !== undefined &&
  690. runCreate.dotted_order !== undefined) {
  691. void this.processRunOperation({
  692. action: "create",
  693. item: runCreate,
  694. }).catch(console.error);
  695. return;
  696. }
  697. const mergedRunCreateParam = mergeRuntimeEnvIntoRunCreate(runCreate);
  698. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs`, {
  699. method: "POST",
  700. headers,
  701. body: serializePayloadForTracing(mergedRunCreateParam, `Creating run with id: ${mergedRunCreateParam.id}`),
  702. signal: AbortSignal.timeout(this.timeout_ms),
  703. ...this.fetchOptions,
  704. });
  705. await raiseForStatus(response, "create run", true);
  706. }
  707. /**
  708. * Batch ingest/upsert multiple runs in the Langsmith system.
  709. * @param runs
  710. */
  711. async batchIngestRuns({ runCreates, runUpdates, }) {
  712. if (runCreates === undefined && runUpdates === undefined) {
  713. return;
  714. }
  715. let preparedCreateParams = await Promise.all(runCreates?.map((create) => this.prepareRunCreateOrUpdateInputs(create)) ?? []);
  716. let preparedUpdateParams = await Promise.all(runUpdates?.map((update) => this.prepareRunCreateOrUpdateInputs(update)) ?? []);
  717. if (preparedCreateParams.length > 0 && preparedUpdateParams.length > 0) {
  718. const createById = preparedCreateParams.reduce((params, run) => {
  719. if (!run.id) {
  720. return params;
  721. }
  722. params[run.id] = run;
  723. return params;
  724. }, {});
  725. const standaloneUpdates = [];
  726. for (const updateParam of preparedUpdateParams) {
  727. if (updateParam.id !== undefined && createById[updateParam.id]) {
  728. createById[updateParam.id] = {
  729. ...createById[updateParam.id],
  730. ...updateParam,
  731. };
  732. }
  733. else {
  734. standaloneUpdates.push(updateParam);
  735. }
  736. }
  737. preparedCreateParams = Object.values(createById);
  738. preparedUpdateParams = standaloneUpdates;
  739. }
  740. const rawBatch = {
  741. post: preparedCreateParams,
  742. patch: preparedUpdateParams,
  743. };
  744. if (!rawBatch.post.length && !rawBatch.patch.length) {
  745. return;
  746. }
  747. const batchChunks = {
  748. post: [],
  749. patch: [],
  750. };
  751. for (const k of ["post", "patch"]) {
  752. const key = k;
  753. const batchItems = rawBatch[key].reverse();
  754. let batchItem = batchItems.pop();
  755. while (batchItem !== undefined) {
  756. // Type is wrong but this is a deprecated code path anyway
  757. batchChunks[key].push(batchItem);
  758. batchItem = batchItems.pop();
  759. }
  760. }
  761. if (batchChunks.post.length > 0 || batchChunks.patch.length > 0) {
  762. const runIds = batchChunks.post
  763. .map((item) => item.id)
  764. .concat(batchChunks.patch.map((item) => item.id))
  765. .join(",");
  766. await this._postBatchIngestRuns(serializePayloadForTracing(batchChunks, `Ingesting runs with ids: ${runIds}`));
  767. }
  768. }
  769. async _postBatchIngestRuns(body) {
  770. const headers = {
  771. ...this.headers,
  772. "Content-Type": "application/json",
  773. Accept: "application/json",
  774. };
  775. const response = await this.batchIngestCaller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/batch`, {
  776. method: "POST",
  777. headers,
  778. body: body,
  779. signal: AbortSignal.timeout(this.timeout_ms),
  780. ...this.fetchOptions,
  781. });
  782. await raiseForStatus(response, "batch create run", true);
  783. }
  784. /**
  785. * Batch ingest/upsert multiple runs in the Langsmith system.
  786. * @param runs
  787. */
  788. async multipartIngestRuns({ runCreates, runUpdates, }) {
  789. if (runCreates === undefined && runUpdates === undefined) {
  790. return;
  791. }
  792. // transform and convert to dicts
  793. const allAttachments = {};
  794. let preparedCreateParams = [];
  795. for (const create of runCreates ?? []) {
  796. const preparedCreate = await this.prepareRunCreateOrUpdateInputs(create);
  797. if (preparedCreate.id !== undefined &&
  798. preparedCreate.attachments !== undefined) {
  799. allAttachments[preparedCreate.id] = preparedCreate.attachments;
  800. }
  801. delete preparedCreate.attachments;
  802. preparedCreateParams.push(preparedCreate);
  803. }
  804. let preparedUpdateParams = [];
  805. for (const update of runUpdates ?? []) {
  806. preparedUpdateParams.push(await this.prepareRunCreateOrUpdateInputs(update));
  807. }
  808. // require trace_id and dotted_order
  809. const invalidRunCreate = preparedCreateParams.find((runCreate) => {
  810. return (runCreate.trace_id === undefined || runCreate.dotted_order === undefined);
  811. });
  812. if (invalidRunCreate !== undefined) {
  813. throw new Error(`Multipart ingest requires "trace_id" and "dotted_order" to be set when creating a run`);
  814. }
  815. const invalidRunUpdate = preparedUpdateParams.find((runUpdate) => {
  816. return (runUpdate.trace_id === undefined || runUpdate.dotted_order === undefined);
  817. });
  818. if (invalidRunUpdate !== undefined) {
  819. throw new Error(`Multipart ingest requires "trace_id" and "dotted_order" to be set when updating a run`);
  820. }
  821. // combine post and patch dicts where possible
  822. if (preparedCreateParams.length > 0 && preparedUpdateParams.length > 0) {
  823. const createById = preparedCreateParams.reduce((params, run) => {
  824. if (!run.id) {
  825. return params;
  826. }
  827. params[run.id] = run;
  828. return params;
  829. }, {});
  830. const standaloneUpdates = [];
  831. for (const updateParam of preparedUpdateParams) {
  832. if (updateParam.id !== undefined && createById[updateParam.id]) {
  833. createById[updateParam.id] = {
  834. ...createById[updateParam.id],
  835. ...updateParam,
  836. };
  837. }
  838. else {
  839. standaloneUpdates.push(updateParam);
  840. }
  841. }
  842. preparedCreateParams = Object.values(createById);
  843. preparedUpdateParams = standaloneUpdates;
  844. }
  845. if (preparedCreateParams.length === 0 &&
  846. preparedUpdateParams.length === 0) {
  847. return;
  848. }
  849. // send the runs in multipart requests
  850. const accumulatedContext = [];
  851. const accumulatedParts = [];
  852. for (const [method, payloads] of [
  853. ["post", preparedCreateParams],
  854. ["patch", preparedUpdateParams],
  855. ]) {
  856. for (const originalPayload of payloads) {
  857. // collect fields to be sent as separate parts
  858. const { inputs, outputs, events, attachments, ...payload } = originalPayload;
  859. const fields = { inputs, outputs, events };
  860. // encode the main run payload
  861. const stringifiedPayload = serializePayloadForTracing(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
  862. accumulatedParts.push({
  863. name: `${method}.${payload.id}`,
  864. payload: new Blob([stringifiedPayload], {
  865. type: `application/json; length=${stringifiedPayload.length}`, // encoding=gzip
  866. }),
  867. });
  868. // encode the fields we collected
  869. for (const [key, value] of Object.entries(fields)) {
  870. if (value === undefined) {
  871. continue;
  872. }
  873. const stringifiedValue = serializePayloadForTracing(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
  874. accumulatedParts.push({
  875. name: `${method}.${payload.id}.${key}`,
  876. payload: new Blob([stringifiedValue], {
  877. type: `application/json; length=${stringifiedValue.length}`,
  878. }),
  879. });
  880. }
  881. // encode the attachments
  882. if (payload.id !== undefined) {
  883. const attachments = allAttachments[payload.id];
  884. if (attachments) {
  885. delete allAttachments[payload.id];
  886. for (const [name, attachment] of Object.entries(attachments)) {
  887. let contentType;
  888. let content;
  889. if (Array.isArray(attachment)) {
  890. [contentType, content] = attachment;
  891. }
  892. else {
  893. contentType = attachment.mimeType;
  894. content = attachment.data;
  895. }
  896. // Validate that the attachment name doesn't contain a '.'
  897. if (name.includes(".")) {
  898. console.warn(`Skipping attachment '${name}' for run ${payload.id}: Invalid attachment name. ` +
  899. `Attachment names must not contain periods ('.'). Please rename the attachment and try again.`);
  900. continue;
  901. }
  902. accumulatedParts.push({
  903. name: `attachment.${payload.id}.${name}`,
  904. payload: new Blob([content], {
  905. type: `${contentType}; length=${content.byteLength}`,
  906. }),
  907. });
  908. }
  909. }
  910. }
  911. // compute context
  912. accumulatedContext.push(`trace=${payload.trace_id},id=${payload.id}`);
  913. }
  914. }
  915. await this._sendMultipartRequest(accumulatedParts, accumulatedContext.join("; "));
  916. }
  917. async _createNodeFetchBody(parts, boundary) {
  918. // Create multipart form data manually using Blobs
  919. const chunks = [];
  920. for (const part of parts) {
  921. // Add field boundary
  922. chunks.push(new Blob([`--${boundary}\r\n`]));
  923. chunks.push(new Blob([
  924. `Content-Disposition: form-data; name="${part.name}"\r\n`,
  925. `Content-Type: ${part.payload.type}\r\n\r\n`,
  926. ]));
  927. chunks.push(part.payload);
  928. chunks.push(new Blob(["\r\n"]));
  929. }
  930. // Add final boundary
  931. chunks.push(new Blob([`--${boundary}--\r\n`]));
  932. // Combine all chunks into a single Blob
  933. const body = new Blob(chunks);
  934. // Convert Blob to ArrayBuffer for compatibility
  935. const arrayBuffer = await body.arrayBuffer();
  936. return arrayBuffer;
  937. }
  938. async _createMultipartStream(parts, boundary) {
  939. const encoder = new TextEncoder();
  940. // Create a ReadableStream for streaming the multipart data
  941. // Only do special handling if we're using node-fetch
  942. const stream = new ReadableStream({
  943. async start(controller) {
  944. // Helper function to write a chunk to the stream
  945. const writeChunk = async (chunk) => {
  946. if (typeof chunk === "string") {
  947. controller.enqueue(encoder.encode(chunk));
  948. }
  949. else {
  950. controller.enqueue(chunk);
  951. }
  952. };
  953. // Write each part to the stream
  954. for (const part of parts) {
  955. // Write boundary and headers
  956. await writeChunk(`--${boundary}\r\n`);
  957. await writeChunk(`Content-Disposition: form-data; name="${part.name}"\r\n`);
  958. await writeChunk(`Content-Type: ${part.payload.type}\r\n\r\n`);
  959. // Write the payload
  960. const payloadStream = part.payload.stream();
  961. const reader = payloadStream.getReader();
  962. try {
  963. let result;
  964. while (!(result = await reader.read()).done) {
  965. controller.enqueue(result.value);
  966. }
  967. }
  968. finally {
  969. reader.releaseLock();
  970. }
  971. await writeChunk("\r\n");
  972. }
  973. // Write final boundary
  974. await writeChunk(`--${boundary}--\r\n`);
  975. controller.close();
  976. },
  977. });
  978. return stream;
  979. }
  980. async _sendMultipartRequest(parts, context) {
  981. try {
  982. // Create multipart form data boundary
  983. const boundary = "----LangSmithFormBoundary" + Math.random().toString(36).slice(2);
  984. const body = await (_globalFetchImplementationIsNodeFetch()
  985. ? this._createNodeFetchBody(parts, boundary)
  986. : this._createMultipartStream(parts, boundary));
  987. const res = await this.batchIngestCaller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/multipart`, {
  988. method: "POST",
  989. headers: {
  990. ...this.headers,
  991. "Content-Type": `multipart/form-data; boundary=${boundary}`,
  992. },
  993. body,
  994. duplex: "half",
  995. signal: AbortSignal.timeout(this.timeout_ms),
  996. ...this.fetchOptions,
  997. });
  998. await raiseForStatus(res, "ingest multipart runs", true);
  999. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  1000. }
  1001. catch (e) {
  1002. console.warn(`${e.message.trim()}\n\nContext: ${context}`);
  1003. }
  1004. }
  1005. async updateRun(runId, run) {
  1006. assertUuid(runId);
  1007. if (run.inputs) {
  1008. run.inputs = await this.processInputs(run.inputs);
  1009. }
  1010. if (run.outputs) {
  1011. run.outputs = await this.processOutputs(run.outputs);
  1012. }
  1013. // TODO: Untangle types
  1014. const data = { ...run, id: runId };
  1015. if (!this._filterForSampling([data], true).length) {
  1016. return;
  1017. }
  1018. if (this.autoBatchTracing &&
  1019. data.trace_id !== undefined &&
  1020. data.dotted_order !== undefined) {
  1021. if (run.end_time !== undefined &&
  1022. data.parent_run_id === undefined &&
  1023. this.blockOnRootRunFinalization &&
  1024. !this.manualFlushMode) {
  1025. // Trigger batches as soon as a root trace ends and wait to ensure trace finishes
  1026. // in serverless environments.
  1027. await this.processRunOperation({ action: "update", item: data }).catch(console.error);
  1028. return;
  1029. }
  1030. else {
  1031. void this.processRunOperation({ action: "update", item: data }).catch(console.error);
  1032. }
  1033. return;
  1034. }
  1035. const headers = { ...this.headers, "Content-Type": "application/json" };
  1036. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/${runId}`, {
  1037. method: "PATCH",
  1038. headers,
  1039. body: serializePayloadForTracing(run, `Serializing payload to update run with id: ${runId}`),
  1040. signal: AbortSignal.timeout(this.timeout_ms),
  1041. ...this.fetchOptions,
  1042. });
  1043. await raiseForStatus(response, "update run", true);
  1044. }
  1045. async readRun(runId, { loadChildRuns } = { loadChildRuns: false }) {
  1046. assertUuid(runId);
  1047. let run = await this._get(`/runs/${runId}`);
  1048. if (loadChildRuns && run.child_run_ids) {
  1049. run = await this._loadChildRuns(run);
  1050. }
  1051. return run;
  1052. }
  1053. async getRunUrl({ runId, run, projectOpts, }) {
  1054. if (run !== undefined) {
  1055. let sessionId;
  1056. if (run.session_id) {
  1057. sessionId = run.session_id;
  1058. }
  1059. else if (projectOpts?.projectName) {
  1060. sessionId = (await this.readProject({ projectName: projectOpts?.projectName })).id;
  1061. }
  1062. else if (projectOpts?.projectId) {
  1063. sessionId = projectOpts?.projectId;
  1064. }
  1065. else {
  1066. const project = await this.readProject({
  1067. projectName: getLangSmithEnvironmentVariable("PROJECT") || "default",
  1068. });
  1069. sessionId = project.id;
  1070. }
  1071. const tenantId = await this._getTenantId();
  1072. return `${this.getHostUrl()}/o/${tenantId}/projects/p/${sessionId}/r/${run.id}?poll=true`;
  1073. }
  1074. else if (runId !== undefined) {
  1075. const run_ = await this.readRun(runId);
  1076. if (!run_.app_path) {
  1077. throw new Error(`Run ${runId} has no app_path`);
  1078. }
  1079. const baseUrl = this.getHostUrl();
  1080. return `${baseUrl}${run_.app_path}`;
  1081. }
  1082. else {
  1083. throw new Error("Must provide either runId or run");
  1084. }
  1085. }
  1086. async _loadChildRuns(run) {
  1087. const childRuns = await toArray(this.listRuns({ id: run.child_run_ids }));
  1088. const treemap = {};
  1089. const runs = {};
  1090. // TODO: make dotted order required when the migration finishes
  1091. childRuns.sort((a, b) => (a?.dotted_order ?? "").localeCompare(b?.dotted_order ?? ""));
  1092. for (const childRun of childRuns) {
  1093. if (childRun.parent_run_id === null ||
  1094. childRun.parent_run_id === undefined) {
  1095. throw new Error(`Child run ${childRun.id} has no parent`);
  1096. }
  1097. if (!(childRun.parent_run_id in treemap)) {
  1098. treemap[childRun.parent_run_id] = [];
  1099. }
  1100. treemap[childRun.parent_run_id].push(childRun);
  1101. runs[childRun.id] = childRun;
  1102. }
  1103. run.child_runs = treemap[run.id] || [];
  1104. for (const runId in treemap) {
  1105. if (runId !== run.id) {
  1106. runs[runId].child_runs = treemap[runId];
  1107. }
  1108. }
  1109. return run;
  1110. }
  1111. /**
  1112. * List runs from the LangSmith server.
  1113. * @param projectId - The ID of the project to filter by.
  1114. * @param projectName - The name of the project to filter by.
  1115. * @param parentRunId - The ID of the parent run to filter by.
  1116. * @param traceId - The ID of the trace to filter by.
  1117. * @param referenceExampleId - The ID of the reference example to filter by.
  1118. * @param startTime - The start time to filter by.
  1119. * @param isRoot - Indicates whether to only return root runs.
  1120. * @param runType - The run type to filter by.
  1121. * @param error - Indicates whether to filter by error runs.
  1122. * @param id - The ID of the run to filter by.
  1123. * @param query - The query string to filter by.
  1124. * @param filter - The filter string to apply to the run spans.
  1125. * @param traceFilter - The filter string to apply on the root run of the trace.
  1126. * @param treeFilter - The filter string to apply on other runs in the trace.
  1127. * @param limit - The maximum number of runs to retrieve.
  1128. * @returns {AsyncIterable<Run>} - The runs.
  1129. *
  1130. * @example
  1131. * // List all runs in a project
  1132. * const projectRuns = client.listRuns({ projectName: "<your_project>" });
  1133. *
  1134. * @example
  1135. * // List LLM and Chat runs in the last 24 hours
  1136. * const todaysLLMRuns = client.listRuns({
  1137. * projectName: "<your_project>",
  1138. * start_time: new Date(Date.now() - 24 * 60 * 60 * 1000),
  1139. * run_type: "llm",
  1140. * });
  1141. *
  1142. * @example
  1143. * // List traces in a project
  1144. * const rootRuns = client.listRuns({
  1145. * projectName: "<your_project>",
  1146. * execution_order: 1,
  1147. * });
  1148. *
  1149. * @example
  1150. * // List runs without errors
  1151. * const correctRuns = client.listRuns({
  1152. * projectName: "<your_project>",
  1153. * error: false,
  1154. * });
  1155. *
  1156. * @example
  1157. * // List runs by run ID
  1158. * const runIds = [
  1159. * "a36092d2-4ad5-4fb4-9c0d-0dba9a2ed836",
  1160. * "9398e6be-964f-4aa4-8ae9-ad78cd4b7074",
  1161. * ];
  1162. * const selectedRuns = client.listRuns({ run_ids: runIds });
  1163. *
  1164. * @example
  1165. * // List all "chain" type runs that took more than 10 seconds and had `total_tokens` greater than 5000
  1166. * const chainRuns = client.listRuns({
  1167. * projectName: "<your_project>",
  1168. * filter: 'and(eq(run_type, "chain"), gt(latency, 10), gt(total_tokens, 5000))',
  1169. * });
  1170. *
  1171. * @example
  1172. * // List all runs called "extractor" whose root of the trace was assigned feedback "user_score" score of 1
  1173. * const goodExtractorRuns = client.listRuns({
  1174. * projectName: "<your_project>",
  1175. * filter: 'eq(name, "extractor")',
  1176. * traceFilter: 'and(eq(feedback_key, "user_score"), eq(feedback_score, 1))',
  1177. * });
  1178. *
  1179. * @example
  1180. * // List all runs that started after a specific timestamp and either have "error" not equal to null or a "Correctness" feedback score equal to 0
  1181. * const complexRuns = client.listRuns({
  1182. * projectName: "<your_project>",
  1183. * filter: 'and(gt(start_time, "2023-07-15T12:34:56Z"), or(neq(error, null), and(eq(feedback_key, "Correctness"), eq(feedback_score, 0.0))))',
  1184. * });
  1185. *
  1186. * @example
  1187. * // List all runs where `tags` include "experimental" or "beta" and `latency` is greater than 2 seconds
  1188. * const taggedRuns = client.listRuns({
  1189. * projectName: "<your_project>",
  1190. * filter: 'and(or(has(tags, "experimental"), has(tags, "beta")), gt(latency, 2))',
  1191. * });
  1192. */
  1193. async *listRuns(props) {
  1194. const { projectId, projectName, parentRunId, traceId, referenceExampleId, startTime, executionOrder, isRoot, runType, error, id, query, filter, traceFilter, treeFilter, limit, select, order, } = props;
  1195. let projectIds = [];
  1196. if (projectId) {
  1197. projectIds = Array.isArray(projectId) ? projectId : [projectId];
  1198. }
  1199. if (projectName) {
  1200. const projectNames = Array.isArray(projectName)
  1201. ? projectName
  1202. : [projectName];
  1203. const projectIds_ = await Promise.all(projectNames.map((name) => this.readProject({ projectName: name }).then((project) => project.id)));
  1204. projectIds.push(...projectIds_);
  1205. }
  1206. const default_select = [
  1207. "app_path",
  1208. "child_run_ids",
  1209. "completion_cost",
  1210. "completion_tokens",
  1211. "dotted_order",
  1212. "end_time",
  1213. "error",
  1214. "events",
  1215. "extra",
  1216. "feedback_stats",
  1217. "first_token_time",
  1218. "id",
  1219. "inputs",
  1220. "name",
  1221. "outputs",
  1222. "parent_run_id",
  1223. "parent_run_ids",
  1224. "prompt_cost",
  1225. "prompt_tokens",
  1226. "reference_example_id",
  1227. "run_type",
  1228. "session_id",
  1229. "start_time",
  1230. "status",
  1231. "tags",
  1232. "total_cost",
  1233. "total_tokens",
  1234. "trace_id",
  1235. ];
  1236. const body = {
  1237. session: projectIds.length ? projectIds : null,
  1238. run_type: runType,
  1239. reference_example: referenceExampleId,
  1240. query,
  1241. filter,
  1242. trace_filter: traceFilter,
  1243. tree_filter: treeFilter,
  1244. execution_order: executionOrder,
  1245. parent_run: parentRunId,
  1246. start_time: startTime ? startTime.toISOString() : null,
  1247. error,
  1248. id,
  1249. limit,
  1250. trace: traceId,
  1251. select: select ? select : default_select,
  1252. is_root: isRoot,
  1253. order,
  1254. };
  1255. let runsYielded = 0;
  1256. for await (const runs of this._getCursorPaginatedList("/runs/query", body)) {
  1257. if (limit) {
  1258. if (runsYielded >= limit) {
  1259. break;
  1260. }
  1261. if (runs.length + runsYielded > limit) {
  1262. const newRuns = runs.slice(0, limit - runsYielded);
  1263. yield* newRuns;
  1264. break;
  1265. }
  1266. runsYielded += runs.length;
  1267. yield* runs;
  1268. }
  1269. else {
  1270. yield* runs;
  1271. }
  1272. }
  1273. }
  1274. async *listGroupRuns(props) {
  1275. const { projectId, projectName, groupBy, filter, startTime, endTime, limit, offset, } = props;
  1276. const sessionId = projectId || (await this.readProject({ projectName })).id;
  1277. const baseBody = {
  1278. session_id: sessionId,
  1279. group_by: groupBy,
  1280. filter,
  1281. start_time: startTime ? startTime.toISOString() : null,
  1282. end_time: endTime ? endTime.toISOString() : null,
  1283. limit: Number(limit) || 100,
  1284. };
  1285. let currentOffset = Number(offset) || 0;
  1286. const path = "/runs/group";
  1287. const url = `${this.apiUrl}${path}`;
  1288. while (true) {
  1289. const currentBody = {
  1290. ...baseBody,
  1291. offset: currentOffset,
  1292. };
  1293. // Remove undefined values from the payload
  1294. const filteredPayload = Object.fromEntries(Object.entries(currentBody).filter(([_, value]) => value !== undefined));
  1295. const response = await this.caller.call(_getFetchImplementation(), url, {
  1296. method: "POST",
  1297. headers: { ...this.headers, "Content-Type": "application/json" },
  1298. body: JSON.stringify(filteredPayload),
  1299. signal: AbortSignal.timeout(this.timeout_ms),
  1300. ...this.fetchOptions,
  1301. });
  1302. await raiseForStatus(response, `Failed to fetch ${path}`);
  1303. const items = await response.json();
  1304. const { groups, total } = items;
  1305. if (groups.length === 0) {
  1306. break;
  1307. }
  1308. for (const thread of groups) {
  1309. yield thread;
  1310. }
  1311. currentOffset += groups.length;
  1312. if (currentOffset >= total) {
  1313. break;
  1314. }
  1315. }
  1316. }
  1317. async getRunStats({ id, trace, parentRun, runType, projectNames, projectIds, referenceExampleIds, startTime, endTime, error, query, filter, traceFilter, treeFilter, isRoot, dataSourceType, }) {
  1318. let projectIds_ = projectIds || [];
  1319. if (projectNames) {
  1320. projectIds_ = [
  1321. ...(projectIds || []),
  1322. ...(await Promise.all(projectNames.map((name) => this.readProject({ projectName: name }).then((project) => project.id)))),
  1323. ];
  1324. }
  1325. const payload = {
  1326. id,
  1327. trace,
  1328. parent_run: parentRun,
  1329. run_type: runType,
  1330. session: projectIds_,
  1331. reference_example: referenceExampleIds,
  1332. start_time: startTime,
  1333. end_time: endTime,
  1334. error,
  1335. query,
  1336. filter,
  1337. trace_filter: traceFilter,
  1338. tree_filter: treeFilter,
  1339. is_root: isRoot,
  1340. data_source_type: dataSourceType,
  1341. };
  1342. // Remove undefined values from the payload
  1343. const filteredPayload = Object.fromEntries(Object.entries(payload).filter(([_, value]) => value !== undefined));
  1344. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/stats`, {
  1345. method: "POST",
  1346. headers: this.headers,
  1347. body: JSON.stringify(filteredPayload),
  1348. signal: AbortSignal.timeout(this.timeout_ms),
  1349. ...this.fetchOptions,
  1350. });
  1351. const result = await response.json();
  1352. return result;
  1353. }
  1354. async shareRun(runId, { shareId } = {}) {
  1355. const data = {
  1356. run_id: runId,
  1357. share_token: shareId || uuid.v4(),
  1358. };
  1359. assertUuid(runId);
  1360. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/${runId}/share`, {
  1361. method: "PUT",
  1362. headers: this.headers,
  1363. body: JSON.stringify(data),
  1364. signal: AbortSignal.timeout(this.timeout_ms),
  1365. ...this.fetchOptions,
  1366. });
  1367. const result = await response.json();
  1368. if (result === null || !("share_token" in result)) {
  1369. throw new Error("Invalid response from server");
  1370. }
  1371. return `${this.getHostUrl()}/public/${result["share_token"]}/r`;
  1372. }
  1373. async unshareRun(runId) {
  1374. assertUuid(runId);
  1375. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/${runId}/share`, {
  1376. method: "DELETE",
  1377. headers: this.headers,
  1378. signal: AbortSignal.timeout(this.timeout_ms),
  1379. ...this.fetchOptions,
  1380. });
  1381. await raiseForStatus(response, "unshare run", true);
  1382. }
  1383. async readRunSharedLink(runId) {
  1384. assertUuid(runId);
  1385. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/${runId}/share`, {
  1386. method: "GET",
  1387. headers: this.headers,
  1388. signal: AbortSignal.timeout(this.timeout_ms),
  1389. ...this.fetchOptions,
  1390. });
  1391. const result = await response.json();
  1392. if (result === null || !("share_token" in result)) {
  1393. return undefined;
  1394. }
  1395. return `${this.getHostUrl()}/public/${result["share_token"]}/r`;
  1396. }
  1397. async listSharedRuns(shareToken, { runIds, } = {}) {
  1398. const queryParams = new URLSearchParams({
  1399. share_token: shareToken,
  1400. });
  1401. if (runIds !== undefined) {
  1402. for (const runId of runIds) {
  1403. queryParams.append("id", runId);
  1404. }
  1405. }
  1406. assertUuid(shareToken);
  1407. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/public/${shareToken}/runs${queryParams}`, {
  1408. method: "GET",
  1409. headers: this.headers,
  1410. signal: AbortSignal.timeout(this.timeout_ms),
  1411. ...this.fetchOptions,
  1412. });
  1413. const runs = await response.json();
  1414. return runs;
  1415. }
  1416. async readDatasetSharedSchema(datasetId, datasetName) {
  1417. if (!datasetId && !datasetName) {
  1418. throw new Error("Either datasetId or datasetName must be given");
  1419. }
  1420. if (!datasetId) {
  1421. const dataset = await this.readDataset({ datasetName });
  1422. datasetId = dataset.id;
  1423. }
  1424. assertUuid(datasetId);
  1425. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId}/share`, {
  1426. method: "GET",
  1427. headers: this.headers,
  1428. signal: AbortSignal.timeout(this.timeout_ms),
  1429. ...this.fetchOptions,
  1430. });
  1431. const shareSchema = await response.json();
  1432. shareSchema.url = `${this.getHostUrl()}/public/${shareSchema.share_token}/d`;
  1433. return shareSchema;
  1434. }
  1435. async shareDataset(datasetId, datasetName) {
  1436. if (!datasetId && !datasetName) {
  1437. throw new Error("Either datasetId or datasetName must be given");
  1438. }
  1439. if (!datasetId) {
  1440. const dataset = await this.readDataset({ datasetName });
  1441. datasetId = dataset.id;
  1442. }
  1443. const data = {
  1444. dataset_id: datasetId,
  1445. };
  1446. assertUuid(datasetId);
  1447. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId}/share`, {
  1448. method: "PUT",
  1449. headers: this.headers,
  1450. body: JSON.stringify(data),
  1451. signal: AbortSignal.timeout(this.timeout_ms),
  1452. ...this.fetchOptions,
  1453. });
  1454. const shareSchema = await response.json();
  1455. shareSchema.url = `${this.getHostUrl()}/public/${shareSchema.share_token}/d`;
  1456. return shareSchema;
  1457. }
  1458. async unshareDataset(datasetId) {
  1459. assertUuid(datasetId);
  1460. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId}/share`, {
  1461. method: "DELETE",
  1462. headers: this.headers,
  1463. signal: AbortSignal.timeout(this.timeout_ms),
  1464. ...this.fetchOptions,
  1465. });
  1466. await raiseForStatus(response, "unshare dataset", true);
  1467. }
  1468. async readSharedDataset(shareToken) {
  1469. assertUuid(shareToken);
  1470. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/public/${shareToken}/datasets`, {
  1471. method: "GET",
  1472. headers: this.headers,
  1473. signal: AbortSignal.timeout(this.timeout_ms),
  1474. ...this.fetchOptions,
  1475. });
  1476. const dataset = await response.json();
  1477. return dataset;
  1478. }
  1479. /**
  1480. * Get shared examples.
  1481. *
  1482. * @param {string} shareToken The share token to get examples for. A share token is the UUID (or LangSmith URL, including UUID) generated when explicitly marking an example as public.
  1483. * @param {Object} [options] Additional options for listing the examples.
  1484. * @param {string[] | undefined} [options.exampleIds] A list of example IDs to filter by.
  1485. * @returns {Promise<Example[]>} The shared examples.
  1486. */
  1487. async listSharedExamples(shareToken, options) {
  1488. const params = {};
  1489. if (options?.exampleIds) {
  1490. params.id = options.exampleIds;
  1491. }
  1492. const urlParams = new URLSearchParams();
  1493. Object.entries(params).forEach(([key, value]) => {
  1494. if (Array.isArray(value)) {
  1495. value.forEach((v) => urlParams.append(key, v));
  1496. }
  1497. else {
  1498. urlParams.append(key, value);
  1499. }
  1500. });
  1501. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/public/${shareToken}/examples?${urlParams.toString()}`, {
  1502. method: "GET",
  1503. headers: this.headers,
  1504. signal: AbortSignal.timeout(this.timeout_ms),
  1505. ...this.fetchOptions,
  1506. });
  1507. const result = await response.json();
  1508. if (!response.ok) {
  1509. if ("detail" in result) {
  1510. throw new Error(`Failed to list shared examples.\nStatus: ${response.status}\nMessage: ${Array.isArray(result.detail)
  1511. ? result.detail.join("\n")
  1512. : "Unspecified error"}`);
  1513. }
  1514. throw new Error(`Failed to list shared examples: ${response.status} ${response.statusText}`);
  1515. }
  1516. return result.map((example) => ({
  1517. ...example,
  1518. _hostUrl: this.getHostUrl(),
  1519. }));
  1520. }
  1521. async createProject({ projectName, description = null, metadata = null, upsert = false, projectExtra = null, referenceDatasetId = null, }) {
  1522. const upsert_ = upsert ? `?upsert=true` : "";
  1523. const endpoint = `${this.apiUrl}/sessions${upsert_}`;
  1524. const extra = projectExtra || {};
  1525. if (metadata) {
  1526. extra["metadata"] = metadata;
  1527. }
  1528. const body = {
  1529. name: projectName,
  1530. extra,
  1531. description,
  1532. };
  1533. if (referenceDatasetId !== null) {
  1534. body["reference_dataset_id"] = referenceDatasetId;
  1535. }
  1536. const response = await this.caller.call(_getFetchImplementation(this.debug), endpoint, {
  1537. method: "POST",
  1538. headers: { ...this.headers, "Content-Type": "application/json" },
  1539. body: JSON.stringify(body),
  1540. signal: AbortSignal.timeout(this.timeout_ms),
  1541. ...this.fetchOptions,
  1542. });
  1543. await raiseForStatus(response, "create project");
  1544. const result = await response.json();
  1545. return result;
  1546. }
  1547. async updateProject(projectId, { name = null, description = null, metadata = null, projectExtra = null, endTime = null, }) {
  1548. const endpoint = `${this.apiUrl}/sessions/${projectId}`;
  1549. let extra = projectExtra;
  1550. if (metadata) {
  1551. extra = { ...(extra || {}), metadata };
  1552. }
  1553. const body = {
  1554. name,
  1555. extra,
  1556. description,
  1557. end_time: endTime ? new Date(endTime).toISOString() : null,
  1558. };
  1559. const response = await this.caller.call(_getFetchImplementation(this.debug), endpoint, {
  1560. method: "PATCH",
  1561. headers: { ...this.headers, "Content-Type": "application/json" },
  1562. body: JSON.stringify(body),
  1563. signal: AbortSignal.timeout(this.timeout_ms),
  1564. ...this.fetchOptions,
  1565. });
  1566. await raiseForStatus(response, "update project");
  1567. const result = await response.json();
  1568. return result;
  1569. }
  1570. async hasProject({ projectId, projectName, }) {
  1571. // TODO: Add a head request
  1572. let path = "/sessions";
  1573. const params = new URLSearchParams();
  1574. if (projectId !== undefined && projectName !== undefined) {
  1575. throw new Error("Must provide either projectName or projectId, not both");
  1576. }
  1577. else if (projectId !== undefined) {
  1578. assertUuid(projectId);
  1579. path += `/${projectId}`;
  1580. }
  1581. else if (projectName !== undefined) {
  1582. params.append("name", projectName);
  1583. }
  1584. else {
  1585. throw new Error("Must provide projectName or projectId");
  1586. }
  1587. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}${path}?${params}`, {
  1588. method: "GET",
  1589. headers: this.headers,
  1590. signal: AbortSignal.timeout(this.timeout_ms),
  1591. ...this.fetchOptions,
  1592. });
  1593. // consume the response body to release the connection
  1594. // https://undici.nodejs.org/#/?id=garbage-collection
  1595. try {
  1596. const result = await response.json();
  1597. if (!response.ok) {
  1598. return false;
  1599. }
  1600. // If it's OK and we're querying by name, need to check the list is not empty
  1601. if (Array.isArray(result)) {
  1602. return result.length > 0;
  1603. }
  1604. // projectId querying
  1605. return true;
  1606. }
  1607. catch (e) {
  1608. return false;
  1609. }
  1610. }
  1611. async readProject({ projectId, projectName, includeStats, }) {
  1612. let path = "/sessions";
  1613. const params = new URLSearchParams();
  1614. if (projectId !== undefined && projectName !== undefined) {
  1615. throw new Error("Must provide either projectName or projectId, not both");
  1616. }
  1617. else if (projectId !== undefined) {
  1618. assertUuid(projectId);
  1619. path += `/${projectId}`;
  1620. }
  1621. else if (projectName !== undefined) {
  1622. params.append("name", projectName);
  1623. }
  1624. else {
  1625. throw new Error("Must provide projectName or projectId");
  1626. }
  1627. if (includeStats !== undefined) {
  1628. params.append("include_stats", includeStats.toString());
  1629. }
  1630. const response = await this._get(path, params);
  1631. let result;
  1632. if (Array.isArray(response)) {
  1633. if (response.length === 0) {
  1634. throw new Error(`Project[id=${projectId}, name=${projectName}] not found`);
  1635. }
  1636. result = response[0];
  1637. }
  1638. else {
  1639. result = response;
  1640. }
  1641. return result;
  1642. }
  1643. async getProjectUrl({ projectId, projectName, }) {
  1644. if (projectId === undefined && projectName === undefined) {
  1645. throw new Error("Must provide either projectName or projectId");
  1646. }
  1647. const project = await this.readProject({ projectId, projectName });
  1648. const tenantId = await this._getTenantId();
  1649. return `${this.getHostUrl()}/o/${tenantId}/projects/p/${project.id}`;
  1650. }
  1651. async getDatasetUrl({ datasetId, datasetName, }) {
  1652. if (datasetId === undefined && datasetName === undefined) {
  1653. throw new Error("Must provide either datasetName or datasetId");
  1654. }
  1655. const dataset = await this.readDataset({ datasetId, datasetName });
  1656. const tenantId = await this._getTenantId();
  1657. return `${this.getHostUrl()}/o/${tenantId}/datasets/${dataset.id}`;
  1658. }
  1659. async _getTenantId() {
  1660. if (this._tenantId !== null) {
  1661. return this._tenantId;
  1662. }
  1663. const queryParams = new URLSearchParams({ limit: "1" });
  1664. for await (const projects of this._getPaginated("/sessions", queryParams)) {
  1665. this._tenantId = projects[0].tenant_id;
  1666. return projects[0].tenant_id;
  1667. }
  1668. throw new Error("No projects found to resolve tenant.");
  1669. }
  1670. async *listProjects({ projectIds, name, nameContains, referenceDatasetId, referenceDatasetName, referenceFree, metadata, } = {}) {
  1671. const params = new URLSearchParams();
  1672. if (projectIds !== undefined) {
  1673. for (const projectId of projectIds) {
  1674. params.append("id", projectId);
  1675. }
  1676. }
  1677. if (name !== undefined) {
  1678. params.append("name", name);
  1679. }
  1680. if (nameContains !== undefined) {
  1681. params.append("name_contains", nameContains);
  1682. }
  1683. if (referenceDatasetId !== undefined) {
  1684. params.append("reference_dataset", referenceDatasetId);
  1685. }
  1686. else if (referenceDatasetName !== undefined) {
  1687. const dataset = await this.readDataset({
  1688. datasetName: referenceDatasetName,
  1689. });
  1690. params.append("reference_dataset", dataset.id);
  1691. }
  1692. if (referenceFree !== undefined) {
  1693. params.append("reference_free", referenceFree.toString());
  1694. }
  1695. if (metadata !== undefined) {
  1696. params.append("metadata", JSON.stringify(metadata));
  1697. }
  1698. for await (const projects of this._getPaginated("/sessions", params)) {
  1699. yield* projects;
  1700. }
  1701. }
  1702. async deleteProject({ projectId, projectName, }) {
  1703. let projectId_;
  1704. if (projectId === undefined && projectName === undefined) {
  1705. throw new Error("Must provide projectName or projectId");
  1706. }
  1707. else if (projectId !== undefined && projectName !== undefined) {
  1708. throw new Error("Must provide either projectName or projectId, not both");
  1709. }
  1710. else if (projectId === undefined) {
  1711. projectId_ = (await this.readProject({ projectName })).id;
  1712. }
  1713. else {
  1714. projectId_ = projectId;
  1715. }
  1716. assertUuid(projectId_);
  1717. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/sessions/${projectId_}`, {
  1718. method: "DELETE",
  1719. headers: this.headers,
  1720. signal: AbortSignal.timeout(this.timeout_ms),
  1721. ...this.fetchOptions,
  1722. });
  1723. await raiseForStatus(response, `delete session ${projectId_} (${projectName})`, true);
  1724. }
  1725. async uploadCsv({ csvFile, fileName, inputKeys, outputKeys, description, dataType, name, }) {
  1726. const url = `${this.apiUrl}/datasets/upload`;
  1727. const formData = new FormData();
  1728. formData.append("file", csvFile, fileName);
  1729. inputKeys.forEach((key) => {
  1730. formData.append("input_keys", key);
  1731. });
  1732. outputKeys.forEach((key) => {
  1733. formData.append("output_keys", key);
  1734. });
  1735. if (description) {
  1736. formData.append("description", description);
  1737. }
  1738. if (dataType) {
  1739. formData.append("data_type", dataType);
  1740. }
  1741. if (name) {
  1742. formData.append("name", name);
  1743. }
  1744. const response = await this.caller.call(_getFetchImplementation(this.debug), url, {
  1745. method: "POST",
  1746. headers: this.headers,
  1747. body: formData,
  1748. signal: AbortSignal.timeout(this.timeout_ms),
  1749. ...this.fetchOptions,
  1750. });
  1751. await raiseForStatus(response, "upload CSV");
  1752. const result = await response.json();
  1753. return result;
  1754. }
  1755. async createDataset(name, { description, dataType, inputsSchema, outputsSchema, metadata, } = {}) {
  1756. const body = {
  1757. name,
  1758. description,
  1759. extra: metadata ? { metadata } : undefined,
  1760. };
  1761. if (dataType) {
  1762. body.data_type = dataType;
  1763. }
  1764. if (inputsSchema) {
  1765. body.inputs_schema_definition = inputsSchema;
  1766. }
  1767. if (outputsSchema) {
  1768. body.outputs_schema_definition = outputsSchema;
  1769. }
  1770. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets`, {
  1771. method: "POST",
  1772. headers: { ...this.headers, "Content-Type": "application/json" },
  1773. body: JSON.stringify(body),
  1774. signal: AbortSignal.timeout(this.timeout_ms),
  1775. ...this.fetchOptions,
  1776. });
  1777. await raiseForStatus(response, "create dataset");
  1778. const result = await response.json();
  1779. return result;
  1780. }
  1781. async readDataset({ datasetId, datasetName, }) {
  1782. let path = "/datasets";
  1783. // limit to 1 result
  1784. const params = new URLSearchParams({ limit: "1" });
  1785. if (datasetId !== undefined && datasetName !== undefined) {
  1786. throw new Error("Must provide either datasetName or datasetId, not both");
  1787. }
  1788. else if (datasetId !== undefined) {
  1789. assertUuid(datasetId);
  1790. path += `/${datasetId}`;
  1791. }
  1792. else if (datasetName !== undefined) {
  1793. params.append("name", datasetName);
  1794. }
  1795. else {
  1796. throw new Error("Must provide datasetName or datasetId");
  1797. }
  1798. const response = await this._get(path, params);
  1799. let result;
  1800. if (Array.isArray(response)) {
  1801. if (response.length === 0) {
  1802. throw new Error(`Dataset[id=${datasetId}, name=${datasetName}] not found`);
  1803. }
  1804. result = response[0];
  1805. }
  1806. else {
  1807. result = response;
  1808. }
  1809. return result;
  1810. }
  1811. async hasDataset({ datasetId, datasetName, }) {
  1812. try {
  1813. await this.readDataset({ datasetId, datasetName });
  1814. return true;
  1815. }
  1816. catch (e) {
  1817. if (
  1818. // eslint-disable-next-line no-instanceof/no-instanceof
  1819. e instanceof Error &&
  1820. e.message.toLocaleLowerCase().includes("not found")) {
  1821. return false;
  1822. }
  1823. throw e;
  1824. }
  1825. }
  1826. async diffDatasetVersions({ datasetId, datasetName, fromVersion, toVersion, }) {
  1827. let datasetId_ = datasetId;
  1828. if (datasetId_ === undefined && datasetName === undefined) {
  1829. throw new Error("Must provide either datasetName or datasetId");
  1830. }
  1831. else if (datasetId_ !== undefined && datasetName !== undefined) {
  1832. throw new Error("Must provide either datasetName or datasetId, not both");
  1833. }
  1834. else if (datasetId_ === undefined) {
  1835. const dataset = await this.readDataset({ datasetName });
  1836. datasetId_ = dataset.id;
  1837. }
  1838. const urlParams = new URLSearchParams({
  1839. from_version: typeof fromVersion === "string"
  1840. ? fromVersion
  1841. : fromVersion.toISOString(),
  1842. to_version: typeof toVersion === "string" ? toVersion : toVersion.toISOString(),
  1843. });
  1844. const response = await this._get(`/datasets/${datasetId_}/versions/diff`, urlParams);
  1845. return response;
  1846. }
  1847. async readDatasetOpenaiFinetuning({ datasetId, datasetName, }) {
  1848. const path = "/datasets";
  1849. if (datasetId !== undefined) {
  1850. // do nothing
  1851. }
  1852. else if (datasetName !== undefined) {
  1853. datasetId = (await this.readDataset({ datasetName })).id;
  1854. }
  1855. else {
  1856. throw new Error("Must provide either datasetName or datasetId");
  1857. }
  1858. const response = await this._getResponse(`${path}/${datasetId}/openai_ft`);
  1859. const datasetText = await response.text();
  1860. const dataset = datasetText
  1861. .trim()
  1862. .split("\n")
  1863. .map((line) => JSON.parse(line));
  1864. return dataset;
  1865. }
  1866. async *listDatasets({ limit = 100, offset = 0, datasetIds, datasetName, datasetNameContains, metadata, } = {}) {
  1867. const path = "/datasets";
  1868. const params = new URLSearchParams({
  1869. limit: limit.toString(),
  1870. offset: offset.toString(),
  1871. });
  1872. if (datasetIds !== undefined) {
  1873. for (const id_ of datasetIds) {
  1874. params.append("id", id_);
  1875. }
  1876. }
  1877. if (datasetName !== undefined) {
  1878. params.append("name", datasetName);
  1879. }
  1880. if (datasetNameContains !== undefined) {
  1881. params.append("name_contains", datasetNameContains);
  1882. }
  1883. if (metadata !== undefined) {
  1884. params.append("metadata", JSON.stringify(metadata));
  1885. }
  1886. for await (const datasets of this._getPaginated(path, params)) {
  1887. yield* datasets;
  1888. }
  1889. }
  1890. /**
  1891. * Update a dataset
  1892. * @param props The dataset details to update
  1893. * @returns The updated dataset
  1894. */
  1895. async updateDataset(props) {
  1896. const { datasetId, datasetName, ...update } = props;
  1897. if (!datasetId && !datasetName) {
  1898. throw new Error("Must provide either datasetName or datasetId");
  1899. }
  1900. const _datasetId = datasetId ?? (await this.readDataset({ datasetName })).id;
  1901. assertUuid(_datasetId);
  1902. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${_datasetId}`, {
  1903. method: "PATCH",
  1904. headers: { ...this.headers, "Content-Type": "application/json" },
  1905. body: JSON.stringify(update),
  1906. signal: AbortSignal.timeout(this.timeout_ms),
  1907. ...this.fetchOptions,
  1908. });
  1909. await raiseForStatus(response, "update dataset");
  1910. return (await response.json());
  1911. }
  1912. /**
  1913. * Updates a tag on a dataset.
  1914. *
  1915. * If the tag is already assigned to a different version of this dataset,
  1916. * the tag will be moved to the new version. The as_of parameter is used to
  1917. * determine which version of the dataset to apply the new tags to.
  1918. *
  1919. * It must be an exact version of the dataset to succeed. You can
  1920. * use the "readDatasetVersion" method to find the exact version
  1921. * to apply the tags to.
  1922. * @param params.datasetId The ID of the dataset to update. Must be provided if "datasetName" is not provided.
  1923. * @param params.datasetName The name of the dataset to update. Must be provided if "datasetId" is not provided.
  1924. * @param params.asOf The timestamp of the dataset to apply the new tags to.
  1925. * @param params.tag The new tag to apply to the dataset.
  1926. */
  1927. async updateDatasetTag(props) {
  1928. const { datasetId, datasetName, asOf, tag } = props;
  1929. if (!datasetId && !datasetName) {
  1930. throw new Error("Must provide either datasetName or datasetId");
  1931. }
  1932. const _datasetId = datasetId ?? (await this.readDataset({ datasetName })).id;
  1933. assertUuid(_datasetId);
  1934. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${_datasetId}/tags`, {
  1935. method: "PUT",
  1936. headers: { ...this.headers, "Content-Type": "application/json" },
  1937. body: JSON.stringify({
  1938. as_of: typeof asOf === "string" ? asOf : asOf.toISOString(),
  1939. tag,
  1940. }),
  1941. signal: AbortSignal.timeout(this.timeout_ms),
  1942. ...this.fetchOptions,
  1943. });
  1944. await raiseForStatus(response, "update dataset tags");
  1945. }
  1946. async deleteDataset({ datasetId, datasetName, }) {
  1947. let path = "/datasets";
  1948. let datasetId_ = datasetId;
  1949. if (datasetId !== undefined && datasetName !== undefined) {
  1950. throw new Error("Must provide either datasetName or datasetId, not both");
  1951. }
  1952. else if (datasetName !== undefined) {
  1953. const dataset = await this.readDataset({ datasetName });
  1954. datasetId_ = dataset.id;
  1955. }
  1956. if (datasetId_ !== undefined) {
  1957. assertUuid(datasetId_);
  1958. path += `/${datasetId_}`;
  1959. }
  1960. else {
  1961. throw new Error("Must provide datasetName or datasetId");
  1962. }
  1963. const response = await this.caller.call(_getFetchImplementation(this.debug), this.apiUrl + path, {
  1964. method: "DELETE",
  1965. headers: this.headers,
  1966. signal: AbortSignal.timeout(this.timeout_ms),
  1967. ...this.fetchOptions,
  1968. });
  1969. await raiseForStatus(response, `delete ${path}`);
  1970. await response.json();
  1971. }
  1972. async indexDataset({ datasetId, datasetName, tag, }) {
  1973. let datasetId_ = datasetId;
  1974. if (!datasetId_ && !datasetName) {
  1975. throw new Error("Must provide either datasetName or datasetId");
  1976. }
  1977. else if (datasetId_ && datasetName) {
  1978. throw new Error("Must provide either datasetName or datasetId, not both");
  1979. }
  1980. else if (!datasetId_) {
  1981. const dataset = await this.readDataset({ datasetName });
  1982. datasetId_ = dataset.id;
  1983. }
  1984. assertUuid(datasetId_);
  1985. const data = {
  1986. tag: tag,
  1987. };
  1988. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId_}/index`, {
  1989. method: "POST",
  1990. headers: { ...this.headers, "Content-Type": "application/json" },
  1991. body: JSON.stringify(data),
  1992. signal: AbortSignal.timeout(this.timeout_ms),
  1993. ...this.fetchOptions,
  1994. });
  1995. await raiseForStatus(response, "index dataset");
  1996. await response.json();
  1997. }
  1998. /**
  1999. * Lets you run a similarity search query on a dataset.
  2000. *
  2001. * Requires the dataset to be indexed. Please see the `indexDataset` method to set up indexing.
  2002. *
  2003. * @param inputs The input on which to run the similarity search. Must have the
  2004. * same schema as the dataset.
  2005. *
  2006. * @param datasetId The dataset to search for similar examples.
  2007. *
  2008. * @param limit The maximum number of examples to return. Will return the top `limit` most
  2009. * similar examples in order of most similar to least similar. If no similar
  2010. * examples are found, random examples will be returned.
  2011. *
  2012. * @param filter A filter string to apply to the search. Only examples will be returned that
  2013. * match the filter string. Some examples of filters
  2014. *
  2015. * - eq(metadata.mykey, "value")
  2016. * - and(neq(metadata.my.nested.key, "value"), neq(metadata.mykey, "value"))
  2017. * - or(eq(metadata.mykey, "value"), eq(metadata.mykey, "othervalue"))
  2018. *
  2019. * @returns A list of similar examples.
  2020. *
  2021. *
  2022. * @example
  2023. * dataset_id = "123e4567-e89b-12d3-a456-426614174000"
  2024. * inputs = {"text": "How many people live in Berlin?"}
  2025. * limit = 5
  2026. * examples = await client.similarExamples(inputs, dataset_id, limit)
  2027. */
  2028. async similarExamples(inputs, datasetId, limit, { filter, } = {}) {
  2029. const data = {
  2030. limit: limit,
  2031. inputs: inputs,
  2032. };
  2033. if (filter !== undefined) {
  2034. data["filter"] = filter;
  2035. }
  2036. assertUuid(datasetId);
  2037. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId}/search`, {
  2038. method: "POST",
  2039. headers: { ...this.headers, "Content-Type": "application/json" },
  2040. body: JSON.stringify(data),
  2041. signal: AbortSignal.timeout(this.timeout_ms),
  2042. ...this.fetchOptions,
  2043. });
  2044. await raiseForStatus(response, "fetch similar examples");
  2045. const result = await response.json();
  2046. return result["examples"];
  2047. }
  2048. async createExample(inputsOrUpdate, outputs, options) {
  2049. if (isExampleCreate(inputsOrUpdate)) {
  2050. if (outputs !== undefined || options !== undefined) {
  2051. throw new Error("Cannot provide outputs or options when using ExampleCreate object");
  2052. }
  2053. }
  2054. let datasetId_ = outputs ? options?.datasetId : inputsOrUpdate.dataset_id;
  2055. const datasetName_ = outputs
  2056. ? options?.datasetName
  2057. : inputsOrUpdate.dataset_name;
  2058. if (datasetId_ === undefined && datasetName_ === undefined) {
  2059. throw new Error("Must provide either datasetName or datasetId");
  2060. }
  2061. else if (datasetId_ !== undefined && datasetName_ !== undefined) {
  2062. throw new Error("Must provide either datasetName or datasetId, not both");
  2063. }
  2064. else if (datasetId_ === undefined) {
  2065. const dataset = await this.readDataset({ datasetName: datasetName_ });
  2066. datasetId_ = dataset.id;
  2067. }
  2068. const createdAt_ = (outputs ? options?.createdAt : inputsOrUpdate.created_at) || new Date();
  2069. let data;
  2070. if (!isExampleCreate(inputsOrUpdate)) {
  2071. data = {
  2072. inputs: inputsOrUpdate,
  2073. outputs,
  2074. created_at: createdAt_?.toISOString(),
  2075. id: options?.exampleId,
  2076. metadata: options?.metadata,
  2077. split: options?.split,
  2078. source_run_id: options?.sourceRunId,
  2079. use_source_run_io: options?.useSourceRunIO,
  2080. use_source_run_attachments: options?.useSourceRunAttachments,
  2081. attachments: options?.attachments,
  2082. };
  2083. }
  2084. else {
  2085. data = inputsOrUpdate;
  2086. }
  2087. const response = await this._uploadExamplesMultipart(datasetId_, [data]);
  2088. const example = await this.readExample(response.example_ids?.[0] ?? uuid.v4());
  2089. return example;
  2090. }
  2091. async createExamples(propsOrUploads) {
  2092. if (Array.isArray(propsOrUploads)) {
  2093. if (propsOrUploads.length === 0) {
  2094. return [];
  2095. }
  2096. const uploads = propsOrUploads;
  2097. let datasetId_ = uploads[0].dataset_id;
  2098. const datasetName_ = uploads[0].dataset_name;
  2099. if (datasetId_ === undefined && datasetName_ === undefined) {
  2100. throw new Error("Must provide either datasetName or datasetId");
  2101. }
  2102. else if (datasetId_ !== undefined && datasetName_ !== undefined) {
  2103. throw new Error("Must provide either datasetName or datasetId, not both");
  2104. }
  2105. else if (datasetId_ === undefined) {
  2106. const dataset = await this.readDataset({ datasetName: datasetName_ });
  2107. datasetId_ = dataset.id;
  2108. }
  2109. const response = await this._uploadExamplesMultipart(datasetId_, uploads);
  2110. const examples = await Promise.all(response.example_ids.map((id) => this.readExample(id)));
  2111. return examples;
  2112. }
  2113. const { inputs, outputs, metadata, splits, sourceRunIds, useSourceRunIOs, useSourceRunAttachments, attachments, exampleIds, datasetId, datasetName, } = propsOrUploads;
  2114. if (inputs === undefined) {
  2115. throw new Error("Must provide inputs when using legacy parameters");
  2116. }
  2117. let datasetId_ = datasetId;
  2118. const datasetName_ = datasetName;
  2119. if (datasetId_ === undefined && datasetName_ === undefined) {
  2120. throw new Error("Must provide either datasetName or datasetId");
  2121. }
  2122. else if (datasetId_ !== undefined && datasetName_ !== undefined) {
  2123. throw new Error("Must provide either datasetName or datasetId, not both");
  2124. }
  2125. else if (datasetId_ === undefined) {
  2126. const dataset = await this.readDataset({ datasetName: datasetName_ });
  2127. datasetId_ = dataset.id;
  2128. }
  2129. const formattedExamples = inputs.map((input, idx) => {
  2130. return {
  2131. dataset_id: datasetId_,
  2132. inputs: input,
  2133. outputs: outputs?.[idx],
  2134. metadata: metadata?.[idx],
  2135. split: splits?.[idx],
  2136. id: exampleIds?.[idx],
  2137. attachments: attachments?.[idx],
  2138. source_run_id: sourceRunIds?.[idx],
  2139. use_source_run_io: useSourceRunIOs?.[idx],
  2140. use_source_run_attachments: useSourceRunAttachments?.[idx],
  2141. };
  2142. });
  2143. const response = await this._uploadExamplesMultipart(datasetId_, formattedExamples);
  2144. const examples = await Promise.all(response.example_ids.map((id) => this.readExample(id)));
  2145. return examples;
  2146. }
  2147. async createLLMExample(input, generation, options) {
  2148. return this.createExample({ input }, { output: generation }, options);
  2149. }
  2150. async createChatExample(input, generations, options) {
  2151. const finalInput = input.map((message) => {
  2152. if (isLangChainMessage(message)) {
  2153. return convertLangChainMessageToExample(message);
  2154. }
  2155. return message;
  2156. });
  2157. const finalOutput = isLangChainMessage(generations)
  2158. ? convertLangChainMessageToExample(generations)
  2159. : generations;
  2160. return this.createExample({ input: finalInput }, { output: finalOutput }, options);
  2161. }
  2162. async readExample(exampleId) {
  2163. assertUuid(exampleId);
  2164. const path = `/examples/${exampleId}`;
  2165. const rawExample = await this._get(path);
  2166. const { attachment_urls, ...rest } = rawExample;
  2167. const example = rest;
  2168. if (attachment_urls) {
  2169. example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
  2170. acc[key.slice("attachment.".length)] = {
  2171. presigned_url: value.presigned_url,
  2172. mime_type: value.mime_type,
  2173. };
  2174. return acc;
  2175. }, {});
  2176. }
  2177. return example;
  2178. }
  2179. async *listExamples({ datasetId, datasetName, exampleIds, asOf, splits, inlineS3Urls, metadata, limit, offset, filter, includeAttachments, } = {}) {
  2180. let datasetId_;
  2181. if (datasetId !== undefined && datasetName !== undefined) {
  2182. throw new Error("Must provide either datasetName or datasetId, not both");
  2183. }
  2184. else if (datasetId !== undefined) {
  2185. datasetId_ = datasetId;
  2186. }
  2187. else if (datasetName !== undefined) {
  2188. const dataset = await this.readDataset({ datasetName });
  2189. datasetId_ = dataset.id;
  2190. }
  2191. else {
  2192. throw new Error("Must provide a datasetName or datasetId");
  2193. }
  2194. const params = new URLSearchParams({ dataset: datasetId_ });
  2195. const dataset_version = asOf
  2196. ? typeof asOf === "string"
  2197. ? asOf
  2198. : asOf?.toISOString()
  2199. : undefined;
  2200. if (dataset_version) {
  2201. params.append("as_of", dataset_version);
  2202. }
  2203. const inlineS3Urls_ = inlineS3Urls ?? true;
  2204. params.append("inline_s3_urls", inlineS3Urls_.toString());
  2205. if (exampleIds !== undefined) {
  2206. for (const id_ of exampleIds) {
  2207. params.append("id", id_);
  2208. }
  2209. }
  2210. if (splits !== undefined) {
  2211. for (const split of splits) {
  2212. params.append("splits", split);
  2213. }
  2214. }
  2215. if (metadata !== undefined) {
  2216. const serializedMetadata = JSON.stringify(metadata);
  2217. params.append("metadata", serializedMetadata);
  2218. }
  2219. if (limit !== undefined) {
  2220. params.append("limit", limit.toString());
  2221. }
  2222. if (offset !== undefined) {
  2223. params.append("offset", offset.toString());
  2224. }
  2225. if (filter !== undefined) {
  2226. params.append("filter", filter);
  2227. }
  2228. if (includeAttachments === true) {
  2229. ["attachment_urls", "outputs", "metadata"].forEach((field) => params.append("select", field));
  2230. }
  2231. let i = 0;
  2232. for await (const rawExamples of this._getPaginated("/examples", params)) {
  2233. for (const rawExample of rawExamples) {
  2234. const { attachment_urls, ...rest } = rawExample;
  2235. const example = rest;
  2236. if (attachment_urls) {
  2237. example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
  2238. acc[key.slice("attachment.".length)] = {
  2239. presigned_url: value.presigned_url,
  2240. mime_type: value.mime_type || undefined,
  2241. };
  2242. return acc;
  2243. }, {});
  2244. }
  2245. yield example;
  2246. i++;
  2247. }
  2248. if (limit !== undefined && i >= limit) {
  2249. break;
  2250. }
  2251. }
  2252. }
  2253. async deleteExample(exampleId) {
  2254. assertUuid(exampleId);
  2255. const path = `/examples/${exampleId}`;
  2256. const response = await this.caller.call(_getFetchImplementation(this.debug), this.apiUrl + path, {
  2257. method: "DELETE",
  2258. headers: this.headers,
  2259. signal: AbortSignal.timeout(this.timeout_ms),
  2260. ...this.fetchOptions,
  2261. });
  2262. await raiseForStatus(response, `delete ${path}`);
  2263. await response.json();
  2264. }
  2265. async updateExample(exampleIdOrUpdate, update) {
  2266. let exampleId;
  2267. if (update) {
  2268. exampleId = exampleIdOrUpdate;
  2269. }
  2270. else {
  2271. exampleId = exampleIdOrUpdate.id;
  2272. }
  2273. assertUuid(exampleId);
  2274. let updateToUse;
  2275. if (update) {
  2276. updateToUse = { id: exampleId, ...update };
  2277. }
  2278. else {
  2279. updateToUse = exampleIdOrUpdate;
  2280. }
  2281. let datasetId;
  2282. if (updateToUse.dataset_id !== undefined) {
  2283. datasetId = updateToUse.dataset_id;
  2284. }
  2285. else {
  2286. const example = await this.readExample(exampleId);
  2287. datasetId = example.dataset_id;
  2288. }
  2289. return this._updateExamplesMultipart(datasetId, [updateToUse]);
  2290. }
  2291. async updateExamples(update) {
  2292. // We will naively get dataset id from first example and assume it works for all
  2293. let datasetId;
  2294. if (update[0].dataset_id === undefined) {
  2295. const example = await this.readExample(update[0].id);
  2296. datasetId = example.dataset_id;
  2297. }
  2298. else {
  2299. datasetId = update[0].dataset_id;
  2300. }
  2301. return this._updateExamplesMultipart(datasetId, update);
  2302. }
  2303. /**
  2304. * Get dataset version by closest date or exact tag.
  2305. *
  2306. * Use this to resolve the nearest version to a given timestamp or for a given tag.
  2307. *
  2308. * @param options The options for getting the dataset version
  2309. * @param options.datasetId The ID of the dataset
  2310. * @param options.datasetName The name of the dataset
  2311. * @param options.asOf The timestamp of the dataset to retrieve
  2312. * @param options.tag The tag of the dataset to retrieve
  2313. * @returns The dataset version
  2314. */
  2315. async readDatasetVersion({ datasetId, datasetName, asOf, tag, }) {
  2316. let resolvedDatasetId;
  2317. if (!datasetId) {
  2318. const dataset = await this.readDataset({ datasetName });
  2319. resolvedDatasetId = dataset.id;
  2320. }
  2321. else {
  2322. resolvedDatasetId = datasetId;
  2323. }
  2324. assertUuid(resolvedDatasetId);
  2325. if ((asOf && tag) || (!asOf && !tag)) {
  2326. throw new Error("Exactly one of asOf and tag must be specified.");
  2327. }
  2328. const params = new URLSearchParams();
  2329. if (asOf !== undefined) {
  2330. params.append("as_of", typeof asOf === "string" ? asOf : asOf.toISOString());
  2331. }
  2332. if (tag !== undefined) {
  2333. params.append("tag", tag);
  2334. }
  2335. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${resolvedDatasetId}/version?${params.toString()}`, {
  2336. method: "GET",
  2337. headers: { ...this.headers },
  2338. signal: AbortSignal.timeout(this.timeout_ms),
  2339. ...this.fetchOptions,
  2340. });
  2341. await raiseForStatus(response, "read dataset version");
  2342. return await response.json();
  2343. }
  2344. async listDatasetSplits({ datasetId, datasetName, asOf, }) {
  2345. let datasetId_;
  2346. if (datasetId === undefined && datasetName === undefined) {
  2347. throw new Error("Must provide dataset name or ID");
  2348. }
  2349. else if (datasetId !== undefined && datasetName !== undefined) {
  2350. throw new Error("Must provide either datasetName or datasetId, not both");
  2351. }
  2352. else if (datasetId === undefined) {
  2353. const dataset = await this.readDataset({ datasetName });
  2354. datasetId_ = dataset.id;
  2355. }
  2356. else {
  2357. datasetId_ = datasetId;
  2358. }
  2359. assertUuid(datasetId_);
  2360. const params = new URLSearchParams();
  2361. const dataset_version = asOf
  2362. ? typeof asOf === "string"
  2363. ? asOf
  2364. : asOf?.toISOString()
  2365. : undefined;
  2366. if (dataset_version) {
  2367. params.append("as_of", dataset_version);
  2368. }
  2369. const response = await this._get(`/datasets/${datasetId_}/splits`, params);
  2370. return response;
  2371. }
  2372. async updateDatasetSplits({ datasetId, datasetName, splitName, exampleIds, remove = false, }) {
  2373. let datasetId_;
  2374. if (datasetId === undefined && datasetName === undefined) {
  2375. throw new Error("Must provide dataset name or ID");
  2376. }
  2377. else if (datasetId !== undefined && datasetName !== undefined) {
  2378. throw new Error("Must provide either datasetName or datasetId, not both");
  2379. }
  2380. else if (datasetId === undefined) {
  2381. const dataset = await this.readDataset({ datasetName });
  2382. datasetId_ = dataset.id;
  2383. }
  2384. else {
  2385. datasetId_ = datasetId;
  2386. }
  2387. assertUuid(datasetId_);
  2388. const data = {
  2389. split_name: splitName,
  2390. examples: exampleIds.map((id) => {
  2391. assertUuid(id);
  2392. return id;
  2393. }),
  2394. remove,
  2395. };
  2396. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId_}/splits`, {
  2397. method: "PUT",
  2398. headers: { ...this.headers, "Content-Type": "application/json" },
  2399. body: JSON.stringify(data),
  2400. signal: AbortSignal.timeout(this.timeout_ms),
  2401. ...this.fetchOptions,
  2402. });
  2403. await raiseForStatus(response, "update dataset splits", true);
  2404. }
  2405. /**
  2406. * @deprecated This method is deprecated and will be removed in future LangSmith versions, use `evaluate` from `langsmith/evaluation` instead.
  2407. */
  2408. async evaluateRun(run, evaluator, { sourceInfo, loadChildRuns, referenceExample, } = { loadChildRuns: false }) {
  2409. warnOnce("This method is deprecated and will be removed in future LangSmith versions, use `evaluate` from `langsmith/evaluation` instead.");
  2410. let run_;
  2411. if (typeof run === "string") {
  2412. run_ = await this.readRun(run, { loadChildRuns });
  2413. }
  2414. else if (typeof run === "object" && "id" in run) {
  2415. run_ = run;
  2416. }
  2417. else {
  2418. throw new Error(`Invalid run type: ${typeof run}`);
  2419. }
  2420. if (run_.reference_example_id !== null &&
  2421. run_.reference_example_id !== undefined) {
  2422. referenceExample = await this.readExample(run_.reference_example_id);
  2423. }
  2424. const feedbackResult = await evaluator.evaluateRun(run_, referenceExample);
  2425. const [_, feedbacks] = await this._logEvaluationFeedback(feedbackResult, run_, sourceInfo);
  2426. return feedbacks[0];
  2427. }
  2428. async createFeedback(runId, key, { score, value, correction, comment, sourceInfo, feedbackSourceType = "api", sourceRunId, feedbackId, feedbackConfig, projectId, comparativeExperimentId, }) {
  2429. if (!runId && !projectId) {
  2430. throw new Error("One of runId or projectId must be provided");
  2431. }
  2432. if (runId && projectId) {
  2433. throw new Error("Only one of runId or projectId can be provided");
  2434. }
  2435. const feedback_source = {
  2436. type: feedbackSourceType ?? "api",
  2437. metadata: sourceInfo ?? {},
  2438. };
  2439. if (sourceRunId !== undefined &&
  2440. feedback_source?.metadata !== undefined &&
  2441. !feedback_source.metadata["__run"]) {
  2442. feedback_source.metadata["__run"] = { run_id: sourceRunId };
  2443. }
  2444. if (feedback_source?.metadata !== undefined &&
  2445. feedback_source.metadata["__run"]?.run_id !== undefined) {
  2446. assertUuid(feedback_source.metadata["__run"].run_id);
  2447. }
  2448. const feedback = {
  2449. id: feedbackId ?? uuid.v4(),
  2450. run_id: runId,
  2451. key,
  2452. score: _formatFeedbackScore(score),
  2453. value,
  2454. correction,
  2455. comment,
  2456. feedback_source: feedback_source,
  2457. comparative_experiment_id: comparativeExperimentId,
  2458. feedbackConfig,
  2459. session_id: projectId,
  2460. };
  2461. const url = `${this.apiUrl}/feedback`;
  2462. const response = await this.caller.call(_getFetchImplementation(this.debug), url, {
  2463. method: "POST",
  2464. headers: { ...this.headers, "Content-Type": "application/json" },
  2465. body: JSON.stringify(feedback),
  2466. signal: AbortSignal.timeout(this.timeout_ms),
  2467. ...this.fetchOptions,
  2468. });
  2469. await raiseForStatus(response, "create feedback", true);
  2470. return feedback;
  2471. }
  2472. async updateFeedback(feedbackId, { score, value, correction, comment, }) {
  2473. const feedbackUpdate = {};
  2474. if (score !== undefined && score !== null) {
  2475. feedbackUpdate["score"] = _formatFeedbackScore(score);
  2476. }
  2477. if (value !== undefined && value !== null) {
  2478. feedbackUpdate["value"] = value;
  2479. }
  2480. if (correction !== undefined && correction !== null) {
  2481. feedbackUpdate["correction"] = correction;
  2482. }
  2483. if (comment !== undefined && comment !== null) {
  2484. feedbackUpdate["comment"] = comment;
  2485. }
  2486. assertUuid(feedbackId);
  2487. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/feedback/${feedbackId}`, {
  2488. method: "PATCH",
  2489. headers: { ...this.headers, "Content-Type": "application/json" },
  2490. body: JSON.stringify(feedbackUpdate),
  2491. signal: AbortSignal.timeout(this.timeout_ms),
  2492. ...this.fetchOptions,
  2493. });
  2494. await raiseForStatus(response, "update feedback", true);
  2495. }
  2496. async readFeedback(feedbackId) {
  2497. assertUuid(feedbackId);
  2498. const path = `/feedback/${feedbackId}`;
  2499. const response = await this._get(path);
  2500. return response;
  2501. }
  2502. async deleteFeedback(feedbackId) {
  2503. assertUuid(feedbackId);
  2504. const path = `/feedback/${feedbackId}`;
  2505. const response = await this.caller.call(_getFetchImplementation(this.debug), this.apiUrl + path, {
  2506. method: "DELETE",
  2507. headers: this.headers,
  2508. signal: AbortSignal.timeout(this.timeout_ms),
  2509. ...this.fetchOptions,
  2510. });
  2511. await raiseForStatus(response, `delete ${path}`);
  2512. await response.json();
  2513. }
  2514. async *listFeedback({ runIds, feedbackKeys, feedbackSourceTypes, } = {}) {
  2515. const queryParams = new URLSearchParams();
  2516. if (runIds) {
  2517. queryParams.append("run", runIds.join(","));
  2518. }
  2519. if (feedbackKeys) {
  2520. for (const key of feedbackKeys) {
  2521. queryParams.append("key", key);
  2522. }
  2523. }
  2524. if (feedbackSourceTypes) {
  2525. for (const type of feedbackSourceTypes) {
  2526. queryParams.append("source", type);
  2527. }
  2528. }
  2529. for await (const feedbacks of this._getPaginated("/feedback", queryParams)) {
  2530. yield* feedbacks;
  2531. }
  2532. }
  2533. /**
  2534. * Creates a presigned feedback token and URL.
  2535. *
  2536. * The token can be used to authorize feedback metrics without
  2537. * needing an API key. This is useful for giving browser-based
  2538. * applications the ability to submit feedback without needing
  2539. * to expose an API key.
  2540. *
  2541. * @param runId The ID of the run.
  2542. * @param feedbackKey The feedback key.
  2543. * @param options Additional options for the token.
  2544. * @param options.expiration The expiration time for the token.
  2545. *
  2546. * @returns A promise that resolves to a FeedbackIngestToken.
  2547. */
  2548. async createPresignedFeedbackToken(runId, feedbackKey, { expiration, feedbackConfig, } = {}) {
  2549. const body = {
  2550. run_id: runId,
  2551. feedback_key: feedbackKey,
  2552. feedback_config: feedbackConfig,
  2553. };
  2554. if (expiration) {
  2555. if (typeof expiration === "string") {
  2556. body["expires_at"] = expiration;
  2557. }
  2558. else if (expiration?.hours || expiration?.minutes || expiration?.days) {
  2559. body["expires_in"] = expiration;
  2560. }
  2561. }
  2562. else {
  2563. body["expires_in"] = {
  2564. hours: 3,
  2565. };
  2566. }
  2567. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/feedback/tokens`, {
  2568. method: "POST",
  2569. headers: { ...this.headers, "Content-Type": "application/json" },
  2570. body: JSON.stringify(body),
  2571. signal: AbortSignal.timeout(this.timeout_ms),
  2572. ...this.fetchOptions,
  2573. });
  2574. const result = await response.json();
  2575. return result;
  2576. }
  2577. async createComparativeExperiment({ name, experimentIds, referenceDatasetId, createdAt, description, metadata, id, }) {
  2578. if (experimentIds.length === 0) {
  2579. throw new Error("At least one experiment is required");
  2580. }
  2581. if (!referenceDatasetId) {
  2582. referenceDatasetId = (await this.readProject({
  2583. projectId: experimentIds[0],
  2584. })).reference_dataset_id;
  2585. }
  2586. if (!referenceDatasetId == null) {
  2587. throw new Error("A reference dataset is required");
  2588. }
  2589. const body = {
  2590. id,
  2591. name,
  2592. experiment_ids: experimentIds,
  2593. reference_dataset_id: referenceDatasetId,
  2594. description,
  2595. created_at: (createdAt ?? new Date())?.toISOString(),
  2596. extra: {},
  2597. };
  2598. if (metadata)
  2599. body.extra["metadata"] = metadata;
  2600. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/comparative`, {
  2601. method: "POST",
  2602. headers: { ...this.headers, "Content-Type": "application/json" },
  2603. body: JSON.stringify(body),
  2604. signal: AbortSignal.timeout(this.timeout_ms),
  2605. ...this.fetchOptions,
  2606. });
  2607. return await response.json();
  2608. }
  2609. /**
  2610. * Retrieves a list of presigned feedback tokens for a given run ID.
  2611. * @param runId The ID of the run.
  2612. * @returns An async iterable of FeedbackIngestToken objects.
  2613. */
  2614. async *listPresignedFeedbackTokens(runId) {
  2615. assertUuid(runId);
  2616. const params = new URLSearchParams({ run_id: runId });
  2617. for await (const tokens of this._getPaginated("/feedback/tokens", params)) {
  2618. yield* tokens;
  2619. }
  2620. }
  2621. _selectEvalResults(results) {
  2622. let results_;
  2623. if ("results" in results) {
  2624. results_ = results.results;
  2625. }
  2626. else if (Array.isArray(results)) {
  2627. results_ = results;
  2628. }
  2629. else {
  2630. results_ = [results];
  2631. }
  2632. return results_;
  2633. }
  2634. async _logEvaluationFeedback(evaluatorResponse, run, sourceInfo) {
  2635. const evalResults = this._selectEvalResults(evaluatorResponse);
  2636. const feedbacks = [];
  2637. for (const res of evalResults) {
  2638. let sourceInfo_ = sourceInfo || {};
  2639. if (res.evaluatorInfo) {
  2640. sourceInfo_ = { ...res.evaluatorInfo, ...sourceInfo_ };
  2641. }
  2642. let runId_ = null;
  2643. if (res.targetRunId) {
  2644. runId_ = res.targetRunId;
  2645. }
  2646. else if (run) {
  2647. runId_ = run.id;
  2648. }
  2649. feedbacks.push(await this.createFeedback(runId_, res.key, {
  2650. score: res.score,
  2651. value: res.value,
  2652. comment: res.comment,
  2653. correction: res.correction,
  2654. sourceInfo: sourceInfo_,
  2655. sourceRunId: res.sourceRunId,
  2656. feedbackConfig: res.feedbackConfig,
  2657. feedbackSourceType: "model",
  2658. }));
  2659. }
  2660. return [evalResults, feedbacks];
  2661. }
  2662. async logEvaluationFeedback(evaluatorResponse, run, sourceInfo) {
  2663. const [results] = await this._logEvaluationFeedback(evaluatorResponse, run, sourceInfo);
  2664. return results;
  2665. }
  2666. /**
  2667. * API for managing annotation queues
  2668. */
  2669. /**
  2670. * List the annotation queues on the LangSmith API.
  2671. * @param options - The options for listing annotation queues
  2672. * @param options.queueIds - The IDs of the queues to filter by
  2673. * @param options.name - The name of the queue to filter by
  2674. * @param options.nameContains - The substring that the queue name should contain
  2675. * @param options.limit - The maximum number of queues to return
  2676. * @returns An iterator of AnnotationQueue objects
  2677. */
  2678. async *listAnnotationQueues(options = {}) {
  2679. const { queueIds, name, nameContains, limit } = options;
  2680. const params = new URLSearchParams();
  2681. if (queueIds) {
  2682. queueIds.forEach((id, i) => {
  2683. assertUuid(id, `queueIds[${i}]`);
  2684. params.append("ids", id);
  2685. });
  2686. }
  2687. if (name)
  2688. params.append("name", name);
  2689. if (nameContains)
  2690. params.append("name_contains", nameContains);
  2691. params.append("limit", (limit !== undefined ? Math.min(limit, 100) : 100).toString());
  2692. let count = 0;
  2693. for await (const queues of this._getPaginated("/annotation-queues", params)) {
  2694. yield* queues;
  2695. count++;
  2696. if (limit !== undefined && count >= limit)
  2697. break;
  2698. }
  2699. }
  2700. /**
  2701. * Create an annotation queue on the LangSmith API.
  2702. * @param options - The options for creating an annotation queue
  2703. * @param options.name - The name of the annotation queue
  2704. * @param options.description - The description of the annotation queue
  2705. * @param options.queueId - The ID of the annotation queue
  2706. * @returns The created AnnotationQueue object
  2707. */
  2708. async createAnnotationQueue(options) {
  2709. const { name, description, queueId, rubricInstructions } = options;
  2710. const body = {
  2711. name,
  2712. description,
  2713. id: queueId || uuid.v4(),
  2714. rubric_instructions: rubricInstructions,
  2715. };
  2716. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues`, {
  2717. method: "POST",
  2718. headers: { ...this.headers, "Content-Type": "application/json" },
  2719. body: JSON.stringify(Object.fromEntries(Object.entries(body).filter(([_, v]) => v !== undefined))),
  2720. signal: AbortSignal.timeout(this.timeout_ms),
  2721. ...this.fetchOptions,
  2722. });
  2723. await raiseForStatus(response, "create annotation queue");
  2724. const data = await response.json();
  2725. return data;
  2726. }
  2727. /**
  2728. * Read an annotation queue with the specified queue ID.
  2729. * @param queueId - The ID of the annotation queue to read
  2730. * @returns The AnnotationQueueWithDetails object
  2731. */
  2732. async readAnnotationQueue(queueId) {
  2733. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}`, {
  2734. method: "GET",
  2735. headers: this.headers,
  2736. signal: AbortSignal.timeout(this.timeout_ms),
  2737. ...this.fetchOptions,
  2738. });
  2739. await raiseForStatus(response, "read annotation queue");
  2740. const data = await response.json();
  2741. return data;
  2742. }
  2743. /**
  2744. * Update an annotation queue with the specified queue ID.
  2745. * @param queueId - The ID of the annotation queue to update
  2746. * @param options - The options for updating the annotation queue
  2747. * @param options.name - The new name for the annotation queue
  2748. * @param options.description - The new description for the annotation queue
  2749. */
  2750. async updateAnnotationQueue(queueId, options) {
  2751. const { name, description, rubricInstructions } = options;
  2752. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}`, {
  2753. method: "PATCH",
  2754. headers: { ...this.headers, "Content-Type": "application/json" },
  2755. body: JSON.stringify({
  2756. name,
  2757. description,
  2758. rubric_instructions: rubricInstructions,
  2759. }),
  2760. signal: AbortSignal.timeout(this.timeout_ms),
  2761. ...this.fetchOptions,
  2762. });
  2763. await raiseForStatus(response, "update annotation queue");
  2764. }
  2765. /**
  2766. * Delete an annotation queue with the specified queue ID.
  2767. * @param queueId - The ID of the annotation queue to delete
  2768. */
  2769. async deleteAnnotationQueue(queueId) {
  2770. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}`, {
  2771. method: "DELETE",
  2772. headers: { ...this.headers, Accept: "application/json" },
  2773. signal: AbortSignal.timeout(this.timeout_ms),
  2774. ...this.fetchOptions,
  2775. });
  2776. await raiseForStatus(response, "delete annotation queue");
  2777. }
  2778. /**
  2779. * Add runs to an annotation queue with the specified queue ID.
  2780. * @param queueId - The ID of the annotation queue
  2781. * @param runIds - The IDs of the runs to be added to the annotation queue
  2782. */
  2783. async addRunsToAnnotationQueue(queueId, runIds) {
  2784. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}/runs`, {
  2785. method: "POST",
  2786. headers: { ...this.headers, "Content-Type": "application/json" },
  2787. body: JSON.stringify(runIds.map((id, i) => assertUuid(id, `runIds[${i}]`).toString())),
  2788. signal: AbortSignal.timeout(this.timeout_ms),
  2789. ...this.fetchOptions,
  2790. });
  2791. await raiseForStatus(response, "add runs to annotation queue");
  2792. }
  2793. /**
  2794. * Get a run from an annotation queue at the specified index.
  2795. * @param queueId - The ID of the annotation queue
  2796. * @param index - The index of the run to retrieve
  2797. * @returns A Promise that resolves to a RunWithAnnotationQueueInfo object
  2798. * @throws {Error} If the run is not found at the given index or for other API-related errors
  2799. */
  2800. async getRunFromAnnotationQueue(queueId, index) {
  2801. const baseUrl = `/annotation-queues/${assertUuid(queueId, "queueId")}/run`;
  2802. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}${baseUrl}/${index}`, {
  2803. method: "GET",
  2804. headers: this.headers,
  2805. signal: AbortSignal.timeout(this.timeout_ms),
  2806. ...this.fetchOptions,
  2807. });
  2808. await raiseForStatus(response, "get run from annotation queue");
  2809. return await response.json();
  2810. }
  2811. /**
  2812. * Delete a run from an an annotation queue.
  2813. * @param queueId - The ID of the annotation queue to delete the run from
  2814. * @param queueRunId - The ID of the run to delete from the annotation queue
  2815. */
  2816. async deleteRunFromAnnotationQueue(queueId, queueRunId) {
  2817. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}/runs/${assertUuid(queueRunId, "queueRunId")}`, {
  2818. method: "DELETE",
  2819. headers: { ...this.headers, Accept: "application/json" },
  2820. signal: AbortSignal.timeout(this.timeout_ms),
  2821. ...this.fetchOptions,
  2822. });
  2823. await raiseForStatus(response, "delete run from annotation queue");
  2824. }
  2825. /**
  2826. * Get the size of an annotation queue.
  2827. * @param queueId - The ID of the annotation queue
  2828. */
  2829. async getSizeFromAnnotationQueue(queueId) {
  2830. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}/size`, {
  2831. method: "GET",
  2832. headers: this.headers,
  2833. signal: AbortSignal.timeout(this.timeout_ms),
  2834. ...this.fetchOptions,
  2835. });
  2836. await raiseForStatus(response, "get size from annotation queue");
  2837. return await response.json();
  2838. }
  2839. async _currentTenantIsOwner(owner) {
  2840. const settings = await this._getSettings();
  2841. return owner == "-" || settings.tenant_handle === owner;
  2842. }
  2843. async _ownerConflictError(action, owner) {
  2844. const settings = await this._getSettings();
  2845. return new Error(`Cannot ${action} for another tenant.\n
  2846. Current tenant: ${settings.tenant_handle}\n
  2847. Requested tenant: ${owner}`);
  2848. }
  2849. async _getLatestCommitHash(promptOwnerAndName) {
  2850. const res = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/commits/${promptOwnerAndName}/?limit=${1}&offset=${0}`, {
  2851. method: "GET",
  2852. headers: this.headers,
  2853. signal: AbortSignal.timeout(this.timeout_ms),
  2854. ...this.fetchOptions,
  2855. });
  2856. const json = await res.json();
  2857. if (!res.ok) {
  2858. const detail = typeof json.detail === "string"
  2859. ? json.detail
  2860. : JSON.stringify(json.detail);
  2861. const error = new Error(`Error ${res.status}: ${res.statusText}\n${detail}`);
  2862. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  2863. error.statusCode = res.status;
  2864. throw error;
  2865. }
  2866. if (json.commits.length === 0) {
  2867. return undefined;
  2868. }
  2869. return json.commits[0].commit_hash;
  2870. }
  2871. async _likeOrUnlikePrompt(promptIdentifier, like) {
  2872. const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
  2873. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/likes/${owner}/${promptName}`, {
  2874. method: "POST",
  2875. body: JSON.stringify({ like: like }),
  2876. headers: { ...this.headers, "Content-Type": "application/json" },
  2877. signal: AbortSignal.timeout(this.timeout_ms),
  2878. ...this.fetchOptions,
  2879. });
  2880. await raiseForStatus(response, `${like ? "like" : "unlike"} prompt`);
  2881. return await response.json();
  2882. }
  2883. async _getPromptUrl(promptIdentifier) {
  2884. const [owner, promptName, commitHash] = parsePromptIdentifier(promptIdentifier);
  2885. if (!(await this._currentTenantIsOwner(owner))) {
  2886. if (commitHash !== "latest") {
  2887. return `${this.getHostUrl()}/hub/${owner}/${promptName}/${commitHash.substring(0, 8)}`;
  2888. }
  2889. else {
  2890. return `${this.getHostUrl()}/hub/${owner}/${promptName}`;
  2891. }
  2892. }
  2893. else {
  2894. const settings = await this._getSettings();
  2895. if (commitHash !== "latest") {
  2896. return `${this.getHostUrl()}/prompts/${promptName}/${commitHash.substring(0, 8)}?organizationId=${settings.id}`;
  2897. }
  2898. else {
  2899. return `${this.getHostUrl()}/prompts/${promptName}?organizationId=${settings.id}`;
  2900. }
  2901. }
  2902. }
  2903. async promptExists(promptIdentifier) {
  2904. const prompt = await this.getPrompt(promptIdentifier);
  2905. return !!prompt;
  2906. }
  2907. async likePrompt(promptIdentifier) {
  2908. return this._likeOrUnlikePrompt(promptIdentifier, true);
  2909. }
  2910. async unlikePrompt(promptIdentifier) {
  2911. return this._likeOrUnlikePrompt(promptIdentifier, false);
  2912. }
  2913. async *listCommits(promptOwnerAndName) {
  2914. for await (const commits of this._getPaginated(`/commits/${promptOwnerAndName}/`, new URLSearchParams(), (res) => res.commits)) {
  2915. yield* commits;
  2916. }
  2917. }
  2918. async *listPrompts(options) {
  2919. const params = new URLSearchParams();
  2920. params.append("sort_field", options?.sortField ?? "updated_at");
  2921. params.append("sort_direction", "desc");
  2922. params.append("is_archived", (!!options?.isArchived).toString());
  2923. if (options?.isPublic !== undefined) {
  2924. params.append("is_public", options.isPublic.toString());
  2925. }
  2926. if (options?.query) {
  2927. params.append("query", options.query);
  2928. }
  2929. for await (const prompts of this._getPaginated("/repos", params, (res) => res.repos)) {
  2930. yield* prompts;
  2931. }
  2932. }
  2933. async getPrompt(promptIdentifier) {
  2934. const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
  2935. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/repos/${owner}/${promptName}`, {
  2936. method: "GET",
  2937. headers: this.headers,
  2938. signal: AbortSignal.timeout(this.timeout_ms),
  2939. ...this.fetchOptions,
  2940. });
  2941. if (response.status === 404) {
  2942. return null;
  2943. }
  2944. await raiseForStatus(response, "get prompt");
  2945. const result = await response.json();
  2946. if (result.repo) {
  2947. return result.repo;
  2948. }
  2949. else {
  2950. return null;
  2951. }
  2952. }
  2953. async createPrompt(promptIdentifier, options) {
  2954. const settings = await this._getSettings();
  2955. if (options?.isPublic && !settings.tenant_handle) {
  2956. throw new Error(`Cannot create a public prompt without first\n
  2957. creating a LangChain Hub handle.
  2958. You can add a handle by creating a public prompt at:\n
  2959. https://smith.langchain.com/prompts`);
  2960. }
  2961. const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
  2962. if (!(await this._currentTenantIsOwner(owner))) {
  2963. throw await this._ownerConflictError("create a prompt", owner);
  2964. }
  2965. const data = {
  2966. repo_handle: promptName,
  2967. ...(options?.description && { description: options.description }),
  2968. ...(options?.readme && { readme: options.readme }),
  2969. ...(options?.tags && { tags: options.tags }),
  2970. is_public: !!options?.isPublic,
  2971. };
  2972. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/repos/`, {
  2973. method: "POST",
  2974. headers: { ...this.headers, "Content-Type": "application/json" },
  2975. body: JSON.stringify(data),
  2976. signal: AbortSignal.timeout(this.timeout_ms),
  2977. ...this.fetchOptions,
  2978. });
  2979. await raiseForStatus(response, "create prompt");
  2980. const { repo } = await response.json();
  2981. return repo;
  2982. }
  2983. async createCommit(promptIdentifier, object, options) {
  2984. if (!(await this.promptExists(promptIdentifier))) {
  2985. throw new Error("Prompt does not exist, you must create it first.");
  2986. }
  2987. const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
  2988. const resolvedParentCommitHash = options?.parentCommitHash === "latest" || !options?.parentCommitHash
  2989. ? await this._getLatestCommitHash(`${owner}/${promptName}`)
  2990. : options?.parentCommitHash;
  2991. const payload = {
  2992. manifest: JSON.parse(JSON.stringify(object)),
  2993. parent_commit: resolvedParentCommitHash,
  2994. };
  2995. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/commits/${owner}/${promptName}`, {
  2996. method: "POST",
  2997. headers: { ...this.headers, "Content-Type": "application/json" },
  2998. body: JSON.stringify(payload),
  2999. signal: AbortSignal.timeout(this.timeout_ms),
  3000. ...this.fetchOptions,
  3001. });
  3002. await raiseForStatus(response, "create commit");
  3003. const result = await response.json();
  3004. return this._getPromptUrl(`${owner}/${promptName}${result.commit_hash ? `:${result.commit_hash}` : ""}`);
  3005. }
  3006. /**
  3007. * Update examples with attachments using multipart form data.
  3008. * @param updates List of ExampleUpdateWithAttachments objects to upsert
  3009. * @returns Promise with the update response
  3010. */
  3011. async updateExamplesMultipart(datasetId, updates = []) {
  3012. return this._updateExamplesMultipart(datasetId, updates);
  3013. }
  3014. async _updateExamplesMultipart(datasetId, updates = []) {
  3015. if (!(await this._getMultiPartSupport())) {
  3016. throw new Error("Your LangSmith deployment does not allow using the multipart examples endpoint, please upgrade your deployment to the latest version.");
  3017. }
  3018. const formData = new FormData();
  3019. for (const example of updates) {
  3020. const exampleId = example.id;
  3021. // Prepare the main example body
  3022. const exampleBody = {
  3023. ...(example.metadata && { metadata: example.metadata }),
  3024. ...(example.split && { split: example.split }),
  3025. };
  3026. // Add main example data
  3027. const stringifiedExample = serializePayloadForTracing(exampleBody, `Serializing body for example with id: ${exampleId}`);
  3028. const exampleBlob = new Blob([stringifiedExample], {
  3029. type: "application/json",
  3030. });
  3031. formData.append(exampleId, exampleBlob);
  3032. // Add inputs if present
  3033. if (example.inputs) {
  3034. const stringifiedInputs = serializePayloadForTracing(example.inputs, `Serializing inputs for example with id: ${exampleId}`);
  3035. const inputsBlob = new Blob([stringifiedInputs], {
  3036. type: "application/json",
  3037. });
  3038. formData.append(`${exampleId}.inputs`, inputsBlob);
  3039. }
  3040. // Add outputs if present
  3041. if (example.outputs) {
  3042. const stringifiedOutputs = serializePayloadForTracing(example.outputs, `Serializing outputs whle updating example with id: ${exampleId}`);
  3043. const outputsBlob = new Blob([stringifiedOutputs], {
  3044. type: "application/json",
  3045. });
  3046. formData.append(`${exampleId}.outputs`, outputsBlob);
  3047. }
  3048. // Add attachments if present
  3049. if (example.attachments) {
  3050. for (const [name, attachment] of Object.entries(example.attachments)) {
  3051. let mimeType;
  3052. let data;
  3053. if (Array.isArray(attachment)) {
  3054. [mimeType, data] = attachment;
  3055. }
  3056. else {
  3057. mimeType = attachment.mimeType;
  3058. data = attachment.data;
  3059. }
  3060. const attachmentBlob = new Blob([data], {
  3061. type: `${mimeType}; length=${data.byteLength}`,
  3062. });
  3063. formData.append(`${exampleId}.attachment.${name}`, attachmentBlob);
  3064. }
  3065. }
  3066. if (example.attachments_operations) {
  3067. const stringifiedAttachmentsOperations = serializePayloadForTracing(example.attachments_operations, `Serializing attachments while updating example with id: ${exampleId}`);
  3068. const attachmentsOperationsBlob = new Blob([stringifiedAttachmentsOperations], {
  3069. type: "application/json",
  3070. });
  3071. formData.append(`${exampleId}.attachments_operations`, attachmentsOperationsBlob);
  3072. }
  3073. }
  3074. const datasetIdToUse = datasetId ?? updates[0]?.dataset_id;
  3075. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/v1/platform/datasets/${datasetIdToUse}/examples`, {
  3076. method: "PATCH",
  3077. headers: this.headers,
  3078. body: formData,
  3079. });
  3080. const result = await response.json();
  3081. return result;
  3082. }
  3083. /**
  3084. * Upload examples with attachments using multipart form data.
  3085. * @param uploads List of ExampleUploadWithAttachments objects to upload
  3086. * @returns Promise with the upload response
  3087. * @deprecated This method is deprecated and will be removed in future LangSmith versions, please use `createExamples` instead
  3088. */
  3089. async uploadExamplesMultipart(datasetId, uploads = []) {
  3090. return this._uploadExamplesMultipart(datasetId, uploads);
  3091. }
  3092. async _uploadExamplesMultipart(datasetId, uploads = []) {
  3093. if (!(await this._getMultiPartSupport())) {
  3094. throw new Error("Your LangSmith deployment does not allow using the multipart examples endpoint, please upgrade your deployment to the latest version.");
  3095. }
  3096. const formData = new FormData();
  3097. for (const example of uploads) {
  3098. const exampleId = (example.id ?? uuid.v4()).toString();
  3099. // Prepare the main example body
  3100. const exampleBody = {
  3101. created_at: example.created_at,
  3102. ...(example.metadata && { metadata: example.metadata }),
  3103. ...(example.split && { split: example.split }),
  3104. ...(example.source_run_id && { source_run_id: example.source_run_id }),
  3105. ...(example.use_source_run_io && {
  3106. use_source_run_io: example.use_source_run_io,
  3107. }),
  3108. ...(example.use_source_run_attachments && {
  3109. use_source_run_attachments: example.use_source_run_attachments,
  3110. }),
  3111. };
  3112. // Add main example data
  3113. const stringifiedExample = serializePayloadForTracing(exampleBody, `Serializing body for uploaded example with id: ${exampleId}`);
  3114. const exampleBlob = new Blob([stringifiedExample], {
  3115. type: "application/json",
  3116. });
  3117. formData.append(exampleId, exampleBlob);
  3118. // Add inputs if present
  3119. if (example.inputs) {
  3120. const stringifiedInputs = serializePayloadForTracing(example.inputs, `Serializing inputs for uploaded example with id: ${exampleId}`);
  3121. const inputsBlob = new Blob([stringifiedInputs], {
  3122. type: "application/json",
  3123. });
  3124. formData.append(`${exampleId}.inputs`, inputsBlob);
  3125. }
  3126. // Add outputs if present
  3127. if (example.outputs) {
  3128. const stringifiedOutputs = serializePayloadForTracing(example.outputs, `Serializing outputs for uploaded example with id: ${exampleId}`);
  3129. const outputsBlob = new Blob([stringifiedOutputs], {
  3130. type: "application/json",
  3131. });
  3132. formData.append(`${exampleId}.outputs`, outputsBlob);
  3133. }
  3134. // Add attachments if present
  3135. if (example.attachments) {
  3136. for (const [name, attachment] of Object.entries(example.attachments)) {
  3137. let mimeType;
  3138. let data;
  3139. if (Array.isArray(attachment)) {
  3140. [mimeType, data] = attachment;
  3141. }
  3142. else {
  3143. mimeType = attachment.mimeType;
  3144. data = attachment.data;
  3145. }
  3146. const attachmentBlob = new Blob([data], {
  3147. type: `${mimeType}; length=${data.byteLength}`,
  3148. });
  3149. formData.append(`${exampleId}.attachment.${name}`, attachmentBlob);
  3150. }
  3151. }
  3152. }
  3153. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/v1/platform/datasets/${datasetId}/examples`, {
  3154. method: "POST",
  3155. headers: this.headers,
  3156. body: formData,
  3157. });
  3158. await raiseForStatus(response, "upload examples");
  3159. const result = await response.json();
  3160. return result;
  3161. }
  3162. async updatePrompt(promptIdentifier, options) {
  3163. if (!(await this.promptExists(promptIdentifier))) {
  3164. throw new Error("Prompt does not exist, you must create it first.");
  3165. }
  3166. const [owner, promptName] = parsePromptIdentifier(promptIdentifier);
  3167. if (!(await this._currentTenantIsOwner(owner))) {
  3168. throw await this._ownerConflictError("update a prompt", owner);
  3169. }
  3170. const payload = {};
  3171. if (options?.description !== undefined)
  3172. payload.description = options.description;
  3173. if (options?.readme !== undefined)
  3174. payload.readme = options.readme;
  3175. if (options?.tags !== undefined)
  3176. payload.tags = options.tags;
  3177. if (options?.isPublic !== undefined)
  3178. payload.is_public = options.isPublic;
  3179. if (options?.isArchived !== undefined)
  3180. payload.is_archived = options.isArchived;
  3181. // Check if payload is empty
  3182. if (Object.keys(payload).length === 0) {
  3183. throw new Error("No valid update options provided");
  3184. }
  3185. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/repos/${owner}/${promptName}`, {
  3186. method: "PATCH",
  3187. body: JSON.stringify(payload),
  3188. headers: {
  3189. ...this.headers,
  3190. "Content-Type": "application/json",
  3191. },
  3192. signal: AbortSignal.timeout(this.timeout_ms),
  3193. ...this.fetchOptions,
  3194. });
  3195. await raiseForStatus(response, "update prompt");
  3196. return response.json();
  3197. }
  3198. async deletePrompt(promptIdentifier) {
  3199. if (!(await this.promptExists(promptIdentifier))) {
  3200. throw new Error("Prompt does not exist, you must create it first.");
  3201. }
  3202. const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
  3203. if (!(await this._currentTenantIsOwner(owner))) {
  3204. throw await this._ownerConflictError("delete a prompt", owner);
  3205. }
  3206. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/repos/${owner}/${promptName}`, {
  3207. method: "DELETE",
  3208. headers: this.headers,
  3209. signal: AbortSignal.timeout(this.timeout_ms),
  3210. ...this.fetchOptions,
  3211. });
  3212. return await response.json();
  3213. }
  3214. async pullPromptCommit(promptIdentifier, options) {
  3215. const [owner, promptName, commitHash] = parsePromptIdentifier(promptIdentifier);
  3216. const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/commits/${owner}/${promptName}/${commitHash}${options?.includeModel ? "?include_model=true" : ""}`, {
  3217. method: "GET",
  3218. headers: this.headers,
  3219. signal: AbortSignal.timeout(this.timeout_ms),
  3220. ...this.fetchOptions,
  3221. });
  3222. await raiseForStatus(response, "pull prompt commit");
  3223. const result = await response.json();
  3224. return {
  3225. owner,
  3226. repo: promptName,
  3227. commit_hash: result.commit_hash,
  3228. manifest: result.manifest,
  3229. examples: result.examples,
  3230. };
  3231. }
  3232. /**
  3233. * This method should not be used directly, use `import { pull } from "langchain/hub"` instead.
  3234. * Using this method directly returns the JSON string of the prompt rather than a LangChain object.
  3235. * @private
  3236. */
  3237. async _pullPrompt(promptIdentifier, options) {
  3238. const promptObject = await this.pullPromptCommit(promptIdentifier, {
  3239. includeModel: options?.includeModel,
  3240. });
  3241. const prompt = JSON.stringify(promptObject.manifest);
  3242. return prompt;
  3243. }
  3244. async pushPrompt(promptIdentifier, options) {
  3245. // Create or update prompt metadata
  3246. if (await this.promptExists(promptIdentifier)) {
  3247. if (options && Object.keys(options).some((key) => key !== "object")) {
  3248. await this.updatePrompt(promptIdentifier, {
  3249. description: options?.description,
  3250. readme: options?.readme,
  3251. tags: options?.tags,
  3252. isPublic: options?.isPublic,
  3253. });
  3254. }
  3255. }
  3256. else {
  3257. await this.createPrompt(promptIdentifier, {
  3258. description: options?.description,
  3259. readme: options?.readme,
  3260. tags: options?.tags,
  3261. isPublic: options?.isPublic,
  3262. });
  3263. }
  3264. if (!options?.object) {
  3265. return await this._getPromptUrl(promptIdentifier);
  3266. }
  3267. // Create a commit with the new manifest
  3268. const url = await this.createCommit(promptIdentifier, options?.object, {
  3269. parentCommitHash: options?.parentCommitHash,
  3270. });
  3271. return url;
  3272. }
  3273. /**
  3274. * Clone a public dataset to your own langsmith tenant.
  3275. * This operation is idempotent. If you already have a dataset with the given name,
  3276. * this function will do nothing.
  3277. * @param {string} tokenOrUrl The token of the public dataset to clone.
  3278. * @param {Object} [options] Additional options for cloning the dataset.
  3279. * @param {string} [options.sourceApiUrl] The URL of the langsmith server where the data is hosted. Defaults to the API URL of your current client.
  3280. * @param {string} [options.datasetName] The name of the dataset to create in your tenant. Defaults to the name of the public dataset.
  3281. * @returns {Promise<void>}
  3282. */
  3283. async clonePublicDataset(tokenOrUrl, options = {}) {
  3284. const { sourceApiUrl = this.apiUrl, datasetName } = options;
  3285. const [parsedApiUrl, tokenUuid] = this.parseTokenOrUrl(tokenOrUrl, sourceApiUrl);
  3286. const sourceClient = new Client({
  3287. apiUrl: parsedApiUrl,
  3288. // Placeholder API key not needed anymore in most cases, but
  3289. // some private deployments may have API key-based rate limiting
  3290. // that would cause this to fail if we provide no value.
  3291. apiKey: "placeholder",
  3292. });
  3293. const ds = await sourceClient.readSharedDataset(tokenUuid);
  3294. const finalDatasetName = datasetName || ds.name;
  3295. try {
  3296. if (await this.hasDataset({ datasetId: finalDatasetName })) {
  3297. console.log(`Dataset ${finalDatasetName} already exists in your tenant. Skipping.`);
  3298. return;
  3299. }
  3300. }
  3301. catch (_) {
  3302. // `.hasDataset` will throw an error if the dataset does not exist.
  3303. // no-op in that case
  3304. }
  3305. // Fetch examples first, then create the dataset
  3306. const examples = await sourceClient.listSharedExamples(tokenUuid);
  3307. const dataset = await this.createDataset(finalDatasetName, {
  3308. description: ds.description,
  3309. dataType: ds.data_type || "kv",
  3310. inputsSchema: ds.inputs_schema_definition ?? undefined,
  3311. outputsSchema: ds.outputs_schema_definition ?? undefined,
  3312. });
  3313. try {
  3314. await this.createExamples({
  3315. inputs: examples.map((e) => e.inputs),
  3316. outputs: examples.flatMap((e) => (e.outputs ? [e.outputs] : [])),
  3317. datasetId: dataset.id,
  3318. });
  3319. }
  3320. catch (e) {
  3321. console.error(`An error occurred while creating dataset ${finalDatasetName}. ` +
  3322. "You should delete it manually.");
  3323. throw e;
  3324. }
  3325. }
  3326. parseTokenOrUrl(urlOrToken, apiUrl, numParts = 2, kind = "dataset") {
  3327. // Try parsing as UUID
  3328. try {
  3329. assertUuid(urlOrToken); // Will throw if it's not a UUID.
  3330. return [apiUrl, urlOrToken];
  3331. }
  3332. catch (_) {
  3333. // no-op if it's not a uuid
  3334. }
  3335. // Parse as URL
  3336. try {
  3337. const parsedUrl = new URL(urlOrToken);
  3338. const pathParts = parsedUrl.pathname
  3339. .split("/")
  3340. .filter((part) => part !== "");
  3341. if (pathParts.length >= numParts) {
  3342. const tokenUuid = pathParts[pathParts.length - numParts];
  3343. return [apiUrl, tokenUuid];
  3344. }
  3345. else {
  3346. throw new Error(`Invalid public ${kind} URL: ${urlOrToken}`);
  3347. }
  3348. }
  3349. catch (error) {
  3350. throw new Error(`Invalid public ${kind} URL or token: ${urlOrToken}`);
  3351. }
  3352. }
  3353. /**
  3354. * Awaits all pending trace batches. Useful for environments where
  3355. * you need to be sure that all tracing requests finish before execution ends,
  3356. * such as serverless environments.
  3357. *
  3358. * @example
  3359. * ```
  3360. * import { Client } from "langsmith";
  3361. *
  3362. * const client = new Client();
  3363. *
  3364. * try {
  3365. * // Tracing happens here
  3366. * ...
  3367. * } finally {
  3368. * await client.awaitPendingTraceBatches();
  3369. * }
  3370. * ```
  3371. *
  3372. * @returns A promise that resolves once all currently pending traces have sent.
  3373. */
  3374. awaitPendingTraceBatches() {
  3375. if (this.manualFlushMode) {
  3376. console.warn("[WARNING]: When tracing in manual flush mode, you must call `await client.flush()` manually to submit trace batches.");
  3377. return Promise.resolve();
  3378. }
  3379. return Promise.all([
  3380. ...this.autoBatchQueue.items.map(({ itemPromise }) => itemPromise),
  3381. this.batchIngestCaller.queue.onIdle(),
  3382. ]);
  3383. }
  3384. }
  3385. function isExampleCreate(input) {
  3386. return "dataset_id" in input || "dataset_name" in input;
  3387. }