Skip to content

Commit bd2c46e

Browse files
fix(@langchain/core): update and bundle dependencies (#9534)
1 parent 138e7fb commit bd2c46e

File tree

11 files changed

+4882
-6445
lines changed

11 files changed

+4882
-6445
lines changed

.changeset/eight-mangos-visit.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@langchain/core": patch
3+
---
4+
5+
fix(@langchain/core): update and bundle `p-retry`, `ansi-styles`, `camelcase` and `decamelize` dependencies

environment_tests/scripts/test-runner.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,14 @@ class EnvironmentTestRunner {
8282
* Copy a directory from src to dest, excluding files that match the excludePatterns
8383
* @param src - The source directory
8484
* @param dest - The destination directory
85-
* @param excludePatterns - An array of file names to exclude from the copy
85+
* @param excludePatterns - An array of file names to exclude from the copy (only at the top level)
86+
* @param isTopLevel - Whether this is the top level call (used to only apply excludePatterns at root)
8687
*/
8788
private async copyDirectory(
8889
src: string,
8990
dest: string,
90-
excludePatterns: string[] = []
91+
excludePatterns: string[] = [],
92+
isTopLevel: boolean = true
9193
): Promise<void> {
9294
await fs.mkdir(dest, { recursive: true });
9395

@@ -98,13 +100,17 @@ class EnvironmentTestRunner {
98100
const srcPath = path.join(src, entry.name);
99101
const destPath = path.join(dest, entry.name);
100102

101-
// Skip excluded patterns
102-
if (excludePatterns.some((pattern) => entry.name === pattern)) {
103+
// Skip excluded patterns only at the top level
104+
// This allows dist/node_modules (bundled dependencies) to be copied
105+
if (
106+
isTopLevel &&
107+
excludePatterns.some((pattern) => entry.name === pattern)
108+
) {
103109
return;
104110
}
105111

106112
if (entry.isDirectory()) {
107-
await this.copyDirectory(srcPath, destPath, excludePatterns);
113+
await this.copyDirectory(srcPath, destPath, excludePatterns, false);
108114
} else {
109115
await fs.copyFile(srcPath, destPath);
110116
}
@@ -227,12 +233,10 @@ class EnvironmentTestRunner {
227233
return;
228234
}
229235

230-
let destDirName: string;
231-
if (pkg.name === "langchain") {
232-
destDirName = "langchain";
233-
} else {
234-
destDirName = pkg.name.replace("@langchain/", "langchain-");
235-
}
236+
const destDirName: string =
237+
pkg.name === "langchain"
238+
? "langchain"
239+
: pkg.name.replace("@langchain/", "langchain-");
236240
const destDir = path.join(libsDir, destDirName);
237241
await fs.mkdir(destDir, { recursive: true });
238242

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
import { langchainConfig, type ConfigArray } from "@langchain/eslint";
22

3+
/**
4+
* Packages bundled via `noExternal` in tsdown.config.ts.
5+
* These packages are ESM-only and bundled into the output for CJS compatibility.
6+
* They only need to be devDependencies, not runtime dependencies.
7+
* Listed here as core-modules so import/no-extraneous-dependencies ignores them.
8+
*/
9+
const bundledPackages = ["p-retry", "ansi-styles", "camelcase", "decamelize"];
10+
311
const config: ConfigArray = [
412
...langchainConfig,
513
{
614
ignores: ["src/utils/zod-to-json-schema/**"],
715
},
16+
{
17+
// Treat bundled packages as core modules so they don't need to be in dependencies
18+
settings: {
19+
"import/core-modules": bundledPackages,
20+
},
21+
},
822
];
923
export default config;

libs/langchain-core/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,10 @@
3131
"license": "MIT",
3232
"dependencies": {
3333
"@cfworker/json-schema": "^4.0.2",
34-
"ansi-styles": "^5.0.0",
35-
"camelcase": "6",
36-
"decamelize": "1.2.0",
3734
"js-tiktoken": "^1.0.12",
3835
"langsmith": "^0.3.64",
3936
"mustache": "^4.2.0",
40-
"p-queue": "^6.6.2",
41-
"p-retry": "^7.0.0",
37+
"p-queue": "^9.0.1",
4238
"uuid": "^10.0.0",
4339
"zod": "^3.25.76 || ^4"
4440
},
@@ -47,10 +43,14 @@
4743
"@types/decamelize": "^1.2.0",
4844
"@types/mustache": "^4",
4945
"@types/uuid": "^10.0.0",
46+
"ansi-styles": "^6.2.3",
47+
"camelcase": "^9.0.0",
48+
"decamelize": "^6.0.1",
5049
"dotenv": "^17.2.1",
5150
"dpdm": "^3.14.0",
5251
"eslint": "^9.34.0",
5352
"ml-matrix": "^6.10.4",
53+
"p-retry": "^7.0.0",
5454
"prettier": "^2.8.3",
5555
"rimraf": "^5.0.1",
5656
"typescript": "~5.8.3",
@@ -843,4 +843,4 @@
843843
"outputs.js"
844844
],
845845
"module": "./dist/index.js"
846-
}
846+
}

libs/langchain-core/src/callbacks/tests/callbacks.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ test("CallbackManager Chat Message Handling", async () => {
242242
await llmCb.handleLLMEnd({ generations: [] });
243243
})
244244
);
245+
246+
// In case background mode is on while running this test
247+
await awaitAllCallbacks();
248+
245249
// Everything treated as llm in handler 1
246250
expect(handler1.llmStarts).toBe(1);
247251
expect(handler2.llmStarts).toBe(0);

libs/langchain-core/src/singletons/callbacks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import PQueueMod from "p-queue";
44
import { getGlobalAsyncLocalStorageInstance } from "./async_local_storage/globals.js";
55
import { getDefaultLangChainClientSingleton } from "./tracer.js";
66

7-
let queue: typeof import("p-queue")["default"]["prototype"];
7+
let queue: (typeof PQueueMod)["prototype"];
88

99
/**
1010
* Creates a queue using the p-queue library. The queue is configured to

libs/langchain-core/src/tracers/tests/__snapshots__/langchain_tracer.test.ts.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,15 @@ exports[`LangChainTracer payload snapshots for run create and update 3`] = `
109109
"inputs": {
110110
"input": "test input",
111111
},
112+
"name": "RunnableLambda",
112113
"outputs": {
113114
"output": "processed: test input",
114115
},
115116
"parent_run_id": Any<String>,
116117
"reference_example_id": undefined,
118+
"run_type": "chain",
117119
"session_name": Any<String>,
120+
"start_time": Any<String>,
118121
"tags": [],
119122
"trace_id": Any<String>,
120123
}
@@ -141,12 +144,15 @@ exports[`LangChainTracer payload snapshots for run create and update 4`] = `
141144
"inputs": {
142145
"input": "test input",
143146
},
147+
"name": "RunnableLambda",
144148
"outputs": {
145149
"output": "parent: processed: test input",
146150
},
147151
"parent_run_id": undefined,
148152
"reference_example_id": undefined,
153+
"run_type": "chain",
149154
"session_name": Any<String>,
155+
"start_time": Any<String>,
150156
"tags": [],
151157
"trace_id": Any<String>,
152158
}

libs/langchain-core/src/tracers/tests/langchain_tracer.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ test("LangChainTracer payload snapshots for run create and update", async () =>
7373
expect(updatePayloads[0]).toMatchSnapshot({
7474
session_name: expect.any(String),
7575
dotted_order: expect.any(String),
76+
start_time: expect.any(String),
7677
end_time: expect.any(Number),
7778
events: expect.arrayContaining([
7879
expect.objectContaining({
@@ -86,6 +87,7 @@ test("LangChainTracer payload snapshots for run create and update", async () =>
8687
expect(updatePayloads[1]).toMatchSnapshot({
8788
session_name: expect.any(String),
8889
dotted_order: expect.any(String),
90+
start_time: expect.any(String),
8991
end_time: expect.any(Number),
9092
events: expect.arrayContaining([
9193
expect.objectContaining({

libs/langchain-core/src/utils/async_caller.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,8 @@
11
import PQueueMod from "p-queue";
2+
import pRetry from "p-retry";
23

34
import { getAbortSignalError } from "./signal.js";
45

5-
// p-retry is ESM-only, so we use dynamic import for CJS compatibility.
6-
// The module is cached after first import, so subsequent calls are essentially free.
7-
// This approach is recommended by the p-retry author for async contexts:
8-
// https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
9-
let pRetryModule: typeof import("p-retry") | null = null;
10-
11-
async function getPRetry() {
12-
if (!pRetryModule) {
13-
pRetryModule = await import("p-retry");
14-
}
15-
return pRetryModule.default;
16-
}
17-
186
const STATUS_NO_RETRY = [
197
400, // Bad Request
208
401, // Unauthorized
@@ -119,7 +107,6 @@ export class AsyncCaller {
119107
callable: T,
120108
...args: Parameters<T>
121109
): Promise<Awaited<ReturnType<T>>> {
122-
const pRetry = await getPRetry();
123110
return this.queue.add(
124111
() =>
125112
pRetry(

libs/langchain-core/tsdown.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import {
77
} from "@langchain/build";
88

99
export default getBuildConfig({
10+
/**
11+
* bundling these packages into the output for CJS compatibility
12+
* they only need to be devDependencies, not runtime dependencies
13+
*/
14+
noExternal: [`ansi-styles`, `camelcase`, `decamelize`, `p-retry`],
1015
entry: [
1116
"./src/index.ts",
1217
"./src/agents.ts",

0 commit comments

Comments
 (0)