mirror of
https://github.com/langchain-ai/data-enrichment-js.git
synced 2026-07-01 20:24:10 -04:00
eslint
This commit is contained in:
+2
-2
@@ -14,7 +14,6 @@ module.exports = {
|
||||
ignorePatterns: [
|
||||
".eslintrc.cjs",
|
||||
"scripts",
|
||||
"src/utils/lodash/*",
|
||||
"node_modules",
|
||||
"dist",
|
||||
"dist-cjs",
|
||||
@@ -23,10 +22,11 @@ module.exports = {
|
||||
"*.d.ts",
|
||||
],
|
||||
rules: {
|
||||
"no-process-env": 2,
|
||||
"no-process-env": 0,
|
||||
"no-instanceof/no-instanceof": 2,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||
"@typescript-eslint/no-empty-function": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-shadow": 0,
|
||||
"@typescript-eslint/no-empty-interface": 0,
|
||||
"@typescript-eslint/no-use-before-define": ["error", "nofunc"],
|
||||
|
||||
@@ -43,11 +43,11 @@ Setup instruction auto-generated by `langgraph template lock`. DO NOT EDIT MANUA
|
||||
-->
|
||||
|
||||
<details>
|
||||
<summary>Setup for `modelName`</summary>
|
||||
<summary>Setup for `model`</summary>
|
||||
The `llm` configuration defaults are shown below:
|
||||
|
||||
```yaml
|
||||
modelName: anthropic/claude-3-5-sonnet-20240620
|
||||
model: anthropic/claude-3-5-sonnet-20240620
|
||||
```
|
||||
|
||||
Follow the instructions below to get set up, or pick one of the additional options.
|
||||
@@ -124,7 +124,7 @@ Configuration auto-generated by `langgraph template lock`. DO NOT EDIT MANUALLY.
|
||||
"agent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"modelName": {
|
||||
"model": {
|
||||
"type": "string",
|
||||
"default": "anthropic/claude-3-5-sonnet-20240620",
|
||||
"description": "The name of the language model to use for the agent. Should be in the form: provider/model-name.",
|
||||
|
||||
@@ -12,4 +12,7 @@ export default {
|
||||
],
|
||||
},
|
||||
extensionsToTreatAsEsm: [".ts"],
|
||||
setupFiles: ["dotenv/config"],
|
||||
passWithNoTests: true,
|
||||
testTimeout: 20_000,
|
||||
};
|
||||
|
||||
+9
-7
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "data-enrichment",
|
||||
"version": "0.0.1",
|
||||
"description": "A LangGraph.JS template that populates ",
|
||||
"description": "A starter template for building a research agent that uses a web search tool to populate a user-provided schema.",
|
||||
"main": "src/enrichment_agent/graph.ts",
|
||||
"author": "William Fu-Hinthorn",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "rm -rf dist",
|
||||
@@ -20,23 +21,24 @@
|
||||
"test:all": "yarn test && yarn test:int && yarn lint:langgraph"
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/anthropic": "^0.3.0",
|
||||
"@langchain/community": "^0.3.0",
|
||||
"@langchain/core": "^0.3.1",
|
||||
"@langchain/langgraph": "^0.2.3",
|
||||
"@langchain/anthropic": "^0.3.1",
|
||||
"@langchain/community": "^0.3.1",
|
||||
"@langchain/core": "^0.3.2",
|
||||
"@langchain/langgraph": "^0.2.8",
|
||||
"langchain": "^0.3.2",
|
||||
"langsmith": "^0.1.55",
|
||||
"langsmith": "^0.1.59",
|
||||
"ts-node": "^10.9.2",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@langchain/openai": "^0.2.7",
|
||||
"@jest/globals": "^29.7.0",
|
||||
"@tsconfig/recommended": "^1.0.7",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.8",
|
||||
"@typescript-eslint/parser": "^5.59.8",
|
||||
"dotenv": "^16.4.5",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
|
||||
@@ -3,49 +3,61 @@
|
||||
*/
|
||||
|
||||
import { RunnableConfig } from "@langchain/core/runnables";
|
||||
import { Annotation } from "@langchain/langgraph";
|
||||
import { MAIN_PROMPT } from "./prompts.js";
|
||||
|
||||
export interface Configuration {
|
||||
/**
|
||||
* The complete configuration for the agent.
|
||||
*/
|
||||
export const ConfigurationAnnotation = Annotation.Root({
|
||||
/**
|
||||
* The name of the language model to use for the agent.
|
||||
*
|
||||
* Should be in the form: provider/model-name.
|
||||
*/
|
||||
modelName: string;
|
||||
model: Annotation<string>,
|
||||
|
||||
/**
|
||||
* The main prompt template to use for the agent's interactions.
|
||||
*
|
||||
* Expects two template literals: ${info} and ${topic}.
|
||||
*/
|
||||
prompt: string;
|
||||
prompt: Annotation<string>,
|
||||
|
||||
/**
|
||||
* The maximum number of search results to return for each search query.
|
||||
*/
|
||||
maxSearchResults: number;
|
||||
maxSearchResults: Annotation<number>,
|
||||
|
||||
/**
|
||||
* The maximum number of times the Info tool can be called during a single interaction.
|
||||
*/
|
||||
maxInfoToolCalls: number;
|
||||
maxInfoToolCalls: Annotation<number>,
|
||||
|
||||
/**
|
||||
* The maximum number of interaction loops allowed before the agent terminates.
|
||||
*/
|
||||
maxLoops: number;
|
||||
}
|
||||
maxLoops: Annotation<number>,
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a typeof ConfigurationAnnotation.State instance from a RunnableConfig object.
|
||||
*
|
||||
* @param config - The configuration object to use.
|
||||
* @returns An instance of typeof ConfigurationAnnotation.State with the specified configuration.
|
||||
*/
|
||||
export function ensureConfiguration(
|
||||
config?: RunnableConfig
|
||||
): typeof ConfigurationAnnotation.State {
|
||||
const configurable = (config?.configurable || {}) as Partial<
|
||||
typeof ConfigurationAnnotation.State
|
||||
>;
|
||||
|
||||
export function ensureConfiguration(config?: RunnableConfig): Configuration {
|
||||
/**
|
||||
* Ensure the defaults are populated.
|
||||
*/
|
||||
const configurable = config?.configurable ?? {};
|
||||
return {
|
||||
modelName: configurable.modelName ?? "anthropic/claude-3-5-sonnet-20240620",
|
||||
prompt: configurable.prompt ?? MAIN_PROMPT,
|
||||
maxSearchResults: configurable.maxSearchResults ?? 10,
|
||||
maxInfoToolCalls: configurable.maxInfoToolCalls ?? 3,
|
||||
maxLoops: configurable.maxLoops ?? 6,
|
||||
model: configurable.model || "anthropic/claude-3-5-sonnet-20240620",
|
||||
prompt: configurable.prompt || MAIN_PROMPT,
|
||||
maxSearchResults: configurable.maxSearchResults || 10,
|
||||
maxInfoToolCalls: configurable.maxInfoToolCalls || 3,
|
||||
maxLoops: configurable.maxLoops || 6,
|
||||
};
|
||||
}
|
||||
|
||||
+187
-98
@@ -10,103 +10,171 @@ import {
|
||||
HumanMessage,
|
||||
ToolMessage,
|
||||
} from "@langchain/core/messages";
|
||||
import { RunnableConfig } from "@langchain/core/runnables";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { StateGraph } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
AnyRecord,
|
||||
InputStateAnnotation,
|
||||
State,
|
||||
StateAnnotation,
|
||||
} from "./state.js";
|
||||
import { loadChatModel } from "./utils.js";
|
||||
import { ensureConfiguration } from "./configuration.js";
|
||||
import { MAIN_PROMPT } from "./prompts.js";
|
||||
ConfigurationAnnotation,
|
||||
ensureConfiguration,
|
||||
} from "./configuration.js";
|
||||
import { AnyRecord, InputStateAnnotation, StateAnnotation } from "./state.js";
|
||||
import { toolNode, TOOLS } from "./tools.js";
|
||||
import { RunnableConfig } from "@langchain/core/runnables";
|
||||
import { loadChatModel } from "./utils.js";
|
||||
|
||||
// Define the nodes
|
||||
/**
|
||||
* Calls the primary Language Model (LLM) to decide on the next research action.
|
||||
*
|
||||
* This function performs the following steps:
|
||||
* 1. Initializes configuration and sets up the 'Info' tool, which is the user-defined extraction schema.
|
||||
* 2. Prepares the prompt and message history for the LLM.
|
||||
* 3. Initializes and configures the LLM with available tools.
|
||||
* 4. Invokes the LLM and processes its response.
|
||||
* 5. Handles the LLM's decision to either continue research or submit final info.
|
||||
*
|
||||
* @param state - The current state of the research process.
|
||||
* @param config - Optional configuration for the runnable.
|
||||
* @returns A Promise resolving to an object containing:
|
||||
* - messages: An array of BaseMessage objects representing the LLM's response.
|
||||
* - info: An optional AnyRecord containing the extracted information if the LLM decided to submit final info.
|
||||
* - loopStep: A number indicating the current step in the research loop.
|
||||
*/
|
||||
|
||||
async function callAgentModel(
|
||||
state: State,
|
||||
config?: RunnableConfig,
|
||||
state: typeof StateAnnotation.State,
|
||||
config: RunnableConfig
|
||||
): Promise<{
|
||||
messages: BaseMessage[];
|
||||
info?: AnyRecord;
|
||||
loopStep: number;
|
||||
}> {
|
||||
const configuration = ensureConfiguration(config);
|
||||
// First, define the info tool. This uses the user-provided
|
||||
// json schema to define the research targets
|
||||
const infoTool = tool(async (_args: AnyRecord) => {}, {
|
||||
name: "Info",
|
||||
description: "Call this when you have gathered all the relevant info",
|
||||
schema: state.extractionSchema,
|
||||
});
|
||||
|
||||
const p = MAIN_PROMPT.replace(
|
||||
"{info}",
|
||||
JSON.stringify(state.extractionSchema, null, 2),
|
||||
).replace("{topic}", state.topic);
|
||||
const messages = [new HumanMessage(p), ...state.messages];
|
||||
const rawModel = await loadChatModel(configuration.modelName);
|
||||
// Next, load the model
|
||||
const rawModel = await loadChatModel(configuration.model);
|
||||
if (!rawModel.bindTools) {
|
||||
throw new Error("Chat model does not support tool binding");
|
||||
}
|
||||
const model = rawModel.bindTools([...TOOLS, infoTool]);
|
||||
const response = await model.invoke(messages);
|
||||
const model = rawModel.bindTools([...TOOLS, infoTool], {
|
||||
tool_choice: "any",
|
||||
});
|
||||
|
||||
// Format the schema into the configurable system prompt
|
||||
const p = configuration.prompt
|
||||
.replace("{info}", JSON.stringify(state.extractionSchema, null, 2))
|
||||
.replace("{topic}", state.topic);
|
||||
const messages = [{ role: "user", content: p }, ...state.messages];
|
||||
|
||||
// Next, we'll call the model.
|
||||
const response: AIMessage = await model.invoke(messages);
|
||||
const response_messages = [response];
|
||||
|
||||
// If the model has collected enough information to fill uot
|
||||
// the provided schema, great! It will call the "Info" tool
|
||||
// We've decided to track this as a separate state variable
|
||||
let info;
|
||||
if (
|
||||
((response as AIMessage)?.tool_calls &&
|
||||
(response as AIMessage).tool_calls?.length) ||
|
||||
0
|
||||
) {
|
||||
for (const tool_call of (response as AIMessage).tool_calls || []) {
|
||||
if ((response?.tool_calls && response.tool_calls?.length) || 0) {
|
||||
for (const tool_call of response.tool_calls || []) {
|
||||
if (tool_call.name === "Info") {
|
||||
info = tool_call.args;
|
||||
// If info was called, the agent is submitting a response.
|
||||
// (it's not actually a function to call, it's a schema to extract)
|
||||
// To ensure that the graph doesn'tend up in an invalid state
|
||||
// (where the AI has called tools but no tool message has been provided)
|
||||
// we will drop any extra tool_calls.
|
||||
response.tool_calls = response.tool_calls?.filter(
|
||||
(tool_call) => tool_call.name === "Info"
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If info was called, the agent is submitting a response. Ensure
|
||||
// it's not also trying to use other functions.
|
||||
if (info) {
|
||||
(response as AIMessage).tool_calls = (
|
||||
response as AIMessage
|
||||
).tool_calls?.filter((tool_call) => tool_call.name === "Info");
|
||||
} else {
|
||||
// If LLM didn't respect the tool_choice
|
||||
response_messages.push(
|
||||
new HumanMessage("Please respond by calling one of the provided tools.")
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
messages: [response],
|
||||
messages: response_messages,
|
||||
info,
|
||||
// This increments the step counter.
|
||||
// We configure a max step count to avoid infinite research loops
|
||||
loopStep: 1,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether the current extracted info is satisfactory and complete.
|
||||
*/
|
||||
const InfoIsSatisfactory = z.object({
|
||||
reason: z
|
||||
.array(z.string())
|
||||
.describe(
|
||||
"First, provide reasoning for why this is either good or bad as a final result. Must include at least 3 reasons.",
|
||||
"First, provide reasoning for why this is either good or bad as a final result. Must include at least 3 reasons."
|
||||
),
|
||||
is_satisfactory: z
|
||||
.boolean()
|
||||
.describe(
|
||||
"After providing your reasoning, provide a value indicating whether the result is satisfactory. If not, you will continue researching.",
|
||||
"After providing your reasoning, provide a value indicating whether the result is satisfactory. If not, you will continue researching."
|
||||
),
|
||||
improvement_instructions: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"If the result is not satisfactory, provide clear and specific instructions on what needs to be improved or added to make the information satisfactory. This should include details on missing information, areas that need more depth, or specific aspects to focus on in further research."
|
||||
),
|
||||
});
|
||||
|
||||
async function callChecker(
|
||||
state: State,
|
||||
config?: RunnableConfig,
|
||||
/**
|
||||
* Validates the quality of the data enrichment agent's output.
|
||||
*
|
||||
* This function performs the following steps:
|
||||
* 1. Prepares the initial prompt using the main prompt template.
|
||||
* 2. Constructs a message history for the model.
|
||||
* 3. Prepares a checker prompt to evaluate the presumed info.
|
||||
* 4. Initializes and configures a language model with structured output.
|
||||
* 5. Invokes the model to assess the quality of the gathered information.
|
||||
* 6. Processes the model's response and determines if the info is satisfactory.
|
||||
*
|
||||
* @param state - The current state of the research process.
|
||||
* @param config - Optional configuration for the runnable.
|
||||
* @returns A Promise resolving to an object containing either:
|
||||
* - messages: An array of BaseMessage objects if the info is not satisfactory.
|
||||
* - info: An AnyRecord containing the extracted information if it is satisfactory.
|
||||
*/
|
||||
async function reflect(
|
||||
state: typeof StateAnnotation.State,
|
||||
config: RunnableConfig
|
||||
): Promise<{ messages: BaseMessage[] } | { info: AnyRecord }> {
|
||||
const configuration = ensureConfiguration(config);
|
||||
const p = MAIN_PROMPT.replace(
|
||||
"{info}",
|
||||
JSON.stringify(state.extractionSchema, null, 2),
|
||||
).replace("{topic}", state.topic);
|
||||
const messages = [new HumanMessage(p), ...state.messages.slice(0, -1)];
|
||||
const presumedInfo = state.info;
|
||||
const presumedInfo = state.info; // The current extracted result
|
||||
const lm = state.messages[state.messages.length - 1];
|
||||
if (!(lm._getType() === "ai")) {
|
||||
throw new Error(
|
||||
`${reflect.name} expects the last message in the state to be an AI message with tool calls. Got: ${lm._getType()}`
|
||||
);
|
||||
}
|
||||
const lastMessage = lm as AIMessage;
|
||||
|
||||
// Load the configured model & provide the reflection/critique schema
|
||||
const rawModel = await loadChatModel(configuration.model);
|
||||
const boundModel = rawModel.withStructuredOutput(InfoIsSatisfactory);
|
||||
// Template in the conversation history:
|
||||
const p = configuration.prompt
|
||||
.replace("{info}", JSON.stringify(state.extractionSchema, null, 2))
|
||||
.replace("{topic}", state.topic);
|
||||
const messages = [
|
||||
{ role: "user", content: p },
|
||||
...state.messages.slice(0, -1),
|
||||
];
|
||||
|
||||
const checker_prompt = `I am thinking of calling the info tool with the info below. \
|
||||
Is this good? Give your reasoning as well. \
|
||||
You can encourage the Assistant to look at specific URLs if that seems relevant, or do more searches.
|
||||
@@ -115,77 +183,95 @@ If you don't think it is good, you should be very specific about what could be i
|
||||
{presumed_info}`;
|
||||
const p1 = checker_prompt.replace(
|
||||
"{presumed_info}",
|
||||
JSON.stringify(presumedInfo ?? {}, null, 2),
|
||||
JSON.stringify(presumedInfo ?? {}, null, 2)
|
||||
);
|
||||
messages.push(new HumanMessage(p1));
|
||||
messages.push({ role: "user", content: p1 });
|
||||
|
||||
const rawModel = await loadChatModel(configuration.modelName);
|
||||
const boundModel = rawModel.withStructuredOutput(InfoIsSatisfactory);
|
||||
// Calll the model
|
||||
const response = await boundModel.invoke(messages);
|
||||
|
||||
const lm = state.messages[state.messages.length - 1];
|
||||
if (!(lm._getType() === "ai")) {
|
||||
throw new Error(
|
||||
`${callChecker.name} expects the last message in the state to be an AI message with tool calls. Got: ${lm._getType()}`,
|
||||
);
|
||||
}
|
||||
const lastMessage = lm as AIMessage;
|
||||
|
||||
if (response.is_satisfactory) {
|
||||
try {
|
||||
return { info: presumedInfo };
|
||||
} catch (e) {
|
||||
return {
|
||||
messages: [
|
||||
new ToolMessage({
|
||||
tool_call_id: lastMessage.tool_calls?.[0]?.id || "",
|
||||
content: `Invalid response: ${e}`,
|
||||
name: "Info",
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
if (response.is_satisfactory && presumedInfo) {
|
||||
return {
|
||||
info: presumedInfo,
|
||||
messages: [
|
||||
new ToolMessage({
|
||||
tool_call_id: lastMessage.tool_calls?.[0]?.id || "",
|
||||
content: response.reason.join("\n"),
|
||||
name: "Info",
|
||||
additional_kwargs: { artifact: response },
|
||||
status: "success",
|
||||
}),
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
messages: [
|
||||
new ToolMessage({
|
||||
tool_call_id: lastMessage.tool_calls?.[0]?.id || "",
|
||||
content: JSON.stringify(response),
|
||||
content: `Unsatisfactory response:\n${response.improvement_instructions}`,
|
||||
name: "Info",
|
||||
additional_kwargs: { artifact: response },
|
||||
status: "error",
|
||||
}),
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function routeAfterAgent(state: State): "callChecker" | "tools" | "__end__" {
|
||||
const lastMessage = state.messages[state.messages.length - 1];
|
||||
/**
|
||||
* Determines the next step in the research process based on the agent's last action.
|
||||
*
|
||||
* @param state - The current state of the research process.
|
||||
* @returns "reflect" if the agent has called the "Info" tool to submit findings,
|
||||
* "tools" if the agent has called any other tool or no tool at all.
|
||||
*/
|
||||
function routeAfterAgent(
|
||||
state: typeof StateAnnotation.State
|
||||
): "callAgentModel" | "reflect" | "tools" | "__end__" {
|
||||
const lastMessage: AIMessage = state.messages[state.messages.length - 1];
|
||||
|
||||
if (
|
||||
((lastMessage as AIMessage)?.tool_calls?.length || 0) > 0 &&
|
||||
(lastMessage as AIMessage)?.tool_calls?.[0]?.name === "Info"
|
||||
) {
|
||||
return "callChecker";
|
||||
} else {
|
||||
return "tools";
|
||||
// If for some reason the last message is not an AIMessage
|
||||
// (if you've modified this template and broken one of the assumptions)
|
||||
// ensure the system doesn't crash but instead tries to recover by calling the agent model again.
|
||||
if (lastMessage._getType() !== "ai") {
|
||||
return "callAgentModel";
|
||||
}
|
||||
|
||||
// If the "Info" tool was called, then the model provided its extraction output. Reflect on the result
|
||||
if (lastMessage.tool_calls && lastMessage.tool_calls[0]?.name === "Info") {
|
||||
return "reflect";
|
||||
}
|
||||
|
||||
// The last message is a tool call that is not "Info" (extraction output)
|
||||
return "tools";
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the next node after the checker's evaluation.
|
||||
*
|
||||
* This function determines whether to continue the research process or end it
|
||||
* based on the checker's evaluation and the current state of the research.
|
||||
*
|
||||
* @param state - The current state of the research process.
|
||||
* @param config - The configuration for the research process.
|
||||
* @returns "__end__" if the research should end, "callAgentModel" if it should continue.
|
||||
*/
|
||||
function routeAfterChecker(
|
||||
state: State,
|
||||
config?: RunnableConfig,
|
||||
state: typeof StateAnnotation.State,
|
||||
config?: RunnableConfig
|
||||
): "__end__" | "callAgentModel" {
|
||||
const configuration = ensureConfiguration(config);
|
||||
if (state.loopStep < configuration.maxInfoToolCalls) {
|
||||
const lastMessage = state.messages[state.messages.length - 1];
|
||||
|
||||
if (state.loopStep < configuration.maxLoops) {
|
||||
if (!state.info) {
|
||||
return "callAgentModel";
|
||||
}
|
||||
const lastMessage = state.messages[state.messages.length - 1];
|
||||
if (
|
||||
lastMessage._getType() === "tool" &&
|
||||
(lastMessage as ToolMessage).status === "error"
|
||||
) {
|
||||
if (lastMessage._getType() !== "tool") {
|
||||
throw new Error(
|
||||
`routeAfterChecker expected a tool message. Received: ${lastMessage._getType()}.`
|
||||
);
|
||||
}
|
||||
if ((lastMessage as ToolMessage).status === "error") {
|
||||
// Research deemed unsatisfactory
|
||||
return "callAgentModel";
|
||||
}
|
||||
@@ -197,17 +283,20 @@ function routeAfterChecker(
|
||||
}
|
||||
|
||||
// Create the graph
|
||||
const workflow = new StateGraph({
|
||||
stateSchema: StateAnnotation,
|
||||
input: InputStateAnnotation,
|
||||
})
|
||||
const workflow = new StateGraph(
|
||||
{
|
||||
stateSchema: StateAnnotation,
|
||||
input: InputStateAnnotation,
|
||||
},
|
||||
ConfigurationAnnotation
|
||||
)
|
||||
.addNode("callAgentModel", callAgentModel)
|
||||
.addNode("callChecker", callChecker)
|
||||
.addNode("reflect", reflect)
|
||||
.addNode("tools", toolNode)
|
||||
.addEdge("__start__", "callAgentModel")
|
||||
.addConditionalEdges("callAgentModel", routeAfterAgent)
|
||||
.addEdge("tools", "callAgentModel")
|
||||
.addConditionalEdges("callChecker", routeAfterChecker);
|
||||
.addConditionalEdges("reflect", routeAfterChecker);
|
||||
|
||||
export const graph = workflow.compile();
|
||||
graph.name = "ResearchTopic";
|
||||
|
||||
@@ -84,5 +84,3 @@ export const StateAnnotation = Annotation.Root({
|
||||
// Feel free to add additional attributes to your state as needed.
|
||||
// Common examples include retrieved documents, extracted entities, API connections, etc.
|
||||
});
|
||||
|
||||
export type State = typeof StateAnnotation.State;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
|
||||
import { RunnableConfig } from "@langchain/core/runnables";
|
||||
|
||||
import { ensureConfiguration } from "./configuration.js";
|
||||
import { AnyRecord, State } from "./state.js";
|
||||
import { AnyRecord, StateAnnotation } from "./state.js";
|
||||
import { StructuredTool, tool } from "@langchain/core/tools";
|
||||
import { curry, getTextContent, loadChatModel } from "./utils.js";
|
||||
import {
|
||||
@@ -58,7 +58,7 @@ async function scrapeWebsite(
|
||||
__state,
|
||||
}: {
|
||||
url: string;
|
||||
__state?: State;
|
||||
__state?: typeof StateAnnotation.State;
|
||||
},
|
||||
config: RunnableConfig,
|
||||
): Promise<string> {
|
||||
@@ -75,13 +75,13 @@ async function scrapeWebsite(
|
||||
.replace("{url}", url)
|
||||
.replace("{content}", content);
|
||||
|
||||
const rawModel = await loadChatModel(configuration.modelName);
|
||||
const rawModel = await loadChatModel(configuration.model);
|
||||
const result = await rawModel.invoke(p, config);
|
||||
return getTextContent(result.content);
|
||||
}
|
||||
|
||||
export const createToolNode = (tools: StructuredTool[]) => {
|
||||
const toolNode = async (state: State, config: RunnableConfig) => {
|
||||
const toolNode = async (state: typeof StateAnnotation.State, config: RunnableConfig) => {
|
||||
const message = state.messages[state.messages.length - 1];
|
||||
const outputs = await Promise.all(
|
||||
(message as AIMessage).tool_calls?.map(async (call) => {
|
||||
|
||||
+102
-17
@@ -1,33 +1,118 @@
|
||||
import { describe, it, expect } from "@jest/globals";
|
||||
import { graph } from "../src/enrichment_agent/graph.js";
|
||||
|
||||
describe("Researcher", () => {
|
||||
it("should initialize and compile the graph", () => {
|
||||
expect(graph).toBeDefined();
|
||||
expect(graph.name).toBe("ResearchTopic");
|
||||
});
|
||||
|
||||
it("Simple runthrough", async () => {
|
||||
const enrichmentSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
founder: {
|
||||
type: "string",
|
||||
description: "The name of the company founder.",
|
||||
},
|
||||
websiteUrl: {
|
||||
type: "string",
|
||||
description:
|
||||
"Website URL of the company, e.g.: https://openai.com/, or https://microsoft.com",
|
||||
},
|
||||
const extractionSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
founder: {
|
||||
type: "string",
|
||||
description: "The name of the company founder.",
|
||||
},
|
||||
required: ["founder", "websiteUrl"],
|
||||
};
|
||||
websiteUrl: {
|
||||
type: "string",
|
||||
description:
|
||||
"Website URL of the company, e.g.: https://openai.com/, or https://microsoft.com",
|
||||
},
|
||||
products_sold: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "A list of products sold by the company.",
|
||||
},
|
||||
},
|
||||
required: ["founder", "websiteUrl", "products_sold"],
|
||||
};
|
||||
|
||||
it("Simple runthrough", async () => {
|
||||
const res = await graph.invoke({
|
||||
topic: "LangChain",
|
||||
extractionSchema: enrichmentSchema,
|
||||
extractionSchema: extractionSchema,
|
||||
});
|
||||
|
||||
expect(res.info).toBeDefined();
|
||||
expect(res.info.founder.toLowerCase()).toContain("harrison");
|
||||
}, 100_000);
|
||||
|
||||
const arrayExtractionSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
providers: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", description: "Company name" },
|
||||
technology_summary: {
|
||||
type: "string",
|
||||
description:
|
||||
"Brief summary of their chip technology for LLM training",
|
||||
},
|
||||
current_market_share: {
|
||||
type: "string",
|
||||
description:
|
||||
"Estimated current market share percentage or position",
|
||||
},
|
||||
future_outlook: {
|
||||
type: "string",
|
||||
description:
|
||||
"Brief paragraph on future prospects and developments",
|
||||
},
|
||||
},
|
||||
required: [
|
||||
"name",
|
||||
"technology_summary",
|
||||
"current_market_share",
|
||||
"future_outlook",
|
||||
],
|
||||
},
|
||||
description: "List of top chip providers for LLM Training",
|
||||
},
|
||||
overall_market_trends: {
|
||||
type: "string",
|
||||
description: "Brief paragraph on general trends in the LLM chip market",
|
||||
},
|
||||
},
|
||||
required: ["providers", "overall_market_trends"],
|
||||
};
|
||||
|
||||
it("Researcher list type", async () => {
|
||||
const res = await graph.invoke({
|
||||
topic: "Top 5 chip providers for LLM training",
|
||||
extractionSchema: arrayExtractionSchema,
|
||||
});
|
||||
|
||||
const info = res.info;
|
||||
expect((info.founder as string).toLowerCase()).toContain("harrison");
|
||||
expect(info.providers).toBeDefined();
|
||||
expect(Array.isArray(info.providers)).toBe(true);
|
||||
expect(info.providers.length).toBe(5);
|
||||
|
||||
const nvidiaPresent = info.providers.some(
|
||||
(provider: { name: string }) =>
|
||||
provider.name.toLowerCase().trim() === "nvidia"
|
||||
);
|
||||
expect(nvidiaPresent).toBe(true);
|
||||
|
||||
info.providers.forEach(
|
||||
(provider: {
|
||||
name: any;
|
||||
technology_summary: any;
|
||||
current_market_share: any;
|
||||
future_outlook: any;
|
||||
}) => {
|
||||
expect(provider.name).toBeDefined();
|
||||
expect(provider.technology_summary).toBeDefined();
|
||||
expect(provider.current_market_share).toBeDefined();
|
||||
expect(provider.future_outlook).toBeDefined();
|
||||
}
|
||||
);
|
||||
|
||||
expect(info.overall_market_trends).toBeDefined();
|
||||
expect(typeof info.overall_market_trends).toBe("string");
|
||||
expect(info.overall_market_trends.length).toBeGreaterThan(0);
|
||||
}, 100_000);
|
||||
});
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
"@jridgewell/gen-mapping" "^0.3.5"
|
||||
"@jridgewell/trace-mapping" "^0.3.24"
|
||||
|
||||
"@anthropic-ai/sdk@^0.25.2":
|
||||
version "0.25.2"
|
||||
resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.25.2.tgz#ad790b0876e2a72da9a9d6b4cea88a10b2dbc181"
|
||||
integrity sha512-F1Hck/asswwidFLtGdMg3XYgRxEUfygNbpkq5KEaEGsHNaSfxeX18/uZGQCL0oQNcj/tYNx8BaFXVwRhFDi45g==
|
||||
"@anthropic-ai/sdk@^0.27.3":
|
||||
version "0.27.3"
|
||||
resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.27.3.tgz#592cdd873c85ffab9589ae6f2e250cbf150e1475"
|
||||
integrity sha512-IjLt0gd3L4jlOfilxVXTifn42FnVffMgDC04RJK1KDZpmkBWLv0XC92MVVmkxrFZNS/7l3xWgP/I3nqtX1sQHw==
|
||||
dependencies:
|
||||
"@types/node" "^18.11.18"
|
||||
"@types/node-fetch" "^2.6.4"
|
||||
@@ -630,20 +630,20 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@langchain/anthropic@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@langchain/anthropic/-/anthropic-0.3.0.tgz#bc8ad0d98a6c1354c51cdd72f29132573946cd71"
|
||||
integrity sha512-vEi2BGadET2nMa+0NaEFm2bcdGjmDhedQvTcX/x6/mYQI2XVi1yJM5Kxq5Os7Wo7EazEngEtC4uoaa3jlQM8hw==
|
||||
"@langchain/anthropic@^0.3.1":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@langchain/anthropic/-/anthropic-0.3.1.tgz#7981e90e87566dedbd6cd5e4534a205cd618ec13"
|
||||
integrity sha512-mE2zfv2IeogDgP53IYlfqZuXbmACkQDtBkg1Nriasrzvr1g+1Q85pfyUCnxc05p4S2IplbJhvoBtgdQUTGenqw==
|
||||
dependencies:
|
||||
"@anthropic-ai/sdk" "^0.25.2"
|
||||
"@anthropic-ai/sdk" "^0.27.3"
|
||||
fast-xml-parser "^4.4.1"
|
||||
zod "^3.22.4"
|
||||
zod-to-json-schema "^3.22.4"
|
||||
|
||||
"@langchain/community@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@langchain/community/-/community-0.3.0.tgz#03a14ca57ec43b3536ab093d9992224b48f4b542"
|
||||
integrity sha512-JUmXn4wt/STMJVN0ZiQGjOF2MMrhv9sJ78KG+7hwSKlxTl1QfhPFO1jr5psls4Ok4TDKaLw/ULziNGEN5cCR/Q==
|
||||
"@langchain/community@^0.3.1":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@langchain/community/-/community-0.3.1.tgz#3e4dc43f1d597395562e30f2f4021f10899f958f"
|
||||
integrity sha512-V6n4kkv3isYoiU2ciLNhcqBT96IwxpMNt5K/KxkzOfGNJIWQcsPzTccuWZXdDOQ40kelCg+S8YKGVy8X4mkgtg==
|
||||
dependencies:
|
||||
"@langchain/openai" ">=0.2.0 <0.4.0"
|
||||
binary-extensions "^2.2.0"
|
||||
@@ -651,38 +651,21 @@
|
||||
flat "^5.0.2"
|
||||
js-yaml "^4.1.0"
|
||||
langchain ">=0.2.3 <0.4.0"
|
||||
langsmith "~0.1.30"
|
||||
langsmith "~0.1.56"
|
||||
uuid "^10.0.0"
|
||||
zod "^3.22.3"
|
||||
zod-to-json-schema "^3.22.5"
|
||||
|
||||
"@langchain/core@>=0.2.26 <0.3.0":
|
||||
version "0.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.2.33.tgz#57cdd6b81dc1173a5ed2a2e38d7563057aa09ea2"
|
||||
integrity sha512-8WMied0Y5c4YDUjvoQqr/M63jAuZ3e+YqCAaszeGeBJbsHFoCdj8Ppr/yP6moAvsLImntxUXC6BxqAIDYkpGkg==
|
||||
"@langchain/core@^0.3.2":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.2.tgz#aff6d83149a40e0e735910f583aca0f1dd7d1bab"
|
||||
integrity sha512-FeoDOStP8l1YdxgykpXnVoEnl4lxGNSOdYzUJN/EdFtkc6cIjDDS5+xewajme0+egaUsO4tGLezKaFpoWxAyQA==
|
||||
dependencies:
|
||||
ansi-styles "^5.0.0"
|
||||
camelcase "6"
|
||||
decamelize "1.2.0"
|
||||
js-tiktoken "^1.0.12"
|
||||
langsmith "^0.1.56-rc.1"
|
||||
mustache "^4.2.0"
|
||||
p-queue "^6.6.2"
|
||||
p-retry "4"
|
||||
uuid "^10.0.0"
|
||||
zod "^3.22.4"
|
||||
zod-to-json-schema "^3.22.3"
|
||||
|
||||
"@langchain/core@^0.3.1":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.3.1.tgz#f06206809575b2a95eaef609b3273842223c0786"
|
||||
integrity sha512-xYdTAgS9hYPt+h0/OwpyRcMB5HKR40LXutbSr2jw3hMVIOwD1DnvhnUEnWgBK4lumulVW2jrosNPyBKMhRZAZg==
|
||||
dependencies:
|
||||
ansi-styles "^5.0.0"
|
||||
camelcase "6"
|
||||
decamelize "1.2.0"
|
||||
js-tiktoken "^1.0.12"
|
||||
langsmith "^0.1.56-rc.1"
|
||||
langsmith "^0.1.56"
|
||||
mustache "^4.2.0"
|
||||
p-queue "^6.6.2"
|
||||
p-retry "4"
|
||||
@@ -697,10 +680,10 @@
|
||||
dependencies:
|
||||
uuid "^10.0.0"
|
||||
|
||||
"@langchain/langgraph@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.3.tgz#34072f68536706a42c7fb978f1ab5373c058e2f5"
|
||||
integrity sha512-agBa79dgKk08B3gNE9+SSLYLmlhBwMaCPsME5BlIFJjs2j2lDnSgKtUfQ9nE4e3Q51L9AA4DjIxmxJiQtS3GOw==
|
||||
"@langchain/langgraph@^0.2.8":
|
||||
version "0.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.2.8.tgz#9606982686ee857064a217dc5599ebdbc9aaf2fe"
|
||||
integrity sha512-sQ3NqwZzdvILeiYQQCDCBFj+FLd3oBfg2sxMo3e5g7vd5+zd/hpK5+JRTHbsMZte0PTAlTbQ5YbfCC2D6K9AVw==
|
||||
dependencies:
|
||||
"@langchain/langgraph-checkpoint" "~0.0.6"
|
||||
double-ended-queue "^2.1.0-0"
|
||||
@@ -717,17 +700,6 @@
|
||||
zod "^3.22.4"
|
||||
zod-to-json-schema "^3.22.3"
|
||||
|
||||
"@langchain/openai@^0.2.7":
|
||||
version "0.2.11"
|
||||
resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.2.11.tgz#b1a0403eb5db8133bb4ff41fe0680e727b78ddfc"
|
||||
integrity sha512-Pu8+WfJojCgSf0bAsXb4AjqvcDyAWyoEB1AoCRNACgEnBWZuitz3hLwCo9I+6hAbeg3QJ37g82yKcmvKAg1feg==
|
||||
dependencies:
|
||||
"@langchain/core" ">=0.2.26 <0.3.0"
|
||||
js-tiktoken "^1.0.12"
|
||||
openai "^4.57.3"
|
||||
zod "^3.22.4"
|
||||
zod-to-json-schema "^3.22.3"
|
||||
|
||||
"@langchain/textsplitters@>=0.0.0 <0.2.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@langchain/textsplitters/-/textsplitters-0.1.0.tgz#f37620992192df09ecda3dfbd545b36a6bcbae46"
|
||||
@@ -904,11 +876,6 @@
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/qs@^6.9.15":
|
||||
version "6.9.16"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.16.tgz#52bba125a07c0482d26747d5d4947a64daf8f794"
|
||||
integrity sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==
|
||||
|
||||
"@types/retry@0.12.0":
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
|
||||
@@ -1619,6 +1586,11 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dotenv@^16.4.5:
|
||||
version "16.4.5"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
|
||||
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
|
||||
|
||||
double-ended-queue@^2.1.0-0:
|
||||
version "2.1.0-0"
|
||||
resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
|
||||
@@ -1632,9 +1604,9 @@ ejs@^3.1.10:
|
||||
jake "^10.8.5"
|
||||
|
||||
electron-to-chromium@^1.5.4:
|
||||
version "1.5.23"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.23.tgz#6dabd8f7fec5cbf618b732ff4c42950dcc7a3be5"
|
||||
integrity sha512-mBhODedOXg4v5QWwl21DjM5amzjmI1zw9EPrPK/5Wx7C8jt33bpZNrC7OhHUG3pxRtbLpr3W2dXT+Ph1SsfRZA==
|
||||
version "1.5.25"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.25.tgz#492ade1cde401332b9b75aa0c55fd5e1550ca66c"
|
||||
integrity sha512-kMb204zvK3PsSlgvvwzI3wBIcAw15tRkYk+NQdsjdDtcQWTp2RABbMQ9rUBy8KNEOM+/E6ep+XC3AykiWZld4g==
|
||||
|
||||
emittery@^0.13.1:
|
||||
version "0.13.1"
|
||||
@@ -3068,22 +3040,10 @@ kleur@^3.0.3:
|
||||
zod "^3.22.4"
|
||||
zod-to-json-schema "^3.22.3"
|
||||
|
||||
langsmith@^0.1.55, langsmith@~0.1.30:
|
||||
version "0.1.55"
|
||||
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.1.55.tgz#bdbb8015a28093f4a248c0ee9b8937731c5baa93"
|
||||
integrity sha512-6NVtI04UUnIY59I/imOX02FG/QMGfqStu8tiJtyyreKMv2GAN0EE9Z5Ap1wzOe6v8ukEcV3NwEO2LYOPwup1PQ==
|
||||
dependencies:
|
||||
"@types/uuid" "^10.0.0"
|
||||
commander "^10.0.1"
|
||||
p-queue "^6.6.2"
|
||||
p-retry "4"
|
||||
semver "^7.6.3"
|
||||
uuid "^10.0.0"
|
||||
|
||||
langsmith@^0.1.56-rc.1:
|
||||
version "0.1.56-rc.1"
|
||||
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.1.56-rc.1.tgz#20900ff0dee51baea359c6f16a4acc260f07fbb7"
|
||||
integrity sha512-XsOxlhBAlTCGR9hNEL2VSREmiz8v6czNuX3CIwec9fH9T0WbNPle8Q/7Jy/h9UCbS9vuzTjfgc4qO5Dc9cu5Ig==
|
||||
langsmith@^0.1.56, langsmith@^0.1.56-rc.1, langsmith@^0.1.59, langsmith@~0.1.56:
|
||||
version "0.1.59"
|
||||
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.1.59.tgz#5fbf6a7e1adc10caf922f0cfa86aa3b5a0408ee7"
|
||||
integrity sha512-dW+z6s538zBswFFP2w/xzvVef7y2+yNt6GkmRCeLtwfpbMaM4di7JboK3vmnZ+0/LjNb2ukiMmgsTNKu/Y43cg==
|
||||
dependencies:
|
||||
"@types/uuid" "^10.0.0"
|
||||
commander "^10.0.1"
|
||||
@@ -3331,19 +3291,17 @@ onetime@^5.1.2:
|
||||
mimic-fn "^2.1.0"
|
||||
|
||||
openai@^4.57.3:
|
||||
version "4.61.1"
|
||||
resolved "https://registry.yarnpkg.com/openai/-/openai-4.61.1.tgz#1fe2fa231b6de54fad32785528d7628dbbf68ab4"
|
||||
integrity sha512-jZ2WRn+f4QWZkYnrUS+xzEUIBllsGN75dUCaXmMIHcv2W9yn7O8amaReTbGHCNEYkL43vuDOcxPUWfNPUmoD3Q==
|
||||
version "4.62.1"
|
||||
resolved "https://registry.yarnpkg.com/openai/-/openai-4.62.1.tgz#ebf9ae0a0c367463162e7b822a76e16efef6139d"
|
||||
integrity sha512-Aa6i4oBR1tV8E2d2p3MvXg57X98i8gZtHq4bQNX274qLKZVX7PXXq5P1FMonTXOrX3zwvkqN1iNccn3XK3CwVg==
|
||||
dependencies:
|
||||
"@types/node" "^18.11.18"
|
||||
"@types/node-fetch" "^2.6.4"
|
||||
"@types/qs" "^6.9.15"
|
||||
abort-controller "^3.0.0"
|
||||
agentkeepalive "^4.2.1"
|
||||
form-data-encoder "1.7.2"
|
||||
formdata-node "^4.3.2"
|
||||
node-fetch "^2.6.7"
|
||||
qs "^6.10.3"
|
||||
|
||||
openapi-types@^12.1.3:
|
||||
version "12.1.3"
|
||||
@@ -3536,13 +3494,6 @@ pure-rand@^6.0.0:
|
||||
resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2"
|
||||
integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==
|
||||
|
||||
qs@^6.10.3:
|
||||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
|
||||
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
|
||||
dependencies:
|
||||
side-channel "^1.0.6"
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
@@ -3686,7 +3637,7 @@ shebang-regex@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||
|
||||
side-channel@^1.0.4, side-channel@^1.0.6:
|
||||
side-channel@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
|
||||
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
|
||||
|
||||
Reference in New Issue
Block a user