Skip to content

Commit 4e53005

Browse files
authored
fix(core): Always inherit parent run id onto callback manager from context (#8849)
1 parent 7b1cdb1 commit 4e53005

File tree

4 files changed

+179
-6
lines changed

4 files changed

+179
-6
lines changed

.changeset/forty-laws-cover.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(core): Always inherit parent run id onto callback manager from context

langchain-core/src/callbacks/manager.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,14 +1251,15 @@ export class CallbackManager
12511251
if (tracingV2Enabled) {
12521252
const tracerV2 = new LangChainTracer();
12531253
callbackManager.addHandler(tracerV2, true);
1254-
1255-
// handoff between langchain and langsmith/traceable
1256-
// override the parent run ID
1257-
callbackManager._parentRunId =
1258-
LangChainTracer.getTraceableRunTree()?.id ??
1259-
callbackManager._parentRunId;
12601254
}
12611255
}
1256+
if (tracingV2Enabled) {
1257+
// handoff between langchain and langsmith/traceable
1258+
// override the parent run ID
1259+
callbackManager._parentRunId =
1260+
LangChainTracer.getTraceableRunTree()?.id ??
1261+
callbackManager._parentRunId;
1262+
}
12621263
}
12631264

12641265
for (const {

langchain-core/src/tracers/tests/langsmith_interop.int.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,25 @@ test("runnable nested within a traceable with manual tracer passed", async () =>
5353

5454
await awaitAllCallbacks();
5555
});
56+
57+
test("runnable nested within a traceable with manual tracer passed", async () => {
58+
const child = RunnableLambda.from(async () => {
59+
return [new HumanMessage({ content: "From child!" })];
60+
}).withConfig({ runName: "child" });
61+
62+
const parent = traceable(
63+
async () => {
64+
return child.invoke(
65+
{},
66+
{
67+
callbacks: [new LangChainTracer()],
68+
}
69+
);
70+
},
71+
{ name: "parent", tracingEnabled: true }
72+
);
73+
74+
await parent();
75+
76+
await awaitAllCallbacks();
77+
});

langchain-core/src/tracers/tests/langsmith_interop.test.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,151 @@ test.each(["true", "false"])(
968968
}
969969
);
970970

971+
test.each(["true", "false"])(
972+
"runnable nested within a traceable with manual tracer passed, background callbacks: %s",
973+
async (value) => {
974+
process.env.LANGCHAIN_CALLBACKS_BACKGROUND = value;
975+
976+
const child = RunnableLambda.from(async () => {
977+
return { message: new HumanMessage({ content: "From child!" }) };
978+
}).withConfig({ runName: "child" });
979+
980+
const parent = traceable(
981+
async () => {
982+
return child.invoke(
983+
{},
984+
{
985+
callbacks: [new LangChainTracer()],
986+
}
987+
);
988+
},
989+
{ name: "parent", tracingEnabled: true, client }
990+
);
991+
992+
await parent();
993+
994+
await awaitAllCallbacks();
995+
await client.awaitPendingTraceBatches();
996+
997+
const relevantCalls = fetchMock.mock.calls.filter((call: any) => {
998+
return call[0].startsWith("https://api.smith.langchain.com/runs");
999+
});
1000+
1001+
expect(relevantCalls.length).toEqual(4);
1002+
const firstCallParams = JSON.parse(
1003+
decoder.decode((relevantCalls[0][1] as any).body)
1004+
);
1005+
const secondCallParams = JSON.parse(
1006+
decoder.decode((relevantCalls[1][1] as any).body)
1007+
);
1008+
const thirdCallParams = JSON.parse(
1009+
decoder.decode((relevantCalls[2][1] as any).body)
1010+
);
1011+
const fourthCallParams = JSON.parse(
1012+
decoder.decode((relevantCalls[3][1] as any).body)
1013+
);
1014+
const callParams = [
1015+
firstCallParams,
1016+
secondCallParams,
1017+
thirdCallParams,
1018+
fourthCallParams,
1019+
];
1020+
const parentCallCreateParams = callParams.find((param) => {
1021+
return param.name === "parent" && param.start_time !== undefined;
1022+
});
1023+
const childCallCreateParams = callParams.find((param) => {
1024+
return param.name === "child" && param.start_time !== undefined;
1025+
});
1026+
const parentCallUpdateParams = callParams.find((param) => {
1027+
return (
1028+
param.dotted_order === parentCallCreateParams.dotted_order &&
1029+
param.end_time !== undefined
1030+
);
1031+
});
1032+
const childCallUpdateParams = callParams.find((param) => {
1033+
return (
1034+
param.dotted_order === childCallCreateParams.dotted_order &&
1035+
param.end_time !== undefined
1036+
);
1037+
});
1038+
expect(parentCallCreateParams).toMatchObject({
1039+
id: parentCallCreateParams.id,
1040+
name: "parent",
1041+
start_time: expect.any(String),
1042+
run_type: "chain",
1043+
extra: expect.any(Object),
1044+
serialized: {},
1045+
inputs: {},
1046+
child_runs: [],
1047+
trace_id: parentCallCreateParams.id,
1048+
dotted_order: parentCallCreateParams.dotted_order,
1049+
tags: [],
1050+
});
1051+
1052+
expect(childCallCreateParams).toMatchObject({
1053+
id: expect.any(String),
1054+
name: "child",
1055+
parent_run_id: parentCallCreateParams.id,
1056+
start_time: expect.any(String),
1057+
serialized: {
1058+
lc: 1,
1059+
type: "not_implemented",
1060+
id: ["langchain_core", "runnables", "RunnableLambda"],
1061+
},
1062+
inputs: {},
1063+
run_type: "chain",
1064+
extra: expect.any(Object),
1065+
tags: [],
1066+
trace_id: parentCallCreateParams.id,
1067+
dotted_order: expect.stringContaining(
1068+
`${parentCallCreateParams.dotted_order}.`
1069+
),
1070+
});
1071+
1072+
expect(childCallUpdateParams).toMatchObject({
1073+
end_time: expect.any(Number),
1074+
outputs: {
1075+
message: {
1076+
lc: 1,
1077+
type: "constructor",
1078+
id: ["langchain_core", "messages", "HumanMessage"],
1079+
kwargs: {
1080+
content: "From child!",
1081+
additional_kwargs: {},
1082+
response_metadata: {},
1083+
},
1084+
},
1085+
},
1086+
inputs: {},
1087+
trace_id: parentCallCreateParams.id,
1088+
dotted_order: expect.stringContaining(
1089+
`${parentCallCreateParams.dotted_order}.`
1090+
),
1091+
});
1092+
1093+
expect(parentCallUpdateParams).toMatchObject({
1094+
end_time: expect.any(Number),
1095+
inputs: {},
1096+
outputs: {
1097+
message: {
1098+
lc: 1,
1099+
type: "constructor",
1100+
id: ["langchain_core", "messages", "HumanMessage"],
1101+
kwargs: {
1102+
content: "From child!",
1103+
additional_kwargs: {},
1104+
response_metadata: {},
1105+
},
1106+
},
1107+
},
1108+
extra: expect.any(Object),
1109+
dotted_order: parentCallCreateParams.dotted_order,
1110+
trace_id: parentCallCreateParams.id,
1111+
tags: [],
1112+
});
1113+
}
1114+
);
1115+
9711116
test("LangChain V2 tracer creates and updates runs with replicas", async () => {
9721117
const projectNames = ["replica1", "replica2"];
9731118
const referenceExampleId = "00000000-0000-0000-0000-000000000000";

0 commit comments

Comments
 (0)