1. 学习背景

LangGraph最近出来后如火如荼,最近AndrewNg又出了新鲜的学习课程,于是乎先了解一波。

学习地址:传送门

代码地址: 传送门

废话不多说,开始干活。

2. 从头开始构建代理(Build an Agent from Scratch)

2.1 准备初始环境

python环境大于等于3.10,其他包自行安装,本实验基于jupyternotebook,接着导入包

import openai
import re
import httpx
import os
# 如果是.env方式存储key,则导入下面的包
from dotenv import load_dotenv

load_dotenv()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

2.2 基础配置及代理

from openai import OpenAI
client = OpenAI(base_url= "XXX/v1", api_key= "XXX")
  • 1.
  • 2.

接着构建一个Agent

class Agent:
    def __init__(self, system=""):
        self.system = system
        self.messages = []
        if self.system:
            self.messages.append({"role": "system", "content": system})

    def __call__(self, message):
        self.messages.append({"role": "user", "content": message})
        result = self.execute()
        self.messages.append({"role": "assistant", "content": result})
        return result

    def execute(self):
        completion = client.chat.completions.create(
                        model="gpt-4o-mini",  # 本例中使用mini模型
                        temperature=0,
                        messages=self.messages)
        return completion.choices[0].message.content
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

至此,一个简单的Agent就构建完成了。

2.3 构建提示词和使用的函数

构建提示词

# 指定提示词格式,回答时候按照Thought, Action, PAUSE, 输出,再根据Observation决定是否调用
prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.

Your available actions are:

calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary

average_dog_weight:
e.g. average_dog_weight: Collie
returns average weight of a dog when given the breed

Example session:

Question: How much does a Bulldog weigh?
Thought: I should look the dogs weight using average_dog_weight
Action: average_dog_weight: Bulldog
PAUSE

You will be called again with this:

Observation: A Bulldog weights 51 lbs

You then output:

Answer: A bulldog weights 51 lbs
""".strip()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

定义可调用的函数

def calculate(what):
    return eval(what) # eval函数可将字符串进行编译执行

def average_dog_weight(name):
    if name in "Scottish Terrier": 
        return("Scottish Terriers average 20 lbs")
    elif name in "Border Collie":
        return("a Border Collies average weight is 37 lbs")
    elif name in "Toy Poodle":
        return("a toy poodles average weight is 7 lbs")
    else:
        return("An average dog weights 50 lbs")

# 定义known_actions,用于后续函数的调用
known_actions = {
    "calculate": calculate,
    "average_dog_weight": average_dog_weight
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

2.4 实际测试

# 实例化Agent
abot = Agent(prompt)

# 调用实例化的agent
result = abot("How much does a toy poodle weigh?")
print(result)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

输出如下:

Thought: I need to find the average weight of a Toy Poodle using the average_dog_weight action. 
Action: average_dog_weight: Toy Poodle
PAUSE
  • 1.
  • 2.
  • 3.

可以看到,模型按照提示词格式给出了回答。接着我们模仿代理,调用对应的函数。

result = average_dog_weight("Toy Poodle")
result
  • 1.
  • 2.

输出如下:

'a toy poodles average weight is 7 lbs'
  • 1.

接着给出对应的观测内容:

next_prompt = "Observation: {}".format(result)
next_prompt
  • 1.
  • 2.

输出如下:

'Observation: a toy poodles average weight is 7 lbs'
  • 1.

接着模型会将调用函数结果作为输出送入大模型。

abot(next_prompt)
  • 1.

输出如下:

'Answer: A Toy Poodle weighs 7 lbs.'
  • 1.

我们可以尝试查看对话内容:

abot.messages
  • 1.

输出如下:

[{'role': 'system',
  'content': 'You run in a loop of Thought, Action, PAUSE, Observation.\nAt the end of the loop you output an Answer\nUse Thought to describe your thoughts about the question you have been asked.\nUse Action to run one of the actions available to you - then return PAUSE.\nObservation will be the result of running those actions.\n\nYour available actions are:\n\ncalculate:\ne.g. calculate: 4 * 7 / 3\nRuns a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary\n\naverage_dog_weight:\ne.g. average_dog_weight: Collie\nreturns average weight of a dog when given the breed\n\nExample session:\n\nQuestion: How much does a Bulldog weigh?\nThought: I should look the dogs weight using average_dog_weight\nAction: average_dog_weight: Bulldog\nPAUSE\n\nYou will be called again with this:\n\nObservation: A Bulldog weights 51 lbs\n\nYou then output:\n\nAnswer: A bulldog weights 51 lbs'},
 {'role': 'user', 'content': 'How much does a toy poodle weigh?'},
 {'role': 'assistant',
  'content': 'Thought: I need to find the average weight of a Toy Poodle using the average_dog_weight action. \nAction: average_dog_weight: Toy Poodle\nPAUSE'},
 {'role': 'user',
  'content': 'Observation: a toy poodles average weight is 7 lbs'},
 {'role': 'assistant', 'content': 'Answer: A Toy Poodle weighs 7 lbs.'}]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

至此,我们自行写的最简单的一个Agent就完成了,其流程是,模拟人的输入,得到Thought;其次执行Action,调用对应的函数得到结果,接着得到执行的结果,送入模型,判断是否需要继续执行,否则认为得到最终的答案结束。

3. 尝试深入学习稍微复杂点的任务

3.1 初始化

abot = Agent(prompt)

question = """I have 2 dogs, a border collie and a scottish terrier. What is their combined weight"""
print(abot(question))
  • 1.
  • 2.
  • 3.
  • 4.

输出如下:

Thought: I need to find the average weights of both a Border Collie and a Scottish Terrier to calculate their combined weight. I'll start by looking up the weight of each breed. 
Action: average_dog_weight: Border Collie
PAUSE
  • 1.
  • 2.
  • 3.

3.2 执行细节

按照上述Action执行,调用average_dog_weight函数,参数Border Colli

next_prompt = "Observation: {}".format(average_dog_weight("Border Collie"))
print(next_prompt)
  • 1.
  • 2.

输出如下:

Observation: a Border Collies average weight is 37 lbs
  • 1.

接下来则将结果反馈给模型:

print(abot(next_prompt))
  • 1.

输出如下:

Thought: Now that I have the weight of the Border Collie, I need to find the average weight of the Scottish Terrier to complete the calculation for their combined weight.
Action: average_dog_weight: Scottish Terrier
PAUSE
  • 1.
  • 2.
  • 3.

按照上述Action执行,调用average_dog_weight函数,参数Scottish Terrier

next_prompt = "Observation: {}".format(average_dog_weight("Scottish Terrier"))
print(next_prompt)
  • 1.
  • 2.

输出如下:

Observation: Scottish Terriers average 20 lbs
  • 1.

接下来则将结果反馈给模型:

print(abot(next_prompt))
  • 1.

输出如下:

Thought: Now that I have the weights of both dogs—37 lbs for the Border Collie and 20 lbs for the Scottish Terrier—I will calculate their combined weight.
Action: calculate: 37 + 20
PAUSE
  • 1.
  • 2.
  • 3.

按照上述Action执行,调用eval函数,参数"37 + 20"

next_prompt = "Observation: {}".format(eval("37 + 20"))
print(next_prompt)
  • 1.
  • 2.

输出如下:

Observation: 57
  • 1.

接下来则将结果反馈给模型:

print(abot(next_prompt))
  • 1.

输出如下:

'Answer: The combined weight of a Border Collie and a Scottish Terrier is 57 lbs.'
  • 1.

4. 尝试循环,让Agent自动化执行

我们所谓的Agent,本质上其实就是用循环去执行任务,在这个过程中,使用关键词匹配,过滤多余信息后,执行对应的函数(其实就是让代理自动执行任务,并调用工具,最后返回结果)。

开始尝试!

4.1 正则过滤

# 该正则表示,用于匹配以"Action: "开头,后面跟着一个或多个单词字符,然后是一个冒号和任意数量的任意字符,直到行尾的字符串。
# 它还特别捕获了"Action: "后面的单词字符和冒号后的所有内容作为两个独立的捕获组。
# 例如,如果使用这个正则表达式来匹配字符串 "Action: login: User has logged in successfully"。
# 那么第一个捕获组将匹配到 "login",第二个捕获组将匹配到 "User has logged in successfully"。

action_re = re.compile('^Action: (\w+): (.*)$')   # regular expression to selection action
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

4.2 定义可查询的循环函数

def query(question, max_turns=5):
    i = 0 # 控制循环轮次
    bot = Agent(prompt) # 初始化
    next_prompt = question
    while i < max_turns:
        i += 1
        result = bot(next_prompt) # 每次将执行的结果,作为下一次提示词返回给模型
        print(result)
        actions = [
            action_re.match(a) 
            for a in result.split('\n') 
            if action_re.match(a)
        ] # Action函数用于得到过滤后的结果,用于获取后续函数执行时的输入和参数
        if actions:
            # There is an action to run
            action, action_input = actions[0].groups()
            if action not in known_actions:
                raise Exception("Unknown action: {}: {}".format(action, action_input))
            print(" -- running {} {}".format(action, action_input))
            observation = known_actions[action](action_input) # 调用函数得到结果
            print("Observation:", observation)
            next_prompt = "Observation: {}".format(observation) # 构造下一次的提示词内容
        else:
            return
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

4.3 实例测试

question = """I have 2 dogs, a border collie and a scottish terrier. \
What is their combined weight"""
query(question)
  • 1.
  • 2.
  • 3.

输出如下:

Thought: I need to find the average weights of both the Border Collie and the Scottish Terrier and then sum them to get the combined weight. I will use the average_dog_weight action for both breeds. 

Action: average_dog_weight: Border Collie 
PAUSE
 -- running average_dog_weight Border Collie 
Observation: An average dog weights 50 lbs
Thought: I just received the average weight for the Border Collie, which is 50 lbs. Now, I need to find the average weight of a Scottish Terrier to complete the calculation. 

Action: average_dog_weight: Scottish Terrier
PAUSE
 -- running average_dog_weight Scottish Terrier
Observation: Scottish Terriers average 20 lbs
Thought: I have the average weights for both dogs now: the Border Collie weighs 50 lbs and the Scottish Terrier weighs 20 lbs. I will add these two weights together to find their combined weight. 

Action: calculate: 50 + 20
PAUSE
 -- running calculate 50 + 20
Observation: 70
Answer: The combined weight of a Border Collie and a Scottish Terrier is 70 lbs.
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

5. 总结

上述执行流程如图所示:

用LangGraph搭建智能体—AI Agents in LangGraph(一)_人工智能

手动写一个简单的Agent执行一次,终于明白了其中的奥秘,原来就是简单的提示词和一次次循环调用,即可完成Thought、Action、Observation等系列操作。