@langchain/core 0.2.12 → 0.2.13-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.
@@ -4,6 +4,7 @@ exports.Graph = exports.nodeDataStr = void 0;
4
4
  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
+ const graph_mermaid_js_1 = require("./graph_mermaid.cjs");
7
8
  const MAX_DATA_DISPLAY_NAME_LENGTH = 42;
8
9
  function nodeDataStr(node) {
9
10
  if (!(0, uuid_1.validate)(node.id)) {
@@ -11,16 +12,12 @@ function nodeDataStr(node) {
11
12
  }
12
13
  else if ((0, utils_js_1.isRunnableInterface)(node.data)) {
13
14
  try {
14
- let data = node.data.toString();
15
- if (data.startsWith("<") ||
16
- data[0] !== data[0].toUpperCase() ||
17
- data.split("\n").length > 1) {
18
- data = node.data.getName();
19
- }
20
- else if (data.length > MAX_DATA_DISPLAY_NAME_LENGTH) {
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) {
21
18
  data = `${data.substring(0, MAX_DATA_DISPLAY_NAME_LENGTH)}...`;
22
19
  }
23
- return data.startsWith("Runnable") ? data.slice("Runnable".length) : data;
20
+ return data;
24
21
  }
25
22
  catch (error) {
26
23
  return node.data.getName();
@@ -160,5 +157,30 @@ class Graph {
160
157
  }
161
158
  }
162
159
  }
160
+ drawMermaid(params) {
161
+ const { withStyles, curveStyle, nodeColors = { start: "#ffdfba", end: "#baffc9", other: "#fad7de" }, wrapLabelNWords, } = params ?? {};
162
+ const nodes = {};
163
+ for (const node of Object.values(this.nodes)) {
164
+ nodes[node.id] = nodeDataStr(node);
165
+ }
166
+ const firstNode = this.firstNode();
167
+ const firstNodeLabel = firstNode ? nodeDataStr(firstNode) : undefined;
168
+ const lastNode = this.lastNode();
169
+ const lastNodeLabel = lastNode ? nodeDataStr(lastNode) : undefined;
170
+ return (0, graph_mermaid_js_1.drawMermaid)(nodes, this.edges, {
171
+ firstNodeLabel,
172
+ lastNodeLabel,
173
+ withStyles,
174
+ curveStyle,
175
+ nodeColors,
176
+ wrapLabelNWords,
177
+ });
178
+ }
179
+ async drawMermaidPng(params) {
180
+ const mermaidSyntax = this.drawMermaid(params);
181
+ return (0, graph_mermaid_js_1.drawMermaidPng)(mermaidSyntax, {
182
+ backgroundColor: params?.backgroundColor,
183
+ });
184
+ }
163
185
  }
164
186
  exports.Graph = Graph;
@@ -1,13 +1,4 @@
1
- import type { RunnableInterface, RunnableIOSchema } from "./types.js";
2
- interface Edge {
3
- source: string;
4
- target: string;
5
- data?: string;
6
- }
7
- interface Node {
8
- id: string;
9
- data: RunnableIOSchema | RunnableInterface;
10
- }
1
+ import type { RunnableInterface, RunnableIOSchema, Node, Edge } from "./types.js";
11
2
  export declare function nodeDataStr(node: Node): string;
12
3
  export declare class Graph {
13
4
  nodes: Record<string, Node>;
@@ -21,5 +12,17 @@ export declare class Graph {
21
12
  extend(graph: Graph): void;
22
13
  trimFirstNode(): void;
23
14
  trimLastNode(): void;
15
+ drawMermaid(params?: {
16
+ withStyles?: boolean;
17
+ curveStyle?: string;
18
+ nodeColors?: Record<string, string>;
19
+ wrapLabelNWords?: number;
20
+ }): string;
21
+ drawMermaidPng(params?: {
22
+ withStyles?: boolean;
23
+ curveStyle?: string;
24
+ nodeColors?: Record<string, string>;
25
+ wrapLabelNWords?: number;
26
+ backgroundColor?: string;
27
+ }): Promise<Blob>;
24
28
  }
25
- export {};
@@ -1,6 +1,7 @@
1
1
  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
+ import { drawMermaid, drawMermaidPng } from "./graph_mermaid.js";
4
5
  const MAX_DATA_DISPLAY_NAME_LENGTH = 42;
5
6
  export function nodeDataStr(node) {
6
7
  if (!isUuid(node.id)) {
@@ -8,16 +9,12 @@ export function nodeDataStr(node) {
8
9
  }
9
10
  else if (isRunnableInterface(node.data)) {
10
11
  try {
11
- let data = node.data.toString();
12
- if (data.startsWith("<") ||
13
- data[0] !== data[0].toUpperCase() ||
14
- data.split("\n").length > 1) {
15
- data = node.data.getName();
16
- }
17
- else if (data.length > MAX_DATA_DISPLAY_NAME_LENGTH) {
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) {
18
15
  data = `${data.substring(0, MAX_DATA_DISPLAY_NAME_LENGTH)}...`;
19
16
  }
20
- return data.startsWith("Runnable") ? data.slice("Runnable".length) : data;
17
+ return data;
21
18
  }
22
19
  catch (error) {
23
20
  return node.data.getName();
@@ -156,4 +153,29 @@ export class Graph {
156
153
  }
157
154
  }
158
155
  }
156
+ drawMermaid(params) {
157
+ const { withStyles, curveStyle, nodeColors = { start: "#ffdfba", end: "#baffc9", other: "#fad7de" }, wrapLabelNWords, } = params ?? {};
158
+ const nodes = {};
159
+ for (const node of Object.values(this.nodes)) {
160
+ nodes[node.id] = nodeDataStr(node);
161
+ }
162
+ const firstNode = this.firstNode();
163
+ const firstNodeLabel = firstNode ? nodeDataStr(firstNode) : undefined;
164
+ const lastNode = this.lastNode();
165
+ const lastNodeLabel = lastNode ? nodeDataStr(lastNode) : undefined;
166
+ return drawMermaid(nodes, this.edges, {
167
+ firstNodeLabel,
168
+ lastNodeLabel,
169
+ withStyles,
170
+ curveStyle,
171
+ nodeColors,
172
+ wrapLabelNWords,
173
+ });
174
+ }
175
+ async drawMermaidPng(params) {
176
+ const mermaidSyntax = this.drawMermaid(params);
177
+ return drawMermaidPng(mermaidSyntax, {
178
+ backgroundColor: params?.backgroundColor,
179
+ });
180
+ }
159
181
  }
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.drawMermaidPng = exports.drawMermaid = void 0;
4
+ function _escapeNodeLabel(nodeLabel) {
5
+ // Escapes the node label for Mermaid syntax.
6
+ return nodeLabel.replace(/[^a-zA-Z-_0-9]/g, "_");
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
+ }
14
+ function _generateMermaidGraphStyles(nodeColors) {
15
+ let styles = "";
16
+ for (const [className, color] of Object.entries(nodeColors)) {
17
+ styles += `\tclassDef ${className}class fill:${color};\n`;
18
+ }
19
+ return styles;
20
+ }
21
+ /**
22
+ * Draws a Mermaid graph using the provided graph data
23
+ */
24
+ function drawMermaid(nodes, edges, config) {
25
+ const { firstNodeLabel, lastNodeLabel, nodeColors, withStyles = true, curveStyle = "linear", wrapLabelNWords = 9, } = config ?? {};
26
+ // Initialize Mermaid graph configuration
27
+ let mermaidGraph = withStyles
28
+ ? `%%{init: {'flowchart': {'curve': '${curveStyle}'}}}%%\ngraph TD;\n`
29
+ : "graph TD;\n";
30
+ if (withStyles) {
31
+ // Node formatting templates
32
+ const defaultClassLabel = "default";
33
+ const formatDict = {
34
+ [defaultClassLabel]: "{0}([{1}]):::otherclass",
35
+ };
36
+ if (firstNodeLabel !== undefined) {
37
+ formatDict[firstNodeLabel] = "{0}[{0}]:::startclass";
38
+ }
39
+ if (lastNodeLabel !== undefined) {
40
+ formatDict[lastNodeLabel] = "{0}[{0}]:::endclass";
41
+ }
42
+ // 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`;
51
+ }
52
+ }
53
+ let subgraph = "";
54
+ // Add edges to the graph
55
+ 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 = "";
67
+ }
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>");
91
+ if (edge.conditional) {
92
+ edgeLabel = ` -. ${edgeData} .-> `;
93
+ }
94
+ else {
95
+ edgeLabel = ` -- ${edgeData} --> `;
96
+ }
97
+ }
98
+ }
99
+ else {
100
+ if (edge.conditional) {
101
+ edgeLabel = ` -.-> `;
102
+ }
103
+ else {
104
+ edgeLabel = ` --> `;
105
+ }
106
+ }
107
+ mermaidGraph += `\t${_escapeNodeLabel(source)}${edgeLabel}${_escapeNodeLabel(target)};\n`;
108
+ }
109
+ if (subgraph !== "") {
110
+ mermaidGraph += "end\n";
111
+ }
112
+ // Add custom styles for nodes
113
+ if (withStyles && nodeColors !== undefined) {
114
+ mermaidGraph += _generateMermaidGraphStyles(nodeColors);
115
+ }
116
+ return mermaidGraph;
117
+ }
118
+ exports.drawMermaid = drawMermaid;
119
+ /**
120
+ * Renders Mermaid graph using the Mermaid.INK API.
121
+ */
122
+ async function drawMermaidPng(mermaidSyntax, config) {
123
+ let { backgroundColor = "white" } = config ?? {};
124
+ // Use btoa for compatibility, assume ASCII
125
+ const mermaidSyntaxEncoded = btoa(mermaidSyntax);
126
+ // Check if the background color is a hexadecimal color code using regex
127
+ if (backgroundColor !== undefined) {
128
+ const hexColorPattern = /^#(?:[0-9a-fA-F]{3}){1,2}$/;
129
+ if (!hexColorPattern.test(backgroundColor)) {
130
+ backgroundColor = `!${backgroundColor}`;
131
+ }
132
+ }
133
+ const imageUrl = `https://mermaid.ink/img/${mermaidSyntaxEncoded}?bgColor=${backgroundColor}`;
134
+ const res = await fetch(imageUrl);
135
+ if (!res.ok) {
136
+ throw new Error([
137
+ `Failed to render the graph using the Mermaid.INK API.`,
138
+ `Status code: ${res.status}`,
139
+ `Status text: ${res.statusText}`,
140
+ ].join("\n"));
141
+ }
142
+ const content = await res.blob();
143
+ return content;
144
+ }
145
+ exports.drawMermaidPng = drawMermaidPng;
@@ -0,0 +1,18 @@
1
+ import { Edge } from "./types.js";
2
+ /**
3
+ * Draws a Mermaid graph using the provided graph data
4
+ */
5
+ export declare function drawMermaid(nodes: Record<string, string>, edges: Edge[], config?: {
6
+ firstNodeLabel?: string;
7
+ lastNodeLabel?: string;
8
+ curveStyle?: string;
9
+ withStyles?: boolean;
10
+ nodeColors?: Record<string, string>;
11
+ wrapLabelNWords?: number;
12
+ }): string;
13
+ /**
14
+ * Renders Mermaid graph using the Mermaid.INK API.
15
+ */
16
+ export declare function drawMermaidPng(mermaidSyntax: string, config?: {
17
+ backgroundColor?: string;
18
+ }): Promise<Blob>;
@@ -0,0 +1,140 @@
1
+ function _escapeNodeLabel(nodeLabel) {
2
+ // Escapes the node label for Mermaid syntax.
3
+ return nodeLabel.replace(/[^a-zA-Z-_0-9]/g, "_");
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
+ }
11
+ function _generateMermaidGraphStyles(nodeColors) {
12
+ let styles = "";
13
+ for (const [className, color] of Object.entries(nodeColors)) {
14
+ styles += `\tclassDef ${className}class fill:${color};\n`;
15
+ }
16
+ return styles;
17
+ }
18
+ /**
19
+ * Draws a Mermaid graph using the provided graph data
20
+ */
21
+ export function drawMermaid(nodes, edges, config) {
22
+ const { firstNodeLabel, lastNodeLabel, nodeColors, withStyles = true, curveStyle = "linear", wrapLabelNWords = 9, } = config ?? {};
23
+ // Initialize Mermaid graph configuration
24
+ let mermaidGraph = withStyles
25
+ ? `%%{init: {'flowchart': {'curve': '${curveStyle}'}}}%%\ngraph TD;\n`
26
+ : "graph TD;\n";
27
+ if (withStyles) {
28
+ // Node formatting templates
29
+ const defaultClassLabel = "default";
30
+ const formatDict = {
31
+ [defaultClassLabel]: "{0}([{1}]):::otherclass",
32
+ };
33
+ if (firstNodeLabel !== undefined) {
34
+ formatDict[firstNodeLabel] = "{0}[{0}]:::startclass";
35
+ }
36
+ if (lastNodeLabel !== undefined) {
37
+ formatDict[lastNodeLabel] = "{0}[{0}]:::endclass";
38
+ }
39
+ // 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`;
48
+ }
49
+ }
50
+ let subgraph = "";
51
+ // Add edges to the graph
52
+ 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 = "";
64
+ }
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>");
88
+ if (edge.conditional) {
89
+ edgeLabel = ` -. ${edgeData} .-> `;
90
+ }
91
+ else {
92
+ edgeLabel = ` -- ${edgeData} --> `;
93
+ }
94
+ }
95
+ }
96
+ else {
97
+ if (edge.conditional) {
98
+ edgeLabel = ` -.-> `;
99
+ }
100
+ else {
101
+ edgeLabel = ` --> `;
102
+ }
103
+ }
104
+ mermaidGraph += `\t${_escapeNodeLabel(source)}${edgeLabel}${_escapeNodeLabel(target)};\n`;
105
+ }
106
+ if (subgraph !== "") {
107
+ mermaidGraph += "end\n";
108
+ }
109
+ // Add custom styles for nodes
110
+ if (withStyles && nodeColors !== undefined) {
111
+ mermaidGraph += _generateMermaidGraphStyles(nodeColors);
112
+ }
113
+ return mermaidGraph;
114
+ }
115
+ /**
116
+ * Renders Mermaid graph using the Mermaid.INK API.
117
+ */
118
+ export async function drawMermaidPng(mermaidSyntax, config) {
119
+ let { backgroundColor = "white" } = config ?? {};
120
+ // Use btoa for compatibility, assume ASCII
121
+ const mermaidSyntaxEncoded = btoa(mermaidSyntax);
122
+ // Check if the background color is a hexadecimal color code using regex
123
+ if (backgroundColor !== undefined) {
124
+ const hexColorPattern = /^#(?:[0-9a-fA-F]{3}){1,2}$/;
125
+ if (!hexColorPattern.test(backgroundColor)) {
126
+ backgroundColor = `!${backgroundColor}`;
127
+ }
128
+ }
129
+ const imageUrl = `https://mermaid.ink/img/${mermaidSyntaxEncoded}?bgColor=${backgroundColor}`;
130
+ const res = await fetch(imageUrl);
131
+ if (!res.ok) {
132
+ throw new Error([
133
+ `Failed to render the graph using the Mermaid.INK API.`,
134
+ `Status code: ${res.status}`,
135
+ `Status text: ${res.statusText}`,
136
+ ].join("\n"));
137
+ }
138
+ const content = await res.blob();
139
+ return content;
140
+ }
@@ -81,4 +81,20 @@ test("Test graph sequence", async () => {
81
81
  { source: 2, target: 3 },
82
82
  ],
83
83
  });
84
+ expect(graph.drawMermaid())
85
+ .toEqual(`%%{init: {'flowchart': {'curve': 'linear'}}}%%
86
+ graph TD;
87
+ \tPromptTemplateInput[PromptTemplateInput]:::startclass;
88
+ \tPromptTemplate([PromptTemplate]):::otherclass;
89
+ \tFakeLLM([FakeLLM]):::otherclass;
90
+ \tCommaSeparatedListOutputParser([CommaSeparatedListOutputParser]):::otherclass;
91
+ \tCommaSeparatedListOutputParserOutput[CommaSeparatedListOutputParserOutput]:::endclass;
92
+ \tPromptTemplateInput --> PromptTemplate;
93
+ \tPromptTemplate --> FakeLLM;
94
+ \tCommaSeparatedListOutputParser --> CommaSeparatedListOutputParserOutput;
95
+ \tFakeLLM --> CommaSeparatedListOutputParser;
96
+ \tclassDef startclass fill:#ffdfba;
97
+ \tclassDef endclass fill:#baffc9;
98
+ \tclassDef otherclass fill:#fad7de;
99
+ `);
84
100
  });
@@ -31,3 +31,13 @@ export interface RunnableInterface<RunInput = any, RunOutput = any, CallOptions
31
31
  transform(generator: AsyncGenerator<RunInput>, options: Partial<CallOptions>): AsyncGenerator<RunOutput>;
32
32
  getName(suffix?: string): string;
33
33
  }
34
+ export interface Edge {
35
+ source: string;
36
+ target: string;
37
+ data?: string;
38
+ conditional?: boolean;
39
+ }
40
+ export interface Node {
41
+ id: string;
42
+ data: RunnableIOSchema | RunnableInterface;
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/core",
3
- "version": "0.2.12",
3
+ "version": "0.2.13-rc.0",
4
4
  "description": "Core LangChain.js abstractions and schemas",
5
5
  "type": "module",
6
6
  "engines": {