
除了RAG,我们也可以定义agentTool交给大模型调用,下面我们看一个调用的例子
agentTools := []tools.Tool{
randomNumberTool{},
}
agent := agents.NewOneShotAgent(llm, agentTools)
executor := agents.NewExecutor(
agent,
agents.WithCallbacksHandler(callbacks.LogHandler{}),
)
result, err := chains.Run(ctx, executor, "告诉我一个随机数")
if err != nil {
log.Fatal(err)
}
fmt.Println(result)其中tool的定义如下,包括Name,Description和Call三个方法
type randomNumberTool struct{}
func (r randomNumberTool) Name() string {
return "随机数计算工具"
}
func (r randomNumberTool) Description() string {
return "用于获取随机数"
}
func (r randomNumberTool) Call(ctx context.Context, input string) (string, error) {
return "1024", nil
}它实现了Tool的所有接口
type Tool interface {
Name() string
Description() string
Call(ctx context.Context, input string) (string, error)
}然后用Tool定义了一个Agent
func NewOneShotAgent(llm llms.Model, tools []tools.Tool, opts ...Option) *OneShotZeroAgent {
options := mrklDefaultOptions()
for _, opt := range opts {
opt(&options)
}
return &OneShotZeroAgent{
Chain: chains.NewLLMChain(
llm,
options.getMrklPrompt(tools),
chains.WithCallback(options.callbacksHandler),
),
Tools: tools,
OutputKey: options.outputKey,
CallbacksHandler: options.callbacksHandler,
}
}看到chains.NewLLMChain我们知道,它其实也是langchain中的一个节点
type OneShotZeroAgent struct {
// Chain is the chain used to call with the values. The chain should have an
// input called "agent_scratchpad" for the agent to put its thoughts in.
Chain chains.Chain
// Tools is a list of the tools the agent can use.
Tools []tools.Tool
// Output key is the key where the final output is placed.
OutputKey string
// CallbacksHandler is the handler for callbacks.
CallbacksHandler callbacks.Handler
}他的提示词定义如下,它把tool的名字和描述写入了提示词
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 NewExecutor(agent Agent, opts ...Option) *Executor {
options := executorDefaultOptions()
for _, opt := range opts {
opt(&options)
}
return &Executor{
Agent: agent,
Memory: options.memory,
MaxIterations: options.maxIterations,
ReturnIntermediateSteps: options.returnIntermediateSteps,
CallbacksHandler: options.callbacksHandler,
ErrorHandler: options.errorHandler,
}
}type Executor struct {
Agent Agent
Memory schema.Memory
CallbacksHandler callbacks.Handler
ErrorHandler *ParserErrorHandler
MaxIterations int
ReturnIntermediateSteps bool
}最后还是调用了langchain的Run方法来执行
func Run(ctx context.Context, c Chain, input any, options ...ChainCallOption) (string, error) {
outputValues, err := Call(ctx, c, inputValues, options...)func Call(ctx context.Context, c Chain, inputValues map[string]any, options ...ChainCallOption) (map[string]any, error) { // nolint: lll
outputValues, err := callChain(ctx, c, fullValues, options...)func callChain(
ctx context.Context,
c Chain,
fullValues map[string]any,
options ...ChainCallOption,
) (map[string]any, error) {
outputValues, err := c.Call(ctx, fullValues, options...)只不过Run调用的Call方法是执行器的
func (e *Executor) Call(ctx context.Context, inputValues map[string]any, _ ...chains.ChainCallOption) (map[string]any, error) { //nolint:lll
inputs, err := inputsToString(inputValues)
if err != nil {
return nil, err
}
nameToTool := getNameToTool(e.Agent.GetTools())
steps := make([]schema.AgentStep, 0)
for i := 0; i < e.MaxIterations; i++ {
var finish map[string]any
steps, finish, err = e.doIteration(ctx, steps, nameToTool, inputs)
if finish != nil || err != nil {
return finish, err
}
}
if e.CallbacksHandler != nil {
e.CallbacksHandler.HandleAgentFinish(ctx, schema.AgentFinish{
ReturnValues: map[string]any{"output": ErrNotFinished.Error()},
})
}
return e.getReturn(
&schema.AgentFinish{ReturnValues: make(map[string]any)},
steps,
), ErrNotFinished
}它把agent的tools做成名字到tool的映射,然后执行
func (e *Executor) doIteration( // nolint
ctx context.Context,
steps []schema.AgentStep,
nameToTool map[string]tools.Tool,
inputs map[string]string,
) ([]schema.AgentStep, map[string]any, error) {
actions, finish, err := e.Agent.Plan(ctx, steps, inputs)
if errors.Is(err, ErrUnableToParseOutput) && e.ErrorHandler != nil {
formattedObservation := err.Error()
if e.ErrorHandler.Formatter != nil {
formattedObservation = e.ErrorHandler.Formatter(formattedObservation)
}
steps = append(steps, schema.AgentStep{
Observation: formattedObservation,
})
return steps, nil, nil
}
if err != nil {
return steps, nil, err
}
if len(actions) == 0 && finish == nil {
return steps, nil, ErrAgentNoReturn
}
if finish != nil {
if e.CallbacksHandler != nil {
e.CallbacksHandler.HandleAgentFinish(ctx, *finish)
}
return steps, e.getReturn(finish, steps), nil
}
for _, action := range actions {
steps, err = e.doAction(ctx, steps, nameToTool, action)
if err != nil {
return steps, nil, err
}
}
return steps, nil, nil
}先通过plain拆分成action列表,然后依次执行每个action
func (a *OneShotZeroAgent) Plan(
ctx context.Context,
intermediateSteps []schema.AgentStep,
inputs map[string]string,
) ([]schema.AgentAction, *schema.AgentFinish, error) {
fullInputs := make(map[string]any, len(inputs))
for key, value := range inputs {
fullInputs[key] = value
}
fullInputs["agent_scratchpad"] = constructMrklScratchPad(intermediateSteps)
fullInputs["today"] = time.Now().Format("January 02, 2006")
var stream func(ctx context.Context, chunk []byte) error
if a.CallbacksHandler != nil {
stream = func(ctx context.Context, chunk []byte) error {
a.CallbacksHandler.HandleStreamingFunc(ctx, chunk)
return nil
}
}
output, err := chains.Predict(
ctx,
a.Chain,
fullInputs,
chains.WithStopWords([]string{"\nObservation:", "\n\tObservation:"}),
chains.WithStreamingFunc(stream),
)
if err != nil {
return nil, nil, err
}
return a.parseOutput(output)
}可以看到它也是调用langchain的Call方法,就是交给大模型来拆分步骤:
// Predict can be used to execute a chain if the chain only expects one string output.
func Predict(ctx context.Context, c Chain, inputValues map[string]any, options ...ChainCallOption) (string, error) {
outputValues, err := Call(ctx, c, inputValues, options...)
if err != nil {
return "", err
}
outputKeys := c.GetOutputKeys()
if len(outputKeys) != 1 {
return "", ErrMultipleOutputsInPredict
}
outputValue, ok := outputValues[outputKeys[0]].(string)
if !ok {
return "", ErrOutputNotStringInPredict
}
return outputValue, nil
}最终调用了tool的Call方法完成了放调用
func (e *Executor) doAction(
ctx context.Context,
steps []schema.AgentStep,
nameToTool map[string]tools.Tool,
action schema.AgentAction,
) ([]schema.AgentStep, error) {
if e.CallbacksHandler != nil {
e.CallbacksHandler.HandleAgentAction(ctx, action)
}
tool, ok := nameToTool[strings.ToUpper(action.Tool)]
if !ok {
return append(steps, schema.AgentStep{
Action: action,
Observation: fmt.Sprintf("%s is not a valid tool, try another one", action.Tool),
}), nil
}
observation, err := tool.Call(ctx, action.ToolInput)
if err != nil {
return nil, err
}
return append(steps, schema.AgentStep{
Action: action,
Observation: observation,
}), nil
}可以看到agent已经有MCP的影子了,只不过,它在langchaingo中规定了接口,限制比较严格,没有MCP那么灵活,本质上都是交给大模型决策,然后有调用端发起请求,获得结果。
本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!