diff --git a/agents/agents.go b/agents/agents.go index 18bed11f..24221bff 100644 --- a/agents/agents.go +++ b/agents/agents.go @@ -1,4 +1,3 @@ -// Package agents defines the types for langchaingo Agent. package agents import ( diff --git a/agents/doc.go b/agents/doc.go index b65a944b..63e1a06e 100644 --- a/agents/doc.go +++ b/agents/doc.go @@ -1,7 +1,22 @@ -// Package agent contains the standard interface all agents must implement. +// Package agent contains the standard interface all agents must implement, +// implementations of this interface and and agent executor. // // An Agent is a wrapper around a model, which takes in user input and returns // a response corresponding to an “action” to take and a corresponding // “action input”. Alternatively the agent can return a finish with the -// finished answer to the query. +// finished answer to the query. This package contains and standard interface +// for such agents. +// +// Package agents provides and implementation of the agent interface called +// OneShotZeroAgent. This agent uses the ReAct Framework (based on the +// descriptions of tools) to decide what action to take. This agent is +// optimized to be used with LLMs. +// +// To make agents more powerful we need to make them iterative, ie. call the +// model multiple times until they arrive at the final answer. That's the job of +// the Executor. The Executor is an Agent and set of Tools. The agent executor is +// responsible for calling the agent, getting back and action and action input, +// calling the tool that the action references with the corresponding input, +// getting the output of the tool, and then passing all that information back +// into the Agent to get the next action it should take. package agents diff --git a/agents/executor/errors.go b/agents/errors.go similarity index 68% rename from agents/executor/errors.go rename to agents/errors.go index 6ee0d077..917ddbf3 100644 --- a/agents/executor/errors.go +++ b/agents/errors.go @@ -1,4 +1,4 @@ -package executor +package agents import "errors" @@ -14,4 +14,10 @@ var ( ErrUnknownAgentType = errors.New("unknown agent type") // ErrInvalidOptions is returned if the options given to the initializer is invalid. ErrInvalidOptions = errors.New("invalid options") + + // ErrUnableToParseOutput is returned if the output of the llm is unparsable. + ErrUnableToParseOutput = errors.New("unable to parse agent output") + // ErrInvalidChainReturnType is returned if the internal chain of the agent eturns a value in the + // "text" filed that is not a string. + ErrInvalidChainReturnType = errors.New("agent chain did not return a string") ) diff --git a/agents/executor/executor.go b/agents/executor.go similarity index 94% rename from agents/executor/executor.go rename to agents/executor.go index 4aae897c..f0969c93 100644 --- a/agents/executor/executor.go +++ b/agents/executor.go @@ -1,10 +1,9 @@ -package executor +package agents import ( "context" "fmt" - "github.com/tmc/langchaingo/agents" "github.com/tmc/langchaingo/chains" "github.com/tmc/langchaingo/memory" "github.com/tmc/langchaingo/schema" @@ -13,7 +12,7 @@ import ( // Executor is the chain responsible for running agents. type Executor struct { - Agent agents.Agent + Agent Agent Tools []tools.Tool MaxIterations int @@ -23,7 +22,7 @@ var _ chains.Chain = Executor{} // New creates a new agent executor with a agent, the tools the agent can use // and the max number of iterations. -func New(agent agents.Agent, tools []tools.Tool, maxIterations int) Executor { +func New(agent Agent, tools []tools.Tool, maxIterations int) Executor { return Executor{ Agent: agent, Tools: tools, diff --git a/agents/executor/doc.go b/agents/executor/doc.go deleted file mode 100644 index ee9818e5..00000000 --- a/agents/executor/doc.go +++ /dev/null @@ -1,21 +0,0 @@ -// Package executor provides a standard chain for executing agent queries. -// -// The Executor is an Agent and set of Tools. The agent executor is -// responsible for calling the agent, getting back and action and action input, -// calling the tool that the action references with the corresponding input, -// getting the output of the tool, and then passing all that information back -// into the Agent to get the next action it should take. -// -// The package also contains functions to initialize executors with agents and -// supports different agent types and options to customize the agent's behavior. -// -// AgentType is a string type representing the type of agent to create. -// The package currently supports the "zeroShotReactDescription" agent type. -// -// Options is a type alias for a map of string keys to any value, -// representing the options for the agent and the executor. -// -// Option is a function type that can be used to modify the Options, -// such as the WithVerbosity function, which sets the verbosity option -// for the agent. -package executor diff --git a/agents/executor/initialize.go b/agents/executor/initialize.go deleted file mode 100644 index e45de890..00000000 --- a/agents/executor/initialize.go +++ /dev/null @@ -1,81 +0,0 @@ -package executor - -import ( - "github.com/tmc/langchaingo/agents" - "github.com/tmc/langchaingo/agents/mrkl" - "github.com/tmc/langchaingo/llms" - "github.com/tmc/langchaingo/tools" -) - -const _defaultMaxIterations = 5 - -// AgentType is a string type representing the type of agent to create. -type AgentType string - -const ( - // ZeroShotReactDescription is an AgentType constant that represents - // the "zeroShotReactDescription" agent type. - ZeroShotReactDescription AgentType = "zeroShotReactDescription" -) - -// options is a type alias for a map of string keys to any value, -// representing the options for the agent and the executor. -type options map[string]any - -// Option is a function type that can be used to modify the creation of the -// executor and agent. -type Option func(p *options) - -func defaultOptions() options { - return options{ - "verbose": false, - "maxIterations": _defaultMaxIterations, - } -} - -// WithVerbosity is a function that sets the verbosity option for the agent. -func WithVerbosity() Option { - return func(p *options) { - (*p)["verbose"] = true - } -} - -// WithMaxIterations is a function that sets the max iterations for the executor. -func WithMaxIterations(maxIterations int) Option { - return func(p *options) { - (*p)["maxIterations"] = maxIterations - } -} - -// Initialize is a function that creates a new executor with the specified LLM -// model, tools, agent type, and options. It returns an Executor or an error -// if there is any issues during the creation process. -func Initialize( - llm llms.LLM, - tools []tools.Tool, - agentType AgentType, - opts ...Option, -) (Executor, error) { - options := defaultOptions() - for _, opt := range opts { - opt(&options) - } - maxIterations, ok := options["maxIterations"].(int) - if !ok { - return Executor{}, ErrInvalidOptions - } - - var agent agents.Agent - switch agentType { - case ZeroShotReactDescription: - agent = mrkl.NewOneShotAgent(llm, tools, options) - default: - return Executor{}, ErrUnknownAgentType - } - - return Executor{ - Agent: agent, - Tools: tools, - MaxIterations: maxIterations, - }, nil -} diff --git a/agents/executor/executor_test.go b/agents/executor_test.go similarity index 87% rename from agents/executor/executor_test.go rename to agents/executor_test.go index ee3ac18a..cdb21734 100644 --- a/agents/executor/executor_test.go +++ b/agents/executor_test.go @@ -1,4 +1,4 @@ -package executor_test +package agents_test import ( "context" @@ -7,7 +7,7 @@ import ( "testing" "github.com/stretchr/testify/require" - "github.com/tmc/langchaingo/agents/executor" + "github.com/tmc/langchaingo/agents" "github.com/tmc/langchaingo/chains" "github.com/tmc/langchaingo/llms/openai" "github.com/tmc/langchaingo/tools" @@ -32,10 +32,10 @@ func TestMRKL(t *testing.T) { calculator := tools.Calculator{} - a, err := executor.Initialize( + a, err := agents.Initialize( llm, []tools.Tool{searchTool, calculator}, - executor.ZeroShotReactDescription, + agents.ZeroShotReactDescription, ) require.NoError(t, err) diff --git a/agents/initialize.go b/agents/initialize.go new file mode 100644 index 00000000..1b4c8a14 --- /dev/null +++ b/agents/initialize.go @@ -0,0 +1,46 @@ +package agents + +import ( + "github.com/tmc/langchaingo/llms" + "github.com/tmc/langchaingo/tools" +) + +const _defaultMaxIterations = 5 + +// AgentType is a string type representing the type of agent to create. +type AgentType string + +const ( + // ZeroShotReactDescription is an AgentType constant that represents + // the "zeroShotReactDescription" agent type. + ZeroShotReactDescription AgentType = "zeroShotReactDescription" +) + +// Initialize is a function that creates a new executor with the specified LLM +// model, tools, agent type, and options. It returns an Executor or an error +// if there is any issues during the creation process. +func Initialize( + llm llms.LLM, + tools []tools.Tool, + agentType AgentType, + opts ...CreationOption, +) (Executor, error) { + options := executorDefaultOptions() + for _, opt := range opts { + opt(&options) + } + + var agent Agent + switch agentType { + case ZeroShotReactDescription: + agent = NewOneShotAgent(llm, tools, opts...) + default: + return Executor{}, ErrUnknownAgentType + } + + return Executor{ + Agent: agent, + Tools: tools, + MaxIterations: options.maxIterations, + }, nil +} diff --git a/agents/mrkl/markl_test.go b/agents/markl_test.go similarity index 98% rename from agents/mrkl/markl_test.go rename to agents/markl_test.go index 3323274a..97373cf9 100644 --- a/agents/mrkl/markl_test.go +++ b/agents/markl_test.go @@ -1,4 +1,4 @@ -package mrkl +package agents import ( "testing" diff --git a/agents/mrkl/mrkl.go b/agents/mrkl.go similarity index 73% rename from agents/mrkl/mrkl.go rename to agents/mrkl.go index 1ce8a39d..40f6c335 100644 --- a/agents/mrkl/mrkl.go +++ b/agents/mrkl.go @@ -1,31 +1,18 @@ -package mrkl +package agents import ( "context" - "errors" "fmt" "regexp" "strings" "time" - "github.com/tmc/langchaingo/agents" "github.com/tmc/langchaingo/chains" "github.com/tmc/langchaingo/llms" "github.com/tmc/langchaingo/schema" "github.com/tmc/langchaingo/tools" ) -var ( - // ErrUnableToParseOutput is returned if the output of the llm is unparsable. - ErrUnableToParseOutput = errors.New("unable to parse agent output") - // ErrInvalidChainReturnType is returned if the internal chain of the agent - // returns a value in the "text" filed that is not a string. - ErrInvalidChainReturnType = errors.New("agent chain did not return a string") - // ErrInvalidOptions is returned if the options given to the NewOneShotAgent - // function is invalid. - ErrInvalidOptions = errors.New("options given are invalid") -) - const ( _finalAnswerAction = "Final Answer:" _defaultOutputKey = "output" @@ -46,31 +33,19 @@ type OneShotZeroAgent struct { OutputKey string } -var _ agents.Agent = (*OneShotZeroAgent)(nil) - -type OneShotZeroAgentOptions struct { - outputKey string -} - -func checkOptions(opts map[string]any) OneShotZeroAgentOptions { - options := OneShotZeroAgentOptions{ - outputKey: _defaultOutputKey, - } - if outputKey, ok := opts["outputKey"].(string); ok { - options.outputKey = outputKey - } - return options -} +var _ Agent = (*OneShotZeroAgent)(nil) // NewOneShotAgent creates a new OneShotZeroAgent with the given LLM model, tools, // and options. It returns a pointer to the created agent. The opts parameter -// represents the options for the agent. "outputKey" sets the output key of the -// agent. -func NewOneShotAgent(llm llms.LLM, tools []tools.Tool, opts map[string]any) *OneShotZeroAgent { - options := checkOptions(opts) +// represents the options for the agent. +func NewOneShotAgent(llm llms.LLM, tools []tools.Tool, opts ...CreationOption) *OneShotZeroAgent { + options := mrklDefaultOptions() + for _, opt := range opts { + opt(&options) + } return &OneShotZeroAgent{ - Chain: chains.NewLLMChain(llm, CreatePrompt(tools)), + Chain: chains.NewLLMChain(llm, options.getMrklPrompt(tools)), Tools: tools, OutputKey: options.outputKey, } diff --git a/agents/mrkl/doc.go b/agents/mrkl/doc.go deleted file mode 100644 index e6155740..00000000 --- a/agents/mrkl/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Package mrkl provides and implementation of the agent interface that -// uses the ReAct Framework (based on the descriptions of tools) to -// decide what action to take. This agent is optimized to be used with LLMs. -package mrkl diff --git a/agents/mrkl/prompt.go b/agents/mrkl/prompt.go deleted file mode 100644 index 6e3d9f9b..00000000 --- a/agents/mrkl/prompt.go +++ /dev/null @@ -1,129 +0,0 @@ -package mrkl - -import ( - "fmt" - "strings" - - "github.com/tmc/langchaingo/prompts" - "github.com/tmc/langchaingo/tools" -) - -const ( - Prefix = `Today is {{.today}} and you can use tools to get new information. -Answer the following questions as best you can using the following tools: - -{{.tool_descriptions}}` - - FormatInstructions = `Use the following format: - -Question: the input question you must answer -Thought: you should always think about what to do -Action: the action to take, should be one of [ {{.tool_names}} ] -Action Input: the input to the action -Observation: the result of the action -... (this Thought/Action/Action Input/Observation can repeat N times) -Thought: I now know the final answer -Final Answer: the final answer to the original input question` - - Suffix = `Begin! - -Question: {{.input}} -Thought:{{.agent_scratchpad}}` -) - -// CreatePromptOptions is a struct that holds options for creating a prompt template. -type CreatePromptOptions struct { - Prefix string - Suffix string - FormatInstructions string - InputVariables []string -} - -// PromptTemplateOption is a function type that can be used to modify the CreatePromptOptions. -type PromptTemplateOption func(p *CreatePromptOptions) - -// WithPrefix is a function that sets a custom prefix for the prompt template. -func WithPrefix(prefix string) PromptTemplateOption { - return func(p *CreatePromptOptions) { - p.Prefix = prefix - } -} - -// WithSuffix is a function that sets a custom suffix for the prompt template. -func WithSuffix(suffix string) PromptTemplateOption { - return func(p *CreatePromptOptions) { - p.Suffix = suffix - } -} - -// WithFormatInstructions is a function that sets custom format instructions for the prompt template. -func WithFormatInstructions(formatInstructions string) PromptTemplateOption { - return func(p *CreatePromptOptions) { - p.FormatInstructions = formatInstructions - } -} - -// WithInputVariables is a function that sets custom input variables for the prompt template. -func WithInputVariables(inputVariables []string) PromptTemplateOption { - return func(p *CreatePromptOptions) { - p.InputVariables = inputVariables - } -} - -func (options *CreatePromptOptions) handleDefaultValues() { - if options.Prefix == "" { - options.Prefix = Prefix - } - - if options.Suffix == "" { - options.Suffix = Suffix - } - - if options.FormatInstructions == "" { - options.FormatInstructions = FormatInstructions - } -} - -func toolNames(tools []tools.Tool) string { - var tn strings.Builder - for i, tool := range tools { - if i > 0 { - tn.WriteString(", ") - } - tn.WriteString(tool.Name()) - } - - return tn.String() -} - -func toolDescriptions(tools []tools.Tool) string { - var ts strings.Builder - for _, tool := range tools { - ts.WriteString(fmt.Sprintf("- %s: %s\n", tool.Name(), tool.Description())) - } - - return ts.String() -} - -// CreatePrompt is a function that takes a slice of tools and a variadic list of prompt -// template options, and returns a prompt template used by the agent with the specified -// options. -func CreatePrompt(tools []tools.Tool, options ...PromptTemplateOption) prompts.PromptTemplate { - opts := &CreatePromptOptions{} - for _, option := range options { - option(opts) - } - opts.handleDefaultValues() - - template := strings.Join([]string{opts.Prefix, opts.FormatInstructions, opts.Suffix}, "\n\n") - - return prompts.PromptTemplate{ - Template: template, - TemplateFormat: prompts.TemplateFormatGoTemplate, - InputVariables: []string{"input", "agent_scratchpad", "today"}, - PartialVariables: map[string]any{ - "tool_names": toolNames(tools), - "tool_descriptions": toolDescriptions(tools), - }, - } -} diff --git a/agents/mrkl_prompt.go b/agents/mrkl_prompt.go new file mode 100644 index 00000000..bec902bc --- /dev/null +++ b/agents/mrkl_prompt.go @@ -0,0 +1,67 @@ +package agents + +import ( + "fmt" + "strings" + + "github.com/tmc/langchaingo/prompts" + "github.com/tmc/langchaingo/tools" +) + +const ( + _defaultMrklPrefix = `Today is {{.today}} and you can use tools to get new information. +Answer the following questions as best you can using the following tools: + +{{.tool_descriptions}}` + + _defaultMrklFormatInstructions = `Use the following format: + +Question: the input question you must answer +Thought: you should always think about what to do +Action: the action to take, should be one of [ {{.tool_names}} ] +Action Input: the input to the action +Observation: the result of the action +... (this Thought/Action/Action Input/Observation can repeat N times) +Thought: I now know the final answer +Final Answer: the final answer to the original input question` + + _defaultMrklSuffix = `Begin! + +Question: {{.input}} +Thought:{{.agent_scratchpad}}` +) + +func createMRKLPrompt(tools []tools.Tool, prefix, instructions, suffix string) prompts.PromptTemplate { + template := strings.Join([]string{prefix, instructions, suffix}, "\n\n") + + return prompts.PromptTemplate{ + Template: template, + TemplateFormat: prompts.TemplateFormatGoTemplate, + InputVariables: []string{"input", "agent_scratchpad", "today"}, + PartialVariables: map[string]any{ + "tool_names": toolNames(tools), + "tool_descriptions": toolDescriptions(tools), + }, + } +} + +func toolNames(tools []tools.Tool) string { + var tn strings.Builder + for i, tool := range tools { + if i > 0 { + tn.WriteString(", ") + } + tn.WriteString(tool.Name()) + } + + return tn.String() +} + +func toolDescriptions(tools []tools.Tool) string { + var ts strings.Builder + for _, tool := range tools { + ts.WriteString(fmt.Sprintf("- %s: %s\n", tool.Name(), tool.Description())) + } + + return ts.String() +} diff --git a/agents/options.go b/agents/options.go new file mode 100644 index 00000000..7aac5291 --- /dev/null +++ b/agents/options.go @@ -0,0 +1,92 @@ +package agents + +import ( + "github.com/tmc/langchaingo/prompts" + "github.com/tmc/langchaingo/tools" +) + +type creationOptions struct { + maxIterations int + outputKey string + promptPrefix string + formatInstructions string + promptSuffix string + prompt prompts.PromptTemplate +} + +// CreationOption is a function type that can be used to modify the creation of the agents +// and executors. +type CreationOption func(*creationOptions) + +func (co creationOptions) getMrklPrompt(tools []tools.Tool) prompts.PromptTemplate { + if co.prompt.Template != "" { + return co.prompt + } + + return createMRKLPrompt( + tools, + co.promptPrefix, + co.formatInstructions, + co.promptSuffix, + ) +} + +func executorDefaultOptions() creationOptions { + return creationOptions{ + maxIterations: _defaultMaxIterations, + outputKey: _defaultOutputKey, + } +} + +func mrklDefaultOptions() creationOptions { + return creationOptions{ + promptPrefix: _defaultMrklPrefix, + formatInstructions: _defaultMrklFormatInstructions, + promptSuffix: _defaultMrklSuffix, + outputKey: _defaultOutputKey, + } +} + +// WithMaxIterations is an option for setting the max number of iterations the executor +// will complete. +func WithMaxIterations(iterations int) CreationOption { + return func(co *creationOptions) { + co.maxIterations = iterations + } +} + +// WithOutputKey is an option for setting the output key of the agent. +func WithOutputKey(outputKey string) CreationOption { + return func(co *creationOptions) { + co.outputKey = outputKey + } +} + +// WithPromptPrefix is an option for setting the prefix of the prompt used by the agent. +func WithPromptPrefix(prefix string) CreationOption { + return func(co *creationOptions) { + co.promptPrefix = prefix + } +} + +// WithPromptFormatInstructions is an option for setting the format instructions of the +// prompt used by the agent. +func WithPromptFormatInstructions(instructions string) CreationOption { + return func(co *creationOptions) { + co.formatInstructions = instructions + } +} + +// WithPromptFormatInstructions is an option for setting the suffix of the prompt used by the agent. +func WithPromptSuffix(suffix string) CreationOption { + return func(co *creationOptions) { + co.promptSuffix = suffix + } +} + +// WithPrompt is an option for setting the prompt the agent will use. +func WithPrompt(prompt prompts.PromptTemplate) CreationOption { + return func(co *creationOptions) { + co.prompt = prompt + } +}