diff --git a/.yarn/cache/langsmith-npm-0.1.1-d4fe34055c-e0283c242e.zip b/.yarn/cache/langsmith-npm-0.1.1-d4fe34055c-e0283c242e.zip deleted file mode 100644 index 014e3cc4f791..000000000000 Binary files a/.yarn/cache/langsmith-npm-0.1.1-d4fe34055c-e0283c242e.zip and /dev/null differ diff --git a/.yarn/cache/langsmith-npm-0.1.30-2856e6f341-61f4f645b0.zip b/.yarn/cache/langsmith-npm-0.1.30-2856e6f341-61f4f645b0.zip new file mode 100644 index 000000000000..29f9696b6f1e Binary files /dev/null and b/.yarn/cache/langsmith-npm-0.1.30-2856e6f341-61f4f645b0.zip differ diff --git a/.yarn/cache/langsmith-npm-0.1.7-df7b9ba814-e8787b21c1.zip b/.yarn/cache/langsmith-npm-0.1.7-df7b9ba814-e8787b21c1.zip deleted file mode 100644 index 4fa3f71bd91e..000000000000 Binary files a/.yarn/cache/langsmith-npm-0.1.7-df7b9ba814-e8787b21c1.zip and /dev/null differ diff --git a/examples/package.json b/examples/package.json index c95c98fa8c93..860521be1817 100644 --- a/examples/package.json +++ b/examples/package.json @@ -85,7 +85,7 @@ "ioredis": "^5.3.2", "js-yaml": "^4.1.0", "langchain": "workspace:*", - "langsmith": "^0.1.1", + "langsmith": "^0.1.30", "ml-distance": "^4.0.0", "mongodb": "^6.3.0", "pg": "^8.11.0", diff --git a/langchain-core/langchain.config.js b/langchain-core/langchain.config.js index e2b25aa4a49c..cc1ba4060870 100644 --- a/langchain-core/langchain.config.js +++ b/langchain-core/langchain.config.js @@ -10,7 +10,7 @@ function abs(relativePath) { } export const config = { - internals: [/node\:/, /js-tiktoken/], + internals: [/node\:/, /js-tiktoken/, /langsmith/], entrypoints: { agents: "agents", caches: "caches", diff --git a/langchain-core/package.json b/langchain-core/package.json index 2e17f0f46fb4..e285b65521c8 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -45,7 +45,7 @@ "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", - "langsmith": "~0.1.7", + "langsmith": "~0.1.30", "ml-distance": "^4.0.0", "mustache": "^4.2.0", "p-queue": "^6.6.2", diff --git a/langchain-core/src/callbacks/manager.ts b/langchain-core/src/callbacks/manager.ts index 632904efbd06..9234b75e9ff6 100644 --- a/langchain-core/src/callbacks/manager.ts +++ b/langchain-core/src/callbacks/manager.ts @@ -551,7 +551,7 @@ export class CallbackManager name = "callback_manager"; - public readonly _parentRunId?: string; + public _parentRunId?: string; constructor( parentRunId?: string, @@ -1010,7 +1010,13 @@ export class CallbackManager ) ) { if (tracingV2Enabled) { - callbackManager.addHandler(await getTracingV2CallbackHandler(), true); + const tracerV2 = await getTracingV2CallbackHandler(); + callbackManager.addHandler(tracerV2, true); + + // handoff between langchain and langsmith/traceable + // override the parent run ID + callbackManager._parentRunId = + tracerV2.getTraceableRunTree()?.id ?? callbackManager._parentRunId; } } } diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index 4d1973115eff..ea4c481253f5 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -2,6 +2,10 @@ import { z } from "zod"; import pRetry from "p-retry"; import { v4 as uuidv4 } from "uuid"; +import { + type TraceableFunction, + isTraceableFunction, +} from "langsmith/singletons/traceable"; import type { RunnableInterface, RunnableBatchOptions } from "./types.js"; import { CallbackManager, @@ -48,6 +52,7 @@ import { consumeAsyncIterableInContext, consumeIteratorInContext, isAsyncIterable, + isIterableIterator, isIterator, } from "./iter.js"; @@ -2062,6 +2067,91 @@ export class RunnableMap< } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyTraceableFunction = TraceableFunction<(...any: any[]) => any>; + +/** + * A runnable that wraps a traced LangSmith function. + */ +export class RunnableTraceable extends Runnable< + RunInput, + RunOutput +> { + lc_serializable = false; + + lc_namespace = ["langchain_core", "runnables"]; + + protected func: AnyTraceableFunction; + + constructor(fields: { func: AnyTraceableFunction }) { + super(fields); + + if (!isTraceableFunction(fields.func)) { + throw new Error( + "RunnableTraceable requires a function that is wrapped in traceable higher-order function" + ); + } + + this.func = fields.func; + } + + async invoke(input: RunInput, options?: Partial) { + const [config] = this._getOptionsList(options ?? {}, 1); + const callbacks = await getCallbackManagerForConfig(config); + + return (await this.func( + patchConfig(config, { callbacks }), + input + )) as RunOutput; + } + + async *_streamIterator( + input: RunInput, + options?: Partial + ): AsyncGenerator { + const result = await this.invoke(input, options); + + if (isAsyncIterable(result)) { + for await (const item of result) { + yield item as RunOutput; + } + return; + } + + if (isIterator(result)) { + while (true) { + const state: IteratorResult = result.next(); + if (state.done) break; + yield state.value as RunOutput; + } + return; + } + + yield result; + } + + static from(func: AnyTraceableFunction) { + return new RunnableTraceable({ func }); + } +} + +function assertNonTraceableFunction( + func: + | RunnableFunc> + | TraceableFunction< + RunnableFunc> + > +): asserts func is RunnableFunc< + RunInput, + RunOutput | Runnable +> { + if (isTraceableFunction(func)) { + throw new Error( + "RunnableLambda requires a function that is not wrapped in traceable higher-order function. This shouldn't happen." + ); + } +} + /** * A runnable that runs a callable. */ @@ -2081,14 +2171,42 @@ export class RunnableLambda extends Runnable< >; constructor(fields: { - func: RunnableFunc>; + func: + | RunnableFunc> + | TraceableFunction< + RunnableFunc> + >; }) { + if (isTraceableFunction(fields.func)) { + // eslint-disable-next-line no-constructor-return + return RunnableTraceable.from(fields.func) as unknown as RunnableLambda< + RunInput, + RunOutput + >; + } + super(fields); + + assertNonTraceableFunction(fields.func); this.func = fields.func; } static from( func: RunnableFunc> + ): RunnableLambda; + + static from( + func: TraceableFunction< + RunnableFunc> + > + ): RunnableLambda; + + static from( + func: + | RunnableFunc> + | TraceableFunction< + RunnableFunc> + > ): RunnableLambda { return new RunnableLambda({ func, @@ -2141,7 +2259,7 @@ export class RunnableLambda extends Runnable< } } output = finalOutput as typeof output; - } else if (isIterator(output)) { + } else if (isIterableIterator(output)) { let finalOutput: RunOutput | undefined; for (const chunk of consumeIteratorInContext( childConfig, @@ -2233,7 +2351,7 @@ export class RunnableLambda extends Runnable< for await (const chunk of consumeAsyncIterableInContext(config, output)) { yield chunk as RunOutput; } - } else if (isIterator(output)) { + } else if (isIterableIterator(output)) { for (const chunk of consumeIteratorInContext(config, output)) { yield chunk as RunOutput; } diff --git a/langchain-core/src/runnables/iter.ts b/langchain-core/src/runnables/iter.ts index 156fc999ece6..33df40c5991f 100644 --- a/langchain-core/src/runnables/iter.ts +++ b/langchain-core/src/runnables/iter.ts @@ -1,7 +1,9 @@ import { AsyncLocalStorageProviderSingleton } from "../singletons/index.js"; import { RunnableConfig } from "./config.js"; -export function isIterator(thing: unknown): thing is IterableIterator { +export function isIterableIterator( + thing: unknown +): thing is IterableIterator { return ( typeof thing === "object" && thing !== null && @@ -11,6 +13,12 @@ export function isIterator(thing: unknown): thing is IterableIterator { ); } +export const isIterator = (x: unknown): x is Iterator => + x != null && + typeof x === "object" && + "next" in x && + typeof x.next === "function"; + export function isAsyncIterable( thing: unknown ): thing is AsyncIterable { diff --git a/langchain-core/src/tracers/tracer_langchain.ts b/langchain-core/src/tracers/tracer_langchain.ts index 937cacd606a5..a85ddf29a523 100644 --- a/langchain-core/src/tracers/tracer_langchain.ts +++ b/langchain-core/src/tracers/tracer_langchain.ts @@ -1,4 +1,7 @@ import { Client } from "langsmith"; +import { RunTree } from "langsmith/run_trees"; +import { getCurrentRunTree } from "langsmith/singletons/traceable"; + import { BaseRun, RunCreate, @@ -57,6 +60,40 @@ export class LangChainTracer getEnvironmentVariable("LANGCHAIN_SESSION"); this.exampleId = exampleId; this.client = client ?? new Client({}); + + // if we're inside traceable, we can obtain the traceable tree + // and populate the run map, which is used to correctly + // infer dotted order and execution order + const traceableTree = this.getTraceableRunTree(); + if (traceableTree) { + let rootRun: RunTree = traceableTree; + const visited = new Set(); + while (rootRun.parent_run) { + if (visited.has(rootRun.id)) break; + visited.add(rootRun.id); + + if (!rootRun.parent_run) break; + rootRun = rootRun.parent_run as RunTree; + } + visited.clear(); + + const queue = [rootRun]; + while (queue.length > 0) { + const current = queue.shift(); + if (!current || visited.has(current.id)) continue; + visited.add(current.id); + + // @ts-expect-error Types of property 'events' are incompatible. + this.runMap.set(current.id, current); + if (current.child_runs) { + queue.push(...current.child_runs); + } + } + + this.client = traceableTree.client ?? this.client; + this.projectName = traceableTree.project_name ?? this.projectName; + this.exampleId = traceableTree.reference_example_id ?? this.exampleId; + } } private async _convertToCreate( @@ -102,4 +139,12 @@ export class LangChainTracer getRun(id: string): Run | undefined { return this.runMap.get(id); } + + getTraceableRunTree(): RunTree | undefined { + try { + return getCurrentRunTree(); + } catch { + return undefined; + } + } } diff --git a/langchain/package.json b/langchain/package.json index 3f2ea5ab7cda..d9f0eb5ebc5b 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -888,7 +888,7 @@ "js-yaml": "^4.1.0", "jsonpointer": "^5.0.1", "langchainhub": "~0.0.8", - "langsmith": "~0.1.7", + "langsmith": "~0.1.30", "ml-distance": "^4.0.0", "openapi-types": "^12.1.3", "p-retry": "4", diff --git a/langchain/src/smith/runner_utils.ts b/langchain/src/smith/runner_utils.ts index 7d7fbe9a7c0a..753deb4929d2 100644 --- a/langchain/src/smith/runner_utils.ts +++ b/langchain/src/smith/runner_utils.ts @@ -25,7 +25,7 @@ import { } from "langsmith"; import { EvaluationResult, RunEvaluator } from "langsmith/evaluation"; import { DataType } from "langsmith/schemas"; -import type { TraceableFunction } from "langsmith/traceable"; +import type { TraceableFunction } from "langsmith/singletons/traceable"; import { LLMStringEvaluator } from "../evaluation/base.js"; import { loadEvaluator } from "../evaluation/loader.js"; import { EvaluatorType } from "../evaluation/types.js"; @@ -165,7 +165,7 @@ class CallbackManagerRunTree extends RunTree { this.callbackManager = callbackManager; } - async createChild(config: RunTreeConfig): Promise { + createChild(config: RunTreeConfig): CallbackManagerRunTree { const child = new CallbackManagerRunTree( { ...config, diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 20ae3d2276c8..18b062880438 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -47,7 +47,7 @@ "flat": "^5.0.2", "js-yaml": "^4.1.0", "langchain": "0.2.3", - "langsmith": "~0.1.1", + "langsmith": "~0.1.30", "uuid": "^9.0.0", "zod": "^3.22.3", "zod-to-json-schema": "^3.22.5" diff --git a/yarn.lock b/yarn.lock index b79551d353b1..0dc77072b126 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9196,7 +9196,7 @@ __metadata: jsdom: ^22.1.0 jsonwebtoken: ^9.0.2 langchain: 0.2.3 - langsmith: ~0.1.1 + langsmith: ~0.1.30 llmonitor: ^0.5.9 lodash: ^4.17.21 lunary: ^0.6.11 @@ -9620,7 +9620,7 @@ __metadata: jest: ^29.5.0 jest-environment-node: ^29.6.4 js-tiktoken: ^1.0.12 - langsmith: ~0.1.7 + langsmith: ~0.1.30 ml-distance: ^4.0.0 ml-matrix: ^6.10.4 mustache: ^4.2.0 @@ -22558,7 +22558,7 @@ __metadata: ioredis: ^5.3.2 js-yaml: ^4.1.0 langchain: "workspace:*" - langsmith: ^0.1.1 + langsmith: ^0.1.30 ml-distance: ^4.0.0 mongodb: ^6.3.0 pg: ^8.11.0 @@ -27500,7 +27500,7 @@ __metadata: jsdom: ^22.1.0 jsonpointer: ^5.0.1 langchainhub: ~0.0.8 - langsmith: ~0.1.7 + langsmith: ~0.1.30 mammoth: ^1.5.1 ml-distance: ^4.0.0 mongodb: ^5.2.0 @@ -27712,33 +27712,27 @@ __metadata: languageName: unknown linkType: soft -"langsmith@npm:^0.1.1, langsmith@npm:~0.1.1": - version: 0.1.1 - resolution: "langsmith@npm:0.1.1" +"langsmith@npm:^0.1.30, langsmith@npm:~0.1.30": + version: 0.1.30 + resolution: "langsmith@npm:0.1.30" dependencies: "@types/uuid": ^9.0.1 commander: ^10.0.1 p-queue: ^6.6.2 p-retry: 4 uuid: ^9.0.0 - bin: - langsmith: dist/cli/main.cjs - checksum: e0283c242edcf25822fe1a1d33ffb80301f5575e60153879972c5dbdaba3873ef460f874e7186be0908edc83b80f6167236415dd1d4cf21f5e5bbb30383699aa - languageName: node - linkType: hard - -"langsmith@npm:~0.1.7": - version: 0.1.7 - resolution: "langsmith@npm:0.1.7" - dependencies: - "@types/uuid": ^9.0.1 - commander: ^10.0.1 - p-queue: ^6.6.2 - p-retry: 4 - uuid: ^9.0.0 - bin: - langsmith: dist/cli/main.cjs - checksum: e8787b21c1ab80c1542b0cca9be99d300124b4e5d665e05f0caf3bc3616392a6fd46ca62466e6ede54062d9273123d0f5f4c40296c11b869a399dca592647b5e + peerDependencies: + "@langchain/core": "*" + langchain: "*" + openai: "*" + peerDependenciesMeta: + "@langchain/core": + optional: true + langchain: + optional: true + openai: + optional: true + checksum: 61f4f645b0d95bf0ddec4a275a2fd6859a650569c1ca0d092b318dcabb96fc72d9ae45f35c20d53c2ff6c2d615a2a99f27bb5a974c44ae57c4f359783c26ee99 languageName: node linkType: hard