agsamantha/node_modules/langchain/dist/agents/agent.js

525 lines
18 KiB
JavaScript
Raw Permalink Normal View History

2024-10-02 15:15:21 -05:00
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");
}
}
}