agsamantha/node_modules/langchain/dist/agents/structured_chat/index.js

250 lines
9.8 KiB
JavaScript
Raw Permalink Normal View History

2024-10-02 15:15:21 -05:00
import { zodToJsonSchema } from "zod-to-json-schema";
import { isOpenAITool, } from "@langchain/core/language_models/base";
import { RunnablePassthrough } from "@langchain/core/runnables";
import { ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate, PromptTemplate, } from "@langchain/core/prompts";
import { isStructuredTool } from "@langchain/core/utils/function_calling";
import { LLMChain } from "../../chains/llm_chain.js";
import { Agent, AgentRunnableSequence, } from "../agent.js";
import { StructuredChatOutputParserWithRetries } from "./outputParser.js";
import { FORMAT_INSTRUCTIONS, PREFIX, SUFFIX } from "./prompt.js";
import { renderTextDescriptionAndArgs } from "../../tools/render.js";
import { formatLogToString } from "../format_scratchpad/log.js";
/**
* Agent that interoperates with Structured Tools using React logic.
* @augments Agent
* @deprecated Use the {@link https://api.js.langchain.com/functions/langchain.agents.createStructuredChatAgent.html | createStructuredChatAgent method instead}.
*/
export class StructuredChatAgent extends Agent {
static lc_name() {
return "StructuredChatAgent";
}
constructor(input) {
const outputParser = input?.outputParser ?? StructuredChatAgent.getDefaultOutputParser();
super({ ...input, outputParser });
Object.defineProperty(this, "lc_namespace", {
enumerable: true,
configurable: true,
writable: true,
value: ["langchain", "agents", "structured_chat"]
});
}
_agentType() {
return "structured-chat-zero-shot-react-description";
}
observationPrefix() {
return "Observation: ";
}
llmPrefix() {
return "Thought:";
}
_stop() {
return ["Observation:"];
}
/**
* Validates that all provided tools have a description. Throws an error
* if any tool lacks a description.
* @param tools Array of StructuredTool instances to validate.
*/
static validateTools(tools) {
const descriptionlessTool = tools.find((tool) => !tool.description);
if (descriptionlessTool) {
const msg = `Got a tool ${descriptionlessTool.name} without a description.` +
` This agent requires descriptions for all tools.`;
throw new Error(msg);
}
}
/**
* Returns a default output parser for the StructuredChatAgent. If an LLM
* is provided, it creates an output parser with retry logic from the LLM.
* @param fields Optional fields to customize the output parser. Can include an LLM and a list of tool names.
* @returns An instance of StructuredChatOutputParserWithRetries.
*/
static getDefaultOutputParser(fields) {
if (fields?.llm) {
return StructuredChatOutputParserWithRetries.fromLLM(fields.llm, {
toolNames: fields.toolNames,
});
}
return new StructuredChatOutputParserWithRetries({
toolNames: fields?.toolNames,
});
}
/**
* Constructs the agent's scratchpad from a list of steps. If the agent's
* scratchpad is not empty, it prepends a message indicating that the
* agent has not seen any previous work.
* @param steps Array of AgentStep instances to construct the scratchpad from.
* @returns A Promise that resolves to a string representing the agent's scratchpad.
*/
async constructScratchPad(steps) {
const agentScratchpad = await super.constructScratchPad(steps);
if (agentScratchpad) {
return `This was your previous work (but I haven't seen any of it! I only see what you return as final answer):\n${agentScratchpad}`;
}
return agentScratchpad;
}
/**
* Creates a string representation of the schemas of the provided tools.
* @param tools Array of StructuredTool instances to create the schemas string from.
* @returns A string representing the schemas of the provided tools.
*/
static createToolSchemasString(tools) {
return tools
.map((tool) => `${tool.name}: ${tool.description}, args: ${JSON.stringify(zodToJsonSchema(tool.schema).properties)}`)
.join("\n");
}
/**
* Create prompt in the style of the agent.
*
* @param tools - List of tools the agent will have access to, used to format the prompt.
* @param args - Arguments to create the prompt with.
* @param args.suffix - String to put after the list of tools.
* @param args.prefix - String to put before the list of tools.
* @param args.inputVariables List of input variables the final prompt will expect.
* @param args.memoryPrompts List of historical prompts from memory.
*/
static createPrompt(tools, args) {
const { prefix = PREFIX, suffix = SUFFIX, inputVariables = ["input", "agent_scratchpad"], humanMessageTemplate = "{input}\n\n{agent_scratchpad}", memoryPrompts = [], } = args ?? {};
const template = [prefix, FORMAT_INSTRUCTIONS, suffix].join("\n\n");
const messages = [
new SystemMessagePromptTemplate(new PromptTemplate({
template,
inputVariables,
partialVariables: {
tool_schemas: StructuredChatAgent.createToolSchemasString(tools),
tool_names: tools.map((tool) => tool.name).join(", "),
},
})),
...memoryPrompts,
new HumanMessagePromptTemplate(new PromptTemplate({
template: humanMessageTemplate,
inputVariables,
})),
];
return ChatPromptTemplate.fromMessages(messages);
}
/**
* Creates a StructuredChatAgent from an LLM and a list of tools.
* Validates the tools, creates a prompt, and sets up an LLM chain for the
* agent.
* @param llm BaseLanguageModel instance to create the agent from.
* @param tools Array of StructuredTool instances to create the agent from.
* @param args Optional arguments to customize the creation of the agent. Can include arguments for creating the prompt and AgentArgs.
* @returns A new instance of StructuredChatAgent.
*/
static fromLLMAndTools(llm, tools, args) {
StructuredChatAgent.validateTools(tools);
const prompt = StructuredChatAgent.createPrompt(tools, args);
const outputParser = args?.outputParser ??
StructuredChatAgent.getDefaultOutputParser({
llm,
toolNames: tools.map((tool) => tool.name),
});
const chain = new LLMChain({
prompt,
llm,
callbacks: args?.callbacks,
});
return new StructuredChatAgent({
llmChain: chain,
outputParser,
allowedTools: tools.map((t) => t.name),
});
}
}
/**
* Create an agent aimed at supporting tools with multiple inputs.
* @param params Params required to create the agent. Includes an LLM, tools, and prompt.
* @returns A runnable sequence representing an agent. It takes as input all the same input
* variables as the prompt passed in does. It returns as output either an
* AgentAction or AgentFinish.
*
* @example
* ```typescript
* import { AgentExecutor, createStructuredChatAgent } from "langchain/agents";
* import { pull } from "langchain/hub";
* import type { ChatPromptTemplate } from "@langchain/core/prompts";
* import { AIMessage, HumanMessage } from "@langchain/core/messages";
*
* import { ChatOpenAI } from "@langchain/openai";
*
* // Define the tools the agent will have access to.
* const tools = [...];
*
* // Get the prompt to use - you can modify this!
* // If you want to see the prompt in full, you can at:
* // https://smith.langchain.com/hub/hwchase17/structured-chat-agent
* const prompt = await pull<ChatPromptTemplate>(
* "hwchase17/structured-chat-agent"
* );
*
* const llm = new ChatOpenAI({
* temperature: 0,
* modelName: "gpt-3.5-turbo-1106",
* });
*
* const agent = await createStructuredChatAgent({
* llm,
* tools,
* prompt,
* });
*
* const agentExecutor = new AgentExecutor({
* agent,
* tools,
* });
*
* const result = await agentExecutor.invoke({
* input: "what is LangChain?",
* });
*
* // With chat history
* const result2 = await agentExecutor.invoke({
* input: "what's my name?",
* chat_history: [
* new HumanMessage("hi! my name is cob"),
* new AIMessage("Hello Cob! How can I assist you today?"),
* ],
* });
* ```
*/
export async function createStructuredChatAgent({ llm, tools, prompt, streamRunnable, }) {
const missingVariables = ["tools", "tool_names", "agent_scratchpad"].filter((v) => !prompt.inputVariables.includes(v));
if (missingVariables.length > 0) {
throw new Error(`Provided prompt is missing required input variables: ${JSON.stringify(missingVariables)}`);
}
let toolNames = [];
if (tools.every(isOpenAITool)) {
toolNames = tools.map((tool) => tool.function.name);
}
else if (tools.every(isStructuredTool)) {
toolNames = tools.map((tool) => tool.name);
}
else {
throw new Error("All tools must be either OpenAI or Structured tools, not a mix.");
}
const partialedPrompt = await prompt.partial({
tools: renderTextDescriptionAndArgs(tools),
tool_names: toolNames.join(", "),
});
// TODO: Add .bind to core runnable interface.
const llmWithStop = llm.bind({
stop: ["Observation"],
});
const agent = AgentRunnableSequence.fromRunnables([
RunnablePassthrough.assign({
agent_scratchpad: (input) => formatLogToString(input.steps),
}),
partialedPrompt,
llmWithStop,
StructuredChatOutputParserWithRetries.fromLLM(llm, {
toolNames,
}),
], {
name: "StructuredChatAgent",
streamRunnable,
singleAction: true,
});
return agent;
}