agents: refactor options

This commit is contained in:
FluffyKebab
2023-06-07 19:24:38 +02:00
parent 0f452d075d
commit 2cd0d13ccf
14 changed files with 246 additions and 282 deletions
-1
View File
@@ -1,4 +1,3 @@
// Package agents defines the types for langchaingo Agent.
package agents
import (
+17 -2
View File
@@ -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
@@ -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")
)
@@ -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,
-21
View File
@@ -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
-81
View File
@@ -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
}
@@ -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)
+46
View File
@@ -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
}
@@ -1,4 +1,4 @@
package mrkl
package agents
import (
"testing"
+9 -34
View File
@@ -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,
}
-4
View File
@@ -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
-129
View File
@@ -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),
},
}
}
+67
View File
@@ -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()
}
+92
View File
@@ -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
}
}