-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): Adds support for context variables (#6967)
- Loading branch information
1 parent
2dcd42c
commit dcef79f
Showing
9 changed files
with
513 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */ | ||
import { AsyncLocalStorage } from "node:async_hooks"; | ||
import { RunTree } from "langsmith"; | ||
import { isRunTree } from "langsmith/run_trees"; | ||
import { | ||
_CONTEXT_VARIABLES_KEY, | ||
AsyncLocalStorageProviderSingleton, | ||
} from "./singletons/index.js"; | ||
|
||
AsyncLocalStorageProviderSingleton.initializeGlobalInstance( | ||
new AsyncLocalStorage() | ||
); | ||
|
||
/** | ||
* Set a context variable. Context variables are scoped to any | ||
* child runnables called by the current runnable, or globally if set outside | ||
* of any runnable. | ||
* | ||
* @remarks | ||
* This function is only supported in environments that support AsyncLocalStorage, | ||
* including Node.js, Deno, and Cloudflare Workers. | ||
* | ||
* @example | ||
* ```ts | ||
* import { RunnableLambda } from "@langchain/core/runnables"; | ||
* import { | ||
* getContextVariable, | ||
* setContextVariable | ||
* } from "@langchain/core/context"; | ||
* | ||
* const nested = RunnableLambda.from(() => { | ||
* // "bar" because it was set by a parent | ||
* console.log(getContextVariable("foo")); | ||
* | ||
* // Override to "baz", but only for child runnables | ||
* setContextVariable("foo", "baz"); | ||
* | ||
* // Now "baz", but only for child runnables | ||
* return getContextVariable("foo"); | ||
* }); | ||
* | ||
* const runnable = RunnableLambda.from(async () => { | ||
* // Set a context variable named "foo" | ||
* setContextVariable("foo", "bar"); | ||
* | ||
* const res = await nested.invoke({}); | ||
* | ||
* // Still "bar" since child changes do not affect parents | ||
* console.log(getContextVariable("foo")); | ||
* | ||
* return res; | ||
* }); | ||
* | ||
* // undefined, because context variable has not been set yet | ||
* console.log(getContextVariable("foo")); | ||
* | ||
* // Final return value is "baz" | ||
* const result = await runnable.invoke({}); | ||
* ``` | ||
* | ||
* @param name The name of the context variable. | ||
* @param value The value to set. | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function setContextVariable(name: PropertyKey, value: any): void { | ||
const runTree = AsyncLocalStorageProviderSingleton.getInstance().getStore(); | ||
const contextVars = { ...runTree?.[_CONTEXT_VARIABLES_KEY] }; | ||
contextVars[name] = value; | ||
let newValue = {}; | ||
if (isRunTree(runTree)) { | ||
newValue = new RunTree(runTree); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
(newValue as any)[_CONTEXT_VARIABLES_KEY] = contextVars; | ||
AsyncLocalStorageProviderSingleton.getInstance().enterWith(newValue); | ||
} | ||
|
||
/** | ||
* Get the value of a previously set context variable. Context variables | ||
* are scoped to any child runnables called by the current runnable, | ||
* or globally if set outside of any runnable. | ||
* | ||
* @remarks | ||
* This function is only supported in environments that support AsyncLocalStorage, | ||
* including Node.js, Deno, and Cloudflare Workers. | ||
* | ||
* @example | ||
* ```ts | ||
* import { RunnableLambda } from "@langchain/core/runnables"; | ||
* import { | ||
* getContextVariable, | ||
* setContextVariable | ||
* } from "@langchain/core/context"; | ||
* | ||
* const nested = RunnableLambda.from(() => { | ||
* // "bar" because it was set by a parent | ||
* console.log(getContextVariable("foo")); | ||
* | ||
* // Override to "baz", but only for child runnables | ||
* setContextVariable("foo", "baz"); | ||
* | ||
* // Now "baz", but only for child runnables | ||
* return getContextVariable("foo"); | ||
* }); | ||
* | ||
* const runnable = RunnableLambda.from(async () => { | ||
* // Set a context variable named "foo" | ||
* setContextVariable("foo", "bar"); | ||
* | ||
* const res = await nested.invoke({}); | ||
* | ||
* // Still "bar" since child changes do not affect parents | ||
* console.log(getContextVariable("foo")); | ||
* | ||
* return res; | ||
* }); | ||
* | ||
* // undefined, because context variable has not been set yet | ||
* console.log(getContextVariable("foo")); | ||
* | ||
* // Final return value is "baz" | ||
* const result = await runnable.invoke({}); | ||
* ``` | ||
* | ||
* @param name The name of the context variable. | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export function getContextVariable(name: PropertyKey): any { | ||
const runTree = AsyncLocalStorageProviderSingleton.getInstance().getStore(); | ||
return runTree?.[_CONTEXT_VARIABLES_KEY]?.[name]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { test, expect } from "@jest/globals"; | ||
import { RunnableLambda } from "../runnables/base.js"; | ||
import { getContextVariable, setContextVariable } from "../context.js"; | ||
|
||
test("Getting and setting context variables within nested runnables", async () => { | ||
const nested = RunnableLambda.from(() => { | ||
expect(getContextVariable("foo")).toEqual("bar"); | ||
expect(getContextVariable("toplevel")).toEqual(9); | ||
setContextVariable("foo", "baz"); | ||
return getContextVariable("foo"); | ||
}); | ||
const runnable = RunnableLambda.from(async () => { | ||
setContextVariable("foo", "bar"); | ||
expect(getContextVariable("foo")).toEqual("bar"); | ||
expect(getContextVariable("toplevel")).toEqual(9); | ||
const res = await nested.invoke({}); | ||
expect(getContextVariable("foo")).toEqual("bar"); | ||
return res; | ||
}); | ||
expect(getContextVariable("foo")).toEqual(undefined); | ||
setContextVariable("toplevel", 9); | ||
expect(getContextVariable("toplevel")).toEqual(9); | ||
const result = await runnable.invoke({}); | ||
expect(getContextVariable("toplevel")).toEqual(9); | ||
expect(result).toEqual("baz"); | ||
}); |
Oops, something went wrong.