524 lines
18 KiB
JavaScript
524 lines
18 KiB
JavaScript
import { Serializable } from "@langchain/core/load/serializable";
|
|
import { patchConfig, RunnableSequence, } from "@langchain/core/runnables";
|
|
/**
|
|
* Error class for parse errors in LangChain. Contains information about
|
|
* the error message and the output that caused the error.
|
|
*/
|
|
class ParseError extends Error {
|
|
constructor(msg, output) {
|
|
super(msg);
|
|
Object.defineProperty(this, "output", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
this.output = output;
|
|
}
|
|
}
|
|
/**
|
|
* Abstract base class for agents in LangChain. Provides common
|
|
* functionality for agents, such as handling inputs and outputs.
|
|
*/
|
|
export class BaseAgent extends Serializable {
|
|
get returnValues() {
|
|
return ["output"];
|
|
}
|
|
get allowedTools() {
|
|
return undefined;
|
|
}
|
|
/**
|
|
* Return the string type key uniquely identifying this class of agent.
|
|
*/
|
|
_agentType() {
|
|
throw new Error("Not implemented");
|
|
}
|
|
/**
|
|
* Return response when agent has been stopped due to max iterations
|
|
*/
|
|
returnStoppedResponse(earlyStoppingMethod, _steps, _inputs, _callbackManager) {
|
|
if (earlyStoppingMethod === "force") {
|
|
return Promise.resolve({
|
|
returnValues: { output: "Agent stopped due to max iterations." },
|
|
log: "",
|
|
});
|
|
}
|
|
throw new Error(`Invalid stopping method: ${earlyStoppingMethod}`);
|
|
}
|
|
/**
|
|
* Prepare the agent for output, if needed
|
|
*/
|
|
async prepareForOutput(_returnValues, _steps) {
|
|
return {};
|
|
}
|
|
}
|
|
/**
|
|
* Abstract base class for single action agents in LangChain. Extends the
|
|
* BaseAgent class and provides additional functionality specific to
|
|
* single action agents.
|
|
*/
|
|
export class BaseSingleActionAgent extends BaseAgent {
|
|
_agentActionType() {
|
|
return "single";
|
|
}
|
|
}
|
|
/**
|
|
* Abstract base class for multi-action agents in LangChain. Extends the
|
|
* BaseAgent class and provides additional functionality specific to
|
|
* multi-action agents.
|
|
*/
|
|
export class BaseMultiActionAgent extends BaseAgent {
|
|
_agentActionType() {
|
|
return "multi";
|
|
}
|
|
}
|
|
function isAgentAction(input) {
|
|
return !Array.isArray(input) && input?.tool !== undefined;
|
|
}
|
|
export function isRunnableAgent(x) {
|
|
return (x.runnable !==
|
|
undefined);
|
|
}
|
|
// TODO: Remove in the future. Only for backwards compatibility.
|
|
// Allows for the creation of runnables with properties that will
|
|
// be passed to the agent executor constructor.
|
|
export class AgentRunnableSequence extends RunnableSequence {
|
|
constructor() {
|
|
super(...arguments);
|
|
Object.defineProperty(this, "streamRunnable", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "singleAction", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
static fromRunnables([first, ...runnables], config) {
|
|
const sequence = RunnableSequence.from([first, ...runnables], config.name);
|
|
sequence.singleAction = config.singleAction;
|
|
sequence.streamRunnable = config.streamRunnable;
|
|
return sequence;
|
|
}
|
|
static isAgentRunnableSequence(x) {
|
|
return typeof x.singleAction === "boolean";
|
|
}
|
|
}
|
|
/**
|
|
* Class representing a single-action agent powered by runnables.
|
|
* Extends the BaseSingleActionAgent class and provides methods for
|
|
* planning agent actions with runnables.
|
|
*/
|
|
export class RunnableSingleActionAgent extends BaseSingleActionAgent {
|
|
get inputKeys() {
|
|
return [];
|
|
}
|
|
constructor(fields) {
|
|
super(fields);
|
|
Object.defineProperty(this, "lc_namespace", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: ["langchain", "agents", "runnable"]
|
|
});
|
|
Object.defineProperty(this, "runnable", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
/**
|
|
* Whether to stream from the runnable or not.
|
|
* If true, the underlying LLM is invoked in a streaming fashion to make it
|
|
* possible to get access to the individual LLM tokens when using
|
|
* `streamLog` with the Agent Executor. If false then LLM is invoked in a
|
|
* non-streaming fashion and individual LLM tokens will not be available
|
|
* in `streamLog`.
|
|
*
|
|
* Note that the runnable should still only stream a single action or
|
|
* finish chunk.
|
|
*/
|
|
Object.defineProperty(this, "streamRunnable", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: true
|
|
});
|
|
Object.defineProperty(this, "defaultRunName", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: "RunnableAgent"
|
|
});
|
|
this.runnable = fields.runnable;
|
|
this.defaultRunName =
|
|
fields.defaultRunName ?? this.runnable.name ?? this.defaultRunName;
|
|
this.streamRunnable = fields.streamRunnable ?? this.streamRunnable;
|
|
}
|
|
async plan(steps, inputs, callbackManager, config) {
|
|
const combinedInput = { ...inputs, steps };
|
|
const combinedConfig = patchConfig(config, {
|
|
callbacks: callbackManager,
|
|
runName: this.defaultRunName,
|
|
});
|
|
if (this.streamRunnable) {
|
|
const stream = await this.runnable.stream(combinedInput, combinedConfig);
|
|
let finalOutput;
|
|
for await (const chunk of stream) {
|
|
if (finalOutput === undefined) {
|
|
finalOutput = chunk;
|
|
}
|
|
else {
|
|
throw new Error([
|
|
`Multiple agent actions/finishes received in streamed agent output.`,
|
|
`Set "streamRunnable: false" when initializing the agent to invoke this agent in non-streaming mode.`,
|
|
].join("\n"));
|
|
}
|
|
}
|
|
if (finalOutput === undefined) {
|
|
throw new Error([
|
|
"No streaming output received from underlying runnable.",
|
|
`Set "streamRunnable: false" when initializing the agent to invoke this agent in non-streaming mode.`,
|
|
].join("\n"));
|
|
}
|
|
return finalOutput;
|
|
}
|
|
else {
|
|
return this.runnable.invoke(combinedInput, combinedConfig);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Class representing a multi-action agent powered by runnables.
|
|
* Extends the BaseMultiActionAgent class and provides methods for
|
|
* planning agent actions with runnables.
|
|
*/
|
|
export class RunnableMultiActionAgent extends BaseMultiActionAgent {
|
|
get inputKeys() {
|
|
return [];
|
|
}
|
|
constructor(fields) {
|
|
super(fields);
|
|
Object.defineProperty(this, "lc_namespace", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: ["langchain", "agents", "runnable"]
|
|
});
|
|
// TODO: Rename input to "intermediate_steps"
|
|
Object.defineProperty(this, "runnable", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "defaultRunName", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: "RunnableAgent"
|
|
});
|
|
Object.defineProperty(this, "stop", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "streamRunnable", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: true
|
|
});
|
|
this.runnable = fields.runnable;
|
|
this.stop = fields.stop;
|
|
this.defaultRunName =
|
|
fields.defaultRunName ?? this.runnable.name ?? this.defaultRunName;
|
|
this.streamRunnable = fields.streamRunnable ?? this.streamRunnable;
|
|
}
|
|
async plan(steps, inputs, callbackManager, config) {
|
|
const combinedInput = { ...inputs, steps };
|
|
const combinedConfig = patchConfig(config, {
|
|
callbacks: callbackManager,
|
|
runName: this.defaultRunName,
|
|
});
|
|
let output;
|
|
if (this.streamRunnable) {
|
|
const stream = await this.runnable.stream(combinedInput, combinedConfig);
|
|
let finalOutput;
|
|
for await (const chunk of stream) {
|
|
if (finalOutput === undefined) {
|
|
finalOutput = chunk;
|
|
}
|
|
else {
|
|
throw new Error([
|
|
`Multiple agent actions/finishes received in streamed agent output.`,
|
|
`Set "streamRunnable: false" when initializing the agent to invoke this agent in non-streaming mode.`,
|
|
].join("\n"));
|
|
}
|
|
}
|
|
if (finalOutput === undefined) {
|
|
throw new Error([
|
|
"No streaming output received from underlying runnable.",
|
|
`Set "streamRunnable: false" when initializing the agent to invoke this agent in non-streaming mode.`,
|
|
].join("\n"));
|
|
}
|
|
output = finalOutput;
|
|
}
|
|
else {
|
|
output = await this.runnable.invoke(combinedInput, combinedConfig);
|
|
}
|
|
if (isAgentAction(output)) {
|
|
return [output];
|
|
}
|
|
return output;
|
|
}
|
|
}
|
|
/** @deprecated Renamed to RunnableMultiActionAgent. */
|
|
export class RunnableAgent extends RunnableMultiActionAgent {
|
|
}
|
|
/**
|
|
* Class representing a single action agent using a LLMChain in LangChain.
|
|
* Extends the BaseSingleActionAgent class and provides methods for
|
|
* planning agent actions based on LLMChain outputs.
|
|
* @example
|
|
* ```typescript
|
|
* const customPromptTemplate = new CustomPromptTemplate({
|
|
* tools: [new Calculator()],
|
|
* inputVariables: ["input", "agent_scratchpad"],
|
|
* });
|
|
* const customOutputParser = new CustomOutputParser();
|
|
* const agent = new LLMSingleActionAgent({
|
|
* llmChain: new LLMChain({
|
|
* prompt: customPromptTemplate,
|
|
* llm: new ChatOpenAI({ temperature: 0 }),
|
|
* }),
|
|
* outputParser: customOutputParser,
|
|
* stop: ["\nObservation"],
|
|
* });
|
|
* const executor = new AgentExecutor({
|
|
* agent,
|
|
* tools: [new Calculator()],
|
|
* });
|
|
* const result = await executor.invoke({
|
|
* input:
|
|
* "Who is Olivia Wilde's boyfriend? What is his current age raised to the 0.23 power?",
|
|
* });
|
|
* ```
|
|
*/
|
|
export class LLMSingleActionAgent extends BaseSingleActionAgent {
|
|
constructor(input) {
|
|
super(input);
|
|
Object.defineProperty(this, "lc_namespace", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: ["langchain", "agents"]
|
|
});
|
|
Object.defineProperty(this, "llmChain", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "outputParser", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "stop", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
this.stop = input.stop;
|
|
this.llmChain = input.llmChain;
|
|
this.outputParser = input.outputParser;
|
|
}
|
|
get inputKeys() {
|
|
return this.llmChain.inputKeys;
|
|
}
|
|
/**
|
|
* Decide what to do given some input.
|
|
*
|
|
* @param steps - Steps the LLM has taken so far, along with observations from each.
|
|
* @param inputs - User inputs.
|
|
* @param callbackManager - Callback manager.
|
|
*
|
|
* @returns Action specifying what tool to use.
|
|
*/
|
|
async plan(steps, inputs, callbackManager) {
|
|
const output = await this.llmChain.call({
|
|
intermediate_steps: steps,
|
|
stop: this.stop,
|
|
...inputs,
|
|
}, callbackManager);
|
|
return this.outputParser.parse(output[this.llmChain.outputKey], callbackManager);
|
|
}
|
|
}
|
|
/**
|
|
* Class responsible for calling a language model and deciding an action.
|
|
*
|
|
* @remarks This is driven by an LLMChain. The prompt in the LLMChain *must*
|
|
* include a variable called "agent_scratchpad" where the agent can put its
|
|
* intermediary work.
|
|
*
|
|
* @deprecated Use {@link https://js.langchain.com/docs/modules/agents/agent_types/ | new agent creation methods}.
|
|
*/
|
|
export class Agent extends BaseSingleActionAgent {
|
|
get allowedTools() {
|
|
return this._allowedTools;
|
|
}
|
|
get inputKeys() {
|
|
return this.llmChain.inputKeys.filter((k) => k !== "agent_scratchpad");
|
|
}
|
|
constructor(input) {
|
|
super(input);
|
|
Object.defineProperty(this, "llmChain", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "outputParser", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: void 0
|
|
});
|
|
Object.defineProperty(this, "_allowedTools", {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: undefined
|
|
});
|
|
this.llmChain = input.llmChain;
|
|
this._allowedTools = input.allowedTools;
|
|
this.outputParser = input.outputParser;
|
|
}
|
|
/**
|
|
* Get the default output parser for this agent.
|
|
*/
|
|
static getDefaultOutputParser(_fields) {
|
|
throw new Error("Not implemented");
|
|
}
|
|
/**
|
|
* Create a prompt for this class
|
|
*
|
|
* @param _tools - List of tools the agent will have access to, used to format the prompt.
|
|
* @param _fields - Additional fields used to format the prompt.
|
|
*
|
|
* @returns A PromptTemplate assembled from the given tools and fields.
|
|
* */
|
|
static createPrompt(_tools,
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
_fields) {
|
|
throw new Error("Not implemented");
|
|
}
|
|
/** Construct an agent from an LLM and a list of tools */
|
|
static fromLLMAndTools(_llm, _tools,
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
_args) {
|
|
throw new Error("Not implemented");
|
|
}
|
|
/**
|
|
* Validate that appropriate tools are passed in
|
|
*/
|
|
static validateTools(_tools) { }
|
|
_stop() {
|
|
return [`\n${this.observationPrefix()}`];
|
|
}
|
|
/**
|
|
* Name of tool to use to terminate the chain.
|
|
*/
|
|
finishToolName() {
|
|
return "Final Answer";
|
|
}
|
|
/**
|
|
* Construct a scratchpad to let the agent continue its thought process
|
|
*/
|
|
async constructScratchPad(steps) {
|
|
return steps.reduce((thoughts, { action, observation }) => thoughts +
|
|
[
|
|
action.log,
|
|
`${this.observationPrefix()}${observation}`,
|
|
this.llmPrefix(),
|
|
].join("\n"), "");
|
|
}
|
|
async _plan(steps, inputs, suffix, callbackManager) {
|
|
const thoughts = await this.constructScratchPad(steps);
|
|
const newInputs = {
|
|
...inputs,
|
|
agent_scratchpad: suffix ? `${thoughts}${suffix}` : thoughts,
|
|
};
|
|
if (this._stop().length !== 0) {
|
|
newInputs.stop = this._stop();
|
|
}
|
|
const output = await this.llmChain.predict(newInputs, callbackManager);
|
|
if (!this.outputParser) {
|
|
throw new Error("Output parser not set");
|
|
}
|
|
return this.outputParser.parse(output, callbackManager);
|
|
}
|
|
/**
|
|
* Decide what to do given some input.
|
|
*
|
|
* @param steps - Steps the LLM has taken so far, along with observations from each.
|
|
* @param inputs - User inputs.
|
|
* @param callbackManager - Callback manager to use for this call.
|
|
*
|
|
* @returns Action specifying what tool to use.
|
|
*/
|
|
plan(steps, inputs, callbackManager) {
|
|
return this._plan(steps, inputs, undefined, callbackManager);
|
|
}
|
|
/**
|
|
* Return response when agent has been stopped due to max iterations
|
|
*/
|
|
async returnStoppedResponse(earlyStoppingMethod, steps, inputs, callbackManager) {
|
|
if (earlyStoppingMethod === "force") {
|
|
return {
|
|
returnValues: { output: "Agent stopped due to max iterations." },
|
|
log: "",
|
|
};
|
|
}
|
|
if (earlyStoppingMethod === "generate") {
|
|
try {
|
|
const action = await this._plan(steps, inputs, "\n\nI now need to return a final answer based on the previous steps:", callbackManager);
|
|
if ("returnValues" in action) {
|
|
return action;
|
|
}
|
|
return { returnValues: { output: action.log }, log: action.log };
|
|
}
|
|
catch (err) {
|
|
// fine to use instanceof because we're in the same module
|
|
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
if (!(err instanceof ParseError)) {
|
|
throw err;
|
|
}
|
|
return { returnValues: { output: err.output }, log: err.output };
|
|
}
|
|
}
|
|
throw new Error(`Invalid stopping method: ${earlyStoppingMethod}`);
|
|
}
|
|
/**
|
|
* Load an agent from a json-like object describing it.
|
|
*/
|
|
static async deserialize(data) {
|
|
switch (data._type) {
|
|
case "zero-shot-react-description": {
|
|
const { ZeroShotAgent } = await import("./mrkl/index.js");
|
|
return ZeroShotAgent.deserialize(data);
|
|
}
|
|
default:
|
|
throw new Error("Unknown agent type");
|
|
}
|
|
}
|
|
}
|