@langchain/core 0.3.3 → 0.3.5-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -88,6 +88,8 @@ class BaseChatModel extends base_js_1.BaseLanguageModel {
88
88
  };
89
89
  const runManagers = await callbackManager_?.handleChatModelStart(this.toJSON(), [messages], runnableConfig.runId, undefined, extra, undefined, undefined, runnableConfig.runName);
90
90
  let generationChunk;
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ let llmOutput;
91
93
  try {
92
94
  for await (const chunk of this._streamResponseChunks(messages, callOptions, runManagers?.[0])) {
93
95
  if (chunk.message.id == null) {
@@ -106,6 +108,16 @@ class BaseChatModel extends base_js_1.BaseLanguageModel {
106
108
  else {
107
109
  generationChunk = generationChunk.concat(chunk);
108
110
  }
111
+ if ((0, index_js_1.isAIMessageChunk)(chunk.message) &&
112
+ chunk.message.usage_metadata !== undefined) {
113
+ llmOutput = {
114
+ tokenUsage: {
115
+ promptTokens: chunk.message.usage_metadata.input_tokens,
116
+ completionTokens: chunk.message.usage_metadata.output_tokens,
117
+ totalTokens: chunk.message.usage_metadata.total_tokens,
118
+ },
119
+ };
120
+ }
109
121
  }
110
122
  }
111
123
  catch (err) {
@@ -115,6 +127,7 @@ class BaseChatModel extends base_js_1.BaseLanguageModel {
115
127
  await Promise.all((runManagers ?? []).map((runManager) => runManager?.handleLLMEnd({
116
128
  // TODO: Remove cast after figuring out inheritance
117
129
  generations: [[generationChunk]],
130
+ llmOutput,
118
131
  })));
119
132
  }
120
133
  }
@@ -154,6 +167,8 @@ class BaseChatModel extends base_js_1.BaseLanguageModel {
154
167
  try {
155
168
  const stream = await this._streamResponseChunks(baseMessages[0], parsedOptions, runManagers?.[0]);
156
169
  let aggregated;
170
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
+ let llmOutput;
157
172
  for await (const chunk of stream) {
158
173
  if (chunk.message.id == null) {
159
174
  const runId = runManagers?.at(0)?.runId;
@@ -166,6 +181,16 @@ class BaseChatModel extends base_js_1.BaseLanguageModel {
166
181
  else {
167
182
  aggregated = (0, stream_js_1.concat)(aggregated, chunk);
168
183
  }
184
+ if ((0, index_js_1.isAIMessageChunk)(chunk.message) &&
185
+ chunk.message.usage_metadata !== undefined) {
186
+ llmOutput = {
187
+ tokenUsage: {
188
+ promptTokens: chunk.message.usage_metadata.input_tokens,
189
+ completionTokens: chunk.message.usage_metadata.output_tokens,
190
+ totalTokens: chunk.message.usage_metadata.total_tokens,
191
+ },
192
+ };
193
+ }
169
194
  }
170
195
  if (aggregated === undefined) {
171
196
  throw new Error("Received empty response from chat model call.");
@@ -173,7 +198,7 @@ class BaseChatModel extends base_js_1.BaseLanguageModel {
173
198
  generations.push([aggregated]);
174
199
  await runManagers?.[0].handleLLMEnd({
175
200
  generations,
176
- llmOutput: {},
201
+ llmOutput,
177
202
  });
178
203
  }
179
204
  catch (e) {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { type BaseMessage, BaseMessageChunk, type BaseMessageLike } from "../messages/index.js";
2
+ import { type BaseMessage, BaseMessageChunk, type BaseMessageLike, AIMessageChunk } from "../messages/index.js";
3
3
  import type { BasePromptValueInterface } from "../prompt_values.js";
4
4
  import { LLMResult, ChatGenerationChunk, type ChatResult, type Generation } from "../outputs.js";
5
5
  import { BaseLanguageModel, type StructuredOutputMethodOptions, type ToolDefinition, type BaseLanguageModelCallOptions, type BaseLanguageModelInput, type BaseLanguageModelParams } from "./base.js";
@@ -66,7 +66,7 @@ export type BindToolsInput = StructuredToolInterface | Record<string, any> | Too
66
66
  * Base class for chat models. It extends the BaseLanguageModel class and
67
67
  * provides methods for generating chat based on input messages.
68
68
  */
69
- export declare abstract class BaseChatModel<CallOptions extends BaseChatModelCallOptions = BaseChatModelCallOptions, OutputMessageType extends BaseMessageChunk = BaseMessageChunk> extends BaseLanguageModel<OutputMessageType, CallOptions> {
69
+ export declare abstract class BaseChatModel<CallOptions extends BaseChatModelCallOptions = BaseChatModelCallOptions, OutputMessageType extends BaseMessageChunk = AIMessageChunk> extends BaseLanguageModel<OutputMessageType, CallOptions> {
70
70
  ParsedCallOptions: Omit<CallOptions, Exclude<keyof RunnableConfig, "signal" | "timeout" | "maxConcurrency">>;
71
71
  lc_namespace: string[];
72
72
  constructor(fields: BaseChatModelParams);
@@ -1,5 +1,5 @@
1
1
  import { zodToJsonSchema } from "zod-to-json-schema";
2
- import { AIMessage, HumanMessage, coerceMessageLikeToMessage, } from "../messages/index.js";
2
+ import { AIMessage, HumanMessage, coerceMessageLikeToMessage, isAIMessageChunk, } from "../messages/index.js";
3
3
  import { RUN_KEY, } from "../outputs.js";
4
4
  import { BaseLanguageModel, } from "./base.js";
5
5
  import { CallbackManager, } from "../callbacks/manager.js";
@@ -84,6 +84,8 @@ export class BaseChatModel extends BaseLanguageModel {
84
84
  };
85
85
  const runManagers = await callbackManager_?.handleChatModelStart(this.toJSON(), [messages], runnableConfig.runId, undefined, extra, undefined, undefined, runnableConfig.runName);
86
86
  let generationChunk;
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ let llmOutput;
87
89
  try {
88
90
  for await (const chunk of this._streamResponseChunks(messages, callOptions, runManagers?.[0])) {
89
91
  if (chunk.message.id == null) {
@@ -102,6 +104,16 @@ export class BaseChatModel extends BaseLanguageModel {
102
104
  else {
103
105
  generationChunk = generationChunk.concat(chunk);
104
106
  }
107
+ if (isAIMessageChunk(chunk.message) &&
108
+ chunk.message.usage_metadata !== undefined) {
109
+ llmOutput = {
110
+ tokenUsage: {
111
+ promptTokens: chunk.message.usage_metadata.input_tokens,
112
+ completionTokens: chunk.message.usage_metadata.output_tokens,
113
+ totalTokens: chunk.message.usage_metadata.total_tokens,
114
+ },
115
+ };
116
+ }
105
117
  }
106
118
  }
107
119
  catch (err) {
@@ -111,6 +123,7 @@ export class BaseChatModel extends BaseLanguageModel {
111
123
  await Promise.all((runManagers ?? []).map((runManager) => runManager?.handleLLMEnd({
112
124
  // TODO: Remove cast after figuring out inheritance
113
125
  generations: [[generationChunk]],
126
+ llmOutput,
114
127
  })));
115
128
  }
116
129
  }
@@ -150,6 +163,8 @@ export class BaseChatModel extends BaseLanguageModel {
150
163
  try {
151
164
  const stream = await this._streamResponseChunks(baseMessages[0], parsedOptions, runManagers?.[0]);
152
165
  let aggregated;
166
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
167
+ let llmOutput;
153
168
  for await (const chunk of stream) {
154
169
  if (chunk.message.id == null) {
155
170
  const runId = runManagers?.at(0)?.runId;
@@ -162,6 +177,16 @@ export class BaseChatModel extends BaseLanguageModel {
162
177
  else {
163
178
  aggregated = concat(aggregated, chunk);
164
179
  }
180
+ if (isAIMessageChunk(chunk.message) &&
181
+ chunk.message.usage_metadata !== undefined) {
182
+ llmOutput = {
183
+ tokenUsage: {
184
+ promptTokens: chunk.message.usage_metadata.input_tokens,
185
+ completionTokens: chunk.message.usage_metadata.output_tokens,
186
+ totalTokens: chunk.message.usage_metadata.total_tokens,
187
+ },
188
+ };
189
+ }
165
190
  }
166
191
  if (aggregated === undefined) {
167
192
  throw new Error("Received empty response from chat model call.");
@@ -169,7 +194,7 @@ export class BaseChatModel extends BaseLanguageModel {
169
194
  generations.push([aggregated]);
170
195
  await runManagers?.[0].handleLLMEnd({
171
196
  generations,
172
- llmOutput: {},
197
+ llmOutput,
173
198
  });
174
199
  }
175
200
  catch (e) {
@@ -38,7 +38,7 @@ type NonAlphanumeric = " " | "\t" | "\n" | "\r" | '"' | "'" | "{" | "[" | "(" |
38
38
  */
39
39
  type ExtractTemplateParamsRecursive<T extends string, Result extends string[] = []> = T extends `${string}{${infer Param}}${infer Rest}` ? Param extends `${NonAlphanumeric}${string}` ? ExtractTemplateParamsRecursive<Rest, Result> : ExtractTemplateParamsRecursive<Rest, [...Result, Param]> : Result;
40
40
  export type ParamsFromFString<T extends string> = {
41
- [Key in ExtractTemplateParamsRecursive<T>[number] | (string & Record<never, never>)]: string;
41
+ [Key in ExtractTemplateParamsRecursive<T>[number] | (string & Record<never, never>)]: any;
42
42
  };
43
43
  export type ExtractedFStringParams<T extends string, RunInput extends InputValues = Symbol> = RunInput extends Symbol ? ParamsFromFString<T> : RunInput;
44
44
  /**
@@ -86,15 +86,20 @@ const parseMustache = (template) => {
86
86
  return mustacheTemplateToNodes(parsed);
87
87
  };
88
88
  exports.parseMustache = parseMustache;
89
- const interpolateFString = (template, values) => (0, exports.parseFString)(template).reduce((res, node) => {
90
- if (node.type === "variable") {
91
- if (node.name in values) {
92
- return res + values[node.name];
89
+ const interpolateFString = (template, values) => {
90
+ return (0, exports.parseFString)(template).reduce((res, node) => {
91
+ if (node.type === "variable") {
92
+ if (node.name in values) {
93
+ const stringValue = typeof values[node.name] === "string"
94
+ ? values[node.name]
95
+ : JSON.stringify(values[node.name]);
96
+ return res + stringValue;
97
+ }
98
+ throw new Error(`(f-string) Missing value for input ${node.name}`);
93
99
  }
94
- throw new Error(`(f-string) Missing value for input ${node.name}`);
95
- }
96
- return res + node.text;
97
- }, "");
100
+ return res + node.text;
101
+ }, "");
102
+ };
98
103
  exports.interpolateFString = interpolateFString;
99
104
  const interpolateMustache = (template, values) => {
100
105
  configureMustache();
@@ -78,15 +78,20 @@ export const parseMustache = (template) => {
78
78
  const parsed = mustache.parse(template);
79
79
  return mustacheTemplateToNodes(parsed);
80
80
  };
81
- export const interpolateFString = (template, values) => parseFString(template).reduce((res, node) => {
82
- if (node.type === "variable") {
83
- if (node.name in values) {
84
- return res + values[node.name];
81
+ export const interpolateFString = (template, values) => {
82
+ return parseFString(template).reduce((res, node) => {
83
+ if (node.type === "variable") {
84
+ if (node.name in values) {
85
+ const stringValue = typeof values[node.name] === "string"
86
+ ? values[node.name]
87
+ : JSON.stringify(values[node.name]);
88
+ return res + stringValue;
89
+ }
90
+ throw new Error(`(f-string) Missing value for input ${node.name}`);
85
91
  }
86
- throw new Error(`(f-string) Missing value for input ${node.name}`);
87
- }
88
- return res + node.text;
89
- }, "");
92
+ return res + node.text;
93
+ }, "");
94
+ };
90
95
  export const interpolateMustache = (template, values) => {
91
96
  configureMustache();
92
97
  return mustache.render(template, values);
@@ -5,30 +5,28 @@ const zod_to_json_schema_1 = require("zod-to-json-schema");
5
5
  const uuid_1 = require("uuid");
6
6
  const utils_js_1 = require("./utils.cjs");
7
7
  const graph_mermaid_js_1 = require("./graph_mermaid.cjs");
8
- const MAX_DATA_DISPLAY_NAME_LENGTH = 42;
9
- function nodeDataStr(node) {
10
- if (!(0, uuid_1.validate)(node.id)) {
11
- return node.id;
8
+ function nodeDataStr(id, data) {
9
+ if (id !== undefined && !(0, uuid_1.validate)(id)) {
10
+ return id;
12
11
  }
13
- else if ((0, utils_js_1.isRunnableInterface)(node.data)) {
12
+ else if ((0, utils_js_1.isRunnableInterface)(data)) {
14
13
  try {
15
- let data = node.data.getName();
16
- data = data.startsWith("Runnable") ? data.slice("Runnable".length) : data;
17
- if (data.length > MAX_DATA_DISPLAY_NAME_LENGTH) {
18
- data = `${data.substring(0, MAX_DATA_DISPLAY_NAME_LENGTH)}...`;
19
- }
20
- return data;
14
+ let dataStr = data.getName();
15
+ dataStr = dataStr.startsWith("Runnable")
16
+ ? dataStr.slice("Runnable".length)
17
+ : dataStr;
18
+ return dataStr;
21
19
  }
22
20
  catch (error) {
23
- return node.data.getName();
21
+ return data.getName();
24
22
  }
25
23
  }
26
24
  else {
27
- return node.data.name ?? "UnknownSchema";
25
+ return data.name ?? "UnknownSchema";
28
26
  }
29
27
  }
30
28
  function nodeDataJson(node) {
31
- // if node.data is implements Runnable
29
+ // if node.data implements Runnable
32
30
  if ((0, utils_js_1.isRunnableInterface)(node.data)) {
33
31
  return {
34
32
  type: "runnable",
@@ -46,7 +44,7 @@ function nodeDataJson(node) {
46
44
  }
47
45
  }
48
46
  class Graph {
49
- constructor() {
47
+ constructor(params) {
50
48
  Object.defineProperty(this, "nodes", {
51
49
  enumerable: true,
52
50
  configurable: true,
@@ -59,6 +57,8 @@ class Graph {
59
57
  writable: true,
60
58
  value: []
61
59
  });
60
+ this.nodes = params?.nodes ?? this.nodes;
61
+ this.edges = params?.edges ?? this.edges;
62
62
  }
63
63
  // Convert the graph to a JSON-serializable format.
64
64
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -87,12 +87,19 @@ class Graph {
87
87
  }),
88
88
  };
89
89
  }
90
- addNode(data, id) {
90
+ addNode(data, id,
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ metadata) {
91
93
  if (id !== undefined && this.nodes[id] !== undefined) {
92
94
  throw new Error(`Node with id ${id} already exists`);
93
95
  }
94
- const nodeId = id || (0, uuid_1.v4)();
95
- const node = { id: nodeId, data };
96
+ const nodeId = id ?? (0, uuid_1.v4)();
97
+ const node = {
98
+ id: nodeId,
99
+ data,
100
+ name: nodeDataStr(id, data),
101
+ metadata,
102
+ };
96
103
  this.nodes[nodeId] = node;
97
104
  return node;
98
105
  }
@@ -188,19 +195,49 @@ class Graph {
188
195
  }
189
196
  }
190
197
  }
198
+ /**
199
+ * Return a new graph with all nodes re-identified,
200
+ * using their unique, readable names where possible.
201
+ */
202
+ reid() {
203
+ const nodeLabels = Object.fromEntries(Object.values(this.nodes).map((node) => [node.id, node.name]));
204
+ const nodeLabelCounts = new Map();
205
+ Object.values(nodeLabels).forEach((label) => {
206
+ nodeLabelCounts.set(label, (nodeLabelCounts.get(label) || 0) + 1);
207
+ });
208
+ const getNodeId = (nodeId) => {
209
+ const label = nodeLabels[nodeId];
210
+ if ((0, uuid_1.validate)(nodeId) && nodeLabelCounts.get(label) === 1) {
211
+ return label;
212
+ }
213
+ else {
214
+ return nodeId;
215
+ }
216
+ };
217
+ return new Graph({
218
+ nodes: Object.fromEntries(Object.entries(this.nodes).map(([id, node]) => [
219
+ getNodeId(id),
220
+ { ...node, id: getNodeId(id) },
221
+ ])),
222
+ edges: this.edges.map((edge) => ({
223
+ ...edge,
224
+ source: getNodeId(edge.source),
225
+ target: getNodeId(edge.target),
226
+ })),
227
+ });
228
+ }
191
229
  drawMermaid(params) {
192
- const { withStyles, curveStyle, nodeColors = { start: "#ffdfba", end: "#baffc9", other: "#fad7de" }, wrapLabelNWords, } = params ?? {};
193
- const nodes = {};
194
- for (const node of Object.values(this.nodes)) {
195
- nodes[node.id] = nodeDataStr(node);
196
- }
197
- const firstNode = this.firstNode();
198
- const firstNodeLabel = firstNode ? nodeDataStr(firstNode) : undefined;
199
- const lastNode = this.lastNode();
200
- const lastNodeLabel = lastNode ? nodeDataStr(lastNode) : undefined;
201
- return (0, graph_mermaid_js_1.drawMermaid)(nodes, this.edges, {
202
- firstNodeLabel,
203
- lastNodeLabel,
230
+ const { withStyles, curveStyle, nodeColors = {
231
+ default: "fill:#f2f0ff,line-height:1.2",
232
+ first: "fill-opacity:0",
233
+ last: "fill:#bfb6fc",
234
+ }, wrapLabelNWords, } = params ?? {};
235
+ const graph = this.reid();
236
+ const firstNode = graph.firstNode();
237
+ const lastNode = graph.lastNode();
238
+ return (0, graph_mermaid_js_1.drawMermaid)(graph.nodes, graph.edges, {
239
+ firstNode: firstNode?.id,
240
+ lastNode: lastNode?.id,
204
241
  withStyles,
205
242
  curveStyle,
206
243
  nodeColors,
@@ -3,8 +3,12 @@ export { Node, Edge };
3
3
  export declare class Graph {
4
4
  nodes: Record<string, Node>;
5
5
  edges: Edge[];
6
+ constructor(params?: {
7
+ nodes: Record<string, Node>;
8
+ edges: Edge[];
9
+ });
6
10
  toJSON(): Record<string, any>;
7
- addNode(data: RunnableInterface | RunnableIOSchema, id?: string): Node;
11
+ addNode(data: RunnableInterface | RunnableIOSchema, id?: string, metadata?: Record<string, any>): Node;
8
12
  removeNode(node: Node): void;
9
13
  addEdge(source: Node, target: Node, data?: string, conditional?: boolean): Edge;
10
14
  firstNode(): Node | undefined;
@@ -19,6 +23,11 @@ export declare class Graph {
19
23
  } | undefined)[];
20
24
  trimFirstNode(): void;
21
25
  trimLastNode(): void;
26
+ /**
27
+ * Return a new graph with all nodes re-identified,
28
+ * using their unique, readable names where possible.
29
+ */
30
+ reid(): Graph;
22
31
  drawMermaid(params?: {
23
32
  withStyles?: boolean;
24
33
  curveStyle?: string;
@@ -2,30 +2,28 @@ import { zodToJsonSchema } from "zod-to-json-schema";
2
2
  import { v4 as uuidv4, validate as isUuid } from "uuid";
3
3
  import { isRunnableInterface } from "./utils.js";
4
4
  import { drawMermaid, drawMermaidPng } from "./graph_mermaid.js";
5
- const MAX_DATA_DISPLAY_NAME_LENGTH = 42;
6
- function nodeDataStr(node) {
7
- if (!isUuid(node.id)) {
8
- return node.id;
5
+ function nodeDataStr(id, data) {
6
+ if (id !== undefined && !isUuid(id)) {
7
+ return id;
9
8
  }
10
- else if (isRunnableInterface(node.data)) {
9
+ else if (isRunnableInterface(data)) {
11
10
  try {
12
- let data = node.data.getName();
13
- data = data.startsWith("Runnable") ? data.slice("Runnable".length) : data;
14
- if (data.length > MAX_DATA_DISPLAY_NAME_LENGTH) {
15
- data = `${data.substring(0, MAX_DATA_DISPLAY_NAME_LENGTH)}...`;
16
- }
17
- return data;
11
+ let dataStr = data.getName();
12
+ dataStr = dataStr.startsWith("Runnable")
13
+ ? dataStr.slice("Runnable".length)
14
+ : dataStr;
15
+ return dataStr;
18
16
  }
19
17
  catch (error) {
20
- return node.data.getName();
18
+ return data.getName();
21
19
  }
22
20
  }
23
21
  else {
24
- return node.data.name ?? "UnknownSchema";
22
+ return data.name ?? "UnknownSchema";
25
23
  }
26
24
  }
27
25
  function nodeDataJson(node) {
28
- // if node.data is implements Runnable
26
+ // if node.data implements Runnable
29
27
  if (isRunnableInterface(node.data)) {
30
28
  return {
31
29
  type: "runnable",
@@ -43,7 +41,7 @@ function nodeDataJson(node) {
43
41
  }
44
42
  }
45
43
  export class Graph {
46
- constructor() {
44
+ constructor(params) {
47
45
  Object.defineProperty(this, "nodes", {
48
46
  enumerable: true,
49
47
  configurable: true,
@@ -56,6 +54,8 @@ export class Graph {
56
54
  writable: true,
57
55
  value: []
58
56
  });
57
+ this.nodes = params?.nodes ?? this.nodes;
58
+ this.edges = params?.edges ?? this.edges;
59
59
  }
60
60
  // Convert the graph to a JSON-serializable format.
61
61
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -84,12 +84,19 @@ export class Graph {
84
84
  }),
85
85
  };
86
86
  }
87
- addNode(data, id) {
87
+ addNode(data, id,
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ metadata) {
88
90
  if (id !== undefined && this.nodes[id] !== undefined) {
89
91
  throw new Error(`Node with id ${id} already exists`);
90
92
  }
91
- const nodeId = id || uuidv4();
92
- const node = { id: nodeId, data };
93
+ const nodeId = id ?? uuidv4();
94
+ const node = {
95
+ id: nodeId,
96
+ data,
97
+ name: nodeDataStr(id, data),
98
+ metadata,
99
+ };
93
100
  this.nodes[nodeId] = node;
94
101
  return node;
95
102
  }
@@ -185,19 +192,49 @@ export class Graph {
185
192
  }
186
193
  }
187
194
  }
195
+ /**
196
+ * Return a new graph with all nodes re-identified,
197
+ * using their unique, readable names where possible.
198
+ */
199
+ reid() {
200
+ const nodeLabels = Object.fromEntries(Object.values(this.nodes).map((node) => [node.id, node.name]));
201
+ const nodeLabelCounts = new Map();
202
+ Object.values(nodeLabels).forEach((label) => {
203
+ nodeLabelCounts.set(label, (nodeLabelCounts.get(label) || 0) + 1);
204
+ });
205
+ const getNodeId = (nodeId) => {
206
+ const label = nodeLabels[nodeId];
207
+ if (isUuid(nodeId) && nodeLabelCounts.get(label) === 1) {
208
+ return label;
209
+ }
210
+ else {
211
+ return nodeId;
212
+ }
213
+ };
214
+ return new Graph({
215
+ nodes: Object.fromEntries(Object.entries(this.nodes).map(([id, node]) => [
216
+ getNodeId(id),
217
+ { ...node, id: getNodeId(id) },
218
+ ])),
219
+ edges: this.edges.map((edge) => ({
220
+ ...edge,
221
+ source: getNodeId(edge.source),
222
+ target: getNodeId(edge.target),
223
+ })),
224
+ });
225
+ }
188
226
  drawMermaid(params) {
189
- const { withStyles, curveStyle, nodeColors = { start: "#ffdfba", end: "#baffc9", other: "#fad7de" }, wrapLabelNWords, } = params ?? {};
190
- const nodes = {};
191
- for (const node of Object.values(this.nodes)) {
192
- nodes[node.id] = nodeDataStr(node);
193
- }
194
- const firstNode = this.firstNode();
195
- const firstNodeLabel = firstNode ? nodeDataStr(firstNode) : undefined;
196
- const lastNode = this.lastNode();
197
- const lastNodeLabel = lastNode ? nodeDataStr(lastNode) : undefined;
198
- return drawMermaid(nodes, this.edges, {
199
- firstNodeLabel,
200
- lastNodeLabel,
227
+ const { withStyles, curveStyle, nodeColors = {
228
+ default: "fill:#f2f0ff,line-height:1.2",
229
+ first: "fill-opacity:0",
230
+ last: "fill:#bfb6fc",
231
+ }, wrapLabelNWords, } = params ?? {};
232
+ const graph = this.reid();
233
+ const firstNode = graph.firstNode();
234
+ const lastNode = graph.lastNode();
235
+ return drawMermaid(graph.nodes, graph.edges, {
236
+ firstNode: firstNode?.id,
237
+ lastNode: lastNode?.id,
201
238
  withStyles,
202
239
  curveStyle,
203
240
  nodeColors,
@@ -5,16 +5,11 @@ function _escapeNodeLabel(nodeLabel) {
5
5
  // Escapes the node label for Mermaid syntax.
6
6
  return nodeLabel.replace(/[^a-zA-Z-_0-9]/g, "_");
7
7
  }
8
- // Adjusts Mermaid edge to map conditional nodes to pure nodes.
9
- function _adjustMermaidEdge(edge, nodes) {
10
- const sourceNodeLabel = nodes[edge.source] ?? edge.source;
11
- const targetNodeLabel = nodes[edge.target] ?? edge.target;
12
- return [sourceNodeLabel, targetNodeLabel];
13
- }
8
+ const MARKDOWN_SPECIAL_CHARS = ["*", "_", "`"];
14
9
  function _generateMermaidGraphStyles(nodeColors) {
15
10
  let styles = "";
16
11
  for (const [className, color] of Object.entries(nodeColors)) {
17
- styles += `\tclassDef ${className}class fill:${color};\n`;
12
+ styles += `\tclassDef ${className} ${color};\n`;
18
13
  }
19
14
  return styles;
20
15
  }
@@ -22,7 +17,7 @@ function _generateMermaidGraphStyles(nodeColors) {
22
17
  * Draws a Mermaid graph using the provided graph data
23
18
  */
24
19
  function drawMermaid(nodes, edges, config) {
25
- const { firstNodeLabel, lastNodeLabel, nodeColors, withStyles = true, curveStyle = "linear", wrapLabelNWords = 9, } = config ?? {};
20
+ const { firstNode, lastNode, nodeColors, withStyles = true, curveStyle = "linear", wrapLabelNWords = 9, } = config ?? {};
26
21
  // Initialize Mermaid graph configuration
27
22
  let mermaidGraph = withStyles
28
23
  ? `%%{init: {'flowchart': {'curve': '${curveStyle}'}}}%%\ngraph TD;\n`
@@ -31,87 +26,99 @@ function drawMermaid(nodes, edges, config) {
31
26
  // Node formatting templates
32
27
  const defaultClassLabel = "default";
33
28
  const formatDict = {
34
- [defaultClassLabel]: "{0}([{1}]):::otherclass",
29
+ [defaultClassLabel]: "{0}({1})",
35
30
  };
36
- if (firstNodeLabel !== undefined) {
37
- formatDict[firstNodeLabel] = "{0}[{0}]:::startclass";
31
+ if (firstNode !== undefined) {
32
+ formatDict[firstNode] = "{0}([{1}]):::first";
38
33
  }
39
- if (lastNodeLabel !== undefined) {
40
- formatDict[lastNodeLabel] = "{0}[{0}]:::endclass";
34
+ if (lastNode !== undefined) {
35
+ formatDict[lastNode] = "{0}([{1}]):::last";
41
36
  }
42
37
  // Add nodes to the graph
43
- for (const node of Object.values(nodes)) {
44
- const nodeLabel = formatDict[node] ?? formatDict[defaultClassLabel];
45
- const escapedNodeLabel = _escapeNodeLabel(node);
46
- const nodeParts = node.split(":");
47
- const nodeSplit = nodeParts[nodeParts.length - 1];
48
- mermaidGraph += `\t${nodeLabel
49
- .replace(/\{0\}/g, escapedNodeLabel)
50
- .replace(/\{1\}/g, nodeSplit)};\n`;
38
+ for (const [key, node] of Object.entries(nodes)) {
39
+ const nodeName = node.name.split(":").pop() ?? "";
40
+ const label = MARKDOWN_SPECIAL_CHARS.some((char) => nodeName.startsWith(char) && nodeName.endsWith(char))
41
+ ? `<p>${nodeName}</p>`
42
+ : nodeName;
43
+ let finalLabel = label;
44
+ if (node.metadata) {
45
+ finalLabel += `<hr/><small><em>${Object.entries(node.metadata)
46
+ .map(([k, v]) => `${k} = ${v}`)
47
+ .join("\n")}</em></small>`;
48
+ }
49
+ const nodeLabel = (formatDict[key] ?? formatDict[defaultClassLabel])
50
+ .replace("{0}", _escapeNodeLabel(key))
51
+ .replace("{1}", finalLabel);
52
+ mermaidGraph += `\t${nodeLabel}\n`;
51
53
  }
52
54
  }
53
- let subgraph = "";
54
- // Add edges to the graph
55
+ // Group edges by their common prefixes
56
+ const edgeGroups = {};
55
57
  for (const edge of edges) {
56
- const sourcePrefix = edge.source.includes(":")
57
- ? edge.source.split(":")[0]
58
- : undefined;
59
- const targetPrefix = edge.target.includes(":")
60
- ? edge.target.split(":")[0]
61
- : undefined;
62
- // Exit subgraph if source or target is not in the same subgraph
63
- if (subgraph !== "" &&
64
- (subgraph !== sourcePrefix || subgraph !== targetPrefix)) {
65
- mermaidGraph += "\tend\n";
66
- subgraph = "";
58
+ const srcParts = edge.source.split(":");
59
+ const tgtParts = edge.target.split(":");
60
+ const commonPrefix = srcParts
61
+ .filter((src, i) => src === tgtParts[i])
62
+ .join(":");
63
+ if (!edgeGroups[commonPrefix]) {
64
+ edgeGroups[commonPrefix] = [];
67
65
  }
68
- // Enter subgraph if source and target are in the same subgraph
69
- if (subgraph === "" &&
70
- sourcePrefix !== undefined &&
71
- sourcePrefix === targetPrefix) {
72
- mermaidGraph = `\tsubgraph ${sourcePrefix}\n`;
73
- subgraph = sourcePrefix;
74
- }
75
- const [source, target] = _adjustMermaidEdge(edge, nodes);
76
- let edgeLabel = "";
77
- // Add BR every wrapLabelNWords words
78
- if (edge.data !== undefined) {
79
- let edgeData = edge.data;
80
- const words = edgeData.split(" ");
81
- // Group words into chunks of wrapLabelNWords size
82
- if (words.length > wrapLabelNWords) {
83
- edgeData = words
84
- .reduce((acc, word, i) => {
85
- if (i % wrapLabelNWords === 0)
86
- acc.push("");
87
- acc[acc.length - 1] += ` ${word}`;
88
- return acc;
89
- }, [])
90
- .join("<br>");
66
+ edgeGroups[commonPrefix].push(edge);
67
+ }
68
+ const seenSubgraphs = new Set();
69
+ function addSubgraph(edges, prefix) {
70
+ const selfLoop = edges.length === 1 && edges[0].source === edges[0].target;
71
+ if (prefix && !selfLoop) {
72
+ const subgraph = prefix.split(":").pop();
73
+ if (seenSubgraphs.has(subgraph)) {
74
+ throw new Error(`Found duplicate subgraph '${subgraph}' -- this likely means that ` +
75
+ "you're reusing a subgraph node with the same name. " +
76
+ "Please adjust your graph to have subgraph nodes with unique names.");
91
77
  }
92
- if (edge.conditional) {
93
- edgeLabel = ` -. ${edgeData} .-> `;
78
+ seenSubgraphs.add(subgraph);
79
+ mermaidGraph += `\tsubgraph ${subgraph}\n`;
80
+ }
81
+ for (const edge of edges) {
82
+ const { source, target, data, conditional } = edge;
83
+ let edgeLabel = "";
84
+ if (data !== undefined) {
85
+ let edgeData = String(data);
86
+ const words = edgeData.split(" ");
87
+ if (words.length > wrapLabelNWords) {
88
+ edgeData = Array.from({ length: Math.ceil(words.length / wrapLabelNWords) }, (_, i) => words
89
+ .slice(i * wrapLabelNWords, (i + 1) * wrapLabelNWords)
90
+ .join(" ")).join("&nbsp;<br>&nbsp;");
91
+ }
92
+ edgeLabel = conditional
93
+ ? ` -. &nbsp;${edgeData}&nbsp; .-> `
94
+ : ` -- &nbsp;${edgeData}&nbsp; --> `;
94
95
  }
95
96
  else {
96
- edgeLabel = ` -- ${edgeData} --> `;
97
+ edgeLabel = conditional ? " -.-> " : " --> ";
97
98
  }
99
+ mermaidGraph += `\t${_escapeNodeLabel(source)}${edgeLabel}${_escapeNodeLabel(target)};\n`;
98
100
  }
99
- else {
100
- if (edge.conditional) {
101
- edgeLabel = ` -.-> `;
102
- }
103
- else {
104
- edgeLabel = ` --> `;
101
+ // Recursively add nested subgraphs
102
+ for (const nestedPrefix in edgeGroups) {
103
+ if (nestedPrefix.startsWith(`${prefix}:`) && nestedPrefix !== prefix) {
104
+ addSubgraph(edgeGroups[nestedPrefix], nestedPrefix);
105
105
  }
106
106
  }
107
- mermaidGraph += `\t${_escapeNodeLabel(source)}${edgeLabel}${_escapeNodeLabel(target)};\n`;
107
+ if (prefix && !selfLoop) {
108
+ mermaidGraph += "\tend\n";
109
+ }
108
110
  }
109
- if (subgraph !== "") {
110
- mermaidGraph += "end\n";
111
+ // Start with the top-level edges (no common prefix)
112
+ addSubgraph(edgeGroups[""] ?? [], "");
113
+ // Add remaining subgraphs
114
+ for (const prefix in edgeGroups) {
115
+ if (!prefix.includes(":") && prefix !== "") {
116
+ addSubgraph(edgeGroups[prefix], prefix);
117
+ }
111
118
  }
112
119
  // Add custom styles for nodes
113
- if (withStyles && nodeColors !== undefined) {
114
- mermaidGraph += _generateMermaidGraphStyles(nodeColors);
120
+ if (withStyles) {
121
+ mermaidGraph += _generateMermaidGraphStyles(nodeColors ?? {});
115
122
  }
116
123
  return mermaidGraph;
117
124
  }
@@ -1,10 +1,10 @@
1
- import { Edge } from "./types.js";
1
+ import { Edge, Node } from "./types.js";
2
2
  /**
3
3
  * Draws a Mermaid graph using the provided graph data
4
4
  */
5
- export declare function drawMermaid(nodes: Record<string, string>, edges: Edge[], config?: {
6
- firstNodeLabel?: string;
7
- lastNodeLabel?: string;
5
+ export declare function drawMermaid(nodes: Record<string, Node>, edges: Edge[], config?: {
6
+ firstNode?: string;
7
+ lastNode?: string;
8
8
  curveStyle?: string;
9
9
  withStyles?: boolean;
10
10
  nodeColors?: Record<string, string>;
@@ -2,16 +2,11 @@ function _escapeNodeLabel(nodeLabel) {
2
2
  // Escapes the node label for Mermaid syntax.
3
3
  return nodeLabel.replace(/[^a-zA-Z-_0-9]/g, "_");
4
4
  }
5
- // Adjusts Mermaid edge to map conditional nodes to pure nodes.
6
- function _adjustMermaidEdge(edge, nodes) {
7
- const sourceNodeLabel = nodes[edge.source] ?? edge.source;
8
- const targetNodeLabel = nodes[edge.target] ?? edge.target;
9
- return [sourceNodeLabel, targetNodeLabel];
10
- }
5
+ const MARKDOWN_SPECIAL_CHARS = ["*", "_", "`"];
11
6
  function _generateMermaidGraphStyles(nodeColors) {
12
7
  let styles = "";
13
8
  for (const [className, color] of Object.entries(nodeColors)) {
14
- styles += `\tclassDef ${className}class fill:${color};\n`;
9
+ styles += `\tclassDef ${className} ${color};\n`;
15
10
  }
16
11
  return styles;
17
12
  }
@@ -19,7 +14,7 @@ function _generateMermaidGraphStyles(nodeColors) {
19
14
  * Draws a Mermaid graph using the provided graph data
20
15
  */
21
16
  export function drawMermaid(nodes, edges, config) {
22
- const { firstNodeLabel, lastNodeLabel, nodeColors, withStyles = true, curveStyle = "linear", wrapLabelNWords = 9, } = config ?? {};
17
+ const { firstNode, lastNode, nodeColors, withStyles = true, curveStyle = "linear", wrapLabelNWords = 9, } = config ?? {};
23
18
  // Initialize Mermaid graph configuration
24
19
  let mermaidGraph = withStyles
25
20
  ? `%%{init: {'flowchart': {'curve': '${curveStyle}'}}}%%\ngraph TD;\n`
@@ -28,87 +23,99 @@ export function drawMermaid(nodes, edges, config) {
28
23
  // Node formatting templates
29
24
  const defaultClassLabel = "default";
30
25
  const formatDict = {
31
- [defaultClassLabel]: "{0}([{1}]):::otherclass",
26
+ [defaultClassLabel]: "{0}({1})",
32
27
  };
33
- if (firstNodeLabel !== undefined) {
34
- formatDict[firstNodeLabel] = "{0}[{0}]:::startclass";
28
+ if (firstNode !== undefined) {
29
+ formatDict[firstNode] = "{0}([{1}]):::first";
35
30
  }
36
- if (lastNodeLabel !== undefined) {
37
- formatDict[lastNodeLabel] = "{0}[{0}]:::endclass";
31
+ if (lastNode !== undefined) {
32
+ formatDict[lastNode] = "{0}([{1}]):::last";
38
33
  }
39
34
  // Add nodes to the graph
40
- for (const node of Object.values(nodes)) {
41
- const nodeLabel = formatDict[node] ?? formatDict[defaultClassLabel];
42
- const escapedNodeLabel = _escapeNodeLabel(node);
43
- const nodeParts = node.split(":");
44
- const nodeSplit = nodeParts[nodeParts.length - 1];
45
- mermaidGraph += `\t${nodeLabel
46
- .replace(/\{0\}/g, escapedNodeLabel)
47
- .replace(/\{1\}/g, nodeSplit)};\n`;
35
+ for (const [key, node] of Object.entries(nodes)) {
36
+ const nodeName = node.name.split(":").pop() ?? "";
37
+ const label = MARKDOWN_SPECIAL_CHARS.some((char) => nodeName.startsWith(char) && nodeName.endsWith(char))
38
+ ? `<p>${nodeName}</p>`
39
+ : nodeName;
40
+ let finalLabel = label;
41
+ if (node.metadata) {
42
+ finalLabel += `<hr/><small><em>${Object.entries(node.metadata)
43
+ .map(([k, v]) => `${k} = ${v}`)
44
+ .join("\n")}</em></small>`;
45
+ }
46
+ const nodeLabel = (formatDict[key] ?? formatDict[defaultClassLabel])
47
+ .replace("{0}", _escapeNodeLabel(key))
48
+ .replace("{1}", finalLabel);
49
+ mermaidGraph += `\t${nodeLabel}\n`;
48
50
  }
49
51
  }
50
- let subgraph = "";
51
- // Add edges to the graph
52
+ // Group edges by their common prefixes
53
+ const edgeGroups = {};
52
54
  for (const edge of edges) {
53
- const sourcePrefix = edge.source.includes(":")
54
- ? edge.source.split(":")[0]
55
- : undefined;
56
- const targetPrefix = edge.target.includes(":")
57
- ? edge.target.split(":")[0]
58
- : undefined;
59
- // Exit subgraph if source or target is not in the same subgraph
60
- if (subgraph !== "" &&
61
- (subgraph !== sourcePrefix || subgraph !== targetPrefix)) {
62
- mermaidGraph += "\tend\n";
63
- subgraph = "";
55
+ const srcParts = edge.source.split(":");
56
+ const tgtParts = edge.target.split(":");
57
+ const commonPrefix = srcParts
58
+ .filter((src, i) => src === tgtParts[i])
59
+ .join(":");
60
+ if (!edgeGroups[commonPrefix]) {
61
+ edgeGroups[commonPrefix] = [];
64
62
  }
65
- // Enter subgraph if source and target are in the same subgraph
66
- if (subgraph === "" &&
67
- sourcePrefix !== undefined &&
68
- sourcePrefix === targetPrefix) {
69
- mermaidGraph = `\tsubgraph ${sourcePrefix}\n`;
70
- subgraph = sourcePrefix;
71
- }
72
- const [source, target] = _adjustMermaidEdge(edge, nodes);
73
- let edgeLabel = "";
74
- // Add BR every wrapLabelNWords words
75
- if (edge.data !== undefined) {
76
- let edgeData = edge.data;
77
- const words = edgeData.split(" ");
78
- // Group words into chunks of wrapLabelNWords size
79
- if (words.length > wrapLabelNWords) {
80
- edgeData = words
81
- .reduce((acc, word, i) => {
82
- if (i % wrapLabelNWords === 0)
83
- acc.push("");
84
- acc[acc.length - 1] += ` ${word}`;
85
- return acc;
86
- }, [])
87
- .join("<br>");
63
+ edgeGroups[commonPrefix].push(edge);
64
+ }
65
+ const seenSubgraphs = new Set();
66
+ function addSubgraph(edges, prefix) {
67
+ const selfLoop = edges.length === 1 && edges[0].source === edges[0].target;
68
+ if (prefix && !selfLoop) {
69
+ const subgraph = prefix.split(":").pop();
70
+ if (seenSubgraphs.has(subgraph)) {
71
+ throw new Error(`Found duplicate subgraph '${subgraph}' -- this likely means that ` +
72
+ "you're reusing a subgraph node with the same name. " +
73
+ "Please adjust your graph to have subgraph nodes with unique names.");
88
74
  }
89
- if (edge.conditional) {
90
- edgeLabel = ` -. ${edgeData} .-> `;
75
+ seenSubgraphs.add(subgraph);
76
+ mermaidGraph += `\tsubgraph ${subgraph}\n`;
77
+ }
78
+ for (const edge of edges) {
79
+ const { source, target, data, conditional } = edge;
80
+ let edgeLabel = "";
81
+ if (data !== undefined) {
82
+ let edgeData = String(data);
83
+ const words = edgeData.split(" ");
84
+ if (words.length > wrapLabelNWords) {
85
+ edgeData = Array.from({ length: Math.ceil(words.length / wrapLabelNWords) }, (_, i) => words
86
+ .slice(i * wrapLabelNWords, (i + 1) * wrapLabelNWords)
87
+ .join(" ")).join("&nbsp;<br>&nbsp;");
88
+ }
89
+ edgeLabel = conditional
90
+ ? ` -. &nbsp;${edgeData}&nbsp; .-> `
91
+ : ` -- &nbsp;${edgeData}&nbsp; --> `;
91
92
  }
92
93
  else {
93
- edgeLabel = ` -- ${edgeData} --> `;
94
+ edgeLabel = conditional ? " -.-> " : " --> ";
94
95
  }
96
+ mermaidGraph += `\t${_escapeNodeLabel(source)}${edgeLabel}${_escapeNodeLabel(target)};\n`;
95
97
  }
96
- else {
97
- if (edge.conditional) {
98
- edgeLabel = ` -.-> `;
99
- }
100
- else {
101
- edgeLabel = ` --> `;
98
+ // Recursively add nested subgraphs
99
+ for (const nestedPrefix in edgeGroups) {
100
+ if (nestedPrefix.startsWith(`${prefix}:`) && nestedPrefix !== prefix) {
101
+ addSubgraph(edgeGroups[nestedPrefix], nestedPrefix);
102
102
  }
103
103
  }
104
- mermaidGraph += `\t${_escapeNodeLabel(source)}${edgeLabel}${_escapeNodeLabel(target)};\n`;
104
+ if (prefix && !selfLoop) {
105
+ mermaidGraph += "\tend\n";
106
+ }
105
107
  }
106
- if (subgraph !== "") {
107
- mermaidGraph += "end\n";
108
+ // Start with the top-level edges (no common prefix)
109
+ addSubgraph(edgeGroups[""] ?? [], "");
110
+ // Add remaining subgraphs
111
+ for (const prefix in edgeGroups) {
112
+ if (!prefix.includes(":") && prefix !== "") {
113
+ addSubgraph(edgeGroups[prefix], prefix);
114
+ }
108
115
  }
109
116
  // Add custom styles for nodes
110
- if (withStyles && nodeColors !== undefined) {
111
- mermaidGraph += _generateMermaidGraphStyles(nodeColors);
117
+ if (withStyles) {
118
+ mermaidGraph += _generateMermaidGraphStyles(nodeColors ?? {});
112
119
  }
113
120
  return mermaidGraph;
114
121
  }
@@ -39,7 +39,9 @@ export interface Edge {
39
39
  }
40
40
  export interface Node {
41
41
  id: string;
42
+ name: string;
42
43
  data: RunnableIOSchema | RunnableInterface;
44
+ metadata?: Record<string, any>;
43
45
  }
44
46
  export interface RunnableConfig extends BaseCallbackConfig {
45
47
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/core",
3
- "version": "0.3.3",
3
+ "version": "0.3.5-rc.0",
4
4
  "description": "Core LangChain.js abstractions and schemas",
5
5
  "type": "module",
6
6
  "engines": {