Closed mamlukishay closed 2 months ago
Hey there, @mamlukishay! I'm here to assist you with your LangChain.js issue. Let's squash this bug together! 🐞
To handle schema validation errors in LangChain.js and ensure that the ToolInputParsingException
is recognized so that the chain does not proceed incorrectly, you can use the call
method of the StructuredTool
class. This method parses the input according to the schema and throws a ToolInputParsingException
if the input does not match the expected schema. Here is an example of how you can handle this:
import { z } from "zod";
import { ToolInputParsingException, StructuredTool } from "path/to/langchain-core/src/tools";
// Define your schema using Zod
const briefSchema = z.object({
requestedKPIs: z.nativeEnum(CampaignOptimizationKPI).optional(),
});
// Create a custom tool by extending StructuredTool
class SubmitCampaignBriefTool extends StructuredTool<typeof briefSchema> {
schema = briefSchema;
async _call(arg: z.output<typeof briefSchema>): Promise<string> {
// Your tool logic here
return `OK, brief updated`;
}
}
// Instantiate your tool
const tool = new SubmitCampaignBriefTool();
// Example usage
async function runTool(input: unknown) {
try {
const result = await tool.call(input);
console.log(result);
} catch (e) {
if (e instanceof ToolInputParsingException) {
console.error("Input parsing error:", e.message);
// Handle the error appropriately
} else {
console.error("Unexpected error:", e);
}
}
}
// Test with invalid input
runTool({ requestedKPIs: 'ctr' });
Additionally, you can handle parsing errors more gracefully by using the handleParsingErrors
option in the AgentExecutor
class. Here is an example:
import { z } from "zod";
import { ChatOpenAI } from "@langchain/openai";
import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents";
import { pull } from "langchain/hub";
import { DynamicStructuredTool } from "@langchain/core/tools";
const model = new ChatOpenAI({ temperature: 0.1 });
const tools = [
new DynamicStructuredTool({
name: "submit-campaign-brief",
description: "submit incremental/full updates about the campaign brief object",
schema: briefSchema,
func: async (input) => {
console.log(input);
return 'OK, brief updated';
},
}),
];
// Get the prompt to use - you can modify this!
const prompt = await pull<ChatPromptTemplate>(
"hwchase17/openai-functions-agent"
);
const agent = await createOpenAIFunctionsAgent({
llm: model,
tools,
prompt,
});
const agentExecutor = new AgentExecutor({
agent,
tools,
verbose: true,
handleParsingErrors:
"Please try again, paying close attention to the allowed enum values",
});
console.log("Loaded agent.");
const input = `Set a reminder to renew our online property ads next week.`;
console.log(`Executing with input "${input}"...`);
const result = await agentExecutor.invoke({ input });
console.log({ result });
In this example, the handleParsingErrors
option provides a custom message to guide the user in correcting their input, ensuring the chain does not proceed incorrectly [1][2].
To continue talking to Dosu, mention @dosu.
@dosu Thanks for giving me a good running example - actually this is a perfect way to reproduce the issue.
The following piece of code doesn't recognize the error as a ToolInputParsingException
, therefore handleParsingErrors
doesn't kick in and I can't use it.
The tool.call()
approach works,, but the agentExecutor.invoke()
approach doesn't.
The problem happens in langchain/dist/agents/executor.js:482
:
if (e instanceof ToolInputParsingException)
returns false although the Error is a ToolInputParsingException
You can see the problem by running the following code. Instead of throwing a ToolInputParsingException
, it returns a "successful" empty observation.
import { z } from "zod";
import { ChatOpenAI } from "@langchain/openai";
import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents";
import { pull } from "langchain/hub";
import { DynamicStructuredTool } from "@langchain/core/tools";
enum CampaignOptimizationKPI {
None = 'none',
Conversions = 'conversions',
Clicks = 'clicks',
CTC = 'ctc',
VideoCompletion = 'video-complation',
Other = 'other',
}
const briefSchema = z.object({
requestedKPIs: z.nativeEnum(CampaignOptimizationKPI).optional(),
});
const model = new ChatOpenAI({ temperature: 0.1 });
const tools = [
new DynamicStructuredTool({
name: "submit-campaign-brief",
description: "submit incremental/full updates about the campaign brief object",
schema: briefSchema,
func: async (input) => {
console.log(input);
return 'OK, brief updated';
},
}),
];
// Get the prompt to use - you can modify this!
const prompt = await pull<ChatPromptTemplate>(
"hwchase17/openai-functions-agent"
);
const agent = await createOpenAIFunctionsAgent({
llm: model,
tools,
prompt,
});
const agentExecutor = new AgentExecutor({
agent,
tools,
verbose: true,
handleParsingErrors:
"Please try again, paying close attention to the allowed enum values",
});
console.log("Loaded agent.");
const input = `set requestedKPIs to 'ctr'`;
console.log(`Executing with input "${input}"...`);
const result = await agentExecutor.invoke({ input });
console.log({ result });
To resolve the issue where the AgentExecutor
in LangChain.js does not recognize a ToolInputParsingException
error, causing the handleParsingErrors
option to not activate and instead return a "successful" empty observation, you need to ensure that the handleParsingErrors
option is correctly set and that the error handling logic is properly implemented.
From the provided code, it appears that the handleParsingErrors
option is already defined and used in multiple places. However, you should verify that the handleParsingErrors
option is correctly passed when creating the AgentExecutor
instance and that the error handling logic is correctly implemented in the _call
and _takeNextStep
methods.
Here is a snippet of the relevant code sections with the error handling logic for ToolInputParsingException
:
// In the _call method
const newSteps = await Promise.all(
actions.map(async (action) => {
await runManager?.handleAgentAction(action);
const tool =
action.tool === "_Exception"
? new ExceptionTool()
: toolsByName[action.tool?.toLowerCase()];
let observation;
try {
observation = tool
? await tool.invoke(
action.toolInput,
patchConfig(config, { callbacks: runManager?.getChild() })
)
: `${action.tool} is not a valid tool, try another one.`;
} catch (e) {
if (e instanceof ToolInputParsingException) {
if (this.handleParsingErrors === true) {
observation = "Invalid or incomplete tool input. Please try again.";
} else if (typeof this.handleParsingErrors === "string") {
observation = this.handleParsingErrors;
} else if (typeof this.handleParsingErrors === "function") {
observation = this.handleParsingErrors(e);
} else {
throw e;
}
observation = await new ExceptionTool().call(
observation,
runManager?.getChild()
);
return { action, observation: observation ?? "" };
}
}
return { action, observation: observation ?? "" };
})
);
// In the _takeNextStep method
for (const agentAction of actions) {
let observation = "";
if (runManager) {
await runManager?.handleAgentAction(agentAction);
}
if (agentAction.tool in nameToolMap) {
const tool = nameToolMap[agentAction.tool];
try {
observation = await tool.call(
agentAction.toolInput,
runManager?.getChild()
);
} catch (e) {
if (e instanceof ToolInputParsingException) {
if (this.handleParsingErrors === true) {
observation = "Invalid or incomplete tool input. Please try again.";
} else if (typeof this.handleParsingErrors === "string") {
observation = this.handleParsingErrors;
} else if (typeof this.handleParsingErrors === "function") {
observation = this.handleParsingErrors(e);
} else {
throw e;
}
observation = await new ExceptionTool().call(
observation,
runManager?.getChild()
);
}
}
} else {
observation = `${
agentAction.tool
} is not a valid tool, try another available tool: ${Object.keys(
nameToolMap
).join(", ")}`;
}
result.push({
action: agentAction,
observation,
});
}
Ensure that the handleParsingErrors
option is correctly set when creating the AgentExecutor
instance:
const executor = new AgentExecutor({
agent: yourAgent,
tools: yourTools,
handleParsingErrors: true, // or a string or function as needed
});
By verifying and ensuring these implementations, the handleParsingErrors
option should correctly handle ToolInputParsingException
errors and prevent returning a "successful" empty observation [1].
To continue talking to Dosu, mention @dosu.
This is not helpful, I need a human hand here
The issue is in the output parser. For example, if you are using react, then this line is to blame for raising an Error instead of an OutputParserException. Changing:
throw new Error(`Could not parse LLM output: ${text}`);
to
throw new OutputParserException(`Could not parse LLM output: ${text}`);
solves the issue for me. If you can validate that this works for you too, I'll submit a pr.
Your fix works and looks good, I think you should open a PR, although it's not exactly my case as I'm using open ai functions agents and not a react agent
Checked other resources
Example Code
The following tool always returns as successful (with empty observation), even if it fails schema validation.
Error Message and Stack Trace (if applicable)
No response
Description
I'm invoking a langchain tool through an agent executor, with a wrong schema. Langchain doesn't identify the error as a
ToolInputParsingException
, therefore my chain proceeds and I get wrong results.This is my tool definition (simplified for the example):
The following input fails schema validation:
This is the stack trace:
This error is checked in executor.js for its instance type, but not recognized as the correct instance.
With this error being swallowed and never thrown, I don't have no ways to handle schema validation problems. This is bad, Please assist. 🙏
System Info
langchain@0.2.12 node v20.11.0 yarn v1.22.22