UNPKG

@langchain/langgraph

Version:

LangGraph

294 lines 10.7 kB
import { AsyncLocalStorageProviderSingleton } from "@langchain/core/singletons"; import { Pregel } from "../pregel/index.js"; import { PregelNode } from "../pregel/read.js"; import { CONFIG_KEY_PREVIOUS_STATE, END, PREVIOUS, START, TAG_HIDDEN, } from "../constants.js"; import { EphemeralValue } from "../channels/ephemeral_value.js"; import { call, getRunnableForEntrypoint } from "../pregel/call.js"; import { LastValue } from "../channels/last_value.js"; import { RunnableCallable, isAsyncGeneratorFunction, isGeneratorFunction, } from "../utils.js"; import { ChannelWrite, PASSTHROUGH } from "../pregel/write.js"; /** * Define a LangGraph task using the `task` function. * * Tasks can only be called from within an {@link entrypoint} or from within a StateGraph. * A task can be called like a regular function with the following differences: * * - When a checkpointer is enabled, the function inputs and outputs must be serializable. * - The wrapped function can only be called from within an entrypoint or StateGraph. * - Calling the function produces a promise. This makes it easy to parallelize tasks. * * @typeParam ArgsT - The type of arguments the task function accepts * @typeParam OutputT - The type of value the task function returns * @param optionsOrName - Either an {@link TaskOptions} object, or a string for the name of the task * @param func - The function that executes this task * @returns A proxy function that accepts the same arguments as the original and always returns the result as a Promise * * @example basic example * ```typescript * const addOne = task("add", async (a: number) => a + 1); * * const workflow = entrypoint("example", async (numbers: number[]) => { * const promises = numbers.map(n => addOne(n)); * const results = await Promise.all(promises); * return results; * }); * * // Call the entrypoint * await workflow.invoke([1, 2, 3]); // Returns [2, 3, 4] * ``` * * @example using a retry policy * ```typescript * const addOne = task({ * name: "add", * retry: { maxAttempts: 3 } * }, * async (a: number) => a + 1 * ); * * const workflow = entrypoint("example", async (numbers: number[]) => { * const promises = numbers.map(n => addOne(n)); * const results = await Promise.all(promises); * return results; * }); * ``` */ export function task(optionsOrName, func) { const options = typeof optionsOrName === "string" ? { name: optionsOrName, retry: undefined, cachePolicy: undefined } : optionsOrName; const { name, retry } = options; if (isAsyncGeneratorFunction(func) || isGeneratorFunction(func)) { throw new Error("Generators are disallowed as tasks. For streaming responses, use config.write."); } const cachePolicy = options.cachePolicy ?? // `cache` was mistakingly used as an alias for `cachePolicy` in v0.3.x, // TODO: remove in 1.x ("cache" in options ? options.cache : undefined); let cache; if (typeof cachePolicy === "boolean") { cache = cachePolicy ? {} : undefined; } else { cache = cachePolicy; } return (...args) => { return call({ func, name, retry, cache }, ...args); }; } /** * Define a LangGraph workflow using the `entrypoint` function. * * ### Function signature * * The wrapped function must accept at most **two parameters**. The first parameter * is the input to the function. The second (optional) parameter is a * {@link LangGraphRunnableConfig} object. If you wish to pass multiple parameters to * the function, you can pass them as an object. * * ### Helper functions * * #### Streaming * To write data to the "custom" stream, use the {@link getWriter} function, or the * {@link LangGraphRunnableConfig.writer} property. * * #### State management * The {@link getPreviousState} function can be used to access the previous state * that was returned from the last invocation of the entrypoint on the same thread id. * * If you wish to save state other than the return value, you can use the * {@link entrypoint.final} function. * * @typeParam InputT - The type of input the entrypoint accepts * @typeParam OutputT - The type of output the entrypoint produces * @param optionsOrName - Either an {@link EntrypointOptions} object, or a string for the name of the entrypoint * @param func - The function that executes this entrypoint * @returns A {@link Pregel} instance that can be run to execute the workflow * * @example Using entrypoint and tasks * ```typescript * import { task, entrypoint } from "@langchain/langgraph"; * import { MemorySaver } from "@langchain/langgraph-checkpoint"; * import { interrupt, Command } from "@langchain/langgraph"; * * const composeEssay = task("compose", async (topic: string) => { * await new Promise(r => setTimeout(r, 1000)); // Simulate slow operation * return `An essay about ${topic}`; * }); * * const reviewWorkflow = entrypoint({ * name: "review", * checkpointer: new MemorySaver() * }, async (topic: string) => { * const essay = await composeEssay(topic); * const humanReview = await interrupt({ * question: "Please provide a review", * essay * }); * return { * essay, * review: humanReview * }; * }); * * // Example configuration for the workflow * const config = { * configurable: { * thread_id: "some_thread" * } * }; * * // Topic for the essay * const topic = "cats"; * * // Stream the workflow to generate the essay and await human review * for await (const result of reviewWorkflow.stream(topic, config)) { * console.log(result); * } * * // Example human review provided after the interrupt * const humanReview = "This essay is great."; * * // Resume the workflow with the provided human review * for await (const result of reviewWorkflow.stream(new Command({ resume: humanReview }), config)) { * console.log(result); * } * ``` * * @example Accessing the previous return value * ```typescript * import { entrypoint, getPreviousState } from "@langchain/langgraph"; * import { MemorySaver } from "@langchain/langgraph-checkpoint"; * * const accumulator = entrypoint({ * name: "accumulator", * checkpointer: new MemorySaver() * }, async (input: string) => { * const previous = getPreviousState<number>(); * return previous !== undefined ? `${previous } ${input}` : input; * }); * * const config = { * configurable: { * thread_id: "some_thread" * } * }; * await accumulator.invoke("hello", config); // returns "hello" * await accumulator.invoke("world", config); // returns "hello world" * ``` * * @example Using entrypoint.final to save a value * ```typescript * import { entrypoint, getPreviousState } from "@langchain/langgraph"; * import { MemorySaver } from "@langchain/langgraph-checkpoint"; * * const myWorkflow = entrypoint({ * name: "accumulator", * checkpointer: new MemorySaver() * }, async (num: number) => { * const previous = getPreviousState<number>(); * * // This will return the previous value to the caller, saving * // 2 * num to the checkpoint, which will be used in the next invocation * // for the `previous` parameter. * return entrypoint.final({ * value: previous ?? 0, * save: 2 * num * }); * }); * * const config = { * configurable: { * thread_id: "some_thread" * } * }; * * await myWorkflow.invoke(3, config); // 0 (previous was undefined) * await myWorkflow.invoke(1, config); // 6 (previous was 3 * 2 from the previous invocation) * ``` */ export const entrypoint = function entrypoint(optionsOrName, func) { const { name, checkpointer, store, cache } = typeof optionsOrName === "string" ? { name: optionsOrName, checkpointer: undefined, store: undefined } : optionsOrName; if (isAsyncGeneratorFunction(func) || isGeneratorFunction(func)) { throw new Error("Generators are disallowed as entrypoints. For streaming responses, use config.write."); } const streamMode = "updates"; const bound = getRunnableForEntrypoint(name, func); // Helper to check if a value is an EntrypointFinal function isEntrypointFinal(value) { return (typeof value === "object" && value !== null && "__lg_type" in value && value.__lg_type === "__pregel_final"); } // Helper function to pluck the return value from EntrypointFinal or passthrough const pluckReturnValue = new RunnableCallable({ name: "pluckReturnValue", func: (value) => { return isEntrypointFinal(value) ? value.value : value; }, }); // Helper function to pluck the save value from EntrypointFinal or passthrough const pluckSaveValue = new RunnableCallable({ name: "pluckSaveValue", func: (value) => { return isEntrypointFinal(value) ? value.save : value; }, }); const entrypointNode = new PregelNode({ bound, triggers: [START], channels: [START], writers: [ new ChannelWrite([ { channel: END, value: PASSTHROUGH, mapper: pluckReturnValue }, { channel: PREVIOUS, value: PASSTHROUGH, mapper: pluckSaveValue }, ], [TAG_HIDDEN]), ], }); return new Pregel({ name, checkpointer, nodes: { [name]: entrypointNode, }, channels: { [START]: new EphemeralValue(), [END]: new LastValue(), [PREVIOUS]: new LastValue(), }, inputChannels: START, outputChannels: END, streamChannels: END, streamMode, store, cache, }); }; // documented by the EntrypointFunction interface entrypoint.final = function final({ value, save, }) { return { value, save, __lg_type: "__pregel_final" }; }; /** * A helper utility function for use with the functional API that returns the previous * state from the checkpoint from the last invocation of the current thread. * * This function allows workflows to access state that was saved in previous runs * using {@link entrypoint.final}. * * @typeParam StateT - The type of the state that was previously saved * @returns The previous saved state from the last invocation of the current thread * * @example * ```typescript * const previousState = getPreviousState<{ counter: number }>(); * const newCount = (previousState?.counter ?? 0) + 1; * ``` */ export function getPreviousState() { const config = AsyncLocalStorageProviderSingleton.getRunnableConfig(); return config.configurable?.[CONFIG_KEY_PREVIOUS_STATE]; } //# sourceMappingURL=index.js.map