@langchain/langgraph 0.0.27-rc.0 → 0.0.28

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.
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TAG_HIDDEN = exports.INTERRUPT = exports.CONFIG_KEY_READ = exports.CONFIG_KEY_SEND = void 0;
3
+ exports.TASKS = exports.TAG_HIDDEN = exports.INTERRUPT = exports.CONFIG_KEY_READ = exports.CONFIG_KEY_SEND = void 0;
4
4
  exports.CONFIG_KEY_SEND = "__pregel_send";
5
5
  exports.CONFIG_KEY_READ = "__pregel_read";
6
6
  exports.INTERRUPT = "__interrupt__";
7
7
  exports.TAG_HIDDEN = "langsmith:hidden";
8
+ exports.TASKS = "__pregel_tasks";
@@ -2,3 +2,4 @@ export declare const CONFIG_KEY_SEND = "__pregel_send";
2
2
  export declare const CONFIG_KEY_READ = "__pregel_read";
3
3
  export declare const INTERRUPT = "__interrupt__";
4
4
  export declare const TAG_HIDDEN = "langsmith:hidden";
5
+ export declare const TASKS = "__pregel_tasks";
package/dist/constants.js CHANGED
@@ -2,3 +2,4 @@ export const CONFIG_KEY_SEND = "__pregel_send";
2
2
  export const CONFIG_KEY_READ = "__pregel_read";
3
3
  export const INTERRUPT = "__interrupt__";
4
4
  export const TAG_HIDDEN = "langsmith:hidden";
5
+ export const TASKS = "__pregel_tasks";
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CompiledGraph = exports.Graph = exports.Branch = exports.END = exports.START = void 0;
4
4
  /* eslint-disable @typescript-eslint/no-use-before-define */
5
5
  const runnables_1 = require("@langchain/core/runnables");
6
+ const graph_1 = require("@langchain/core/runnables/graph");
6
7
  const zod_1 = require("zod");
7
8
  const read_js_1 = require("../pregel/read.cjs");
8
9
  const index_js_1 = require("../pregel/index.cjs");
@@ -317,10 +318,12 @@ class CompiledGraph extends index_js_1.Pregel {
317
318
  }
318
319
  }
319
320
  }
320
- // Returns a drawable representation of the computation graph.
321
+ /**
322
+ * Returns a drawable representation of the computation graph.
323
+ */
321
324
  getGraph(config) {
322
325
  const xray = config?.xray;
323
- const graph = new utils_js_1.DrawableGraph();
326
+ const graph = new graph_1.Graph();
324
327
  const startNodes = {
325
328
  [exports.START]: graph.addNode({
326
329
  schema: zod_1.z.any(),
@@ -332,7 +335,7 @@ class CompiledGraph extends index_js_1.Pregel {
332
335
  }, exports.END),
333
336
  };
334
337
  for (const [key, node] of Object.entries(this.builder.nodes)) {
335
- if (!!config?.xray) {
338
+ if (config?.xray) {
336
339
  const subgraph = isCompiledGraph(node)
337
340
  ? node.getGraph({
338
341
  ...config,
@@ -370,7 +373,7 @@ class CompiledGraph extends index_js_1.Pregel {
370
373
  ...Object.fromEntries(Object.keys(this.builder.nodes)
371
374
  .filter((k) => k !== start)
372
375
  .map((k) => [k, k])),
373
- END: exports.END,
376
+ [exports.END]: exports.END,
374
377
  };
375
378
  for (const branch of Object.values(branches)) {
376
379
  let ends;
@@ -389,7 +392,11 @@ class CompiledGraph extends index_js_1.Pregel {
389
392
  }
390
393
  }
391
394
  exports.CompiledGraph = CompiledGraph;
395
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
392
396
  function isCompiledGraph(x) {
393
- return (typeof x.attachNode === "function" &&
397
+ return (
398
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
399
+ typeof x.attachNode === "function" &&
400
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
394
401
  typeof x.attachEdge === "function");
395
402
  }
@@ -1,10 +1,11 @@
1
1
  import { Runnable, RunnableConfig, RunnableLike } from "@langchain/core/runnables";
2
+ import { Graph as RunnableGraph } from "@langchain/core/runnables/graph";
2
3
  import { PregelNode } from "../pregel/read.js";
3
4
  import { Pregel, PregelInterface } from "../pregel/index.js";
4
5
  import { BaseCheckpointSaver } from "../checkpoint/base.js";
5
6
  import { BaseChannel } from "../channels/base.js";
6
7
  import { All } from "../pregel/types.js";
7
- import { DrawableGraph, RunnableCallable } from "../utils.js";
8
+ import { RunnableCallable } from "../utils.js";
8
9
  export declare const START = "__start__";
9
10
  export declare const END = "__end__";
10
11
  export interface BranchOptions<IO, N extends string> {
@@ -56,7 +57,10 @@ export declare class CompiledGraph<N extends string, RunInput = any, RunOutput =
56
57
  attachNode(key: N, node: Runnable<RunInput, RunOutput>): void;
57
58
  attachEdge(start: N | typeof START, end: N | typeof END): void;
58
59
  attachBranch(start: N | typeof START, name: string, branch: Branch<RunInput, N>): void;
60
+ /**
61
+ * Returns a drawable representation of the computation graph.
62
+ */
59
63
  getGraph(config?: RunnableConfig & {
60
64
  xray?: boolean | number;
61
- }): DrawableGraph;
65
+ }): RunnableGraph;
62
66
  }
@@ -1,12 +1,13 @@
1
1
  /* eslint-disable @typescript-eslint/no-use-before-define */
2
2
  import { _coerceToRunnable, } from "@langchain/core/runnables";
3
+ import { Graph as RunnableGraph, } from "@langchain/core/runnables/graph";
3
4
  import { z } from "zod";
4
5
  import { PregelNode } from "../pregel/read.js";
5
6
  import { Channel, Pregel } from "../pregel/index.js";
6
7
  import { EphemeralValue } from "../channels/ephemeral_value.js";
7
8
  import { ChannelWrite, PASSTHROUGH } from "../pregel/write.js";
8
9
  import { TAG_HIDDEN } from "../constants.js";
9
- import { DrawableGraph, RunnableCallable } from "../utils.js";
10
+ import { RunnableCallable } from "../utils.js";
10
11
  export const START = "__start__";
11
12
  export const END = "__end__";
12
13
  export class Branch {
@@ -312,10 +313,12 @@ export class CompiledGraph extends Pregel {
312
313
  }
313
314
  }
314
315
  }
315
- // Returns a drawable representation of the computation graph.
316
+ /**
317
+ * Returns a drawable representation of the computation graph.
318
+ */
316
319
  getGraph(config) {
317
320
  const xray = config?.xray;
318
- const graph = new DrawableGraph();
321
+ const graph = new RunnableGraph();
319
322
  const startNodes = {
320
323
  [START]: graph.addNode({
321
324
  schema: z.any(),
@@ -327,7 +330,7 @@ export class CompiledGraph extends Pregel {
327
330
  }, END),
328
331
  };
329
332
  for (const [key, node] of Object.entries(this.builder.nodes)) {
330
- if (!!config?.xray) {
333
+ if (config?.xray) {
331
334
  const subgraph = isCompiledGraph(node)
332
335
  ? node.getGraph({
333
336
  ...config,
@@ -365,7 +368,7 @@ export class CompiledGraph extends Pregel {
365
368
  ...Object.fromEntries(Object.keys(this.builder.nodes)
366
369
  .filter((k) => k !== start)
367
370
  .map((k) => [k, k])),
368
- END: END,
371
+ [END]: END,
369
372
  };
370
373
  for (const branch of Object.values(branches)) {
371
374
  let ends;
@@ -383,7 +386,11 @@ export class CompiledGraph extends Pregel {
383
386
  return graph;
384
387
  }
385
388
  }
389
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
386
390
  function isCompiledGraph(x) {
387
- return (typeof x.attachNode === "function" &&
391
+ return (
392
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
393
+ typeof x.attachNode === "function" &&
394
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
388
395
  typeof x.attachEdge === "function");
389
396
  }
@@ -1,5 +1,6 @@
1
- import { StructuredTool } from "@langchain/core/tools";
1
+ import { StructuredToolInterface } from "@langchain/core/tools";
2
2
  import { BaseMessage } from "@langchain/core/messages";
3
+ import { RunnableToolLike } from "@langchain/core/runnables";
3
4
  import { ToolExecutor } from "./tool_executor.js";
4
5
  import { CompiledStateGraph } from "../graph/state.js";
5
6
  import { START } from "../graph/index.js";
@@ -8,5 +9,5 @@ export type FunctionCallingExecutorState = {
8
9
  };
9
10
  export declare function createFunctionCallingExecutor<Model extends object>({ model, tools, }: {
10
11
  model: Model;
11
- tools: Array<StructuredTool> | ToolExecutor;
12
+ tools: Array<StructuredToolInterface | RunnableToolLike> | ToolExecutor;
12
13
  }): CompiledStateGraph<FunctionCallingExecutorState, Partial<FunctionCallingExecutorState>, typeof START | "agent" | "action">;
@@ -1,6 +1,6 @@
1
1
  import { convertToOpenAIFunction } from "@langchain/core/utils/function_calling";
2
2
  import { FunctionMessage } from "@langchain/core/messages";
3
- import { RunnableLambda } from "@langchain/core/runnables";
3
+ import { RunnableLambda, } from "@langchain/core/runnables";
4
4
  import { ToolExecutor } from "./tool_executor.js";
5
5
  import { StateGraph, } from "../graph/state.js";
6
6
  import { END, START } from "../graph/index.js";
@@ -1,7 +1,7 @@
1
1
  import { BaseChatModel } from "@langchain/core/language_models/chat_models";
2
2
  import { BaseMessage, SystemMessage } from "@langchain/core/messages";
3
- import { Runnable } from "@langchain/core/runnables";
4
- import { StructuredTool } from "@langchain/core/tools";
3
+ import { Runnable, RunnableToolLike } from "@langchain/core/runnables";
4
+ import { StructuredToolInterface } from "@langchain/core/tools";
5
5
  import { BaseCheckpointSaver } from "../checkpoint/base.js";
6
6
  import { START } from "../graph/index.js";
7
7
  import { MessagesState } from "../graph/message.js";
@@ -14,7 +14,7 @@ export interface AgentState {
14
14
  export type N = typeof START | "agent" | "tools";
15
15
  export type CreateReactAgentParams = {
16
16
  llm: BaseChatModel;
17
- tools: ToolNode<MessagesState> | StructuredTool[];
17
+ tools: ToolNode<MessagesState> | (StructuredToolInterface | RunnableToolLike)[];
18
18
  messageModifier?: SystemMessage | string | ((messages: BaseMessage[]) => BaseMessage[]) | ((messages: BaseMessage[]) => Promise<BaseMessage[]>) | Runnable;
19
19
  checkpointSaver?: BaseCheckpointSaver;
20
20
  interruptBefore?: N[] | All;
@@ -45,6 +45,13 @@ class ToolExecutor extends runnables_1.RunnableBinding {
45
45
  return acc;
46
46
  }, {});
47
47
  }
48
+ /**
49
+ * Execute a tool invocation
50
+ *
51
+ * @param {ToolInvocationInterface} toolInvocation The tool to invoke and the input to pass to it.
52
+ * @param {RunnableConfig | undefined} config Optional configuration to pass to the tool when invoked.
53
+ * @returns Either the result of the tool invocation (`string` or `ToolMessage`, set by the `ToolOutput` generic) or a string error message.
54
+ */
48
55
  async _execute(toolInvocation, config) {
49
56
  if (!(toolInvocation.tool in this.toolMap)) {
50
57
  return this.invalidToolMsgTemplate
@@ -1,7 +1,7 @@
1
- import { RunnableBinding, RunnableConfig } from "@langchain/core/runnables";
2
- import { StructuredTool } from "@langchain/core/tools";
1
+ import { RunnableBinding, RunnableConfig, RunnableToolLike } from "@langchain/core/runnables";
2
+ import { StructuredToolInterface } from "@langchain/core/tools";
3
3
  export interface ToolExecutorArgs {
4
- tools: Array<StructuredTool>;
4
+ tools: Array<StructuredToolInterface | RunnableToolLike>;
5
5
  /**
6
6
  * @default {INVALID_TOOL_MSG_TEMPLATE}
7
7
  */
@@ -18,10 +18,17 @@ type ToolExecutorInputType = any;
18
18
  type ToolExecutorOutputType = any;
19
19
  export declare class ToolExecutor extends RunnableBinding<ToolExecutorInputType, ToolExecutorOutputType> {
20
20
  lc_graph_name: string;
21
- tools: Array<StructuredTool>;
22
- toolMap: Record<string, StructuredTool>;
21
+ tools: Array<StructuredToolInterface | RunnableToolLike>;
22
+ toolMap: Record<string, StructuredToolInterface | RunnableToolLike>;
23
23
  invalidToolMsgTemplate: string;
24
24
  constructor(fields: ToolExecutorArgs);
25
- _execute(toolInvocation: ToolInvocationInterface, config?: RunnableConfig): Promise<string>;
25
+ /**
26
+ * Execute a tool invocation
27
+ *
28
+ * @param {ToolInvocationInterface} toolInvocation The tool to invoke and the input to pass to it.
29
+ * @param {RunnableConfig | undefined} config Optional configuration to pass to the tool when invoked.
30
+ * @returns Either the result of the tool invocation (`string` or `ToolMessage`, set by the `ToolOutput` generic) or a string error message.
31
+ */
32
+ _execute(toolInvocation: ToolInvocationInterface, config?: RunnableConfig): Promise<ToolExecutorOutputType>;
26
33
  }
27
34
  export {};
@@ -42,6 +42,13 @@ export class ToolExecutor extends RunnableBinding {
42
42
  return acc;
43
43
  }, {});
44
44
  }
45
+ /**
46
+ * Execute a tool invocation
47
+ *
48
+ * @param {ToolInvocationInterface} toolInvocation The tool to invoke and the input to pass to it.
49
+ * @param {RunnableConfig | undefined} config Optional configuration to pass to the tool when invoked.
50
+ * @returns Either the result of the tool invocation (`string` or `ToolMessage`, set by the `ToolOutput` generic) or a string error message.
51
+ */
45
52
  async _execute(toolInvocation, config) {
46
53
  if (!(toolInvocation.tool in this.toolMap)) {
47
54
  return this.invalidToolMsgTemplate
@@ -33,12 +33,17 @@ class ToolNode extends utils_js_1.RunnableCallable {
33
33
  if (tool === undefined) {
34
34
  throw new Error(`Tool ${call.name} not found.`);
35
35
  }
36
- const output = await tool.invoke(call.args, config);
37
- return new messages_1.ToolMessage({
38
- name: tool.name,
39
- content: typeof output === "string" ? output : JSON.stringify(output),
40
- tool_call_id: call.id,
41
- });
36
+ const output = await tool.invoke({ ...call, type: "tool_call" }, config);
37
+ if ((0, messages_1.isBaseMessage)(output) && output._getType() === "tool") {
38
+ return output;
39
+ }
40
+ else {
41
+ return new messages_1.ToolMessage({
42
+ name: tool.name,
43
+ content: typeof output === "string" ? output : JSON.stringify(output),
44
+ tool_call_id: call.id,
45
+ });
46
+ }
42
47
  }) ?? []);
43
48
  return Array.isArray(input) ? outputs : { messages: outputs };
44
49
  }
@@ -1,5 +1,6 @@
1
1
  import { BaseMessage } from "@langchain/core/messages";
2
- import { StructuredTool } from "@langchain/core/tools";
2
+ import { RunnableToolLike } from "@langchain/core/runnables";
3
+ import { StructuredToolInterface } from "@langchain/core/tools";
3
4
  import { RunnableCallable } from "../utils.js";
4
5
  import { END } from "../graph/graph.js";
5
6
  import { MessagesState } from "../graph/message.js";
@@ -10,8 +11,8 @@ export declare class ToolNode<T extends BaseMessage[] | MessagesState> extends R
10
11
  tool calls are requested, they will be run in parallel. The output will be
11
12
  a list of ToolMessages, one for each tool call.
12
13
  */
13
- tools: StructuredTool[];
14
- constructor(tools: StructuredTool[], name?: string, tags?: string[]);
14
+ tools: (StructuredToolInterface | RunnableToolLike)[];
15
+ constructor(tools: (StructuredToolInterface | RunnableToolLike)[], name?: string, tags?: string[]);
15
16
  private run;
16
17
  }
17
18
  export declare function toolsCondition(state: BaseMessage[] | MessagesState): "tools" | typeof END;
@@ -1,4 +1,4 @@
1
- import { ToolMessage } from "@langchain/core/messages";
1
+ import { ToolMessage, isBaseMessage, } from "@langchain/core/messages";
2
2
  import { RunnableCallable } from "../utils.js";
3
3
  import { END } from "../graph/graph.js";
4
4
  export class ToolNode extends RunnableCallable {
@@ -30,12 +30,17 @@ export class ToolNode extends RunnableCallable {
30
30
  if (tool === undefined) {
31
31
  throw new Error(`Tool ${call.name} not found.`);
32
32
  }
33
- const output = await tool.invoke(call.args, config);
34
- return new ToolMessage({
35
- name: tool.name,
36
- content: typeof output === "string" ? output : JSON.stringify(output),
37
- tool_call_id: call.id,
38
- });
33
+ const output = await tool.invoke({ ...call, type: "tool_call" }, config);
34
+ if (isBaseMessage(output) && output._getType() === "tool") {
35
+ return output;
36
+ }
37
+ else {
38
+ return new ToolMessage({
39
+ name: tool.name,
40
+ content: typeof output === "string" ? output : JSON.stringify(output),
41
+ tool_call_id: call.id,
42
+ });
43
+ }
39
44
  }) ?? []);
40
45
  return Array.isArray(input) ? outputs : { messages: outputs };
41
46
  }
@@ -442,10 +442,17 @@ class Pregel extends runnables_1.Runnable {
442
442
  }
443
443
  const tasksWithConfig = nextTasks.map(
444
444
  // eslint-disable-next-line no-loop-func
445
- (task) => [
445
+ (task, i) => [
446
446
  task.proc,
447
447
  task.input,
448
- (0, runnables_1.patchConfig)(restConfig, {
448
+ (0, runnables_1.patchConfig)((0, runnables_1.mergeConfigs)(restConfig, processes[task.name].config, {
449
+ metadata: {
450
+ langgraph_step: step,
451
+ langgraph_node: task.name,
452
+ langgraph_triggers: [constants_js_1.TASKS],
453
+ langgraph_task_idx: i,
454
+ },
455
+ }), {
449
456
  callbacks: runManager?.getChild(`graph:step:${step}`),
450
457
  runName: task.name,
451
458
  configurable: {
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable no-param-reassign */
2
- import { Runnable, RunnableSequence, _coerceToRunnable, ensureConfig, patchConfig, } from "@langchain/core/runnables";
2
+ import { Runnable, RunnableSequence, _coerceToRunnable, ensureConfig, mergeConfigs, patchConfig, } from "@langchain/core/runnables";
3
3
  import { IterableReadableStream } from "@langchain/core/utils/stream";
4
4
  import { createCheckpoint, emptyChannels, } from "../channels/base.js";
5
5
  import { copyCheckpoint, emptyCheckpoint, getChannelVersion, getVersionSeen, } from "../checkpoint/base.js";
@@ -7,7 +7,7 @@ import { PregelNode } from "./read.js";
7
7
  import { validateGraph, validateKeys } from "./validate.js";
8
8
  import { mapInput, mapOutputUpdates, mapOutputValues, readChannel, readChannels, single, } from "./io.js";
9
9
  import { ChannelWrite, PASSTHROUGH } from "./write.js";
10
- import { CONFIG_KEY_READ, CONFIG_KEY_SEND, INTERRUPT, TAG_HIDDEN, } from "../constants.js";
10
+ import { CONFIG_KEY_READ, CONFIG_KEY_SEND, INTERRUPT, TAG_HIDDEN, TASKS, } from "../constants.js";
11
11
  import { EmptyChannelError, GraphRecursionError, GraphValueError, InvalidUpdateError, } from "../errors.js";
12
12
  const DEFAULT_LOOP_LIMIT = 25;
13
13
  function isString(value) {
@@ -438,10 +438,17 @@ export class Pregel extends Runnable {
438
438
  }
439
439
  const tasksWithConfig = nextTasks.map(
440
440
  // eslint-disable-next-line no-loop-func
441
- (task) => [
441
+ (task, i) => [
442
442
  task.proc,
443
443
  task.input,
444
- patchConfig(restConfig, {
444
+ patchConfig(mergeConfigs(restConfig, processes[task.name].config, {
445
+ metadata: {
446
+ langgraph_step: step,
447
+ langgraph_node: task.name,
448
+ langgraph_triggers: [TASKS],
449
+ langgraph_task_idx: i,
450
+ },
451
+ }), {
445
452
  callbacks: runManager?.getChild(`graph:step:${step}`),
446
453
  runName: task.name,
447
454
  configurable: {
@@ -1,11 +1,10 @@
1
1
  import { test, expect } from "@jest/globals";
2
- import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
3
- import { ChatOpenAI } from "@langchain/openai";
4
2
  import { createReactAgent } from "../prebuilt/index.js";
5
- test("langgraph", async () => {
3
+ import { FakeSearchTool, FakeToolCallingChatModel } from "./utils.js";
4
+ test("prebuilt agent", async () => {
6
5
  // Define the tools for the agent to use
7
- const tools = [new TavilySearchResults({ maxResults: 1 })];
8
- const model = new ChatOpenAI({ temperature: 0 });
6
+ const tools = [new FakeSearchTool()];
7
+ const model = new FakeToolCallingChatModel({});
9
8
  const app = createReactAgent({ llm: model, tools });
10
9
  const graph = app.getGraph();
11
10
  const mermaid = graph.drawMermaid();
@@ -3,6 +3,8 @@ import { it, beforeAll, describe, expect } from "@jest/globals";
3
3
  import { Tool } from "@langchain/core/tools";
4
4
  import { ChatOpenAI } from "@langchain/openai";
5
5
  import { HumanMessage } from "@langchain/core/messages";
6
+ import { RunnableLambda } from "@langchain/core/runnables";
7
+ import { z } from "zod";
6
8
  import { createReactAgent, createFunctionCallingExecutor, } from "../prebuilt/index.js";
7
9
  import { initializeAsyncLocalStorageSingleton } from "../setup/async_local_storage.js";
8
10
  // Tracing slows down the tests
@@ -97,6 +99,32 @@ describe("createFunctionCallingExecutor", () => {
97
99
  const functionCall = endState.messages.find((message) => message._getType() === "function");
98
100
  expect(functionCall.content).toBe(weatherResponse);
99
101
  });
102
+ it("can accept RunnableToolLike tools", async () => {
103
+ const weatherResponse = `Not too cold, not too hot 😎`;
104
+ const model = new ChatOpenAI();
105
+ const sfWeatherTool = RunnableLambda.from(async (_) => weatherResponse);
106
+ const tools = [
107
+ sfWeatherTool.asTool({
108
+ name: "current_weather",
109
+ description: "Get the current weather report for San Francisco, CA",
110
+ schema: z.object({
111
+ location: z.string(),
112
+ }),
113
+ }),
114
+ ];
115
+ const functionsAgentExecutor = createFunctionCallingExecutor({
116
+ model,
117
+ tools,
118
+ });
119
+ const response = await functionsAgentExecutor.invoke({
120
+ messages: [new HumanMessage("What's the weather like in SF?")],
121
+ });
122
+ // It needs at least one human message, one AI and one function message.
123
+ expect(response.messages.length > 3).toBe(true);
124
+ const firstFunctionMessage = response.messages.find((message) => message._getType() === "function");
125
+ expect(firstFunctionMessage).toBeDefined();
126
+ expect(firstFunctionMessage?.content).toBe(weatherResponse);
127
+ });
100
128
  });
101
129
  describe("createReactAgent", () => {
102
130
  it("can call a tool", async () => {