Skip to main content

LangChain中的生成式Agent

本笔记本实现了一种基于论文Generative Agents: Interactive Simulacra of Human Behavior的生成式Agent,作者是Park等人。

在这个实现中,我们利用了一个由LangChain Retriever支持的时间加权的Memory对象。

# 使用termcolor使输出易于着色。
pip install termcolor > /dev/null
import logging

logging.basicConfig(level=logging.ERROR)
from datetime import datetime, timedelta
from typing import List
from termcolor import colored


from langchain.chat_models import ChatOpenAI
from langchain.docstore import InMemoryDocstore
from langchain.embeddings import OpenAIEmbeddings
from langchain.retrievers import TimeWeightedVectorStoreRetriever
from langchain.vectorstores import FAISS
USER_NAME = "Person A"  # 在与Agent进行面试时要使用的名称。
LLM = ChatOpenAI(max_tokens=1500) # 可以是任何您想要的LLM。

生成式代理记忆组件 (Generative Agent Memory Components)

本教程重点介绍生成式代理的记忆及其对其行为的影响。该记忆与标准的 LangChain Chat 记忆在两个方面有所不同:

  1. 记忆形成

    生成式代理具有扩展的记忆,以单一流的形式存储:

    1. 观察 - 来自对话或与虚拟世界的交互,关于自己或他人的观察
    2. 反思 - 重新浮现和总结的核心记忆
  1. 记忆使用

    记忆通过权重和重要性的加权和来检索。

您可以在参考文档中查看 GenerativeAgentGenerativeAgentMemory 的定义,重点关注 add_memorysummarize_related_memories 方法。

from langchain_experimental.generative_agents import (
GenerativeAgent,
GenerativeAgentMemory,
)

记忆生命周期 (Memory Lifecycle)

总结上述的关键方法:add_memorysummarize_related_memories

当一个代理进行观察时,它会存储记忆:

  1. 语言模型评分记忆的重要性(平凡为1,深刻为10)。
  2. 观察和重要性通过 TimeWeightedVectorStoreRetriever 存储在一个文档中,并带有 last_accessed_time

当一个代理对观察做出回应时:

  1. 为检索器生成查询,根据显著性、最新性和重要性获取文档。
  2. 总结检索到的信息。
  3. 更新已使用文档的 last_accessed_time

创建一个生成性角色 (Create a Generative Character)

现在我们已经了解了定义,我们将创建两个名为"Tommie"和"Eve"的角色。

import math
import faiss


def relevance_score_fn(score: float) -> float:
"""返回一个在[0, 1]范围内的相似度分数。"""
# 这将根据以下几个因素而有所不同:
# - VectorStore使用的距离/相似度度量
# - 嵌入的规模(OpenAI的单位规范,其他许多嵌入不是!)
# 此函数将归一化嵌入的欧几里得范数(0最相似,sqrt(2)最不相似)
# 转换为相似度函数(0到1)
return 1.0 - score / math.sqrt(2)


def create_new_memory_retriever():
"""创建一个与代理程序唯一对应的新向量存储检索器。"""
# 定义嵌入模型
embeddings_model = OpenAIEmbeddings()
# 将向量存储初始化为空
embedding_size = 1536
index = faiss.IndexFlatL2(embedding_size)
vectorstore = FAISS(
embeddings_model.embed_query,
index,
InMemoryDocstore({}),
{},
relevance_score_fn=relevance_score_fn,
)
return TimeWeightedVectorStoreRetriever(
vectorstore=vectorstore, other_score_keys=["importance"], k=15
)
tommies_memory = GenerativeAgentMemory(
llm=LLM,
memory_retriever=create_new_memory_retriever(),
verbose=False,
reflection_threshold=8, # 我们将给它一个相对较低的数字以展示反思的工作原理
)

tommie = GenerativeAgent(
name="Tommie",
age=25,
traits="anxious, likes design, talkative", # 您可以在这里添加更多持久的特征
status="looking for a job", # 当连接到虚拟世界时,我们可以让角色更新其状态
memory_retriever=create_new_memory_retriever(),
llm=LLM,
memory=tommies_memory,
)
# 由于代理程序尚未进行任何观察,因此无法生成当前角色的"Summary"。
print(tommie.get_summary())
    Name: Tommie (age: 25)
Innate traits: anxious, likes design, talkative
No information about Tommie's core characteristics is provided in the given statements.
# 我们可以直接将记忆添加到记忆对象中
tommie_observations = [
"Tommie remembers his dog, Bruno, from when he was a kid",
"Tommie feels tired from driving so far",
"Tommie sees the new home",
"The new neighbors have a cat",
"The road is noisy at night",
"Tommie is hungry",
"Tommie tries to get some rest.",
]
for observation in tommie_observations:
tommie.memory.add_memory(observation)
# 现在Tommie有了"记忆",他们的自我总结更加描述性,尽管仍然简陋。
# 我们将看到在更多观察之后,这个总结如何更新以创建更丰富的描述。
print(tommie.get_summary(force_refresh=True))
    Name: Tommie (age: 25)
Innate traits: anxious, likes design, talkative
Tommie is a person who is observant of his surroundings, has a sentimental side, and experiences basic human needs such as hunger and the need for rest. He also tends to get tired easily and is affected by external factors such as noise from the road or a neighbor's pet.

面试前与角色交流

在让我们的角色上路之前,让我们问他们几个问题。

def interview_agent(agent: GenerativeAgent, message: str) -> str:
"""帮助笔记本用户与代理进行交互。"""
new_message = f"{USER_NAME}说:{message}"
return agent.generate_dialogue_response(new_message)[1]
interview_agent(tommie, "你喜欢做什么?")
    'Tommie说:“我非常喜欢设计和创造。最近我一直在做一些个人项目。你呢,A先生/女士?你喜欢做什么?”'
interview_agent(tommie, "你今天期待做什么?")
    'Tommie说:“嗯,实际上我正在找工作,希望我能在网上找到一些工作岗位并开始申请。你呢,A先生/女士?你今天的日程安排是什么?”'
interview_agent(tommie, "你今天最担心什么?")
    'Tommie说:“老实说,我对找工作感到非常焦虑。最近有点困难,但我试图保持积极并继续寻找。你呢,A先生/女士?你担心什么?”'

逐步观察一天的情况。

# 让Tommie开始一天的生活。
observations = [
"Tommie wakes up to the sound of a noisy construction site outside his window.",
"Tommie gets out of bed and heads to the kitchen to make himself some coffee.",
"Tommie realizes he forgot to buy coffee filters and starts rummaging through his moving boxes to find some.",
"Tommie finally finds the filters and makes himself a cup of coffee.",
"The coffee tastes bitter, and Tommie regrets not buying a better brand.",
"Tommie checks his email and sees that he has no job offers yet.",
"Tommie spends some time updating his resume and cover letter.",
"Tommie heads out to explore the city and look for job openings.",
"Tommie sees a sign for a job fair and decides to attend.",
"The line to get in is long, and Tommie has to wait for an hour.",
"Tommie meets several potential employers at the job fair but doesn't receive any offers.",
"Tommie leaves the job fair feeling disappointed.",
"Tommie stops by a local diner to grab some lunch.",
"The service is slow, and Tommie has to wait for 30 minutes to get his food.",
"Tommie overhears a conversation at the next table about a job opening.",
"Tommie asks the diners about the job opening and gets some information about the company.",
"Tommie decides to apply for the job and sends his resume and cover letter.",
"Tommie continues his search for job openings and drops off his resume at several local businesses.",
"Tommie takes a break from his job search to go for a walk in a nearby park.",
"A dog approaches and licks Tommie's feet, and he pets it for a few minutes.",
"Tommie sees a group of people playing frisbee and decides to join in.",
"Tommie has fun playing frisbee but gets hit in the face with the frisbee and hurts his nose.",
"Tommie goes back to his apartment to rest for a bit.",
"A raccoon tore open the trash bag outside his apartment, and the garbage is all over the floor.",
"Tommie starts to feel frustrated with his job search.",
"Tommie calls his best friend to vent about his struggles.",
"Tommie's friend offers some words of encouragement and tells him to keep trying.",
"Tommie feels slightly better after talking to his friend.",
]
# 让Tommie继续前进。我们将每隔几个观察结果检查一次摘要,以观察其演变过程
for i, observation in enumerate(observations):
_, reaction = tommie.generate_reaction(observation)
print(colored(observation, "green"), reaction)
if ((i + 1) % 20) == 0:
print("*" * 40)
print(
colored(
f"After {i+1} observations, Tommie's summary is:\n{tommie.get_summary(force_refresh=True)}",
"blue",
)
)
print("*" * 40)
    Tommie wakes up to the sound of a noisy construction site outside his window. Tommie groans and covers his head with a pillow, trying to block out the noise.
Tommie gets out of bed and heads to the kitchen to make himself some coffee. Tommie stretches his arms and yawns before starting to make the coffee.
Tommie realizes he forgot to buy coffee filters and starts rummaging through his moving boxes to find some. Tommie sighs in frustration and continues searching through the boxes.
Tommie finally finds the filters and makes himself a cup of coffee. Tommie takes a deep breath and enjoys the aroma of the fresh coffee.
The coffee tastes bitter, and Tommie regrets not buying a better brand. Tommie grimaces and sets the coffee mug aside.
Tommie checks his email and sees that he has no job offers yet. Tommie sighs and closes his laptop, feeling discouraged.
Tommie spends some time updating his resume and cover letter. Tommie nods, feeling satisfied with his progress.
Tommie heads out to explore the city and look for job openings. Tommie feels a surge of excitement and anticipation as he steps out into the city.
Tommie sees a sign for a job fair and decides to attend. Tommie feels hopeful and excited about the possibility of finding job opportunities at the job fair.
The line to get in is long, and Tommie has to wait for an hour. Tommie taps his foot impatiently and checks his phone for the time.
Tommie meets several potential employers at the job fair but doesn't receive any offers. Tommie feels disappointed and discouraged, but he remains determined to keep searching for job opportunities.
Tommie leaves the job fair feeling disappointed. Tommie feels disappointed and discouraged, but he remains determined to keep searching for job opportunities.
Tommie stops by a local diner to grab some lunch. Tommie feels relieved to take a break and satisfy his hunger.
The service is slow, and Tommie has to wait for 30 minutes to get his food. Tommie feels frustrated and impatient due to the slow service.
Tommie overhears a conversation at the next table about a job opening. Tommie feels a surge of hope and excitement at the possibility of a job opportunity but decides not to interfere with the conversation at the next table.
Tommie asks the diners about the job opening and gets some information about the company. Tommie said "Excuse me, I couldn't help but overhear your conversation about the job opening. Could you give me some more information about the company?"
Tommie decides to apply for the job and sends his resume and cover letter. Tommie feels hopeful and proud of himself for taking action towards finding a job.
Tommie continues his search for job openings and drops off his resume at several local businesses. Tommie feels hopeful and determined to keep searching for job opportunities.
Tommie takes a break from his job search to go for a walk in a nearby park. Tommie feels refreshed and rejuvenated after taking a break in the park.
A dog approaches and licks Tommie's feet, and he pets it for a few minutes. Tommie feels happy and enjoys the brief interaction with the dog.
****************************************
After 20 observations, Tommie's summary is:
Name: Tommie (age: 25)
Innate traits: anxious, likes design, talkative
Tommie is determined and hopeful in his search for job opportunities, despite encountering setbacks and disappointments. He is also able to take breaks and care for his physical needs, such as getting rest and satisfying his hunger. Tommie is nostalgic towards his past, as shown by his memory of his childhood dog. Overall, Tommie is a hardworking and resilient individual who remains focused on his goals.
****************************************
Tommie sees a group of people playing frisbee and decides to join in. Do nothing.
Tommie has fun playing frisbee but gets hit in the face with the frisbee and hurts his nose. Tommie feels pain and puts a hand to his nose to check for any injury.
Tommie goes back to his apartment to rest for a bit. Tommie feels relieved to take a break and rest for a bit.
A raccoon tore open the trash bag outside his apartment, and the garbage is all over the floor. Tommie feels annoyed and frustrated at the mess caused by the raccoon.
Tommie starts to feel frustrated with his job search. Tommie feels discouraged but remains determined to keep searching for job opportunities.
Tommie calls his best friend to vent about his struggles. Tommie said "Hey, can I talk to you for a bit? I'm feeling really frustrated with my job search."
Tommie's friend offers some words of encouragement and tells him to keep trying. Tommie said "Thank you, I really appreciate your support and encouragement."
Tommie feels slightly better after talking to his friend. Tommie feels grateful for his friend's support.

一天后的面试

interview_agent(tommie, "告诉我你今天过得怎么样")
    'Tommie说:“说实话,今天过得有点起伏不定。我在找工作方面遇到了一些挫折,但也有一些好的时刻,比如发出了几份简历,并在招聘会上遇到了一些潜在雇主。你呢?”'
interview_agent(tommie, "你对咖啡有什么感觉?")
    'Tommie说:“我真的很喜欢咖啡,但有时候我后悔没有买更好的品牌。你呢?”'
interview_agent(tommie, "告诉我关于你小时候的狗!")
    'Tommie说:“哦,我小时候有一只叫布鲁诺的狗。他是一只金毛寻回犬,是我的最好的朋友。我有很多美好的回忆。”'

添加多个角色 (Adding Multiple Characters)

让我们添加第二个角色来与 Tommie 进行对话。可以根据需要配置不同的特征。

eves_memory = GenerativeAgentMemory(
llm=LLM,
memory_retriever=create_new_memory_retriever(),
verbose=False,
reflection_threshold=5,
)


eve = GenerativeAgent(
name="Eve",
age=34,
traits="curious, helpful", # 在这里可以添加更多的持久特征
status="N/A", # 当连接到虚拟世界时,角色可以更新他们的状态
llm=LLM,
daily_summaries=[
(
"Eve 上周开始了她的新工作,成为一名职业顾问,并接到了第一个任务,一个名叫 Tommie 的客户。"
)
],
memory=eves_memory,
verbose=False,
)
yesterday = (datetime.now() - timedelta(days=1)).strftime("%A %B %d")
eve_observations = [
"Eve 醒来听到闹钟响了",
"Eve 吃了一碗粥",
"Eve 帮助同事完成了一个任务",
"Eve 和她的朋友 Xu 打了一场网球比赛,然后去上班",
"Eve 听到同事说 Tommie 很难相处",
]
for observation in eve_observations:
eve.memory.add_memory(observation)
print(eve.get_summary())
    姓名: Eve (年龄: 34)
先天特征: 好奇, 乐于助人
Eve 是一个乐于助人和积极的人,喜欢运动并照顾自己的身体健康。她对周围的环境非常关注,包括她的同事,并且具有良好的时间管理能力。

面谈前的访谈

让我们在Eve与Tommie交谈之前对她进行“访谈”。

interview_agent(eve, "你对今天的感觉如何?")
    'Eve说:“我感觉很好,谢谢你的关心!我只是想保持高效并充分利用这一天。你呢?”'
interview_agent(eve, "你对Tommie了解多少?")
    'Eve说:“我对Tommie了解不多,但我听说有人提到他们很难一起工作。你有过和Tommie一起工作的经历吗?”'
interview_agent(
eve,
"Tommie正在寻找工作。你想问他些什么问题?",
)
    'Eve说:“很有意思。我对Tommie的工作经验了解不多,但我可能会问他的优势和需要改进的地方。你呢?”'
interview_agent(
eve,
"你得问他。他可能有点焦虑,所以如果你能继续对话并尽可能多地提问,我会很感激。",
)
    'Eve说:“当然,我可以继续对话并提出很多问题。我希望确保Tommie感到舒适和支持。谢谢你告诉我。”'

生成式代理之间的对话 (Dialogue between Generative Agents)

生成式代理在与虚拟环境或其他代理进行交互时更加复杂。下面,我们展示了Tommie和Eve之间的简单对话。

def run_conversation(agents: List[GenerativeAgent], initial_observation: str) -> None:
"""运行代理之间的对话。"""
_, observation = agents[1].generate_reaction(initial_observation)
print(observation)
turns = 0
while True:
break_dialogue = False
for agent in agents:
stay_in_dialogue, observation = agent.generate_dialogue_response(
observation
)
print(observation)
# observation = f"{agent.name} said {reaction}"
if not stay_in_dialogue:
break_dialogue = True
if break_dialogue:
break
turns += 1
agents = [tommie, eve]
run_conversation(
agents,
"Tommie说:嗨,Eve。谢谢你同意今天和我见面。我有很多问题,不确定从哪里开始。也许你可以先分享一下你的经验?",
)
    Eve说:“当然,Tommie。我很乐意和你分享我的经验。你想我从哪里开始?”
Tommie说:“太好了,谢谢!你可以先告诉我一下你之前的工作经验吗?”
Eve说:“当然,我很乐意和你分享我之前的工作经验。我在几个不同的行业工作过,包括市场营销和活动策划。你对我有什么具体的问题吗?”
Tommie说:“听起来很不错。你能告诉我更多关于你在活动策划方面的经验吗?我一直对这个领域很感兴趣。”
Eve说:“当然,我很乐意和你分享我在活动策划方面的经验。我参与过各种各样的活动,从企业会议到婚礼。我面临的最大挑战之一是管理多个供应商,并确保活动当天一切顺利进行。你有什么具体的问题吗?”
Tommie说:“听起来责任很重啊!你能告诉我更多关于你在这些活动中如何处理出现的挑战吗?”
Eve说:“当然,Tommie。我很乐意和你分享我是如何处理这些挑战的。一个对我很有效的方法是保持组织并为活动制定详细的时间表。这有助于我跟踪所有需要完成的不同任务以及它们的完成时间。我还确保与参与活动的所有供应商和团队成员进行清晰的沟通,以确保大家都在同一个页面上。你想我详细介绍一下吗?”
Tommie说:“谢谢你和我分享这些,Eve。听起来你对管理活动有很好的方法。你能告诉我更多关于你在活动中处理出现的意外问题的经验吗?”
Eve说:“当然,Tommie。我面临的一个意外问题的例子是其中一个供应商没有按时出现。为了解决这个问题,我迅速联系了备用供应商,并成功让一切恢复正常。在计划活动时,始终重要的是有备用计划以防事情不按计划进行。你对活动策划还有其他问题吗?”
Tommie说:“谢谢你和我分享这些,Eve。听到你是如何处理这样的意外问题对我很有帮助。你能给我举个例子,说明你是如何与团队沟通以确保大家在活动期间都在同一个页面上吗?”
Eve说:“当然,Tommie。为了确保大家在同一个页面上,我会定期与团队进行检查和会议。这有助于我们及早解决任何问题或关注点,并确保每个人都清楚自己的角色和责任。你以前有没有管理过活动的团队?”
Tommie说:“这是个好主意,Eve。我还没有机会管理过活动的团队,但我会记住这个建议的。谢谢你与我分享你的经验。”
Eve说:“谢谢你给我分享经验的机会,Tommie。今天很高兴与你见面。”

让我们在他们对话后面面试我们的代理 (Let's interview our agents after their conversation)

由于生成的代理保留了他们一天的记忆,我们可以询问他们关于他们的计划、对话和其他记忆的情况。

# 我们可以根据他们对自己的感知来查看角色的当前“摘要”是否发生了变化
print(tommie.get_summary(force_refresh=True))
    姓名:Tommie(年龄:25)
先天特征:焦虑,喜欢设计,健谈
Tommie在找工作方面坚定而充满希望,但有时也会感到沮丧和挫败。他与童年的狗布鲁诺有着深厚的感情。当他感到不知所措时,Tommie会寻求朋友的支持,并对他们的帮助心存感激。他也喜欢探索他的新城市。
print(eve.get_summary(force_refresh=True))
    姓名:Eve(年龄:34)
先天特征:好奇,乐于助人
Eve是一个乐于助人和友好的人,喜欢运动和保持高效。她对他人的需求非常关注和回应,积极倾听并提问以了解他们的观点。Eve在活动策划和沟通方面有经验,并愿意与他人分享她的知识和专长。她重视团队合作,努力为每个人创造一个舒适和支持性的环境。
interview_agent(tommie, "你和Eve的对话怎么样?")
    'Tommie说:“实际上非常有帮助。Eve分享了一些关于管理活动和处理意外问题的好建议。我觉得从她的经验中学到了很多东西。”'
interview_agent(eve, "你和Tommie的对话怎么样?")
    'Eve说:“很好,谢谢你的关心。Tommie非常乐于接受,并对活动策划提出了一些很棒的问题。你呢,你和Tommie有过任何互动吗?”'
interview_agent(eve, "你希望对Tommie说些什么?")
    'Eve说:“很高兴见到你,Tommie。如果你将来有任何问题或需要帮助,请随时联系我。祝你有美好的一天!”'