Skip to content

Commit f212784

Browse files
authored
chore(core/v1): patch changes (#9172)
1 parent be5dfc6 commit f212784

File tree

10 files changed

+241
-20
lines changed

10 files changed

+241
-20
lines changed

libs/langchain-core/CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# @langchain/core
2+
3+
## 0.3.78
4+
5+
### Patch Changes
6+
7+
- 1519a97: update chunk concat logic to match on missing ID fields
8+
- 079e11d: omit tool call chunks without tool call id
9+
10+
## 0.3.76
11+
12+
### Patch Changes
13+
14+
- 41bd944: support base64 embeddings format
15+
- e90bc0a: fix(core): prevent tool call chunks from merging incorrectly in AIMes…
16+
- 3a99a40: Fix deserialization of RemoveMessage if represented as a plain object
17+
- 58e9522: make mustache prompt with nested object working correctly
18+
- e44dc1b: handle backticks in structured output
19+
20+
## 0.3.75
21+
22+
### Patch Changes
23+
24+
- d6d841f: fix(core): Fix deep nesting of runnables within traceables
25+
26+
## 0.3.74
27+
28+
### Patch Changes
29+
30+
- 4e53005: fix(core): Always inherit parent run id onto callback manager from context
31+
32+
## 0.3.73
33+
34+
### Patch Changes
35+
36+
- a5a2e10: add root export to satisfy bundler requirements

libs/langchain-core/src/messages/tool.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface ToolMessageFields<
2424
artifact?: any;
2525
tool_call_id: string;
2626
status?: "success" | "error";
27+
metadata?: Record<string, unknown>;
2728
}
2829

2930
/**
@@ -74,6 +75,8 @@ export class ToolMessage<TStructure extends MessageStructure = MessageStructure>
7475

7576
tool_call_id: string;
7677

78+
metadata?: Record<string, unknown>;
79+
7780
/**
7881
* Artifact of the Tool execution which is not meant to be sent to the model.
7982
*
@@ -107,6 +110,7 @@ export class ToolMessage<TStructure extends MessageStructure = MessageStructure>
107110
this.tool_call_id = toolMessageFields.tool_call_id;
108111
this.artifact = toolMessageFields.artifact;
109112
this.status = toolMessageFields.status;
113+
this.metadata = toolMessageFields.metadata;
110114
}
111115

112116
static isInstance(message: unknown): message is ToolMessage {

libs/langchain-core/src/output_parsers/json.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ export class JsonOutputParser<
1818

1919
lc_serializable = true;
2020

21+
/** @internal */
22+
override _concatOutputChunks<T>(first: T, second: T): T {
23+
if (this.diff) {
24+
return super._concatOutputChunks(first, second);
25+
}
26+
return second;
27+
}
28+
2129
protected _diff(
2230
prev: unknown | undefined,
2331
next: unknown

libs/langchain-core/src/output_parsers/tests/json.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,15 +336,51 @@ test("JSONOutputParser parses streamed JSON", async () => {
336336
);
337337
});
338338

339+
test("JSONOutputParser traces streamed JSON as just the last chunk", async () => {
340+
async function* generator() {
341+
for (const token of STREAMED_TOKENS) {
342+
yield token;
343+
}
344+
}
345+
const parser = new JsonOutputParser();
346+
let tracedOutput;
347+
const result = await acc(
348+
parser.transform(generator(), {
349+
callbacks: [
350+
{
351+
handleChainEnd(outputs) {
352+
tracedOutput = outputs;
353+
},
354+
},
355+
],
356+
})
357+
);
358+
expect(result.at(-1)).toEqual(tracedOutput);
359+
});
360+
339361
test("JSONOutputParser parses streamed JSON diff", async () => {
340362
async function* generator() {
341363
for (const token of STREAMED_TOKENS) {
342364
yield token;
343365
}
344366
}
345367
const parser = new JsonOutputParser({ diff: true });
346-
const result = await acc(parser.transform(generator(), {}));
368+
let tracedOutput;
369+
const result = await acc(
370+
parser.transform(generator(), {
371+
callbacks: [
372+
{
373+
handleChainEnd(outputs) {
374+
tracedOutput = outputs;
375+
},
376+
},
377+
],
378+
})
379+
);
347380
expect(result).toEqual(EXPECTED_STREAMED_JSON_DIFF);
381+
expect(tracedOutput).toEqual(
382+
EXPECTED_STREAMED_JSON_DIFF.map((patch) => patch[0])
383+
);
348384
});
349385

350386
test("JsonOutputParser supports a type param", async () => {

libs/langchain-core/src/prompts/chat.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -641,11 +641,13 @@ class _StringImageMessagePromptTemplate<
641641
// eslint-disable-next-line @typescript-eslint/no-explicit-any
642642
additionalContentFields = prompt.additionalContentFields as any;
643643
}
644-
content.push({
645-
...additionalContentFields,
646-
type: "text",
647-
text: formatted,
648-
});
644+
if (formatted !== "") {
645+
content.push({
646+
...additionalContentFields,
647+
type: "text",
648+
text: formatted,
649+
});
650+
}
649651
/** @TODO replace this */
650652
// eslint-disable-next-line no-instanceof/no-instanceof
651653
} else if (prompt instanceof ImagePromptTemplate) {

libs/langchain-core/src/runnables/base.ts

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,11 @@ export abstract class Runnable<
456456
return outputs;
457457
}
458458

459+
/** @internal */
460+
_concatOutputChunks<O>(first: O, second: O): O {
461+
return concat(first, second);
462+
}
463+
459464
/**
460465
* Helper method to transform an Iterator of Input values into an Iterator of
461466
* Output values, with callbacks.
@@ -480,15 +485,19 @@ export abstract class Runnable<
480485

481486
const config = ensureConfig(options);
482487
const callbackManager_ = await getCallbackManagerForConfig(config);
488+
const outerThis = this;
483489
async function* wrapInputForTracing() {
484490
for await (const chunk of inputGenerator) {
485491
if (finalInputSupported) {
486492
if (finalInput === undefined) {
487493
finalInput = chunk;
488494
} else {
489495
try {
490-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
491-
finalInput = concat(finalInput, chunk as any);
496+
finalInput = outerThis._concatOutputChunks(
497+
finalInput,
498+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
499+
chunk as any
500+
);
492501
} catch {
493502
finalInput = undefined;
494503
finalInputSupported = false;
@@ -546,8 +555,11 @@ export abstract class Runnable<
546555
finalOutput = chunk;
547556
} else {
548557
try {
549-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
550-
finalOutput = concat(finalOutput, chunk as any);
558+
finalOutput = this._concatOutputChunks(
559+
finalOutput,
560+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
561+
chunk as any
562+
);
551563
} catch {
552564
finalOutput = undefined;
553565
finalOutputSupported = false;
@@ -650,7 +662,7 @@ export abstract class Runnable<
650662
// Make a best effort to gather, for any type that supports concat.
651663
// This method should throw an error if gathering fails.
652664
// eslint-disable-next-line @typescript-eslint/no-explicit-any
653-
finalChunk = concat(finalChunk, chunk as any);
665+
finalChunk = this._concatOutputChunks(finalChunk, chunk as any);
654666
}
655667
}
656668
yield* this._streamIterator(finalChunk, ensureConfig(options));
@@ -1365,6 +1377,11 @@ export class RunnableBinding<
13651377
return this.bound.batch(inputs, mergedOptions, batchOptions);
13661378
}
13671379

1380+
/** @internal */
1381+
override _concatOutputChunks<O>(first: O, second: O): O {
1382+
return this.bound._concatOutputChunks(first, second);
1383+
}
1384+
13681385
async *_streamIterator(
13691386
input: RunInput,
13701387
options?: Partial<CallOptions> | undefined
@@ -1979,6 +1996,11 @@ export class RunnableSequence<
19791996
return nextStepInputs;
19801997
}
19811998

1999+
/** @internal */
2000+
override _concatOutputChunks<O>(first: O, second: O): O {
2001+
return this.last._concatOutputChunks(first, second);
2002+
}
2003+
19822004
async *_streamIterator(
19832005
input: RunInput,
19842006
options?: RunnableConfig
@@ -2029,7 +2051,7 @@ export class RunnableSequence<
20292051
} else {
20302052
try {
20312053
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2032-
finalOutput = concat(finalOutput, chunk as any);
2054+
finalOutput = this._concatOutputChunks(finalOutput, chunk as any);
20332055
} catch {
20342056
finalOutput = undefined;
20352057
concatSupported = false;
@@ -2563,8 +2585,11 @@ export class RunnableLambda<
25632585
} else {
25642586
// Make a best effort to gather, for any type that supports concat.
25652587
try {
2566-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2567-
finalOutput = concat(finalOutput, chunk as any);
2588+
finalOutput = this._concatOutputChunks(
2589+
finalOutput,
2590+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2591+
chunk as any
2592+
);
25682593
} catch {
25692594
finalOutput = chunk as RunOutput;
25702595
}
@@ -2583,8 +2608,11 @@ export class RunnableLambda<
25832608
} else {
25842609
// Make a best effort to gather, for any type that supports concat.
25852610
try {
2586-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2587-
finalOutput = concat(finalOutput, chunk as any);
2611+
finalOutput = this._concatOutputChunks(
2612+
finalOutput,
2613+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2614+
chunk as any
2615+
);
25882616
} catch {
25892617
finalOutput = chunk as RunOutput;
25902618
}
@@ -2621,7 +2649,7 @@ export class RunnableLambda<
26212649
// Make a best effort to gather, for any type that supports concat.
26222650
try {
26232651
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2624-
finalChunk = concat(finalChunk, chunk as any);
2652+
finalChunk = this._concatOutputChunks(finalChunk, chunk as any);
26252653
} catch {
26262654
finalChunk = chunk;
26272655
}
@@ -2928,7 +2956,10 @@ export class RunnableWithFallbacks<RunInput, RunOutput> extends Runnable<
29282956
for await (const chunk of stream) {
29292957
yield chunk;
29302958
try {
2931-
output = output === undefined ? output : concat(output, chunk);
2959+
output =
2960+
output === undefined
2961+
? output
2962+
: this._concatOutputChunks(output, chunk);
29322963
} catch {
29332964
output = undefined;
29342965
}

libs/langchain-core/src/tools/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export abstract class StructuredTool<
139139
fields?.verboseParsingErrors ?? this.verboseParsingErrors;
140140
this.responseFormat = fields?.responseFormat ?? this.responseFormat;
141141
this.defaultConfig = fields?.defaultConfig ?? this.defaultConfig;
142+
this.metadata = fields?.metadata ?? this.metadata;
142143
}
143144

144145
protected abstract _call(
@@ -313,6 +314,7 @@ export abstract class StructuredTool<
313314
artifact,
314315
toolCallId,
315316
name: this.name,
317+
metadata: this.metadata,
316318
});
317319
await runManager?.handleToolEnd(formattedOutput);
318320
return formattedOutput as ToolReturnType<TArg, TConfig, ToolOutputT>;
@@ -732,26 +734,31 @@ function _formatToolOutput<TOutput extends ToolOutputType>(params: {
732734
name: string;
733735
artifact?: unknown;
734736
toolCallId?: string;
737+
metadata?: Record<string, unknown>;
735738
}): ToolMessage | TOutput {
736-
const { content, artifact, toolCallId } = params;
739+
const { content, artifact, toolCallId, metadata } = params;
737740
if (toolCallId && !isDirectToolOutput(content)) {
738741
if (
739742
typeof content === "string" ||
740743
(Array.isArray(content) &&
741744
content.every((item) => typeof item === "object"))
742745
) {
743746
return new ToolMessage({
747+
status: "success",
744748
content,
745749
artifact,
746750
tool_call_id: toolCallId,
747751
name: params.name,
752+
metadata,
748753
});
749754
} else {
750755
return new ToolMessage({
756+
status: "success",
751757
content: _stringify(content),
752758
artifact,
753759
tool_call_id: toolCallId,
754760
name: params.name,
761+
metadata,
755762
});
756763
}
757764
} else {

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import { z as z4 } from "zod/v4";
44

55
import {
66
DynamicStructuredTool,
7+
DynamicTool,
78
StructuredToolParams,
89
ToolInputParsingException,
910
isStructuredToolParams,
1011
tool,
1112
} from "../index.js";
12-
import { ToolMessage } from "../../messages/tool.js";
13+
import { ToolCall, ToolMessage } from "../../messages/tool.js";
1314
import { RunnableConfig } from "../../runnables/types.js";
1415

1516
test("Tool should error if responseFormat is content_and_artifact but the function doesn't return a tuple", async () => {
@@ -447,3 +448,30 @@ describe("isStructuredToolParams", () => {
447448
expect(isStructuredToolParams(nonStructuredToolParams)).toBe(false);
448449
});
449450
});
451+
452+
describe("DynamicTool", () => {
453+
test("will thread metadata through to a resulting ToolMessage", async () => {
454+
const tool = new DynamicTool({
455+
name: "test",
456+
description: "test",
457+
metadata: {
458+
foo: "bar",
459+
},
460+
func: async () => "test",
461+
});
462+
463+
const input: ToolCall = {
464+
id: "test_id",
465+
name: "test",
466+
args: { input: "test" },
467+
type: "tool_call",
468+
};
469+
470+
const result = await tool.invoke(input);
471+
472+
expect(result).toBeInstanceOf(ToolMessage);
473+
expect(result.metadata).toEqual({
474+
foo: "bar",
475+
});
476+
});
477+
});

libs/langchain-core/src/tools/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ export interface ToolParams extends BaseLangChainParams {
8181
* @default false
8282
*/
8383
verboseParsingErrors?: boolean;
84+
/**
85+
* Metadata for the tool.
86+
*/
87+
metadata?: Record<string, unknown>;
8488
}
8589

8690
export type ToolRunnableConfig<

0 commit comments

Comments
 (0)