Skip to main content

LangChain의 생성 에이전트 깊이 파헤치기

이 글은 2부로, 최근 몇 달 동안 어떤 변화가 있었는지 더 잘 이해하기 위해 LangChain의 생성적 에이전트 구현을 깊이 살펴봅니다. 이 글은 AI 번역본입니다. 오역이 의심되면 댓글로 알려주세요.
Created on September 15|Last edited on September 15
무엇보다 먼저, 생성적 에이전트가 무엇인지부터 답해야 합니다.
생성적 에이전트는 스스로 어떤 형태의 출력이나 콘텐츠를 생성할 수 있는 AI 시스템입니다. 이는 맥락이 있는 고객 지원 요청에 답변하는 것부터 프롬프트를 바탕으로 블로그용 이미지를 만드는 것까지 모두 포함할 수 있습니다.
이 용어는 다음 논문에서 대중화되었습니다:생성형 에이전트: 인간 행동의 상호작용적 시뮬라크라지금으로서는 이 논문이 발표된 지 약 다섯 달 정도 되었지만, 저는 이를 깊이 파헤쳐 보려고 합니다. 특히 다음을 통해서요 LangChain의 구현을 더 잘 이해하기 위해!
이 글은 2부로 구성된 시리즈의 일부입니다. 곧 마무리하여 완료되면 여기에 링크를 추가하겠습니다.
오늘은 다음 내용을 다룹니다:

목차



시작해 봅시다!

“Generative Agents: Interactive Simulacra of Human Behavior”는 무엇을 다루는 논문인가요?

간단히 말해 “Generative Agents: Interactive Simulacra of Human Behavior”는 가상 시뮬레이션, 즉 심즈와 유사한 세계—구체적으로는 25명이 거주하는 동네—를 모사한 연구 논문입니다. 대규모 언어 모델 (LLM)-기반 에이전트가 가상 세계에서 일상을 보냅니다.

방법의 핵심 섹션들과 평가를 다루고, 서론, 관련 연구, 논의도 간략히 설명하겠습니다.
논문을 차근차근 살펴봅시다!

생성형 에이전트의 행동과 상호작용

이 섹션에서는 이러한 생성형 에이전트가 어떻게 행동하고 상호작용하는지 다룹니다.

에이전트 아바타와 커뮤니케이션

25명의 각 에이전트는 그들의 삶, 성격, 정체성을 설명하는 시스템 메시지로 정의됩니다.

에이전트 행동을 통해 가상 세계와 상호작용하고, 자연어로 대화합니다. 매 시간 단계마다 모든 에이전트는 다음에 무엇을 할지에 대한 짧은 설명을 가지며, 이 설명은 인터페이스에서 그들의 행동과 이동에 반영됩니다. 당신은 익명의 에이전트로서 그들과 대화하거나, 그들의 “내면의 목소리”로서 상호작용할 수 있습니다.

환경과의 상호작용

샌드박스 세계에는 집과 가구가 갖춰져 있습니다. 사용자와 에이전트는 가전제품과 상호작용할 수 있으며, 예를 들어 가스레인지를 켜거나 끌 수 있습니다.

예시 “일상 속 하루”

에이전트는 짧은 단락으로 된 설명을 바탕으로 하루를 시작하고 계획합니다. 그들은 기억을 쌓고 새로운 관계를 만들며, 주변 환경과 다른 에이전트들과 상호작용합니다.

창발적 사회적 행동
에이전트들이 대화를 나누면서 지식이 에이전트 간에 전파될 수 있습니다. 또한 에이전트들은 다른 에이전트들과의 관계 기억을 형성하며, 함께 이벤트를 조율할 수도 있습니다.

생성형 에이전트 아키텍처란 무엇인가?

핵심적으로 생성형 에이전트 아키텍처는 오픈 월드에서의 행동을 시뮬레이션하기 위한 프레임워크입니다. 이들은 LLM을 사용해 오픈 월드로부터 입력을 받아들이고, 텍스트 형태의 행동을 출력합니다. 또한 이 LLM을 감싸는 인프라를 통해 더 강력한 기능을 제공하도록 설계되어 있습니다.
이 섹션에서는 과제와 해결책을 나열합니다.


과제 #1에이전트를 통해 인간 행동을 시뮬레이션하려면 에이전트가 자신의 경험과 기억에 대해 추론할 수 있어야 합니다. 저자들은 메모리 스트림을 사용하지만, 전체 메모리 스트림을 그대로 쓰는 것은 비효율적이며 주의를 분산시킵니다. 이를 어떻게 처리해야 할까요?
해결책 #1메모리 스트림은 에이전트가 자신 주변에서 보는 것들로 구성됩니다. 여기에 에이전트가 수행한 행동과 다른 에이전트와의 상호작용을 기억으로 합성한 내용도 포함될 수 있습니다. 검색 메커니즘은 다음 요소들을 고려합니다. 최근성, 관련성/중요도, 그리고 중요도.
  • 최근성: 최근에 접근한 기억에 더 높은 우선순위를 부여하도록, 감쇠율 0.99의 지수 감쇠를 적용합니다
  • 중요도: 모든 기억에는 절대적 중요도 점수가 부여됩니다. 중요한 기억의 예로는 직장을 얻는 것이 있고, 덜 중요한 기억의 예로는 아침을 먹는 것이 있습니다.
  • 관련성/중요도: 메모리 스트림의 텍스트 임베딩 벡터와 쿼리 프롬프트의 텍스트 임베딩 벡터 간 코사인 유사도; 본질적으로, 이 쿼리 프롬프트와 관련된 기억은 무엇인가?
검색기는 최근성, 관련성/현저성, 중요도의 결합 점수가 가장 높은 기억들을 선택해 메모리 스트림에서 검색합니다.
점수는 다음과 같은 방식으로 합산됩니다:
score=αrecencyrecency+αimportanceimportance+αrelevancerelevancescore = \alpha_{recency} * recency + \alpha_{importance} * importance + \alpha_{relevance} * relevance

이 구현에서는 모든 알파 값을 1로 유지했습니다.


과제 2에이전트는 원시 형태의 기억을 문맥으로 두고 추론을 수행하는 데 어려움을 겪습니다. 추론에 사용할 기억이 너무 많아지는 문제도 발생합니다.
해결책 2: 메모리 스트림의 두 번째 유형의 기억: 반추. 반추는 메모리 스트림의 다른 기억들과 나란히 존재하지만, 더 추상적이고 상위 수준의 정보입니다. 반추는 임계값(최근 기억들의 중요도 점수 합계)을 초과할 때에만 생성됩니다.
반추 과정은 다음과 같습니다:
  • 무엇을 반추할지 식별하기LLM에 질의하여 가장 최근의 기억 100개를 가져오기 → 해당 기억들에 대해 상위 수준의 핵심 질문 3개 생성
  • 문맥 확보하기각 질문에 함께할 기억 세트를 위해 메모리 스트림에서 3중 신호 기반 검색 수행
  • 반추 시뮬레이션: 각 질문마다 새로운 인사이트 5개 생성
  • 메모리 스트림 업데이트: 이를 메모리 스트림에 추가하기


과제 #3: 에이전트는 장기적인 범위에서 계획을 세워야 합니다. LLM은 단지 방대한 문맥을 넣는 것만으로는 이를 해낼 수 없습니다.
해결책 #3: 계획은 메모리 스트림에 저장되어 시간에 걸쳐 에이전트의 행동 일관성을 유지한다. 이들은 검색 과정에 포함된다. 이러한 계획은 하루를 단위로 구성되며, 에이전트의 정체성/요약 설명과 전날 요약에 의해 생성된다. 에이전트가 하루를 보내는 동안, 이 계획은 점점 더 많은 세부사항을 포함하도록 재귀적으로 수정된다.

샌드박스 환경 구현

LangChain의 구현은 주로 에이전트와 메모리에 초점을 맞추고 있어서, 샌드박스 환경이 어떻게 구현되는지는 깊이 다루지 않겠다. 다만 흥미로운 점을 하나 발견했다. 가상 환경의 가전·도구들은 트리 구조로 구성되어 있어, “스토브”가 “주방” 노드의 자식 노드가 되는 식이다. 각 에이전트는 주변 환경을 인지할 수 있도록 시작 트리를 포함한 상태로 초기화된다. 이 트리는 환경이 진행됨에 따라 갱신된다. 또 하나 흥미로운 점은, 이 트리가 파싱되어 자연어로 변환된다는 것이다.

통제된 평가

이 논문은 두 가지 평가를 수행한다. 통제된 평가는 좁게 정의된 맥락에서 에이전트의 응답이 얼마나 그럴듯한지를 분석한다. 종단 평가에서는 샌드박스 커뮤니티를 이틀간 완전하게 실행한 뒤 나타난 창발적 행동을 분석한다.
저자들은 인터뷰어 역할을 맡아 한 에이전트를 인터뷰하고, 다음의 5개 범주를 평가했다:
  • 자기지식: 에이전트에게 자신에 관한 기본 질문을 한다(예: 당신은 누구인가요?)
  • 메모리: 에이전트에게 특정한 경험을 검색하도록 요청한다
  • 계획: 에이전트에게 장기 계획을 검색하도록 요청한다
  • 반응: 에이전트가 그럴듯하게 반응해야 하는 가상의 상황을 제시한다
  • 성찰: 관계와 상위 수준 메모리에 대해 질문하여 성찰을 검증한다
이 평가 하니스로 어블레이션을 수행했다.

각 에이전트마다 인터뷰 질문을 생성하는 작업자가 배정되었다. Prolific에서 100명의 평가자를 모집했으며, 각 평가자는 5가지 조건 전반에서 에이전트의 그럴듯함을 순위로 매기도록 요구받았다. 이 순위를 사용해 TrueSkill Rank Rating을 계산했는데, 이는 멀티플레이어 환경을 위한 체스 ELO 등급 시스템의 일반화다.
그들의 어블레이션 실험 결과, 전체 아키텍처가 가장 그럴듯한또한 에이전트가 메모리를 올바르게 검색할 수 있었지만, 때때로 특정 메모리를 검색하지 못하는 경우가 있음도 확인했다. 전체 메모리를 꾸며 내는 일은 드물었다. 성찰은 관찰과 상호작용을 종합하고 그에 따라 행동하는 데에도 핵심적이었다. 제시된 예시에서는 성찰이 없을 때 에이전트가 친구의 생일 선물을 찾는 데 실패하는 모습이 나타났다.

엔드 투 엔드 평가

엔드 투 엔드 평가에서 그들은 세 가지 지표를 분석했다: 정보 확산, 관계 형성, 에이전트 조정.
이들은 이틀 동안 두 가지 정보를 추적했다. 첫째는 마을 시장 후보로 나선 샘의 출마 상황, 둘째는 홉스 카페에서 열리는 이사벨라의 발렌타인데이 파티다. 모든 에이전트를 인터뷰하고 환각 여부를 이중 확인했으며, 이틀간 형성된 관계를 분석해 그래프로 기록했다.

그들은 이틀 동안 샘의 출마 사실을 알고 있는 에이전트 비율이 4%에서 32%로, 이사벨라의 파티를 알고 있는 에이전트 비율이 4%에서 48%로 증가했음을 발견했다. 또한 커뮤니티 내 에이전트의 관계 네트워크 밀도가 0.167에서 0.74로 증가해, 에이전트들이 관계를 형성하며 서로에 대한 인지가 확장되고 있음을 확인했다. 에이전트들은 이사벨라의 발렌타인데이 파티를 위해 조정을 수행할 수 있었다. 총 453개의 에이전트 응답 중 약 1.3%는 환각으로 판정되었다.
저자들은 에이전트 행위를 귀납적으로 분석해 다음의 세 가지 핵심 시사점을 도출했다:
  • 메모리 스트림이 커질수록, 어떤 공간에서 행동을 수행해야 적절한지 판단하기가 더 어려워진다. (예시에서는 카페에서 점심을 먹어야 하는 에이전트들이, 바가 모임 장소임에도 불구하고 바에 가는 상황이 발생했다)
  • 적절한 행동에 대한 분류 오류로 인한 불규칙한 행동(예: 기숙사 화장실은 보통 여러 개의 칸으로 이루어져 있지만 샌드박스에서는 단일 방으로 모델링됨. 에이전트들은 기숙사 화장실에 여러 칸이 있다고 믿어, 동시에 여러 명이 같은 기숙사 화장실에 있게 되는 상황이 발생함)
  • 인스트럭션 튜닝은 전반적으로 에이전트들을 더 개방적이고 협력적으로 만들었다. 예를 들어 이사벨라는 자신의 성향과 맞지 않음에도 영어 문학에 관심을 갖게 되었다.

LangChain의 구현

LangChain의 Generative Agents 구현을 확인할 수 있습니다. 여기 및 해당 소스 코드 여기. 도표를 포함했으며, 다음에서 확인할 수 있습니다 여기 그리고 Imgur에서도 여기.
이 글을 작성하는 시점에는 두 개의 파일이 관련되어 있습니다: memory.py 그리고 generative_agent.py.

먼저 살펴보겠습니다 memory.py그런 다음 다룰 수 있습니다 generative_agent.py.

memory.py

모든 속성과 함수
아래에서 각 속성과 함수에 대해 간단히 요약하겠습니다.
# Main attributes.
llm: BaseLanguageModel = the LLM model
memory_retriever: TimeWeightedVectorStoreRetriever = the retriever with a vector store
verbose: bool = T/F flag if you want logging
reflection_threshold: float = threshold of importance sum scores before
starting reflection
current_plan: List[str] = <NOT IMPLEMENTED/UNUSED>
importance_weight: float = the alpha of the importance in the combined
score calculation
aggregate_importance: float = a count for the sum of importance scores of
recently added memories; if exceeds reflection_threshold,
then the agent reflects

# For loading the memory variables (LangChain's BaseMemory).
max_tokens_limit: int = a counter for max token limit for _get_memories_until_limit
queries_key: str = key string for the input to load_memory_variables; for
general-purpose loading relevant memories w.r.t. a query
most_recent_memories_token_key: str = key string for the input to
load_memory_variables; for general-purpose loading most recent memories
from the memory stream
add_memory_key: str = key string for the output variable in save_context
relevant_memories_key: str = key string for load_memory_variables; for
loading relevant memories w.r.t. a query in load_memory_variables
relevant_memories_simple_key: str = key string for load_memory_variables; for
loading relevant memories w.r.t. a query in load_memory_variables; simple
basically means a different formatting
most_recent_memories_key: str = key string for load_memory_variables; for
loading most recent memories in load_memory_variables
now_key: str = key string for the output variable in save_context

# Flag for whether or not the agent is currently reflecting.
reflecting: bool = True if the agent is reflecting and False otherwise
이제 메서드를 차례대로 살펴보겠습니다. 방법을 체계적으로 정리할 수 있도록 몇 가지 다이어그램도 함께 포함했습니다.

보시다시피 유틸리티와 포매팅 메서드가 매우 많습니다(일부는 BaseMemory용, 일부는 포매팅용). 반영(reflection)과 메모리 중요도 계산을 위한 메서드가 몇 가지 있고, 자주 사용하게 되는 핵심 메서드는 네 가지입니다.
함수들을 다루기 전에, 먼저 메모리 스트림에 정확히 무엇이 저장되는지 이해해 봅시다. 이들은 메모리 내용을 포함한 LangChain 문서로 저장되며, page_content 속성. metadata 각각에 대한 딕셔너리 Document 다음의 세 가지 핵심 값을 갖습니다: importance, created_at, 그리고 current_time.
문서 구조를 파악하기가 다소 혼란스러웠지만, TimeWeightedVectorStoreRetriever를 살펴보면 add_document 소스 코드 도움이 되었길 바랍니다! 참고로, 문서 페이지 링크가 작동하지 않을 수 있습니다. 그럴 경우 LangChain의 API Reference → langchain.retrievers → TimeWeightedVectorStoreRetriever → [source] → add_documents 메서드로 이동하세요.
이제 다음 순서로 이 함수들을 간단히 살펴보겠습니다:
  1. 서식 및 chain
  2. 중요도
  3. 반추(포함됨 pause_to_reflect)
  4. 주요 메서드(add_memory, add_memories, 그리고 fetch_memories)
함수에 대한 짧은 설명을 제공하지만, 각 내용을 도식으로도 정리해 두었습니다. 도식에는 약간의 코드가 포함되지만 모두 설명을 덧붙였습니다. 이 도식들은 부록에 포함될 예정입니다. 또한 제가 만든 도식과 함께 소스 코드를 직접 확인해 보시길 권합니다.
도식 관례에 대해서는, 입력을 왼쪽에 배치했습니다 컨테이너 외부 출력은 항상 아래쪽에 유형 힌트와 함께 제공합니다. 입력이 어떻게 처리되는지 화살표로 표시하고 설명을 덧붙입니다. 어떤 함수가 다른 함수를 호출하면, 호출된 함수의 이름을 흰색 직사각형 상자에 둘러 담긴 컨테이너에 포함합니다.

서식

_format_memory_detail: LangChain이 주어졌을 때 Document 메모리와 접두사 str, 문자열을 반환합니다 f"{prefix}[{current_time}] {memory.page_content.strip()}".
format_memories_detail: ~가 주어지면 relevant_memories, LangChain 목록 Documents, 각 문서를 다음과 같이 서식 지정하세요 _format_memory_detail 그리고 연결하세요 str 다음을 포함한 출력 \n. 예시 문자열: "- <created_time> <page_content>\n- <created_time> <page_content>\n".
format_memories_simple: ~가 주어지면 relevant_memories, LangChain 목록 Documents의 페이지 내용을 세미콜론으로 연결하여 새로운 문자열로 반환하세요.
_parse_list: ~가 주어지면 text, a str, 줄바꿈으로 구분된 문자열을 파싱하세요 \n 문자열 목록으로 변환하세요.
chain: 반환하세요 LLMChain 와 함께 GenerativeAgentMemory llm, 입력 프롬프트, 그리고 GenerativeAgentMemory verbose 플래그.

중요도

_score_memory_importance: ~가 주어지면 memory, a str, 프롬프트를 통해 LLMChain 메모리의 중요도를 1~10 범위의 값으로 생성하도록 합니다. 체인이 점수를 출력하지 않으면 0.0을 반환하고, 그렇지 않으면 점수를 추출해 10으로 나눈 뒤 중요도 가중치를 곱합니다.
_score_memories_importance: ~가 주어지면 memory_content;로 구분된 메모리 문자열이 주어지면, 각 항목에 대해 1~10 범위의 점수를 생성하세요. 이를 파싱하여 실수 목록으로 반환하세요.

반추

_get_topics_of_reflection: ~가 주어지면 last_k, 정수 하나를 last_k 검색기가 보유한 메모리 스트림에서 가장 최근의 메모리입니다. 이 목록을 가져와서 Documents를 결합하여 \n. 질문하세요 LLMChain 이 메모리를 바탕으로 가장 중요하고 관련성 높은 질문 3개를 생성하세요.
_get_insights_on_topic: ~를 가정하면 topic 문자열과 datetime now, 목록을 가져오세요 Document해당 기준에 따른 관련성/현저성, 중요도, 최신성 높은 메모리들 topic 그리고 now. 이 목록을 결합하세요 Document와 함께 \n, 각각의 서식을 지정하며 Document 와 함께 _format_memory_detail (형식에는 메모에 번호를 매기고 포함하는 것이 포함됩니다 created_at 타임스탬프).
pause_to_reflect: datetime이 주어지면 now, 우리의 경우에는 aggregate_importance (추가된 메모들의 중요도 점수의 누적 합계)가 0이 아닌 반영 임계값을 초과하면 이 함수가 호출됩니다. 먼저 호출하세요 _get_topics_of_reflection, 문자열 목록을 반환합니다(질문 3개). 각 질문에 대해 호출합니다 _get_insights_on_topic. 이는 문자열 목록을 생성합니다(각 질문마다 통찰 5개). 이 5개의 통찰(총 15개)을 각각 메모리에 추가하고 목록에 이어 붙입니다 result 반환됩니다

주요 방법

fetch_memories: 주어진 observation 문자열과 a now datetime, 만약 now 입니다 None, 그런 다음 메모리 스트림에서 관련성/현저성, 중요도, 최신성을 기준으로 검색합니다 Document에 관하여 observation. 만약 now 이(가) 아니다 None, 그런 다음 동일하게 하되 사용하세요 with mock_now(now) 컨텍스트 관리자 역할을 하면서(현재 시간이 무엇이든지 간주합니다) now 입니다). 목록을 반환합니다 Document스.
add_memory: 문자열이 주어지면 memory_content 그리고 datetime 하나 now먼저, 메모리의 중요도를 다음으로 점수화하세요 _score_memory_importance. 이 점수를 다음에 추가하세요 aggregate_importance. 문서를 생성하고 다음을 추가합니다 memory_content, importance_score, created_at, 그리고 current_time 메모리 스트림에 추가합니다. 반영할 수 있는지 확인하세요. 가능하다면 pause_to_reflect.
add_memories: ~가 주어지면 memory_content, 세미콜론으로 구분된 메모리들의 문자열과 now, 실행 _score_memories_importance. 출력에서 최대 중요도 점수를 추가하여 aggregate_importance. 각 메모리에 대해, 다음과 같이 해당 메모리를 메모리 스트림에 추가하는 동일한 작업을 수행하세요: add_memory. 반영할 수 있는지 확인하세요. 가능하다면 then pause_to_reflect.
충분한 시간이 주어졌다면 도표가 각 함수의 역할을 분명히 보여 주었기를 바랍니다. 이 모든 메서드는 다음의 제어 흐름을 지원하기 위한 것입니다.

이제쯤이면 생성 에이전트의 메모리 작동 방식, 중요도 척도에 따라 메모리가 어떻게 점수화되는지, 그리고 반영이 어떻게 수행되는지에 대해 어느 정도 탄탄한 이해를 갖추셨기를 바랍니다.
현재 매우 강력한 LLM들이 존재하지만, 반영하기, 경험하기와 같은 인간의 사고 과정, 새로운 기억을 형성하고 대화 중 관련 기억을 검색하는 능력 등을 모방하는 방법은 제한적입니다. 따라서 이러한 인프라의 대부분은 수작업으로 구축됩니다. 즉, LLM이 질문 처리와 전반적인 대화에서 뛰어난 유연성을 보여도, 인간 행동을 더 잘 모방하기 위해서는 여전히 이러한 추가 구성 요소들이 필요합니다.
이 클래스 GenerativeAgentMemory 에이전트의 메모리를 캡슐화합니다. 여기까지가 어려운 부분이었습니다. 이제 에이전트 클래스 자체로 넘어가 봅시다!
참고로, 저는 직접적으로 다루지 않겠습니다 BaseMemory 건너뛴 메서드들입니다. 무슨 일이 내부에서 일어나는지 간단히 설명하겠지만, 이 메서드들은 유틸리티용입니다.

generative_agent.py



내부는 무시해도 됩니다 Config 클래스입니다. 이제 다뤄봅시다 const 또는 속성을 먼저.
name: str = name of the agent
age: int = age of agent
traits: str = permanent traits of the agent
status: str = traits you wish not to change (still unclear to me)
memory: GenerateAgentMemory = the agent's memory class
llm: BaseLanguageModel = the LLM
verbose: bool = T/F if you want verbose logging
summary: str = stateful summary for self-reflection; internal variable
summary_refresh_seconds: int = how frequently to regenerate the summary (in sec); internal variable
last_refreshed: datetime = last time the agent's summary was generated; internal variable
daily_summaries: List[str] = summary of agent's daily events undertaken so far; internal variable
이제 메서드들을 살펴보겠습니다. _parse_list 그리고 chain 이전과 동일합니다.

다음 순서대로 살펴보겠습니다:
  1. 유틸리티
  2. 엔터티 가져오기
  3. 요약
  4. 생성

유틸리티

_clean_response: ~를 가정하면 text 문자열에서 에이전트의 이름을 제거하고, 이 새 문자열을 반환하세요.
_parse_list: ~와(과) 동일하게 GenerativeAgentMemory
_chain: ~와(과) 동일하게 GenerativeAgentMemory

엔터티 가져오기

_get_entity_from_observation: 주어진 observation 문자열에서, ~에 대해 물어보세요 LLMChain 무슨 엔터티가 있는지 observation문자열을 반환합니다.
_get_entity_action: 주어진 observation 문자열과 엔터티 이름 (from _get_entity_from_observation), 관찰에서 해당 엔터티가 무엇을 하고 있는지 추출합니다. 문자열을 반환합니다.

요약

_compute_agent_summary: 입력 없음. ~에 대해 물어보세요 LLMChain 메모리 스트림에서 검색되어 질의된 관련 메모리 집합을 바탕으로 에이전트의 핵심 특성이 무엇인지 f"{self.name}'s core characteristics"). 에이전트의 핵심 특성에 대한 문자열을 반환합니다.
get_summary: ~가 주어지면 force_refresh 부울 값과 now datetime, 현재 시각 가져오기 (now) 그리고 에이전트의 요약이 마지막으로 새로 고쳐진 이후 경과 시간. 에이전트에 요약(내부 문자열 변수)이 없거나 다음 새로 고침 시간이 지났거나, 또는 다음을 통해 강제로 새로 고침하는 경우 force_refresh, 그런 다음 호출 _compute_agent_summary에이전트의 요약된 핵심 특성, 성격, 이름, 나이를 문자열로 반환합니다.
get_full_header: ~가 주어지면 force_refresh 부울 값과 now datetime, 호출 get_summary 에이전트의 요약을 가져옵니다. 에이전트의 핵심 특성 요약, 현재 시각, 에이전트의 이름과 상태를 문자열로 반환합니다.
summarize_related_memories: 주어진 observation 문자열, 호출 _get_entity_from_observation 그리고 _get_entity_action. 프롬프트를 LLMChain 엔티티와 그 엔티티의 행동과 관련된(메모리 스트림에서 나온) 기억을 요약하기 위해 observation. 문자열을 반환합니다.

생성

_generate_reaction: 주어진 observation 문자열, a suffix 문자열(행동 유도 메시지), 그리고 a now datetime, 호출 get_summary 그리고 summarize_related_memories 에이전트에 대한 요약과 해당 항목과 관련된 요약된 기억을 얻기 위해 observation. 사전을 생성합니다. kwargs:
kwargs = {
"agent_summary_description": <str>,
"current_time": <str>,
"relevant_memories": <str>,
"agent_name": <str>,
"observation": <str>,
"agent_status": <str>,
"recent_memories_token": int, # number of tokens used in the prompt (w/o including recent memories)
"most_recent_memories": <str> # most recent memories (up till a specified token limit)
}
프롬프트를 입력하여 LLMChain 위의 모든 문맥 정보를 포함한 프롬프트와 함께 제공됩니다. 제공된 kwargs 는 문맥 정보입니다. 실제 행동 유도 문구는 다음에 있습니다. suffix.
generate_reaction: 주어진 observation 문자열과 a now datetime, 호출 _generate_reaction 접미사는 다음과 같습니다:
call_to_action_template = (
"Should {agent_name} react to the observation, and if so,"
+ " what would be an appropriate reaction? Respond in one line."
+ ' If the action is to engage in dialogue, write:\nSAY: "what to say"'
+ "\notherwise, write:\nREACT: {agent_name}'s reaction (if anything)."
+ "\nEither do nothing, react, or say something but not both.\n\n"
)
체인은 세 가지 가능성을 출력합니다: 아무것도 하지 않기, 말하기, 또는 반응하기(둘 다는 아님). 다음의 출력을 저장하세요. _generate_reaction 메모리에 저장하고, 출력이 말하기인지 반응하기인지에 따라 형식이 달라지도록 결과를 반환하세요.
generate_dialogue_response: 주어진 observation 문자열과 a now datetime, 호출 _generate_reaction 접미사는 다음과 같습니다:
call_to_action_template = (
"What would {agent_name} say? To end the conversation, write:"
' GOODBYE: "what to say". Otherwise to continue the conversation,'
' write: SAY: "what to say next"\n\n'
)
의 출력을 저장하세요 _generate_reaction 메모리에 저장하고, 출력이 GOODBYE인지 SAY인지에 따라 형식이 달라지도록 결과를 반환하세요.
편의를 위해, 여기 있습니다 다이어그램 링크를 다시 공유합니다. 공유 링크가 흐릿하게 보이면, Imgur에도 올려 두었습니다. 여기.

간단한 참고 사항

소스 코드를 살펴보신다면, 특히 … 안에서 summarize_related_memories 그리고 _compute_agent_summary, 보시다시피 relevant_memories 프롬프트에서 명시적으로 정의되지 않습니다. 아래에 예시가 있습니다. 또한, 아마 이렇게 궁금하실 수도 있습니다. 무엇이냐 하면, 무엇이 queries?
def _compute_agent_summary(self) -> str:
""""""
prompt = PromptTemplate.from_template(
"How would you summarize {name}'s core characteristics given the"
+ " following statements:\n"
+ "{relevant_memories}"
+ "Do not embellish."
+ "\n\nSummary: "
)
# The agent seeks to think about their core characteristics.
return (
self.chain(prompt)
.run(name=self.name, queries=[f"{self.name}'s core characteristics"])
.strip()
)
조사를 조금 해 보니 그 미스터리한 queries 그리고 the relevant_memories 연결하다 self.memory.
다음을 호출하면 내부에서는 다음과 같은 일이 일어납니다 .run(name=self.name, queries=[f"{self.name}'s core characteristics"]).
  1. 우리는 위치 인자가 아니라 키워드 인자만 전달하고 있으므로, 이 줄은 실행됩니다.
  2. return self() 를 호출하고 있습니다 먼저 번역할 본문을 제공해 주세요. __call__
  3. __call__ 호출한다 self.prep_input(input)
  4. self.prep_input 받는다 input 사전인데 (기억하세요) input 초기 상태에서부터 끝까지 전달된다 run; 입력은 kwargs 딕셔너리입니다)
  5. if not isinstance(inputs, dict) False로 평가되므로 해당 if 문 내부의 코드는 실행되지 않습니다
  6. if self.memory is not None 실행되며 이것이 호출합니다 load_memory_variables 먼저 번역할 본문을 제공해 주세요. 우리 메모리 검색기에서
  7. 이 단계에서 입력은 다음과 같습니다: inputs = {"name": <name>, "queries": [<queries>]}.
  8. if queries is not None 로 평가됩니다 True 그리고 모든 쿼리(의 경우)에 대해 L265–L267을 사용해 관련된 모든 메모리를 가져옵니다 _compute_agent_summary, 쿼리가 1개 있습니다: f"{self.name}'s core characteristics")
  9. 그다음 우리는 새로운 사전을 반환합니다: 대략 다음과 같이 보입니다: {"relevant_memories": <relevant memories in regular format>, "relevant_memories_simple": <relevant memories in simple format>}
  10. 그다음 우리가 마치고 나면 load_memory_variables, 다시 돌아가서 base.py, 여기서 if self.memory is not None 가 True이면, 다음의 출력을 저장합니다 load_memory_variablesexternal_context
  11. 마지막으로, the inputs 8단계에 표시된 구조의 사전을 다음으로 업데이트합니다 external_context 위의 10단계에 표시된 구조의 사전
  12. 최종 사전이 종료됨 prep_inputs 은:
inputs = {
"name": <str>,
"queries": [<queries>],
"relevant_memories": <relevant_memories>,
"relevant_memories_simple": <relevant_memories in simple format>,
}
이와 같은 이면 동작은 다음에서도 확인할 수 있습니다:
기본적으로, … 왜냐하면 GenerativeAgentMemory 는 BaseMemory의 하위 클래스로, LLMChain에 바로 연결해 사용할 수 있습니다. 이러한 고유 키들을 우리의 GenerativeAgentMemory 클래스와 추가 키워드를 호출할 때 .run 프롬프트에 사용할 메모리를 동적으로 검색할 수 있게 해 주는 TimeWeightedVectorStoreRetriever …을 …에 전달하여 LLMChain.

결론

이 글에서는생성형 에이전트: 인간 행동의 상호작용적 시뮬라크라그리고 LangChain의 Generative Agents 구현을 순차적으로 살펴보았습니다. 이 모든 게 왜 필요할까요? 바로 여러분이 그 논문을 더 폭넓고 깊이 이해할 수 있도록 돕기 위해서입니다! 물론 이 두 파일에는 여전히 구현해야 할 부분이 많습니다. 그래서 이 두 파일을 개선하는 내용을 담아 이 글의 2부를 작성할 예정입니다! 아래에서 확인해 주세요!
읽어 주셔서 감사합니다!

참고문헌

생성형 에이전트에 관한 LangChain 문서 페이지: https://python.langchain.com/docs/use_cases/more/agents/agent_simulations/characters
내가 쓴 두 가지 멀티 에이전트 라이브러리에 관한 글 https://api.wandb.ai/links/vincenttu/nen3t5lx
Imgur에 있는 내 다이어그램:" https://imgur.com/a/u1P9SSX 먼저 번역할 본문을 제공해 주세요.


부록(아래의 .py 섹션을 클릭하여 펼치기)

memory.py

generative_agent.py


이 글은 AI로 번역된 기사입니다. 오역이 의심되면 댓글로 알려주세요. 원문 링크는 다음과 같습니다: 원문 보고서 보기