1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388 |
- import * as uuid from "uuid";
- import { AsyncCaller } from "./utils/async_caller.js";
- import { convertLangChainMessageToExample, isLangChainMessage, } from "./utils/messages.js";
- import { getEnvironmentVariable, getLangChainEnvVarsMetadata, getLangSmithEnvironmentVariable, getRuntimeEnvironment, } from "./utils/env.js";
- import { __version__ } from "./index.js";
- import { assertUuid } from "./utils/_uuid.js";
- import { warnOnce } from "./utils/warn.js";
- import { parsePromptIdentifier } from "./utils/prompts.js";
- import { raiseForStatus } from "./utils/error.js";
- import { _globalFetchImplementationIsNodeFetch, _getFetchImplementation, } from "./singletons/fetch.js";
- import { serialize as serializePayloadForTracing } from "./utils/fast-safe-stringify/index.js";
- export function mergeRuntimeEnvIntoRunCreate(run) {
- const runtimeEnv = getRuntimeEnvironment();
- const envVars = getLangChainEnvVarsMetadata();
- const extra = run.extra ?? {};
- const metadata = extra.metadata;
- run.extra = {
- ...extra,
- runtime: {
- ...runtimeEnv,
- ...extra?.runtime,
- },
- metadata: {
- ...envVars,
- ...(envVars.revision_id || run.revision_id
- ? { revision_id: run.revision_id ?? envVars.revision_id }
- : {}),
- ...metadata,
- },
- };
- return run;
- }
- const getTracingSamplingRate = (configRate) => {
- const samplingRateStr = configRate?.toString() ??
- getLangSmithEnvironmentVariable("TRACING_SAMPLING_RATE");
- if (samplingRateStr === undefined) {
- return undefined;
- }
- const samplingRate = parseFloat(samplingRateStr);
- if (samplingRate < 0 || samplingRate > 1) {
- throw new Error(`LANGSMITH_TRACING_SAMPLING_RATE must be between 0 and 1 if set. Got: ${samplingRate}`);
- }
- return samplingRate;
- };
- // utility functions
- const isLocalhost = (url) => {
- const strippedUrl = url.replace("http://", "").replace("https://", "");
- const hostname = strippedUrl.split("/")[0].split(":")[0];
- return (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1");
- };
- async function toArray(iterable) {
- const result = [];
- for await (const item of iterable) {
- result.push(item);
- }
- return result;
- }
- function trimQuotes(str) {
- if (str === undefined) {
- return undefined;
- }
- return str
- .trim()
- .replace(/^"(.*)"$/, "$1")
- .replace(/^'(.*)'$/, "$1");
- }
- const handle429 = async (response) => {
- if (response?.status === 429) {
- const retryAfter = parseInt(response.headers.get("retry-after") ?? "30", 10) * 1000;
- if (retryAfter > 0) {
- await new Promise((resolve) => setTimeout(resolve, retryAfter));
- // Return directly after calling this check
- return true;
- }
- }
- // Fall back to existing status checks
- return false;
- };
- function _formatFeedbackScore(score) {
- if (typeof score === "number") {
- // Truncate at 4 decimal places
- return Number(score.toFixed(4));
- }
- return score;
- }
- export class AutoBatchQueue {
- constructor() {
- Object.defineProperty(this, "items", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: []
- });
- Object.defineProperty(this, "sizeBytes", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: 0
- });
- }
- peek() {
- return this.items[0];
- }
- push(item) {
- let itemPromiseResolve;
- const itemPromise = new Promise((resolve) => {
- // Setting itemPromiseResolve is synchronous with promise creation:
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
- itemPromiseResolve = resolve;
- });
- const size = serializePayloadForTracing(item.item, `Serializing run with id: ${item.item.id}`).length;
- this.items.push({
- action: item.action,
- payload: item.item,
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- itemPromiseResolve: itemPromiseResolve,
- itemPromise,
- size,
- });
- this.sizeBytes += size;
- return itemPromise;
- }
- pop(upToSizeBytes) {
- if (upToSizeBytes < 1) {
- throw new Error("Number of bytes to pop off may not be less than 1.");
- }
- const popped = [];
- let poppedSizeBytes = 0;
- // Pop items until we reach or exceed the size limit
- while (poppedSizeBytes + (this.peek()?.size ?? 0) < upToSizeBytes &&
- this.items.length > 0) {
- const item = this.items.shift();
- if (item) {
- popped.push(item);
- poppedSizeBytes += item.size;
- this.sizeBytes -= item.size;
- }
- }
- // If there is an item on the queue we were unable to pop,
- // just return it as a single batch.
- if (popped.length === 0 && this.items.length > 0) {
- const item = this.items.shift();
- popped.push(item);
- poppedSizeBytes += item.size;
- this.sizeBytes -= item.size;
- }
- return [
- popped.map((it) => ({ action: it.action, item: it.payload })),
- () => popped.forEach((it) => it.itemPromiseResolve()),
- ];
- }
- }
- // 20 MB
- export const DEFAULT_BATCH_SIZE_LIMIT_BYTES = 20_971_520;
- const SERVER_INFO_REQUEST_TIMEOUT = 2500;
- export class Client {
- constructor(config = {}) {
- Object.defineProperty(this, "apiKey", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "apiUrl", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "webUrl", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "caller", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "batchIngestCaller", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "timeout_ms", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "_tenantId", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: null
- });
- Object.defineProperty(this, "hideInputs", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "hideOutputs", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "tracingSampleRate", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "filteredPostUuids", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: new Set()
- });
- Object.defineProperty(this, "autoBatchTracing", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: true
- });
- Object.defineProperty(this, "autoBatchQueue", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: new AutoBatchQueue()
- });
- Object.defineProperty(this, "autoBatchTimeout", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "autoBatchAggregationDelayMs", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: 250
- });
- Object.defineProperty(this, "batchSizeBytesLimit", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "fetchOptions", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "settings", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "blockOnRootRunFinalization", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: getEnvironmentVariable("LANGSMITH_TRACING_BACKGROUND") === "false"
- });
- Object.defineProperty(this, "traceBatchConcurrency", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: 5
- });
- Object.defineProperty(this, "_serverInfo", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- Object.defineProperty(this, "_getServerInfoPromise", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: void 0
- });
- Object.defineProperty(this, "manualFlushMode", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: false
- });
- Object.defineProperty(this, "debug", {
- enumerable: true,
- configurable: true,
- writable: true,
- value: getEnvironmentVariable("LANGSMITH_DEBUG") === "true"
- });
- const defaultConfig = Client.getDefaultClientConfig();
- this.tracingSampleRate = getTracingSamplingRate(config.tracingSamplingRate);
- this.apiUrl = trimQuotes(config.apiUrl ?? defaultConfig.apiUrl) ?? "";
- if (this.apiUrl.endsWith("/")) {
- this.apiUrl = this.apiUrl.slice(0, -1);
- }
- this.apiKey = trimQuotes(config.apiKey ?? defaultConfig.apiKey);
- this.webUrl = trimQuotes(config.webUrl ?? defaultConfig.webUrl);
- if (this.webUrl?.endsWith("/")) {
- this.webUrl = this.webUrl.slice(0, -1);
- }
- this.timeout_ms = config.timeout_ms ?? 90_000;
- this.caller = new AsyncCaller({
- ...(config.callerOptions ?? {}),
- debug: config.debug ?? this.debug,
- });
- this.traceBatchConcurrency =
- config.traceBatchConcurrency ?? this.traceBatchConcurrency;
- if (this.traceBatchConcurrency < 1) {
- throw new Error("Trace batch concurrency must be positive.");
- }
- this.debug = config.debug ?? this.debug;
- this.batchIngestCaller = new AsyncCaller({
- maxRetries: 2,
- maxConcurrency: this.traceBatchConcurrency,
- ...(config.callerOptions ?? {}),
- onFailedResponseHook: handle429,
- debug: config.debug ?? this.debug,
- });
- this.hideInputs =
- config.hideInputs ?? config.anonymizer ?? defaultConfig.hideInputs;
- this.hideOutputs =
- config.hideOutputs ?? config.anonymizer ?? defaultConfig.hideOutputs;
- this.autoBatchTracing = config.autoBatchTracing ?? this.autoBatchTracing;
- this.blockOnRootRunFinalization =
- config.blockOnRootRunFinalization ?? this.blockOnRootRunFinalization;
- this.batchSizeBytesLimit = config.batchSizeBytesLimit;
- this.fetchOptions = config.fetchOptions || {};
- this.manualFlushMode = config.manualFlushMode ?? this.manualFlushMode;
- }
- static getDefaultClientConfig() {
- const apiKey = getLangSmithEnvironmentVariable("API_KEY");
- const apiUrl = getLangSmithEnvironmentVariable("ENDPOINT") ??
- "https://api.smith.langchain.com";
- const hideInputs = getLangSmithEnvironmentVariable("HIDE_INPUTS") === "true";
- const hideOutputs = getLangSmithEnvironmentVariable("HIDE_OUTPUTS") === "true";
- return {
- apiUrl: apiUrl,
- apiKey: apiKey,
- webUrl: undefined,
- hideInputs: hideInputs,
- hideOutputs: hideOutputs,
- };
- }
- getHostUrl() {
- if (this.webUrl) {
- return this.webUrl;
- }
- else if (isLocalhost(this.apiUrl)) {
- this.webUrl = "http://localhost:3000";
- return this.webUrl;
- }
- else if (this.apiUrl.endsWith("/api/v1")) {
- this.webUrl = this.apiUrl.replace("/api/v1", "");
- return this.webUrl;
- }
- else if (this.apiUrl.includes("/api") &&
- !this.apiUrl.split(".", 1)[0].endsWith("api")) {
- this.webUrl = this.apiUrl.replace("/api", "");
- return this.webUrl;
- }
- else if (this.apiUrl.split(".", 1)[0].includes("dev")) {
- this.webUrl = "https://dev.smith.langchain.com";
- return this.webUrl;
- }
- else if (this.apiUrl.split(".", 1)[0].includes("eu")) {
- this.webUrl = "https://eu.smith.langchain.com";
- return this.webUrl;
- }
- else if (this.apiUrl.split(".", 1)[0].includes("beta")) {
- this.webUrl = "https://beta.smith.langchain.com";
- return this.webUrl;
- }
- else {
- this.webUrl = "https://smith.langchain.com";
- return this.webUrl;
- }
- }
- get headers() {
- const headers = {
- "User-Agent": `langsmith-js/${__version__}`,
- };
- if (this.apiKey) {
- headers["x-api-key"] = `${this.apiKey}`;
- }
- return headers;
- }
- async processInputs(inputs) {
- if (this.hideInputs === false) {
- return inputs;
- }
- if (this.hideInputs === true) {
- return {};
- }
- if (typeof this.hideInputs === "function") {
- return this.hideInputs(inputs);
- }
- return inputs;
- }
- async processOutputs(outputs) {
- if (this.hideOutputs === false) {
- return outputs;
- }
- if (this.hideOutputs === true) {
- return {};
- }
- if (typeof this.hideOutputs === "function") {
- return this.hideOutputs(outputs);
- }
- return outputs;
- }
- async prepareRunCreateOrUpdateInputs(run) {
- const runParams = { ...run };
- if (runParams.inputs !== undefined) {
- runParams.inputs = await this.processInputs(runParams.inputs);
- }
- if (runParams.outputs !== undefined) {
- runParams.outputs = await this.processOutputs(runParams.outputs);
- }
- return runParams;
- }
- async _getResponse(path, queryParams) {
- const paramsString = queryParams?.toString() ?? "";
- const url = `${this.apiUrl}${path}?${paramsString}`;
- const response = await this.caller.call(_getFetchImplementation(this.debug), url, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, `Failed to fetch ${path}`);
- return response;
- }
- async _get(path, queryParams) {
- const response = await this._getResponse(path, queryParams);
- return response.json();
- }
- async *_getPaginated(path, queryParams = new URLSearchParams(), transform) {
- let offset = Number(queryParams.get("offset")) || 0;
- const limit = Number(queryParams.get("limit")) || 100;
- while (true) {
- queryParams.set("offset", String(offset));
- queryParams.set("limit", String(limit));
- const url = `${this.apiUrl}${path}?${queryParams}`;
- const response = await this.caller.call(_getFetchImplementation(this.debug), url, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, `Failed to fetch ${path}`);
- const items = transform
- ? transform(await response.json())
- : await response.json();
- if (items.length === 0) {
- break;
- }
- yield items;
- if (items.length < limit) {
- break;
- }
- offset += items.length;
- }
- }
- async *_getCursorPaginatedList(path, body = null, requestMethod = "POST", dataKey = "runs") {
- const bodyParams = body ? { ...body } : {};
- while (true) {
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}${path}`, {
- method: requestMethod,
- headers: { ...this.headers, "Content-Type": "application/json" },
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- body: JSON.stringify(bodyParams),
- });
- const responseBody = await response.json();
- if (!responseBody) {
- break;
- }
- if (!responseBody[dataKey]) {
- break;
- }
- yield responseBody[dataKey];
- const cursors = responseBody.cursors;
- if (!cursors) {
- break;
- }
- if (!cursors.next) {
- break;
- }
- bodyParams.cursor = cursors.next;
- }
- }
- // Allows mocking for tests
- _shouldSample() {
- if (this.tracingSampleRate === undefined) {
- return true;
- }
- return Math.random() < this.tracingSampleRate;
- }
- _filterForSampling(runs, patch = false) {
- if (this.tracingSampleRate === undefined) {
- return runs;
- }
- if (patch) {
- const sampled = [];
- for (const run of runs) {
- if (!this.filteredPostUuids.has(run.id)) {
- sampled.push(run);
- }
- else {
- this.filteredPostUuids.delete(run.id);
- }
- }
- return sampled;
- }
- else {
- // For new runs, sample at trace level to maintain consistency
- const sampled = [];
- for (const run of runs) {
- const traceId = run.trace_id ?? run.id;
- // If we've already made a decision about this trace, follow it
- if (this.filteredPostUuids.has(traceId)) {
- continue;
- }
- // For new traces, apply sampling
- if (run.id === traceId) {
- if (this._shouldSample()) {
- sampled.push(run);
- }
- else {
- this.filteredPostUuids.add(traceId);
- }
- }
- else {
- // Child runs follow their trace's sampling decision
- sampled.push(run);
- }
- }
- return sampled;
- }
- }
- async _getBatchSizeLimitBytes() {
- const serverInfo = await this._ensureServerInfo();
- return (this.batchSizeBytesLimit ??
- serverInfo.batch_ingest_config?.size_limit_bytes ??
- DEFAULT_BATCH_SIZE_LIMIT_BYTES);
- }
- async _getMultiPartSupport() {
- const serverInfo = await this._ensureServerInfo();
- return (serverInfo.instance_flags?.dataset_examples_multipart_enabled ?? false);
- }
- drainAutoBatchQueue(batchSizeLimit) {
- const promises = [];
- while (this.autoBatchQueue.items.length > 0) {
- const [batch, done] = this.autoBatchQueue.pop(batchSizeLimit);
- if (!batch.length) {
- done();
- break;
- }
- const batchPromise = this._processBatch(batch, done).catch(console.error);
- promises.push(batchPromise);
- }
- return Promise.all(promises);
- }
- async _processBatch(batch, done) {
- if (!batch.length) {
- done();
- return;
- }
- try {
- const ingestParams = {
- runCreates: batch
- .filter((item) => item.action === "create")
- .map((item) => item.item),
- runUpdates: batch
- .filter((item) => item.action === "update")
- .map((item) => item.item),
- };
- const serverInfo = await this._ensureServerInfo();
- if (serverInfo?.batch_ingest_config?.use_multipart_endpoint) {
- await this.multipartIngestRuns(ingestParams);
- }
- else {
- await this.batchIngestRuns(ingestParams);
- }
- }
- finally {
- done();
- }
- }
- async processRunOperation(item) {
- clearTimeout(this.autoBatchTimeout);
- this.autoBatchTimeout = undefined;
- if (item.action === "create") {
- item.item = mergeRuntimeEnvIntoRunCreate(item.item);
- }
- const itemPromise = this.autoBatchQueue.push(item);
- if (this.manualFlushMode) {
- // Rely on manual flushing in serverless environments
- return itemPromise;
- }
- const sizeLimitBytes = await this._getBatchSizeLimitBytes();
- if (this.autoBatchQueue.sizeBytes > sizeLimitBytes) {
- void this.drainAutoBatchQueue(sizeLimitBytes);
- }
- if (this.autoBatchQueue.items.length > 0) {
- this.autoBatchTimeout = setTimeout(() => {
- this.autoBatchTimeout = undefined;
- void this.drainAutoBatchQueue(sizeLimitBytes);
- }, this.autoBatchAggregationDelayMs);
- }
- return itemPromise;
- }
- async _getServerInfo() {
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/info`, {
- method: "GET",
- headers: { Accept: "application/json" },
- signal: AbortSignal.timeout(SERVER_INFO_REQUEST_TIMEOUT),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "get server info");
- const json = await response.json();
- if (this.debug) {
- console.log("\n=== LangSmith Server Configuration ===\n" +
- JSON.stringify(json, null, 2) +
- "\n");
- }
- return json;
- }
- async _ensureServerInfo() {
- if (this._getServerInfoPromise === undefined) {
- this._getServerInfoPromise = (async () => {
- if (this._serverInfo === undefined) {
- try {
- this._serverInfo = await this._getServerInfo();
- }
- catch (e) {
- console.warn(`[WARNING]: LangSmith failed to fetch info on supported operations with status code ${e.status}. Falling back to batch operations and default limits.`);
- }
- }
- return this._serverInfo ?? {};
- })();
- }
- return this._getServerInfoPromise.then((serverInfo) => {
- if (this._serverInfo === undefined) {
- this._getServerInfoPromise = undefined;
- }
- return serverInfo;
- });
- }
- async _getSettings() {
- if (!this.settings) {
- this.settings = this._get("/settings");
- }
- return await this.settings;
- }
- /**
- * Flushes current queued traces.
- */
- async flush() {
- const sizeLimitBytes = await this._getBatchSizeLimitBytes();
- await this.drainAutoBatchQueue(sizeLimitBytes);
- }
- async createRun(run) {
- if (!this._filterForSampling([run]).length) {
- return;
- }
- const headers = { ...this.headers, "Content-Type": "application/json" };
- const session_name = run.project_name;
- delete run.project_name;
- const runCreate = await this.prepareRunCreateOrUpdateInputs({
- session_name,
- ...run,
- start_time: run.start_time ?? Date.now(),
- });
- if (this.autoBatchTracing &&
- runCreate.trace_id !== undefined &&
- runCreate.dotted_order !== undefined) {
- void this.processRunOperation({
- action: "create",
- item: runCreate,
- }).catch(console.error);
- return;
- }
- const mergedRunCreateParam = mergeRuntimeEnvIntoRunCreate(runCreate);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs`, {
- method: "POST",
- headers,
- body: serializePayloadForTracing(mergedRunCreateParam, `Creating run with id: ${mergedRunCreateParam.id}`),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "create run", true);
- }
- /**
- * Batch ingest/upsert multiple runs in the Langsmith system.
- * @param runs
- */
- async batchIngestRuns({ runCreates, runUpdates, }) {
- if (runCreates === undefined && runUpdates === undefined) {
- return;
- }
- let preparedCreateParams = await Promise.all(runCreates?.map((create) => this.prepareRunCreateOrUpdateInputs(create)) ?? []);
- let preparedUpdateParams = await Promise.all(runUpdates?.map((update) => this.prepareRunCreateOrUpdateInputs(update)) ?? []);
- if (preparedCreateParams.length > 0 && preparedUpdateParams.length > 0) {
- const createById = preparedCreateParams.reduce((params, run) => {
- if (!run.id) {
- return params;
- }
- params[run.id] = run;
- return params;
- }, {});
- const standaloneUpdates = [];
- for (const updateParam of preparedUpdateParams) {
- if (updateParam.id !== undefined && createById[updateParam.id]) {
- createById[updateParam.id] = {
- ...createById[updateParam.id],
- ...updateParam,
- };
- }
- else {
- standaloneUpdates.push(updateParam);
- }
- }
- preparedCreateParams = Object.values(createById);
- preparedUpdateParams = standaloneUpdates;
- }
- const rawBatch = {
- post: preparedCreateParams,
- patch: preparedUpdateParams,
- };
- if (!rawBatch.post.length && !rawBatch.patch.length) {
- return;
- }
- const batchChunks = {
- post: [],
- patch: [],
- };
- for (const k of ["post", "patch"]) {
- const key = k;
- const batchItems = rawBatch[key].reverse();
- let batchItem = batchItems.pop();
- while (batchItem !== undefined) {
- // Type is wrong but this is a deprecated code path anyway
- batchChunks[key].push(batchItem);
- batchItem = batchItems.pop();
- }
- }
- if (batchChunks.post.length > 0 || batchChunks.patch.length > 0) {
- const runIds = batchChunks.post
- .map((item) => item.id)
- .concat(batchChunks.patch.map((item) => item.id))
- .join(",");
- await this._postBatchIngestRuns(serializePayloadForTracing(batchChunks, `Ingesting runs with ids: ${runIds}`));
- }
- }
- async _postBatchIngestRuns(body) {
- const headers = {
- ...this.headers,
- "Content-Type": "application/json",
- Accept: "application/json",
- };
- const response = await this.batchIngestCaller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/batch`, {
- method: "POST",
- headers,
- body: body,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "batch create run", true);
- }
- /**
- * Batch ingest/upsert multiple runs in the Langsmith system.
- * @param runs
- */
- async multipartIngestRuns({ runCreates, runUpdates, }) {
- if (runCreates === undefined && runUpdates === undefined) {
- return;
- }
- // transform and convert to dicts
- const allAttachments = {};
- let preparedCreateParams = [];
- for (const create of runCreates ?? []) {
- const preparedCreate = await this.prepareRunCreateOrUpdateInputs(create);
- if (preparedCreate.id !== undefined &&
- preparedCreate.attachments !== undefined) {
- allAttachments[preparedCreate.id] = preparedCreate.attachments;
- }
- delete preparedCreate.attachments;
- preparedCreateParams.push(preparedCreate);
- }
- let preparedUpdateParams = [];
- for (const update of runUpdates ?? []) {
- preparedUpdateParams.push(await this.prepareRunCreateOrUpdateInputs(update));
- }
- // require trace_id and dotted_order
- const invalidRunCreate = preparedCreateParams.find((runCreate) => {
- return (runCreate.trace_id === undefined || runCreate.dotted_order === undefined);
- });
- if (invalidRunCreate !== undefined) {
- throw new Error(`Multipart ingest requires "trace_id" and "dotted_order" to be set when creating a run`);
- }
- const invalidRunUpdate = preparedUpdateParams.find((runUpdate) => {
- return (runUpdate.trace_id === undefined || runUpdate.dotted_order === undefined);
- });
- if (invalidRunUpdate !== undefined) {
- throw new Error(`Multipart ingest requires "trace_id" and "dotted_order" to be set when updating a run`);
- }
- // combine post and patch dicts where possible
- if (preparedCreateParams.length > 0 && preparedUpdateParams.length > 0) {
- const createById = preparedCreateParams.reduce((params, run) => {
- if (!run.id) {
- return params;
- }
- params[run.id] = run;
- return params;
- }, {});
- const standaloneUpdates = [];
- for (const updateParam of preparedUpdateParams) {
- if (updateParam.id !== undefined && createById[updateParam.id]) {
- createById[updateParam.id] = {
- ...createById[updateParam.id],
- ...updateParam,
- };
- }
- else {
- standaloneUpdates.push(updateParam);
- }
- }
- preparedCreateParams = Object.values(createById);
- preparedUpdateParams = standaloneUpdates;
- }
- if (preparedCreateParams.length === 0 &&
- preparedUpdateParams.length === 0) {
- return;
- }
- // send the runs in multipart requests
- const accumulatedContext = [];
- const accumulatedParts = [];
- for (const [method, payloads] of [
- ["post", preparedCreateParams],
- ["patch", preparedUpdateParams],
- ]) {
- for (const originalPayload of payloads) {
- // collect fields to be sent as separate parts
- const { inputs, outputs, events, attachments, ...payload } = originalPayload;
- const fields = { inputs, outputs, events };
- // encode the main run payload
- const stringifiedPayload = serializePayloadForTracing(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
- accumulatedParts.push({
- name: `${method}.${payload.id}`,
- payload: new Blob([stringifiedPayload], {
- type: `application/json; length=${stringifiedPayload.length}`, // encoding=gzip
- }),
- });
- // encode the fields we collected
- for (const [key, value] of Object.entries(fields)) {
- if (value === undefined) {
- continue;
- }
- const stringifiedValue = serializePayloadForTracing(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
- accumulatedParts.push({
- name: `${method}.${payload.id}.${key}`,
- payload: new Blob([stringifiedValue], {
- type: `application/json; length=${stringifiedValue.length}`,
- }),
- });
- }
- // encode the attachments
- if (payload.id !== undefined) {
- const attachments = allAttachments[payload.id];
- if (attachments) {
- delete allAttachments[payload.id];
- for (const [name, attachment] of Object.entries(attachments)) {
- let contentType;
- let content;
- if (Array.isArray(attachment)) {
- [contentType, content] = attachment;
- }
- else {
- contentType = attachment.mimeType;
- content = attachment.data;
- }
- // Validate that the attachment name doesn't contain a '.'
- if (name.includes(".")) {
- console.warn(`Skipping attachment '${name}' for run ${payload.id}: Invalid attachment name. ` +
- `Attachment names must not contain periods ('.'). Please rename the attachment and try again.`);
- continue;
- }
- accumulatedParts.push({
- name: `attachment.${payload.id}.${name}`,
- payload: new Blob([content], {
- type: `${contentType}; length=${content.byteLength}`,
- }),
- });
- }
- }
- }
- // compute context
- accumulatedContext.push(`trace=${payload.trace_id},id=${payload.id}`);
- }
- }
- await this._sendMultipartRequest(accumulatedParts, accumulatedContext.join("; "));
- }
- async _createNodeFetchBody(parts, boundary) {
- // Create multipart form data manually using Blobs
- const chunks = [];
- for (const part of parts) {
- // Add field boundary
- chunks.push(new Blob([`--${boundary}\r\n`]));
- chunks.push(new Blob([
- `Content-Disposition: form-data; name="${part.name}"\r\n`,
- `Content-Type: ${part.payload.type}\r\n\r\n`,
- ]));
- chunks.push(part.payload);
- chunks.push(new Blob(["\r\n"]));
- }
- // Add final boundary
- chunks.push(new Blob([`--${boundary}--\r\n`]));
- // Combine all chunks into a single Blob
- const body = new Blob(chunks);
- // Convert Blob to ArrayBuffer for compatibility
- const arrayBuffer = await body.arrayBuffer();
- return arrayBuffer;
- }
- async _createMultipartStream(parts, boundary) {
- const encoder = new TextEncoder();
- // Create a ReadableStream for streaming the multipart data
- // Only do special handling if we're using node-fetch
- const stream = new ReadableStream({
- async start(controller) {
- // Helper function to write a chunk to the stream
- const writeChunk = async (chunk) => {
- if (typeof chunk === "string") {
- controller.enqueue(encoder.encode(chunk));
- }
- else {
- controller.enqueue(chunk);
- }
- };
- // Write each part to the stream
- for (const part of parts) {
- // Write boundary and headers
- await writeChunk(`--${boundary}\r\n`);
- await writeChunk(`Content-Disposition: form-data; name="${part.name}"\r\n`);
- await writeChunk(`Content-Type: ${part.payload.type}\r\n\r\n`);
- // Write the payload
- const payloadStream = part.payload.stream();
- const reader = payloadStream.getReader();
- try {
- let result;
- while (!(result = await reader.read()).done) {
- controller.enqueue(result.value);
- }
- }
- finally {
- reader.releaseLock();
- }
- await writeChunk("\r\n");
- }
- // Write final boundary
- await writeChunk(`--${boundary}--\r\n`);
- controller.close();
- },
- });
- return stream;
- }
- async _sendMultipartRequest(parts, context) {
- try {
- // Create multipart form data boundary
- const boundary = "----LangSmithFormBoundary" + Math.random().toString(36).slice(2);
- const body = await (_globalFetchImplementationIsNodeFetch()
- ? this._createNodeFetchBody(parts, boundary)
- : this._createMultipartStream(parts, boundary));
- const res = await this.batchIngestCaller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/multipart`, {
- method: "POST",
- headers: {
- ...this.headers,
- "Content-Type": `multipart/form-data; boundary=${boundary}`,
- },
- body,
- duplex: "half",
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(res, "ingest multipart runs", true);
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- }
- catch (e) {
- console.warn(`${e.message.trim()}\n\nContext: ${context}`);
- }
- }
- async updateRun(runId, run) {
- assertUuid(runId);
- if (run.inputs) {
- run.inputs = await this.processInputs(run.inputs);
- }
- if (run.outputs) {
- run.outputs = await this.processOutputs(run.outputs);
- }
- // TODO: Untangle types
- const data = { ...run, id: runId };
- if (!this._filterForSampling([data], true).length) {
- return;
- }
- if (this.autoBatchTracing &&
- data.trace_id !== undefined &&
- data.dotted_order !== undefined) {
- if (run.end_time !== undefined &&
- data.parent_run_id === undefined &&
- this.blockOnRootRunFinalization &&
- !this.manualFlushMode) {
- // Trigger batches as soon as a root trace ends and wait to ensure trace finishes
- // in serverless environments.
- await this.processRunOperation({ action: "update", item: data }).catch(console.error);
- return;
- }
- else {
- void this.processRunOperation({ action: "update", item: data }).catch(console.error);
- }
- return;
- }
- const headers = { ...this.headers, "Content-Type": "application/json" };
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/${runId}`, {
- method: "PATCH",
- headers,
- body: serializePayloadForTracing(run, `Serializing payload to update run with id: ${runId}`),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "update run", true);
- }
- async readRun(runId, { loadChildRuns } = { loadChildRuns: false }) {
- assertUuid(runId);
- let run = await this._get(`/runs/${runId}`);
- if (loadChildRuns && run.child_run_ids) {
- run = await this._loadChildRuns(run);
- }
- return run;
- }
- async getRunUrl({ runId, run, projectOpts, }) {
- if (run !== undefined) {
- let sessionId;
- if (run.session_id) {
- sessionId = run.session_id;
- }
- else if (projectOpts?.projectName) {
- sessionId = (await this.readProject({ projectName: projectOpts?.projectName })).id;
- }
- else if (projectOpts?.projectId) {
- sessionId = projectOpts?.projectId;
- }
- else {
- const project = await this.readProject({
- projectName: getLangSmithEnvironmentVariable("PROJECT") || "default",
- });
- sessionId = project.id;
- }
- const tenantId = await this._getTenantId();
- return `${this.getHostUrl()}/o/${tenantId}/projects/p/${sessionId}/r/${run.id}?poll=true`;
- }
- else if (runId !== undefined) {
- const run_ = await this.readRun(runId);
- if (!run_.app_path) {
- throw new Error(`Run ${runId} has no app_path`);
- }
- const baseUrl = this.getHostUrl();
- return `${baseUrl}${run_.app_path}`;
- }
- else {
- throw new Error("Must provide either runId or run");
- }
- }
- async _loadChildRuns(run) {
- const childRuns = await toArray(this.listRuns({ id: run.child_run_ids }));
- const treemap = {};
- const runs = {};
- // TODO: make dotted order required when the migration finishes
- childRuns.sort((a, b) => (a?.dotted_order ?? "").localeCompare(b?.dotted_order ?? ""));
- for (const childRun of childRuns) {
- if (childRun.parent_run_id === null ||
- childRun.parent_run_id === undefined) {
- throw new Error(`Child run ${childRun.id} has no parent`);
- }
- if (!(childRun.parent_run_id in treemap)) {
- treemap[childRun.parent_run_id] = [];
- }
- treemap[childRun.parent_run_id].push(childRun);
- runs[childRun.id] = childRun;
- }
- run.child_runs = treemap[run.id] || [];
- for (const runId in treemap) {
- if (runId !== run.id) {
- runs[runId].child_runs = treemap[runId];
- }
- }
- return run;
- }
- /**
- * List runs from the LangSmith server.
- * @param projectId - The ID of the project to filter by.
- * @param projectName - The name of the project to filter by.
- * @param parentRunId - The ID of the parent run to filter by.
- * @param traceId - The ID of the trace to filter by.
- * @param referenceExampleId - The ID of the reference example to filter by.
- * @param startTime - The start time to filter by.
- * @param isRoot - Indicates whether to only return root runs.
- * @param runType - The run type to filter by.
- * @param error - Indicates whether to filter by error runs.
- * @param id - The ID of the run to filter by.
- * @param query - The query string to filter by.
- * @param filter - The filter string to apply to the run spans.
- * @param traceFilter - The filter string to apply on the root run of the trace.
- * @param treeFilter - The filter string to apply on other runs in the trace.
- * @param limit - The maximum number of runs to retrieve.
- * @returns {AsyncIterable<Run>} - The runs.
- *
- * @example
- * // List all runs in a project
- * const projectRuns = client.listRuns({ projectName: "<your_project>" });
- *
- * @example
- * // List LLM and Chat runs in the last 24 hours
- * const todaysLLMRuns = client.listRuns({
- * projectName: "<your_project>",
- * start_time: new Date(Date.now() - 24 * 60 * 60 * 1000),
- * run_type: "llm",
- * });
- *
- * @example
- * // List traces in a project
- * const rootRuns = client.listRuns({
- * projectName: "<your_project>",
- * execution_order: 1,
- * });
- *
- * @example
- * // List runs without errors
- * const correctRuns = client.listRuns({
- * projectName: "<your_project>",
- * error: false,
- * });
- *
- * @example
- * // List runs by run ID
- * const runIds = [
- * "a36092d2-4ad5-4fb4-9c0d-0dba9a2ed836",
- * "9398e6be-964f-4aa4-8ae9-ad78cd4b7074",
- * ];
- * const selectedRuns = client.listRuns({ run_ids: runIds });
- *
- * @example
- * // List all "chain" type runs that took more than 10 seconds and had `total_tokens` greater than 5000
- * const chainRuns = client.listRuns({
- * projectName: "<your_project>",
- * filter: 'and(eq(run_type, "chain"), gt(latency, 10), gt(total_tokens, 5000))',
- * });
- *
- * @example
- * // List all runs called "extractor" whose root of the trace was assigned feedback "user_score" score of 1
- * const goodExtractorRuns = client.listRuns({
- * projectName: "<your_project>",
- * filter: 'eq(name, "extractor")',
- * traceFilter: 'and(eq(feedback_key, "user_score"), eq(feedback_score, 1))',
- * });
- *
- * @example
- * // 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
- * const complexRuns = client.listRuns({
- * projectName: "<your_project>",
- * filter: 'and(gt(start_time, "2023-07-15T12:34:56Z"), or(neq(error, null), and(eq(feedback_key, "Correctness"), eq(feedback_score, 0.0))))',
- * });
- *
- * @example
- * // List all runs where `tags` include "experimental" or "beta" and `latency` is greater than 2 seconds
- * const taggedRuns = client.listRuns({
- * projectName: "<your_project>",
- * filter: 'and(or(has(tags, "experimental"), has(tags, "beta")), gt(latency, 2))',
- * });
- */
- async *listRuns(props) {
- const { projectId, projectName, parentRunId, traceId, referenceExampleId, startTime, executionOrder, isRoot, runType, error, id, query, filter, traceFilter, treeFilter, limit, select, order, } = props;
- let projectIds = [];
- if (projectId) {
- projectIds = Array.isArray(projectId) ? projectId : [projectId];
- }
- if (projectName) {
- const projectNames = Array.isArray(projectName)
- ? projectName
- : [projectName];
- const projectIds_ = await Promise.all(projectNames.map((name) => this.readProject({ projectName: name }).then((project) => project.id)));
- projectIds.push(...projectIds_);
- }
- const default_select = [
- "app_path",
- "child_run_ids",
- "completion_cost",
- "completion_tokens",
- "dotted_order",
- "end_time",
- "error",
- "events",
- "extra",
- "feedback_stats",
- "first_token_time",
- "id",
- "inputs",
- "name",
- "outputs",
- "parent_run_id",
- "parent_run_ids",
- "prompt_cost",
- "prompt_tokens",
- "reference_example_id",
- "run_type",
- "session_id",
- "start_time",
- "status",
- "tags",
- "total_cost",
- "total_tokens",
- "trace_id",
- ];
- const body = {
- session: projectIds.length ? projectIds : null,
- run_type: runType,
- reference_example: referenceExampleId,
- query,
- filter,
- trace_filter: traceFilter,
- tree_filter: treeFilter,
- execution_order: executionOrder,
- parent_run: parentRunId,
- start_time: startTime ? startTime.toISOString() : null,
- error,
- id,
- limit,
- trace: traceId,
- select: select ? select : default_select,
- is_root: isRoot,
- order,
- };
- let runsYielded = 0;
- for await (const runs of this._getCursorPaginatedList("/runs/query", body)) {
- if (limit) {
- if (runsYielded >= limit) {
- break;
- }
- if (runs.length + runsYielded > limit) {
- const newRuns = runs.slice(0, limit - runsYielded);
- yield* newRuns;
- break;
- }
- runsYielded += runs.length;
- yield* runs;
- }
- else {
- yield* runs;
- }
- }
- }
- async *listGroupRuns(props) {
- const { projectId, projectName, groupBy, filter, startTime, endTime, limit, offset, } = props;
- const sessionId = projectId || (await this.readProject({ projectName })).id;
- const baseBody = {
- session_id: sessionId,
- group_by: groupBy,
- filter,
- start_time: startTime ? startTime.toISOString() : null,
- end_time: endTime ? endTime.toISOString() : null,
- limit: Number(limit) || 100,
- };
- let currentOffset = Number(offset) || 0;
- const path = "/runs/group";
- const url = `${this.apiUrl}${path}`;
- while (true) {
- const currentBody = {
- ...baseBody,
- offset: currentOffset,
- };
- // Remove undefined values from the payload
- const filteredPayload = Object.fromEntries(Object.entries(currentBody).filter(([_, value]) => value !== undefined));
- const response = await this.caller.call(_getFetchImplementation(), url, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(filteredPayload),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, `Failed to fetch ${path}`);
- const items = await response.json();
- const { groups, total } = items;
- if (groups.length === 0) {
- break;
- }
- for (const thread of groups) {
- yield thread;
- }
- currentOffset += groups.length;
- if (currentOffset >= total) {
- break;
- }
- }
- }
- async getRunStats({ id, trace, parentRun, runType, projectNames, projectIds, referenceExampleIds, startTime, endTime, error, query, filter, traceFilter, treeFilter, isRoot, dataSourceType, }) {
- let projectIds_ = projectIds || [];
- if (projectNames) {
- projectIds_ = [
- ...(projectIds || []),
- ...(await Promise.all(projectNames.map((name) => this.readProject({ projectName: name }).then((project) => project.id)))),
- ];
- }
- const payload = {
- id,
- trace,
- parent_run: parentRun,
- run_type: runType,
- session: projectIds_,
- reference_example: referenceExampleIds,
- start_time: startTime,
- end_time: endTime,
- error,
- query,
- filter,
- trace_filter: traceFilter,
- tree_filter: treeFilter,
- is_root: isRoot,
- data_source_type: dataSourceType,
- };
- // Remove undefined values from the payload
- const filteredPayload = Object.fromEntries(Object.entries(payload).filter(([_, value]) => value !== undefined));
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/stats`, {
- method: "POST",
- headers: this.headers,
- body: JSON.stringify(filteredPayload),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- const result = await response.json();
- return result;
- }
- async shareRun(runId, { shareId } = {}) {
- const data = {
- run_id: runId,
- share_token: shareId || uuid.v4(),
- };
- assertUuid(runId);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/${runId}/share`, {
- method: "PUT",
- headers: this.headers,
- body: JSON.stringify(data),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- const result = await response.json();
- if (result === null || !("share_token" in result)) {
- throw new Error("Invalid response from server");
- }
- return `${this.getHostUrl()}/public/${result["share_token"]}/r`;
- }
- async unshareRun(runId) {
- assertUuid(runId);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/${runId}/share`, {
- method: "DELETE",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "unshare run", true);
- }
- async readRunSharedLink(runId) {
- assertUuid(runId);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/runs/${runId}/share`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- const result = await response.json();
- if (result === null || !("share_token" in result)) {
- return undefined;
- }
- return `${this.getHostUrl()}/public/${result["share_token"]}/r`;
- }
- async listSharedRuns(shareToken, { runIds, } = {}) {
- const queryParams = new URLSearchParams({
- share_token: shareToken,
- });
- if (runIds !== undefined) {
- for (const runId of runIds) {
- queryParams.append("id", runId);
- }
- }
- assertUuid(shareToken);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/public/${shareToken}/runs${queryParams}`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- const runs = await response.json();
- return runs;
- }
- async readDatasetSharedSchema(datasetId, datasetName) {
- if (!datasetId && !datasetName) {
- throw new Error("Either datasetId or datasetName must be given");
- }
- if (!datasetId) {
- const dataset = await this.readDataset({ datasetName });
- datasetId = dataset.id;
- }
- assertUuid(datasetId);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId}/share`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- const shareSchema = await response.json();
- shareSchema.url = `${this.getHostUrl()}/public/${shareSchema.share_token}/d`;
- return shareSchema;
- }
- async shareDataset(datasetId, datasetName) {
- if (!datasetId && !datasetName) {
- throw new Error("Either datasetId or datasetName must be given");
- }
- if (!datasetId) {
- const dataset = await this.readDataset({ datasetName });
- datasetId = dataset.id;
- }
- const data = {
- dataset_id: datasetId,
- };
- assertUuid(datasetId);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId}/share`, {
- method: "PUT",
- headers: this.headers,
- body: JSON.stringify(data),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- const shareSchema = await response.json();
- shareSchema.url = `${this.getHostUrl()}/public/${shareSchema.share_token}/d`;
- return shareSchema;
- }
- async unshareDataset(datasetId) {
- assertUuid(datasetId);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId}/share`, {
- method: "DELETE",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "unshare dataset", true);
- }
- async readSharedDataset(shareToken) {
- assertUuid(shareToken);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/public/${shareToken}/datasets`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- const dataset = await response.json();
- return dataset;
- }
- /**
- * Get shared examples.
- *
- * @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.
- * @param {Object} [options] Additional options for listing the examples.
- * @param {string[] | undefined} [options.exampleIds] A list of example IDs to filter by.
- * @returns {Promise<Example[]>} The shared examples.
- */
- async listSharedExamples(shareToken, options) {
- const params = {};
- if (options?.exampleIds) {
- params.id = options.exampleIds;
- }
- const urlParams = new URLSearchParams();
- Object.entries(params).forEach(([key, value]) => {
- if (Array.isArray(value)) {
- value.forEach((v) => urlParams.append(key, v));
- }
- else {
- urlParams.append(key, value);
- }
- });
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/public/${shareToken}/examples?${urlParams.toString()}`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- const result = await response.json();
- if (!response.ok) {
- if ("detail" in result) {
- throw new Error(`Failed to list shared examples.\nStatus: ${response.status}\nMessage: ${Array.isArray(result.detail)
- ? result.detail.join("\n")
- : "Unspecified error"}`);
- }
- throw new Error(`Failed to list shared examples: ${response.status} ${response.statusText}`);
- }
- return result.map((example) => ({
- ...example,
- _hostUrl: this.getHostUrl(),
- }));
- }
- async createProject({ projectName, description = null, metadata = null, upsert = false, projectExtra = null, referenceDatasetId = null, }) {
- const upsert_ = upsert ? `?upsert=true` : "";
- const endpoint = `${this.apiUrl}/sessions${upsert_}`;
- const extra = projectExtra || {};
- if (metadata) {
- extra["metadata"] = metadata;
- }
- const body = {
- name: projectName,
- extra,
- description,
- };
- if (referenceDatasetId !== null) {
- body["reference_dataset_id"] = referenceDatasetId;
- }
- const response = await this.caller.call(_getFetchImplementation(this.debug), endpoint, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(body),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "create project");
- const result = await response.json();
- return result;
- }
- async updateProject(projectId, { name = null, description = null, metadata = null, projectExtra = null, endTime = null, }) {
- const endpoint = `${this.apiUrl}/sessions/${projectId}`;
- let extra = projectExtra;
- if (metadata) {
- extra = { ...(extra || {}), metadata };
- }
- const body = {
- name,
- extra,
- description,
- end_time: endTime ? new Date(endTime).toISOString() : null,
- };
- const response = await this.caller.call(_getFetchImplementation(this.debug), endpoint, {
- method: "PATCH",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(body),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "update project");
- const result = await response.json();
- return result;
- }
- async hasProject({ projectId, projectName, }) {
- // TODO: Add a head request
- let path = "/sessions";
- const params = new URLSearchParams();
- if (projectId !== undefined && projectName !== undefined) {
- throw new Error("Must provide either projectName or projectId, not both");
- }
- else if (projectId !== undefined) {
- assertUuid(projectId);
- path += `/${projectId}`;
- }
- else if (projectName !== undefined) {
- params.append("name", projectName);
- }
- else {
- throw new Error("Must provide projectName or projectId");
- }
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}${path}?${params}`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- // consume the response body to release the connection
- // https://undici.nodejs.org/#/?id=garbage-collection
- try {
- const result = await response.json();
- if (!response.ok) {
- return false;
- }
- // If it's OK and we're querying by name, need to check the list is not empty
- if (Array.isArray(result)) {
- return result.length > 0;
- }
- // projectId querying
- return true;
- }
- catch (e) {
- return false;
- }
- }
- async readProject({ projectId, projectName, includeStats, }) {
- let path = "/sessions";
- const params = new URLSearchParams();
- if (projectId !== undefined && projectName !== undefined) {
- throw new Error("Must provide either projectName or projectId, not both");
- }
- else if (projectId !== undefined) {
- assertUuid(projectId);
- path += `/${projectId}`;
- }
- else if (projectName !== undefined) {
- params.append("name", projectName);
- }
- else {
- throw new Error("Must provide projectName or projectId");
- }
- if (includeStats !== undefined) {
- params.append("include_stats", includeStats.toString());
- }
- const response = await this._get(path, params);
- let result;
- if (Array.isArray(response)) {
- if (response.length === 0) {
- throw new Error(`Project[id=${projectId}, name=${projectName}] not found`);
- }
- result = response[0];
- }
- else {
- result = response;
- }
- return result;
- }
- async getProjectUrl({ projectId, projectName, }) {
- if (projectId === undefined && projectName === undefined) {
- throw new Error("Must provide either projectName or projectId");
- }
- const project = await this.readProject({ projectId, projectName });
- const tenantId = await this._getTenantId();
- return `${this.getHostUrl()}/o/${tenantId}/projects/p/${project.id}`;
- }
- async getDatasetUrl({ datasetId, datasetName, }) {
- if (datasetId === undefined && datasetName === undefined) {
- throw new Error("Must provide either datasetName or datasetId");
- }
- const dataset = await this.readDataset({ datasetId, datasetName });
- const tenantId = await this._getTenantId();
- return `${this.getHostUrl()}/o/${tenantId}/datasets/${dataset.id}`;
- }
- async _getTenantId() {
- if (this._tenantId !== null) {
- return this._tenantId;
- }
- const queryParams = new URLSearchParams({ limit: "1" });
- for await (const projects of this._getPaginated("/sessions", queryParams)) {
- this._tenantId = projects[0].tenant_id;
- return projects[0].tenant_id;
- }
- throw new Error("No projects found to resolve tenant.");
- }
- async *listProjects({ projectIds, name, nameContains, referenceDatasetId, referenceDatasetName, referenceFree, metadata, } = {}) {
- const params = new URLSearchParams();
- if (projectIds !== undefined) {
- for (const projectId of projectIds) {
- params.append("id", projectId);
- }
- }
- if (name !== undefined) {
- params.append("name", name);
- }
- if (nameContains !== undefined) {
- params.append("name_contains", nameContains);
- }
- if (referenceDatasetId !== undefined) {
- params.append("reference_dataset", referenceDatasetId);
- }
- else if (referenceDatasetName !== undefined) {
- const dataset = await this.readDataset({
- datasetName: referenceDatasetName,
- });
- params.append("reference_dataset", dataset.id);
- }
- if (referenceFree !== undefined) {
- params.append("reference_free", referenceFree.toString());
- }
- if (metadata !== undefined) {
- params.append("metadata", JSON.stringify(metadata));
- }
- for await (const projects of this._getPaginated("/sessions", params)) {
- yield* projects;
- }
- }
- async deleteProject({ projectId, projectName, }) {
- let projectId_;
- if (projectId === undefined && projectName === undefined) {
- throw new Error("Must provide projectName or projectId");
- }
- else if (projectId !== undefined && projectName !== undefined) {
- throw new Error("Must provide either projectName or projectId, not both");
- }
- else if (projectId === undefined) {
- projectId_ = (await this.readProject({ projectName })).id;
- }
- else {
- projectId_ = projectId;
- }
- assertUuid(projectId_);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/sessions/${projectId_}`, {
- method: "DELETE",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, `delete session ${projectId_} (${projectName})`, true);
- }
- async uploadCsv({ csvFile, fileName, inputKeys, outputKeys, description, dataType, name, }) {
- const url = `${this.apiUrl}/datasets/upload`;
- const formData = new FormData();
- formData.append("file", csvFile, fileName);
- inputKeys.forEach((key) => {
- formData.append("input_keys", key);
- });
- outputKeys.forEach((key) => {
- formData.append("output_keys", key);
- });
- if (description) {
- formData.append("description", description);
- }
- if (dataType) {
- formData.append("data_type", dataType);
- }
- if (name) {
- formData.append("name", name);
- }
- const response = await this.caller.call(_getFetchImplementation(this.debug), url, {
- method: "POST",
- headers: this.headers,
- body: formData,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "upload CSV");
- const result = await response.json();
- return result;
- }
- async createDataset(name, { description, dataType, inputsSchema, outputsSchema, metadata, } = {}) {
- const body = {
- name,
- description,
- extra: metadata ? { metadata } : undefined,
- };
- if (dataType) {
- body.data_type = dataType;
- }
- if (inputsSchema) {
- body.inputs_schema_definition = inputsSchema;
- }
- if (outputsSchema) {
- body.outputs_schema_definition = outputsSchema;
- }
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets`, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(body),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "create dataset");
- const result = await response.json();
- return result;
- }
- async readDataset({ datasetId, datasetName, }) {
- let path = "/datasets";
- // limit to 1 result
- const params = new URLSearchParams({ limit: "1" });
- if (datasetId !== undefined && datasetName !== undefined) {
- throw new Error("Must provide either datasetName or datasetId, not both");
- }
- else if (datasetId !== undefined) {
- assertUuid(datasetId);
- path += `/${datasetId}`;
- }
- else if (datasetName !== undefined) {
- params.append("name", datasetName);
- }
- else {
- throw new Error("Must provide datasetName or datasetId");
- }
- const response = await this._get(path, params);
- let result;
- if (Array.isArray(response)) {
- if (response.length === 0) {
- throw new Error(`Dataset[id=${datasetId}, name=${datasetName}] not found`);
- }
- result = response[0];
- }
- else {
- result = response;
- }
- return result;
- }
- async hasDataset({ datasetId, datasetName, }) {
- try {
- await this.readDataset({ datasetId, datasetName });
- return true;
- }
- catch (e) {
- if (
- // eslint-disable-next-line no-instanceof/no-instanceof
- e instanceof Error &&
- e.message.toLocaleLowerCase().includes("not found")) {
- return false;
- }
- throw e;
- }
- }
- async diffDatasetVersions({ datasetId, datasetName, fromVersion, toVersion, }) {
- let datasetId_ = datasetId;
- if (datasetId_ === undefined && datasetName === undefined) {
- throw new Error("Must provide either datasetName or datasetId");
- }
- else if (datasetId_ !== undefined && datasetName !== undefined) {
- throw new Error("Must provide either datasetName or datasetId, not both");
- }
- else if (datasetId_ === undefined) {
- const dataset = await this.readDataset({ datasetName });
- datasetId_ = dataset.id;
- }
- const urlParams = new URLSearchParams({
- from_version: typeof fromVersion === "string"
- ? fromVersion
- : fromVersion.toISOString(),
- to_version: typeof toVersion === "string" ? toVersion : toVersion.toISOString(),
- });
- const response = await this._get(`/datasets/${datasetId_}/versions/diff`, urlParams);
- return response;
- }
- async readDatasetOpenaiFinetuning({ datasetId, datasetName, }) {
- const path = "/datasets";
- if (datasetId !== undefined) {
- // do nothing
- }
- else if (datasetName !== undefined) {
- datasetId = (await this.readDataset({ datasetName })).id;
- }
- else {
- throw new Error("Must provide either datasetName or datasetId");
- }
- const response = await this._getResponse(`${path}/${datasetId}/openai_ft`);
- const datasetText = await response.text();
- const dataset = datasetText
- .trim()
- .split("\n")
- .map((line) => JSON.parse(line));
- return dataset;
- }
- async *listDatasets({ limit = 100, offset = 0, datasetIds, datasetName, datasetNameContains, metadata, } = {}) {
- const path = "/datasets";
- const params = new URLSearchParams({
- limit: limit.toString(),
- offset: offset.toString(),
- });
- if (datasetIds !== undefined) {
- for (const id_ of datasetIds) {
- params.append("id", id_);
- }
- }
- if (datasetName !== undefined) {
- params.append("name", datasetName);
- }
- if (datasetNameContains !== undefined) {
- params.append("name_contains", datasetNameContains);
- }
- if (metadata !== undefined) {
- params.append("metadata", JSON.stringify(metadata));
- }
- for await (const datasets of this._getPaginated(path, params)) {
- yield* datasets;
- }
- }
- /**
- * Update a dataset
- * @param props The dataset details to update
- * @returns The updated dataset
- */
- async updateDataset(props) {
- const { datasetId, datasetName, ...update } = props;
- if (!datasetId && !datasetName) {
- throw new Error("Must provide either datasetName or datasetId");
- }
- const _datasetId = datasetId ?? (await this.readDataset({ datasetName })).id;
- assertUuid(_datasetId);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${_datasetId}`, {
- method: "PATCH",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(update),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "update dataset");
- return (await response.json());
- }
- /**
- * Updates a tag on a dataset.
- *
- * If the tag is already assigned to a different version of this dataset,
- * the tag will be moved to the new version. The as_of parameter is used to
- * determine which version of the dataset to apply the new tags to.
- *
- * It must be an exact version of the dataset to succeed. You can
- * use the "readDatasetVersion" method to find the exact version
- * to apply the tags to.
- * @param params.datasetId The ID of the dataset to update. Must be provided if "datasetName" is not provided.
- * @param params.datasetName The name of the dataset to update. Must be provided if "datasetId" is not provided.
- * @param params.asOf The timestamp of the dataset to apply the new tags to.
- * @param params.tag The new tag to apply to the dataset.
- */
- async updateDatasetTag(props) {
- const { datasetId, datasetName, asOf, tag } = props;
- if (!datasetId && !datasetName) {
- throw new Error("Must provide either datasetName or datasetId");
- }
- const _datasetId = datasetId ?? (await this.readDataset({ datasetName })).id;
- assertUuid(_datasetId);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${_datasetId}/tags`, {
- method: "PUT",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify({
- as_of: typeof asOf === "string" ? asOf : asOf.toISOString(),
- tag,
- }),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "update dataset tags");
- }
- async deleteDataset({ datasetId, datasetName, }) {
- let path = "/datasets";
- let datasetId_ = datasetId;
- if (datasetId !== undefined && datasetName !== undefined) {
- throw new Error("Must provide either datasetName or datasetId, not both");
- }
- else if (datasetName !== undefined) {
- const dataset = await this.readDataset({ datasetName });
- datasetId_ = dataset.id;
- }
- if (datasetId_ !== undefined) {
- assertUuid(datasetId_);
- path += `/${datasetId_}`;
- }
- else {
- throw new Error("Must provide datasetName or datasetId");
- }
- const response = await this.caller.call(_getFetchImplementation(this.debug), this.apiUrl + path, {
- method: "DELETE",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, `delete ${path}`);
- await response.json();
- }
- async indexDataset({ datasetId, datasetName, tag, }) {
- let datasetId_ = datasetId;
- if (!datasetId_ && !datasetName) {
- throw new Error("Must provide either datasetName or datasetId");
- }
- else if (datasetId_ && datasetName) {
- throw new Error("Must provide either datasetName or datasetId, not both");
- }
- else if (!datasetId_) {
- const dataset = await this.readDataset({ datasetName });
- datasetId_ = dataset.id;
- }
- assertUuid(datasetId_);
- const data = {
- tag: tag,
- };
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId_}/index`, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(data),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "index dataset");
- await response.json();
- }
- /**
- * Lets you run a similarity search query on a dataset.
- *
- * Requires the dataset to be indexed. Please see the `indexDataset` method to set up indexing.
- *
- * @param inputs The input on which to run the similarity search. Must have the
- * same schema as the dataset.
- *
- * @param datasetId The dataset to search for similar examples.
- *
- * @param limit The maximum number of examples to return. Will return the top `limit` most
- * similar examples in order of most similar to least similar. If no similar
- * examples are found, random examples will be returned.
- *
- * @param filter A filter string to apply to the search. Only examples will be returned that
- * match the filter string. Some examples of filters
- *
- * - eq(metadata.mykey, "value")
- * - and(neq(metadata.my.nested.key, "value"), neq(metadata.mykey, "value"))
- * - or(eq(metadata.mykey, "value"), eq(metadata.mykey, "othervalue"))
- *
- * @returns A list of similar examples.
- *
- *
- * @example
- * dataset_id = "123e4567-e89b-12d3-a456-426614174000"
- * inputs = {"text": "How many people live in Berlin?"}
- * limit = 5
- * examples = await client.similarExamples(inputs, dataset_id, limit)
- */
- async similarExamples(inputs, datasetId, limit, { filter, } = {}) {
- const data = {
- limit: limit,
- inputs: inputs,
- };
- if (filter !== undefined) {
- data["filter"] = filter;
- }
- assertUuid(datasetId);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId}/search`, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(data),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "fetch similar examples");
- const result = await response.json();
- return result["examples"];
- }
- async createExample(inputsOrUpdate, outputs, options) {
- if (isExampleCreate(inputsOrUpdate)) {
- if (outputs !== undefined || options !== undefined) {
- throw new Error("Cannot provide outputs or options when using ExampleCreate object");
- }
- }
- let datasetId_ = outputs ? options?.datasetId : inputsOrUpdate.dataset_id;
- const datasetName_ = outputs
- ? options?.datasetName
- : inputsOrUpdate.dataset_name;
- if (datasetId_ === undefined && datasetName_ === undefined) {
- throw new Error("Must provide either datasetName or datasetId");
- }
- else if (datasetId_ !== undefined && datasetName_ !== undefined) {
- throw new Error("Must provide either datasetName or datasetId, not both");
- }
- else if (datasetId_ === undefined) {
- const dataset = await this.readDataset({ datasetName: datasetName_ });
- datasetId_ = dataset.id;
- }
- const createdAt_ = (outputs ? options?.createdAt : inputsOrUpdate.created_at) || new Date();
- let data;
- if (!isExampleCreate(inputsOrUpdate)) {
- data = {
- inputs: inputsOrUpdate,
- outputs,
- created_at: createdAt_?.toISOString(),
- id: options?.exampleId,
- metadata: options?.metadata,
- split: options?.split,
- source_run_id: options?.sourceRunId,
- use_source_run_io: options?.useSourceRunIO,
- use_source_run_attachments: options?.useSourceRunAttachments,
- attachments: options?.attachments,
- };
- }
- else {
- data = inputsOrUpdate;
- }
- const response = await this._uploadExamplesMultipart(datasetId_, [data]);
- const example = await this.readExample(response.example_ids?.[0] ?? uuid.v4());
- return example;
- }
- async createExamples(propsOrUploads) {
- if (Array.isArray(propsOrUploads)) {
- if (propsOrUploads.length === 0) {
- return [];
- }
- const uploads = propsOrUploads;
- let datasetId_ = uploads[0].dataset_id;
- const datasetName_ = uploads[0].dataset_name;
- if (datasetId_ === undefined && datasetName_ === undefined) {
- throw new Error("Must provide either datasetName or datasetId");
- }
- else if (datasetId_ !== undefined && datasetName_ !== undefined) {
- throw new Error("Must provide either datasetName or datasetId, not both");
- }
- else if (datasetId_ === undefined) {
- const dataset = await this.readDataset({ datasetName: datasetName_ });
- datasetId_ = dataset.id;
- }
- const response = await this._uploadExamplesMultipart(datasetId_, uploads);
- const examples = await Promise.all(response.example_ids.map((id) => this.readExample(id)));
- return examples;
- }
- const { inputs, outputs, metadata, splits, sourceRunIds, useSourceRunIOs, useSourceRunAttachments, attachments, exampleIds, datasetId, datasetName, } = propsOrUploads;
- if (inputs === undefined) {
- throw new Error("Must provide inputs when using legacy parameters");
- }
- let datasetId_ = datasetId;
- const datasetName_ = datasetName;
- if (datasetId_ === undefined && datasetName_ === undefined) {
- throw new Error("Must provide either datasetName or datasetId");
- }
- else if (datasetId_ !== undefined && datasetName_ !== undefined) {
- throw new Error("Must provide either datasetName or datasetId, not both");
- }
- else if (datasetId_ === undefined) {
- const dataset = await this.readDataset({ datasetName: datasetName_ });
- datasetId_ = dataset.id;
- }
- const formattedExamples = inputs.map((input, idx) => {
- return {
- dataset_id: datasetId_,
- inputs: input,
- outputs: outputs?.[idx],
- metadata: metadata?.[idx],
- split: splits?.[idx],
- id: exampleIds?.[idx],
- attachments: attachments?.[idx],
- source_run_id: sourceRunIds?.[idx],
- use_source_run_io: useSourceRunIOs?.[idx],
- use_source_run_attachments: useSourceRunAttachments?.[idx],
- };
- });
- const response = await this._uploadExamplesMultipart(datasetId_, formattedExamples);
- const examples = await Promise.all(response.example_ids.map((id) => this.readExample(id)));
- return examples;
- }
- async createLLMExample(input, generation, options) {
- return this.createExample({ input }, { output: generation }, options);
- }
- async createChatExample(input, generations, options) {
- const finalInput = input.map((message) => {
- if (isLangChainMessage(message)) {
- return convertLangChainMessageToExample(message);
- }
- return message;
- });
- const finalOutput = isLangChainMessage(generations)
- ? convertLangChainMessageToExample(generations)
- : generations;
- return this.createExample({ input: finalInput }, { output: finalOutput }, options);
- }
- async readExample(exampleId) {
- assertUuid(exampleId);
- const path = `/examples/${exampleId}`;
- const rawExample = await this._get(path);
- const { attachment_urls, ...rest } = rawExample;
- const example = rest;
- if (attachment_urls) {
- example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
- acc[key.slice("attachment.".length)] = {
- presigned_url: value.presigned_url,
- mime_type: value.mime_type,
- };
- return acc;
- }, {});
- }
- return example;
- }
- async *listExamples({ datasetId, datasetName, exampleIds, asOf, splits, inlineS3Urls, metadata, limit, offset, filter, includeAttachments, } = {}) {
- let datasetId_;
- if (datasetId !== undefined && datasetName !== undefined) {
- throw new Error("Must provide either datasetName or datasetId, not both");
- }
- else if (datasetId !== undefined) {
- datasetId_ = datasetId;
- }
- else if (datasetName !== undefined) {
- const dataset = await this.readDataset({ datasetName });
- datasetId_ = dataset.id;
- }
- else {
- throw new Error("Must provide a datasetName or datasetId");
- }
- const params = new URLSearchParams({ dataset: datasetId_ });
- const dataset_version = asOf
- ? typeof asOf === "string"
- ? asOf
- : asOf?.toISOString()
- : undefined;
- if (dataset_version) {
- params.append("as_of", dataset_version);
- }
- const inlineS3Urls_ = inlineS3Urls ?? true;
- params.append("inline_s3_urls", inlineS3Urls_.toString());
- if (exampleIds !== undefined) {
- for (const id_ of exampleIds) {
- params.append("id", id_);
- }
- }
- if (splits !== undefined) {
- for (const split of splits) {
- params.append("splits", split);
- }
- }
- if (metadata !== undefined) {
- const serializedMetadata = JSON.stringify(metadata);
- params.append("metadata", serializedMetadata);
- }
- if (limit !== undefined) {
- params.append("limit", limit.toString());
- }
- if (offset !== undefined) {
- params.append("offset", offset.toString());
- }
- if (filter !== undefined) {
- params.append("filter", filter);
- }
- if (includeAttachments === true) {
- ["attachment_urls", "outputs", "metadata"].forEach((field) => params.append("select", field));
- }
- let i = 0;
- for await (const rawExamples of this._getPaginated("/examples", params)) {
- for (const rawExample of rawExamples) {
- const { attachment_urls, ...rest } = rawExample;
- const example = rest;
- if (attachment_urls) {
- example.attachments = Object.entries(attachment_urls).reduce((acc, [key, value]) => {
- acc[key.slice("attachment.".length)] = {
- presigned_url: value.presigned_url,
- mime_type: value.mime_type || undefined,
- };
- return acc;
- }, {});
- }
- yield example;
- i++;
- }
- if (limit !== undefined && i >= limit) {
- break;
- }
- }
- }
- async deleteExample(exampleId) {
- assertUuid(exampleId);
- const path = `/examples/${exampleId}`;
- const response = await this.caller.call(_getFetchImplementation(this.debug), this.apiUrl + path, {
- method: "DELETE",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, `delete ${path}`);
- await response.json();
- }
- async updateExample(exampleIdOrUpdate, update) {
- let exampleId;
- if (update) {
- exampleId = exampleIdOrUpdate;
- }
- else {
- exampleId = exampleIdOrUpdate.id;
- }
- assertUuid(exampleId);
- let updateToUse;
- if (update) {
- updateToUse = { id: exampleId, ...update };
- }
- else {
- updateToUse = exampleIdOrUpdate;
- }
- let datasetId;
- if (updateToUse.dataset_id !== undefined) {
- datasetId = updateToUse.dataset_id;
- }
- else {
- const example = await this.readExample(exampleId);
- datasetId = example.dataset_id;
- }
- return this._updateExamplesMultipart(datasetId, [updateToUse]);
- }
- async updateExamples(update) {
- // We will naively get dataset id from first example and assume it works for all
- let datasetId;
- if (update[0].dataset_id === undefined) {
- const example = await this.readExample(update[0].id);
- datasetId = example.dataset_id;
- }
- else {
- datasetId = update[0].dataset_id;
- }
- return this._updateExamplesMultipart(datasetId, update);
- }
- /**
- * Get dataset version by closest date or exact tag.
- *
- * Use this to resolve the nearest version to a given timestamp or for a given tag.
- *
- * @param options The options for getting the dataset version
- * @param options.datasetId The ID of the dataset
- * @param options.datasetName The name of the dataset
- * @param options.asOf The timestamp of the dataset to retrieve
- * @param options.tag The tag of the dataset to retrieve
- * @returns The dataset version
- */
- async readDatasetVersion({ datasetId, datasetName, asOf, tag, }) {
- let resolvedDatasetId;
- if (!datasetId) {
- const dataset = await this.readDataset({ datasetName });
- resolvedDatasetId = dataset.id;
- }
- else {
- resolvedDatasetId = datasetId;
- }
- assertUuid(resolvedDatasetId);
- if ((asOf && tag) || (!asOf && !tag)) {
- throw new Error("Exactly one of asOf and tag must be specified.");
- }
- const params = new URLSearchParams();
- if (asOf !== undefined) {
- params.append("as_of", typeof asOf === "string" ? asOf : asOf.toISOString());
- }
- if (tag !== undefined) {
- params.append("tag", tag);
- }
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${resolvedDatasetId}/version?${params.toString()}`, {
- method: "GET",
- headers: { ...this.headers },
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "read dataset version");
- return await response.json();
- }
- async listDatasetSplits({ datasetId, datasetName, asOf, }) {
- let datasetId_;
- if (datasetId === undefined && datasetName === undefined) {
- throw new Error("Must provide dataset name or ID");
- }
- else if (datasetId !== undefined && datasetName !== undefined) {
- throw new Error("Must provide either datasetName or datasetId, not both");
- }
- else if (datasetId === undefined) {
- const dataset = await this.readDataset({ datasetName });
- datasetId_ = dataset.id;
- }
- else {
- datasetId_ = datasetId;
- }
- assertUuid(datasetId_);
- const params = new URLSearchParams();
- const dataset_version = asOf
- ? typeof asOf === "string"
- ? asOf
- : asOf?.toISOString()
- : undefined;
- if (dataset_version) {
- params.append("as_of", dataset_version);
- }
- const response = await this._get(`/datasets/${datasetId_}/splits`, params);
- return response;
- }
- async updateDatasetSplits({ datasetId, datasetName, splitName, exampleIds, remove = false, }) {
- let datasetId_;
- if (datasetId === undefined && datasetName === undefined) {
- throw new Error("Must provide dataset name or ID");
- }
- else if (datasetId !== undefined && datasetName !== undefined) {
- throw new Error("Must provide either datasetName or datasetId, not both");
- }
- else if (datasetId === undefined) {
- const dataset = await this.readDataset({ datasetName });
- datasetId_ = dataset.id;
- }
- else {
- datasetId_ = datasetId;
- }
- assertUuid(datasetId_);
- const data = {
- split_name: splitName,
- examples: exampleIds.map((id) => {
- assertUuid(id);
- return id;
- }),
- remove,
- };
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/${datasetId_}/splits`, {
- method: "PUT",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(data),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "update dataset splits", true);
- }
- /**
- * @deprecated This method is deprecated and will be removed in future LangSmith versions, use `evaluate` from `langsmith/evaluation` instead.
- */
- async evaluateRun(run, evaluator, { sourceInfo, loadChildRuns, referenceExample, } = { loadChildRuns: false }) {
- warnOnce("This method is deprecated and will be removed in future LangSmith versions, use `evaluate` from `langsmith/evaluation` instead.");
- let run_;
- if (typeof run === "string") {
- run_ = await this.readRun(run, { loadChildRuns });
- }
- else if (typeof run === "object" && "id" in run) {
- run_ = run;
- }
- else {
- throw new Error(`Invalid run type: ${typeof run}`);
- }
- if (run_.reference_example_id !== null &&
- run_.reference_example_id !== undefined) {
- referenceExample = await this.readExample(run_.reference_example_id);
- }
- const feedbackResult = await evaluator.evaluateRun(run_, referenceExample);
- const [_, feedbacks] = await this._logEvaluationFeedback(feedbackResult, run_, sourceInfo);
- return feedbacks[0];
- }
- async createFeedback(runId, key, { score, value, correction, comment, sourceInfo, feedbackSourceType = "api", sourceRunId, feedbackId, feedbackConfig, projectId, comparativeExperimentId, }) {
- if (!runId && !projectId) {
- throw new Error("One of runId or projectId must be provided");
- }
- if (runId && projectId) {
- throw new Error("Only one of runId or projectId can be provided");
- }
- const feedback_source = {
- type: feedbackSourceType ?? "api",
- metadata: sourceInfo ?? {},
- };
- if (sourceRunId !== undefined &&
- feedback_source?.metadata !== undefined &&
- !feedback_source.metadata["__run"]) {
- feedback_source.metadata["__run"] = { run_id: sourceRunId };
- }
- if (feedback_source?.metadata !== undefined &&
- feedback_source.metadata["__run"]?.run_id !== undefined) {
- assertUuid(feedback_source.metadata["__run"].run_id);
- }
- const feedback = {
- id: feedbackId ?? uuid.v4(),
- run_id: runId,
- key,
- score: _formatFeedbackScore(score),
- value,
- correction,
- comment,
- feedback_source: feedback_source,
- comparative_experiment_id: comparativeExperimentId,
- feedbackConfig,
- session_id: projectId,
- };
- const url = `${this.apiUrl}/feedback`;
- const response = await this.caller.call(_getFetchImplementation(this.debug), url, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(feedback),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "create feedback", true);
- return feedback;
- }
- async updateFeedback(feedbackId, { score, value, correction, comment, }) {
- const feedbackUpdate = {};
- if (score !== undefined && score !== null) {
- feedbackUpdate["score"] = _formatFeedbackScore(score);
- }
- if (value !== undefined && value !== null) {
- feedbackUpdate["value"] = value;
- }
- if (correction !== undefined && correction !== null) {
- feedbackUpdate["correction"] = correction;
- }
- if (comment !== undefined && comment !== null) {
- feedbackUpdate["comment"] = comment;
- }
- assertUuid(feedbackId);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/feedback/${feedbackId}`, {
- method: "PATCH",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(feedbackUpdate),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "update feedback", true);
- }
- async readFeedback(feedbackId) {
- assertUuid(feedbackId);
- const path = `/feedback/${feedbackId}`;
- const response = await this._get(path);
- return response;
- }
- async deleteFeedback(feedbackId) {
- assertUuid(feedbackId);
- const path = `/feedback/${feedbackId}`;
- const response = await this.caller.call(_getFetchImplementation(this.debug), this.apiUrl + path, {
- method: "DELETE",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, `delete ${path}`);
- await response.json();
- }
- async *listFeedback({ runIds, feedbackKeys, feedbackSourceTypes, } = {}) {
- const queryParams = new URLSearchParams();
- if (runIds) {
- queryParams.append("run", runIds.join(","));
- }
- if (feedbackKeys) {
- for (const key of feedbackKeys) {
- queryParams.append("key", key);
- }
- }
- if (feedbackSourceTypes) {
- for (const type of feedbackSourceTypes) {
- queryParams.append("source", type);
- }
- }
- for await (const feedbacks of this._getPaginated("/feedback", queryParams)) {
- yield* feedbacks;
- }
- }
- /**
- * Creates a presigned feedback token and URL.
- *
- * The token can be used to authorize feedback metrics without
- * needing an API key. This is useful for giving browser-based
- * applications the ability to submit feedback without needing
- * to expose an API key.
- *
- * @param runId The ID of the run.
- * @param feedbackKey The feedback key.
- * @param options Additional options for the token.
- * @param options.expiration The expiration time for the token.
- *
- * @returns A promise that resolves to a FeedbackIngestToken.
- */
- async createPresignedFeedbackToken(runId, feedbackKey, { expiration, feedbackConfig, } = {}) {
- const body = {
- run_id: runId,
- feedback_key: feedbackKey,
- feedback_config: feedbackConfig,
- };
- if (expiration) {
- if (typeof expiration === "string") {
- body["expires_at"] = expiration;
- }
- else if (expiration?.hours || expiration?.minutes || expiration?.days) {
- body["expires_in"] = expiration;
- }
- }
- else {
- body["expires_in"] = {
- hours: 3,
- };
- }
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/feedback/tokens`, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(body),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- const result = await response.json();
- return result;
- }
- async createComparativeExperiment({ name, experimentIds, referenceDatasetId, createdAt, description, metadata, id, }) {
- if (experimentIds.length === 0) {
- throw new Error("At least one experiment is required");
- }
- if (!referenceDatasetId) {
- referenceDatasetId = (await this.readProject({
- projectId: experimentIds[0],
- })).reference_dataset_id;
- }
- if (!referenceDatasetId == null) {
- throw new Error("A reference dataset is required");
- }
- const body = {
- id,
- name,
- experiment_ids: experimentIds,
- reference_dataset_id: referenceDatasetId,
- description,
- created_at: (createdAt ?? new Date())?.toISOString(),
- extra: {},
- };
- if (metadata)
- body.extra["metadata"] = metadata;
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/datasets/comparative`, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(body),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- return await response.json();
- }
- /**
- * Retrieves a list of presigned feedback tokens for a given run ID.
- * @param runId The ID of the run.
- * @returns An async iterable of FeedbackIngestToken objects.
- */
- async *listPresignedFeedbackTokens(runId) {
- assertUuid(runId);
- const params = new URLSearchParams({ run_id: runId });
- for await (const tokens of this._getPaginated("/feedback/tokens", params)) {
- yield* tokens;
- }
- }
- _selectEvalResults(results) {
- let results_;
- if ("results" in results) {
- results_ = results.results;
- }
- else if (Array.isArray(results)) {
- results_ = results;
- }
- else {
- results_ = [results];
- }
- return results_;
- }
- async _logEvaluationFeedback(evaluatorResponse, run, sourceInfo) {
- const evalResults = this._selectEvalResults(evaluatorResponse);
- const feedbacks = [];
- for (const res of evalResults) {
- let sourceInfo_ = sourceInfo || {};
- if (res.evaluatorInfo) {
- sourceInfo_ = { ...res.evaluatorInfo, ...sourceInfo_ };
- }
- let runId_ = null;
- if (res.targetRunId) {
- runId_ = res.targetRunId;
- }
- else if (run) {
- runId_ = run.id;
- }
- feedbacks.push(await this.createFeedback(runId_, res.key, {
- score: res.score,
- value: res.value,
- comment: res.comment,
- correction: res.correction,
- sourceInfo: sourceInfo_,
- sourceRunId: res.sourceRunId,
- feedbackConfig: res.feedbackConfig,
- feedbackSourceType: "model",
- }));
- }
- return [evalResults, feedbacks];
- }
- async logEvaluationFeedback(evaluatorResponse, run, sourceInfo) {
- const [results] = await this._logEvaluationFeedback(evaluatorResponse, run, sourceInfo);
- return results;
- }
- /**
- * API for managing annotation queues
- */
- /**
- * List the annotation queues on the LangSmith API.
- * @param options - The options for listing annotation queues
- * @param options.queueIds - The IDs of the queues to filter by
- * @param options.name - The name of the queue to filter by
- * @param options.nameContains - The substring that the queue name should contain
- * @param options.limit - The maximum number of queues to return
- * @returns An iterator of AnnotationQueue objects
- */
- async *listAnnotationQueues(options = {}) {
- const { queueIds, name, nameContains, limit } = options;
- const params = new URLSearchParams();
- if (queueIds) {
- queueIds.forEach((id, i) => {
- assertUuid(id, `queueIds[${i}]`);
- params.append("ids", id);
- });
- }
- if (name)
- params.append("name", name);
- if (nameContains)
- params.append("name_contains", nameContains);
- params.append("limit", (limit !== undefined ? Math.min(limit, 100) : 100).toString());
- let count = 0;
- for await (const queues of this._getPaginated("/annotation-queues", params)) {
- yield* queues;
- count++;
- if (limit !== undefined && count >= limit)
- break;
- }
- }
- /**
- * Create an annotation queue on the LangSmith API.
- * @param options - The options for creating an annotation queue
- * @param options.name - The name of the annotation queue
- * @param options.description - The description of the annotation queue
- * @param options.queueId - The ID of the annotation queue
- * @returns The created AnnotationQueue object
- */
- async createAnnotationQueue(options) {
- const { name, description, queueId, rubricInstructions } = options;
- const body = {
- name,
- description,
- id: queueId || uuid.v4(),
- rubric_instructions: rubricInstructions,
- };
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues`, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(Object.fromEntries(Object.entries(body).filter(([_, v]) => v !== undefined))),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "create annotation queue");
- const data = await response.json();
- return data;
- }
- /**
- * Read an annotation queue with the specified queue ID.
- * @param queueId - The ID of the annotation queue to read
- * @returns The AnnotationQueueWithDetails object
- */
- async readAnnotationQueue(queueId) {
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "read annotation queue");
- const data = await response.json();
- return data;
- }
- /**
- * Update an annotation queue with the specified queue ID.
- * @param queueId - The ID of the annotation queue to update
- * @param options - The options for updating the annotation queue
- * @param options.name - The new name for the annotation queue
- * @param options.description - The new description for the annotation queue
- */
- async updateAnnotationQueue(queueId, options) {
- const { name, description, rubricInstructions } = options;
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}`, {
- method: "PATCH",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify({
- name,
- description,
- rubric_instructions: rubricInstructions,
- }),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "update annotation queue");
- }
- /**
- * Delete an annotation queue with the specified queue ID.
- * @param queueId - The ID of the annotation queue to delete
- */
- async deleteAnnotationQueue(queueId) {
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}`, {
- method: "DELETE",
- headers: { ...this.headers, Accept: "application/json" },
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "delete annotation queue");
- }
- /**
- * Add runs to an annotation queue with the specified queue ID.
- * @param queueId - The ID of the annotation queue
- * @param runIds - The IDs of the runs to be added to the annotation queue
- */
- async addRunsToAnnotationQueue(queueId, runIds) {
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}/runs`, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(runIds.map((id, i) => assertUuid(id, `runIds[${i}]`).toString())),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "add runs to annotation queue");
- }
- /**
- * Get a run from an annotation queue at the specified index.
- * @param queueId - The ID of the annotation queue
- * @param index - The index of the run to retrieve
- * @returns A Promise that resolves to a RunWithAnnotationQueueInfo object
- * @throws {Error} If the run is not found at the given index or for other API-related errors
- */
- async getRunFromAnnotationQueue(queueId, index) {
- const baseUrl = `/annotation-queues/${assertUuid(queueId, "queueId")}/run`;
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}${baseUrl}/${index}`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "get run from annotation queue");
- return await response.json();
- }
- /**
- * Delete a run from an an annotation queue.
- * @param queueId - The ID of the annotation queue to delete the run from
- * @param queueRunId - The ID of the run to delete from the annotation queue
- */
- async deleteRunFromAnnotationQueue(queueId, queueRunId) {
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}/runs/${assertUuid(queueRunId, "queueRunId")}`, {
- method: "DELETE",
- headers: { ...this.headers, Accept: "application/json" },
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "delete run from annotation queue");
- }
- /**
- * Get the size of an annotation queue.
- * @param queueId - The ID of the annotation queue
- */
- async getSizeFromAnnotationQueue(queueId) {
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/annotation-queues/${assertUuid(queueId, "queueId")}/size`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "get size from annotation queue");
- return await response.json();
- }
- async _currentTenantIsOwner(owner) {
- const settings = await this._getSettings();
- return owner == "-" || settings.tenant_handle === owner;
- }
- async _ownerConflictError(action, owner) {
- const settings = await this._getSettings();
- return new Error(`Cannot ${action} for another tenant.\n
- Current tenant: ${settings.tenant_handle}\n
- Requested tenant: ${owner}`);
- }
- async _getLatestCommitHash(promptOwnerAndName) {
- const res = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/commits/${promptOwnerAndName}/?limit=${1}&offset=${0}`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- const json = await res.json();
- if (!res.ok) {
- const detail = typeof json.detail === "string"
- ? json.detail
- : JSON.stringify(json.detail);
- const error = new Error(`Error ${res.status}: ${res.statusText}\n${detail}`);
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- error.statusCode = res.status;
- throw error;
- }
- if (json.commits.length === 0) {
- return undefined;
- }
- return json.commits[0].commit_hash;
- }
- async _likeOrUnlikePrompt(promptIdentifier, like) {
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/likes/${owner}/${promptName}`, {
- method: "POST",
- body: JSON.stringify({ like: like }),
- headers: { ...this.headers, "Content-Type": "application/json" },
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, `${like ? "like" : "unlike"} prompt`);
- return await response.json();
- }
- async _getPromptUrl(promptIdentifier) {
- const [owner, promptName, commitHash] = parsePromptIdentifier(promptIdentifier);
- if (!(await this._currentTenantIsOwner(owner))) {
- if (commitHash !== "latest") {
- return `${this.getHostUrl()}/hub/${owner}/${promptName}/${commitHash.substring(0, 8)}`;
- }
- else {
- return `${this.getHostUrl()}/hub/${owner}/${promptName}`;
- }
- }
- else {
- const settings = await this._getSettings();
- if (commitHash !== "latest") {
- return `${this.getHostUrl()}/prompts/${promptName}/${commitHash.substring(0, 8)}?organizationId=${settings.id}`;
- }
- else {
- return `${this.getHostUrl()}/prompts/${promptName}?organizationId=${settings.id}`;
- }
- }
- }
- async promptExists(promptIdentifier) {
- const prompt = await this.getPrompt(promptIdentifier);
- return !!prompt;
- }
- async likePrompt(promptIdentifier) {
- return this._likeOrUnlikePrompt(promptIdentifier, true);
- }
- async unlikePrompt(promptIdentifier) {
- return this._likeOrUnlikePrompt(promptIdentifier, false);
- }
- async *listCommits(promptOwnerAndName) {
- for await (const commits of this._getPaginated(`/commits/${promptOwnerAndName}/`, new URLSearchParams(), (res) => res.commits)) {
- yield* commits;
- }
- }
- async *listPrompts(options) {
- const params = new URLSearchParams();
- params.append("sort_field", options?.sortField ?? "updated_at");
- params.append("sort_direction", "desc");
- params.append("is_archived", (!!options?.isArchived).toString());
- if (options?.isPublic !== undefined) {
- params.append("is_public", options.isPublic.toString());
- }
- if (options?.query) {
- params.append("query", options.query);
- }
- for await (const prompts of this._getPaginated("/repos", params, (res) => res.repos)) {
- yield* prompts;
- }
- }
- async getPrompt(promptIdentifier) {
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/repos/${owner}/${promptName}`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- if (response.status === 404) {
- return null;
- }
- await raiseForStatus(response, "get prompt");
- const result = await response.json();
- if (result.repo) {
- return result.repo;
- }
- else {
- return null;
- }
- }
- async createPrompt(promptIdentifier, options) {
- const settings = await this._getSettings();
- if (options?.isPublic && !settings.tenant_handle) {
- throw new Error(`Cannot create a public prompt without first\n
- creating a LangChain Hub handle.
- You can add a handle by creating a public prompt at:\n
- https://smith.langchain.com/prompts`);
- }
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
- if (!(await this._currentTenantIsOwner(owner))) {
- throw await this._ownerConflictError("create a prompt", owner);
- }
- const data = {
- repo_handle: promptName,
- ...(options?.description && { description: options.description }),
- ...(options?.readme && { readme: options.readme }),
- ...(options?.tags && { tags: options.tags }),
- is_public: !!options?.isPublic,
- };
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/repos/`, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(data),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "create prompt");
- const { repo } = await response.json();
- return repo;
- }
- async createCommit(promptIdentifier, object, options) {
- if (!(await this.promptExists(promptIdentifier))) {
- throw new Error("Prompt does not exist, you must create it first.");
- }
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
- const resolvedParentCommitHash = options?.parentCommitHash === "latest" || !options?.parentCommitHash
- ? await this._getLatestCommitHash(`${owner}/${promptName}`)
- : options?.parentCommitHash;
- const payload = {
- manifest: JSON.parse(JSON.stringify(object)),
- parent_commit: resolvedParentCommitHash,
- };
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/commits/${owner}/${promptName}`, {
- method: "POST",
- headers: { ...this.headers, "Content-Type": "application/json" },
- body: JSON.stringify(payload),
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "create commit");
- const result = await response.json();
- return this._getPromptUrl(`${owner}/${promptName}${result.commit_hash ? `:${result.commit_hash}` : ""}`);
- }
- /**
- * Update examples with attachments using multipart form data.
- * @param updates List of ExampleUpdateWithAttachments objects to upsert
- * @returns Promise with the update response
- */
- async updateExamplesMultipart(datasetId, updates = []) {
- return this._updateExamplesMultipart(datasetId, updates);
- }
- async _updateExamplesMultipart(datasetId, updates = []) {
- if (!(await this._getMultiPartSupport())) {
- throw new Error("Your LangSmith deployment does not allow using the multipart examples endpoint, please upgrade your deployment to the latest version.");
- }
- const formData = new FormData();
- for (const example of updates) {
- const exampleId = example.id;
- // Prepare the main example body
- const exampleBody = {
- ...(example.metadata && { metadata: example.metadata }),
- ...(example.split && { split: example.split }),
- };
- // Add main example data
- const stringifiedExample = serializePayloadForTracing(exampleBody, `Serializing body for example with id: ${exampleId}`);
- const exampleBlob = new Blob([stringifiedExample], {
- type: "application/json",
- });
- formData.append(exampleId, exampleBlob);
- // Add inputs if present
- if (example.inputs) {
- const stringifiedInputs = serializePayloadForTracing(example.inputs, `Serializing inputs for example with id: ${exampleId}`);
- const inputsBlob = new Blob([stringifiedInputs], {
- type: "application/json",
- });
- formData.append(`${exampleId}.inputs`, inputsBlob);
- }
- // Add outputs if present
- if (example.outputs) {
- const stringifiedOutputs = serializePayloadForTracing(example.outputs, `Serializing outputs whle updating example with id: ${exampleId}`);
- const outputsBlob = new Blob([stringifiedOutputs], {
- type: "application/json",
- });
- formData.append(`${exampleId}.outputs`, outputsBlob);
- }
- // Add attachments if present
- if (example.attachments) {
- for (const [name, attachment] of Object.entries(example.attachments)) {
- let mimeType;
- let data;
- if (Array.isArray(attachment)) {
- [mimeType, data] = attachment;
- }
- else {
- mimeType = attachment.mimeType;
- data = attachment.data;
- }
- const attachmentBlob = new Blob([data], {
- type: `${mimeType}; length=${data.byteLength}`,
- });
- formData.append(`${exampleId}.attachment.${name}`, attachmentBlob);
- }
- }
- if (example.attachments_operations) {
- const stringifiedAttachmentsOperations = serializePayloadForTracing(example.attachments_operations, `Serializing attachments while updating example with id: ${exampleId}`);
- const attachmentsOperationsBlob = new Blob([stringifiedAttachmentsOperations], {
- type: "application/json",
- });
- formData.append(`${exampleId}.attachments_operations`, attachmentsOperationsBlob);
- }
- }
- const datasetIdToUse = datasetId ?? updates[0]?.dataset_id;
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/v1/platform/datasets/${datasetIdToUse}/examples`, {
- method: "PATCH",
- headers: this.headers,
- body: formData,
- });
- const result = await response.json();
- return result;
- }
- /**
- * Upload examples with attachments using multipart form data.
- * @param uploads List of ExampleUploadWithAttachments objects to upload
- * @returns Promise with the upload response
- * @deprecated This method is deprecated and will be removed in future LangSmith versions, please use `createExamples` instead
- */
- async uploadExamplesMultipart(datasetId, uploads = []) {
- return this._uploadExamplesMultipart(datasetId, uploads);
- }
- async _uploadExamplesMultipart(datasetId, uploads = []) {
- if (!(await this._getMultiPartSupport())) {
- throw new Error("Your LangSmith deployment does not allow using the multipart examples endpoint, please upgrade your deployment to the latest version.");
- }
- const formData = new FormData();
- for (const example of uploads) {
- const exampleId = (example.id ?? uuid.v4()).toString();
- // Prepare the main example body
- const exampleBody = {
- created_at: example.created_at,
- ...(example.metadata && { metadata: example.metadata }),
- ...(example.split && { split: example.split }),
- ...(example.source_run_id && { source_run_id: example.source_run_id }),
- ...(example.use_source_run_io && {
- use_source_run_io: example.use_source_run_io,
- }),
- ...(example.use_source_run_attachments && {
- use_source_run_attachments: example.use_source_run_attachments,
- }),
- };
- // Add main example data
- const stringifiedExample = serializePayloadForTracing(exampleBody, `Serializing body for uploaded example with id: ${exampleId}`);
- const exampleBlob = new Blob([stringifiedExample], {
- type: "application/json",
- });
- formData.append(exampleId, exampleBlob);
- // Add inputs if present
- if (example.inputs) {
- const stringifiedInputs = serializePayloadForTracing(example.inputs, `Serializing inputs for uploaded example with id: ${exampleId}`);
- const inputsBlob = new Blob([stringifiedInputs], {
- type: "application/json",
- });
- formData.append(`${exampleId}.inputs`, inputsBlob);
- }
- // Add outputs if present
- if (example.outputs) {
- const stringifiedOutputs = serializePayloadForTracing(example.outputs, `Serializing outputs for uploaded example with id: ${exampleId}`);
- const outputsBlob = new Blob([stringifiedOutputs], {
- type: "application/json",
- });
- formData.append(`${exampleId}.outputs`, outputsBlob);
- }
- // Add attachments if present
- if (example.attachments) {
- for (const [name, attachment] of Object.entries(example.attachments)) {
- let mimeType;
- let data;
- if (Array.isArray(attachment)) {
- [mimeType, data] = attachment;
- }
- else {
- mimeType = attachment.mimeType;
- data = attachment.data;
- }
- const attachmentBlob = new Blob([data], {
- type: `${mimeType}; length=${data.byteLength}`,
- });
- formData.append(`${exampleId}.attachment.${name}`, attachmentBlob);
- }
- }
- }
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/v1/platform/datasets/${datasetId}/examples`, {
- method: "POST",
- headers: this.headers,
- body: formData,
- });
- await raiseForStatus(response, "upload examples");
- const result = await response.json();
- return result;
- }
- async updatePrompt(promptIdentifier, options) {
- if (!(await this.promptExists(promptIdentifier))) {
- throw new Error("Prompt does not exist, you must create it first.");
- }
- const [owner, promptName] = parsePromptIdentifier(promptIdentifier);
- if (!(await this._currentTenantIsOwner(owner))) {
- throw await this._ownerConflictError("update a prompt", owner);
- }
- const payload = {};
- if (options?.description !== undefined)
- payload.description = options.description;
- if (options?.readme !== undefined)
- payload.readme = options.readme;
- if (options?.tags !== undefined)
- payload.tags = options.tags;
- if (options?.isPublic !== undefined)
- payload.is_public = options.isPublic;
- if (options?.isArchived !== undefined)
- payload.is_archived = options.isArchived;
- // Check if payload is empty
- if (Object.keys(payload).length === 0) {
- throw new Error("No valid update options provided");
- }
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/repos/${owner}/${promptName}`, {
- method: "PATCH",
- body: JSON.stringify(payload),
- headers: {
- ...this.headers,
- "Content-Type": "application/json",
- },
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "update prompt");
- return response.json();
- }
- async deletePrompt(promptIdentifier) {
- if (!(await this.promptExists(promptIdentifier))) {
- throw new Error("Prompt does not exist, you must create it first.");
- }
- const [owner, promptName, _] = parsePromptIdentifier(promptIdentifier);
- if (!(await this._currentTenantIsOwner(owner))) {
- throw await this._ownerConflictError("delete a prompt", owner);
- }
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/repos/${owner}/${promptName}`, {
- method: "DELETE",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- return await response.json();
- }
- async pullPromptCommit(promptIdentifier, options) {
- const [owner, promptName, commitHash] = parsePromptIdentifier(promptIdentifier);
- const response = await this.caller.call(_getFetchImplementation(this.debug), `${this.apiUrl}/commits/${owner}/${promptName}/${commitHash}${options?.includeModel ? "?include_model=true" : ""}`, {
- method: "GET",
- headers: this.headers,
- signal: AbortSignal.timeout(this.timeout_ms),
- ...this.fetchOptions,
- });
- await raiseForStatus(response, "pull prompt commit");
- const result = await response.json();
- return {
- owner,
- repo: promptName,
- commit_hash: result.commit_hash,
- manifest: result.manifest,
- examples: result.examples,
- };
- }
- /**
- * This method should not be used directly, use `import { pull } from "langchain/hub"` instead.
- * Using this method directly returns the JSON string of the prompt rather than a LangChain object.
- * @private
- */
- async _pullPrompt(promptIdentifier, options) {
- const promptObject = await this.pullPromptCommit(promptIdentifier, {
- includeModel: options?.includeModel,
- });
- const prompt = JSON.stringify(promptObject.manifest);
- return prompt;
- }
- async pushPrompt(promptIdentifier, options) {
- // Create or update prompt metadata
- if (await this.promptExists(promptIdentifier)) {
- if (options && Object.keys(options).some((key) => key !== "object")) {
- await this.updatePrompt(promptIdentifier, {
- description: options?.description,
- readme: options?.readme,
- tags: options?.tags,
- isPublic: options?.isPublic,
- });
- }
- }
- else {
- await this.createPrompt(promptIdentifier, {
- description: options?.description,
- readme: options?.readme,
- tags: options?.tags,
- isPublic: options?.isPublic,
- });
- }
- if (!options?.object) {
- return await this._getPromptUrl(promptIdentifier);
- }
- // Create a commit with the new manifest
- const url = await this.createCommit(promptIdentifier, options?.object, {
- parentCommitHash: options?.parentCommitHash,
- });
- return url;
- }
- /**
- * Clone a public dataset to your own langsmith tenant.
- * This operation is idempotent. If you already have a dataset with the given name,
- * this function will do nothing.
-
- * @param {string} tokenOrUrl The token of the public dataset to clone.
- * @param {Object} [options] Additional options for cloning the dataset.
- * @param {string} [options.sourceApiUrl] The URL of the langsmith server where the data is hosted. Defaults to the API URL of your current client.
- * @param {string} [options.datasetName] The name of the dataset to create in your tenant. Defaults to the name of the public dataset.
- * @returns {Promise<void>}
- */
- async clonePublicDataset(tokenOrUrl, options = {}) {
- const { sourceApiUrl = this.apiUrl, datasetName } = options;
- const [parsedApiUrl, tokenUuid] = this.parseTokenOrUrl(tokenOrUrl, sourceApiUrl);
- const sourceClient = new Client({
- apiUrl: parsedApiUrl,
- // Placeholder API key not needed anymore in most cases, but
- // some private deployments may have API key-based rate limiting
- // that would cause this to fail if we provide no value.
- apiKey: "placeholder",
- });
- const ds = await sourceClient.readSharedDataset(tokenUuid);
- const finalDatasetName = datasetName || ds.name;
- try {
- if (await this.hasDataset({ datasetId: finalDatasetName })) {
- console.log(`Dataset ${finalDatasetName} already exists in your tenant. Skipping.`);
- return;
- }
- }
- catch (_) {
- // `.hasDataset` will throw an error if the dataset does not exist.
- // no-op in that case
- }
- // Fetch examples first, then create the dataset
- const examples = await sourceClient.listSharedExamples(tokenUuid);
- const dataset = await this.createDataset(finalDatasetName, {
- description: ds.description,
- dataType: ds.data_type || "kv",
- inputsSchema: ds.inputs_schema_definition ?? undefined,
- outputsSchema: ds.outputs_schema_definition ?? undefined,
- });
- try {
- await this.createExamples({
- inputs: examples.map((e) => e.inputs),
- outputs: examples.flatMap((e) => (e.outputs ? [e.outputs] : [])),
- datasetId: dataset.id,
- });
- }
- catch (e) {
- console.error(`An error occurred while creating dataset ${finalDatasetName}. ` +
- "You should delete it manually.");
- throw e;
- }
- }
- parseTokenOrUrl(urlOrToken, apiUrl, numParts = 2, kind = "dataset") {
- // Try parsing as UUID
- try {
- assertUuid(urlOrToken); // Will throw if it's not a UUID.
- return [apiUrl, urlOrToken];
- }
- catch (_) {
- // no-op if it's not a uuid
- }
- // Parse as URL
- try {
- const parsedUrl = new URL(urlOrToken);
- const pathParts = parsedUrl.pathname
- .split("/")
- .filter((part) => part !== "");
- if (pathParts.length >= numParts) {
- const tokenUuid = pathParts[pathParts.length - numParts];
- return [apiUrl, tokenUuid];
- }
- else {
- throw new Error(`Invalid public ${kind} URL: ${urlOrToken}`);
- }
- }
- catch (error) {
- throw new Error(`Invalid public ${kind} URL or token: ${urlOrToken}`);
- }
- }
- /**
- * Awaits all pending trace batches. Useful for environments where
- * you need to be sure that all tracing requests finish before execution ends,
- * such as serverless environments.
- *
- * @example
- * ```
- * import { Client } from "langsmith";
- *
- * const client = new Client();
- *
- * try {
- * // Tracing happens here
- * ...
- * } finally {
- * await client.awaitPendingTraceBatches();
- * }
- * ```
- *
- * @returns A promise that resolves once all currently pending traces have sent.
- */
- awaitPendingTraceBatches() {
- if (this.manualFlushMode) {
- console.warn("[WARNING]: When tracing in manual flush mode, you must call `await client.flush()` manually to submit trace batches.");
- return Promise.resolve();
- }
- return Promise.all([
- ...this.autoBatchQueue.items.map(({ itemPromise }) => itemPromise),
- this.batchIngestCaller.queue.onIdle(),
- ]);
- }
- }
- function isExampleCreate(input) {
- return "dataset_id" in input || "dataset_name" in input;
- }
|