
에이전트에 메모리 추가하기
요약
Amazon Bedrock AgentCore Runtime을 사용하여 AWS Briefing Agent에 메모리 기능을 추가하는 방법을 다룹니다. 세션 간 상태 공유가 불가능한 마이크로VM 환경에서 AgentCore Memory와 Strands Agents를 활용해 사용자 개인화 정보를 유지하는 아키텍처를 설명합니다.
핵심 포인트
- AgentCore Runtime은 세션 종료 시 파괴되는 격리된 microVM을 사용함
- 세션 간 데이터 유출 방지를 위해 기본적으로 상태 공유가 제한됨
- AgentCore Memory를 통해 사용자 선호도 등 개인화된 정보를 유지 가능
- agentcore.json 파일을 사용하여 에이전트와 메모리 저장소를 정의함
이 글은 Amazon Bedrock AgentCore Runtime에 배포된 개인화된 AWS 어시스턴트인 AWS Briefing Agent의 아키텍처, 구현 및 구축 과정에서 얻은 교훈을 기록하는 시리즈 포스트 중 네 번째 글입니다.
- Part 1: Bedrock AgentCore에서 풀스택 AI 에이전트 구축하기
- Part 2: 데이터 인제스션 (Data Ingestion): RSS 피드, 지식 베이스 (Knowledge Base), S3 벡터 (S3 Vectors) 및 메타데이터 필터링 (Metadata Filtering)
- Part 3: Strands Agents + AgentCore Runtime - 완벽한 조합
- Part 4: 에이전트에 메모리 추가하기
- Part 5: API Gateway 실험하기
- Part 6: 관찰 가능성 (Observability) 및 평가 (Evaluations)
- Part 7: 제3자 통합 (Third Party Integrations) - ID (Identity), 게이트웨이 (Gateway) 및 Slack 알림
첫 번째 블로그 포스트에서 언급했듯이, AgentCore Runtime의 각 세션에는 격리된 CPU, 메모리 및 파일 시스템 리소스를 가진 전용 Firecracker 마이크로VM (microVM)이 할당됩니다. 세션이 종료되면 전체 마이크로VM이 파괴됩니다. 세션 간에 공유되는 상태(state)가 없으므로 세션 간 데이터 유출을 방지할 수 있습니다.
사용자가 우리의 AWS Briefing Agent 서비스에 처음 접속하면, 몇 가지 질문을 받게 됩니다.
여기에는 사용자가 관심을 가진 주요 AWS 서비스, AWS 경험 수준, 그리고 밀착하여 추적하고 싶은 특정 AWS 분야에 대한 질문이 포함됩니다.
메모리 기능이 없다면, 사용자는 새로운 세션을 시작할 때마다 동일한 정보를 매번 제공해야 합니다. 바로 이 지점에서 AgentCore Memory가 역할을 하게 됩니다. 이 포스트에서는 Strands Agents를 사용하여 AgentCore Memory를 설정하는 과정을 살펴봅니다.
AgentCore에서 메모리 설정하기
agentcore.json 파일은 Amazon Bedrock AgentCore에서 AI 에이전트(Agents), 게이트웨이(Gateways), 메모리 저장소(Memory stores) 및 데이터셋(Datasets)을 정의하고 관리하는 데 사용되는 기본 설정 파일입니다. 이 파일은 에이전트 인프라를 패키징하는 중앙 오케스트레이터(Orchestrator) 역할을 합니다.
agentcore deploy 명령을 실행하면, CLI가 이 파일을 읽고 AWS CDK를 사용하여 CloudFormation 리소스를 합성(Synthesize)하고 배포합니다. 우리는 메모리(Memory) 섹션에서 리소스 식별자(Resource identifier)로 "BriefingAgentMemory"를 사용하여 에이전트에 장기 메모리(Long term memory)를 추가합니다. 이것이 우리의 핸들러(Handler)에서 참조되는 식별자입니다.
AgentCore Memory 자체는 아래 다이어그램에 표시된 것처럼 에이전트에게 단기 컨텍스트(Short-term context)와 장기 지능(Long-term intelligence)을 모두 제공하기 위해 함께 작동하는 몇 가지 핵심 구성 요소로 이루어져 있습니다:
사용자와의 상호작용은 이벤트 만료 기간(Event expiry duration) 속성에 지정된 대로 90일 동안 단기(Short term)에 저장됩니다. 그런 다음 우리는 이러한 단기 원시 이벤트(Short term raw events)를 장기 메모리(Long-term memory)로 변환하는 두 가지 별도의 메모리 전략(Memory strategies)을 지정합니다. 모든 전략은 기본적으로 장기 메모리 기록에서 개인 식별 정보(PII) 데이터를 무시한다는 점에 유의하세요.
우리는 agentcore.json 파일에서 다음과 같은 전략을 정의합니다:
- SEMANTIC - 이 메모리 전략은 대화 데이터에서 핵심적인 사실 정보와 문맥적 지식(Contextual knowledge)을 식별하고 추출합니다. 예를 들어, 사용자가 프로덕션 환경에서 AWS Lambda를 실행하고 있는 경우입니다.
- USER_PREFERENCE - 이 메모리 전략은 대화에서 사용자의 선호도(Preferences), 선택(Choices) 및 스타일(Styles)을 자동으로 식별하고 추출하도록 설계되었습니다. 예를 들어, 사용자가 서버리스(Serverless)와 컨테이너(Containers)에 관심이 있는 경우입니다.
각 전략은 네임스페이스 (namespace) 내의 계층적 구조에 장기 메모리 (long-term memory)를 저장합니다. 이러한 네임스페이스는 별개의 논리적 컨테이너 (logical containers) 역할을 합니다. 우리는 특수한 {actorId} 플레이스홀더 (placeholder) 변수를 사용하여 이들을 분리하며, 이를 통해 각 사용자 간의 분리를 보장합니다.
우리 agentcore.json 파일의 관련 메모리 섹션 전체는 아래와 같습니다:
"memories": [
{
"name": "BriefingAgentMemory",
...
Cognito 및 AgentCore Runtime 통합
이 시점에서, 우리는 에이전트에 대한 요청을 어떻게 인증하는지에 대해 전환(segway)할 필요가 있습니다. AgentCore Runtime은 두 가지 인바운드 인증 (inbound authentication) 메커니즘을 지원합니다:
- AWS IAM SigV4 -
InvokeAgentRuntimeAPI에 대한 요청이bedrock-agentcore:InvokeAgentRuntimeIAM 권한을 가진 유효한 AWS 자격 증명으로 SigV4 서명되는 방식입니다. - JWT Bearer Token 인증 - 인바운드 JWT 인증기 (Inbound JWT authoriser)로 구성됩니다.
프론트엔드 (frontend)가 에이전트를 호출할 때, 에이전트의 공개 엔드포인트 (public endpoint) URL로 요청을 보냅니다:
이 URL은 AgentCore Runtime이 노출하는 특수한 공개 엔드포인트입니다. 우리는 이를 agentcore.json 파일에 지정합니다:
"runtimes": [
{
"name": "AWSBriefingAgent",
...
discoveryUrl은 프론트엔드에서 사용자를 인증하는 데 사용되는 지정된 ID의 AWS Cognito User Pool에 대한 Cognito의 OpenID Connect discovery 문서를 가리킵니다. AgentCore Runtime이 JWT 토큰을 검증하고자 할 때, 이 엔드포인트로부터 발행자 (issuer) 및 JWKS 엔드포인트 (JWT 서명 검증에 사용되는 공개 키를 포함)와 같은 정보를 가져옵니다.
allowedClients는 Cognito 애플리케이션 클라이언트 ID (Application Client ID)를 보여줍니다. 사용자가 로그인하면, Cognito는 토큰에 client_id를 찍습니다. AgentCore는 JWT의 client_id 클레임 (claim)을 검증하므로, 허용된 애플리케이션 클라이언트 중 하나에 대해 발행된 토큰만이 런타임 (runtime)을 호출할 수 있습니다.
사용자가 이메일 주소와 비밀번호로 우리의 프론트엔드 애플리케이션에 로그인하면, 프론트엔드는 검증을 위해 Cognito를 직접 호출하고, 다음과 같은 값을 돌려받습니다
- Access token (액세스 토큰) — 당신이 누구인지, 그리고 무엇을 할 수 있는 권한이 있는지를 증명합니다.
- ID token (ID 토큰) — 프로필 정보(이메일, 이름)를 포함합니다. 프론트엔드에서 사용자 이름을 표시하는 데 사용됩니다.
- Refresh token (리프레시 토큰) — 토큰이 만료되었을 때(보통 1시간 후) 새로운 액세스/ID 토큰을 가져오는 데 사용됩니다. 이 토큰들은 프론트엔드 인증 라이브러리에 의해 저장됩니다.
프론트엔드가 에이전트에 요청을 보낼 때, 액세스 토큰을 Bearer 토큰으로 첨부합니다.
POST /invocations
Authorization: Bearer eyJraWQi...
Body: {"prompt": "Give me a briefing"}
이것은 AgentCore Runtime에 의해 검증되는 JWT 토큰입니다.
핸들러 함수에서 메모리 레코드 반환하기
다음 코드 스니펫은 프론트엔드의 사이드바에 표시할 메모리 레코드를 어떻게 가져오는지 보여줍니다.
@app.entrypoint
async def invoke(payload: Dict[str, Any], context: Any = None):
message = payload.get("prompt", payload.get("message", ""))
...
@app.entrypoint 데코레이터는 특정 함수를 /invocations에 대한 POST 요청의 핸들러로 등록합니다. 클라이언트가 에이전트를 호출하면 AgentCore Runtime은 이 핸들러 함수를 호출합니다. 우리의 핸들러 함수는 비동기 제너레이터(async generator)이며, 이는 응답을 Server-Sent Events (SSE)로 자동 스트리밍하여 클라이언트에 실시간으로 전달함을 의미합니다 (이에 대한 자세한 내용은 다음 블로그 포스트에서 다룹니다).
핸들러 내에서, 우리는 페이로드(payload)에 담겨 전송된 메시지를 가져옵니다. 그런 다음 Cognito가 발급한 JWT 토큰에서 사용자의 신원(identity)을 추출합니다. JWT 토큰의 클레임(claims) 중 하나는 sub 또는 subject이며, 이는 사용자가 처음 등록할 때 Cognito가 사용자에게 할당하는 고유 사용자 ID입니다. 우리는 JWT 토큰이 핸들러 함수에 도달하기 전에 Cognito에 의해 암호학적으로 서명되었고 AgentCore Runtime에 의해 검증되었음을 알고 있습니다. 우리는 이 sub 값을 actor_id로 할당합니다. 또한 실제 값에 지원되지 않는 문자가 포함되어 있지 않도록 정규 표현식(regex)을 적용합니다.
그다음 get_memory_records 함수를 호출합니다. 이 함수는 AgentCore의 메모리 레코드 검색 (retrieve memory records) API를 호출하여, 방금 전달된 프롬프트 (prompt)와 관련된 사실 및 선호도를 장기 메모리 (long-term memory)에서 검색합니다. 벡터 검색 (vector search)을 통해 가장 높은 점수를 받은 5개의 결과를 가져와 records 배열에 저장하며, 이 배열은 사이드바 (sidebar)에 표시되도록 프론트엔드 (frontend)로 스트리밍됩니다.
def get_memory_records(actor_id: str, prompt: str) -> List[Dict[str, Any]]:
"""사용자의 프롬프트와 관련된 장기 메모리 레코드를 검색합니다.
...
아래에서 프론트엔드의 사이드바 예시를 확인할 수 있습니다:
Strands를 이용한 메모리 설정
단기 메모리 (short-term memory)와 장기 메모리 (long-term memory) 모두 Strands를 위한 AgentCore 메모리 세션 관리자 (Memory session manager) 통합을 통해 자동으로 처리됩니다.
메모리 ID는 모듈 수준의 상수로 가져옵니다:
MEMORY_ID = os.environ.get("MEMORY_BRIEFINGAGENTMEMORY_ID")
이는 AgentCore 런타임 (Runtime)이 실행 시점에 컨테이너에 환경 변수 (environment variable)로 자동으로 주입하는 메모리 리소스 ID를 읽어옵니다. 명명 규칙 (naming convention)은 MEMORY__ID입니다. agentcore.json 파일에서 메모리 이름이 "BriefingAgentMemory"로 지정되었으므로, AgentCore는 MEMORY_BRIEFINGAGENTMEMORY_ID를 실제 메모리 리소스 ID(예: AWSBriefingAgent_BriefingAgentMemory-q2iBfL64BS와 같은 값)로 설정합니다.
코드의 다음 함수는 모든 요청 시 호출됩니다. 호출될 때마다 새로운 상태 비저장 (stateless) Strands 에이전트 (Agent) 인스턴스가 생성되며, AgentCore 메모리로부터 대화 기록을 로드하는 관련 세션 관리자, 도구 (tools) 및 모델 설정이 구성됩니다.
def _create_agent(session_id: str, actor_id: str, gateway_tools: list = None) -> Agent:
"""KB 검색, AgentCore 메모리, Gateway 도구(tools)를 갖춘 Strands 에이전트를 생성합니다."""
session_manager = None
...
코드에서 메모리가 설정되어 있다면, AgentCoreMemorySessionManager를 임포트합니다. 이 세션 관리자(session manager)는 단기 메모리(short-term memory)와 장기 메모리(long-term memory) 기능을 동기화하여 Strands 에이전트를 AgentCore 메모리와 통합합니다. 주요 기능으로는 에이전트 초기화(initialisation) 시 단기 메모리로부터 대화 기록을 로드하는 것과, 에이전트 상태(state)에 컨텍스트를 주입하기 위해 장기 메모리와 통합하는 것 등이 있습니다.
다음으로 세션 관리자에 전달할 AgentCoreMemoryConfig 설정 객체를 생성하며, 여기에는 다음 정보가 포함됩니다:
- memory_id - 연결할 AgentCore 메모리 리소스
- session_id - 대화 세션의 식별자
- actor_id - 사용자의 고유 식별자
- retrieval_config - 네임스페이스(namespaces)를 검색 설정(retrieval configurations)에 매핑하는 딕셔너리(dictionary). 이는 세션 관리자에게 두 네임스페이스에서 관련 있는 장기 메모리를 검색하고, 가장 관련성이 높은 5개의 사실과 사용자 선호도를 가져오도록 지시합니다.
이제 AgentCore 메모리 사용은 Strands 에이전트 세션 관리자에 의해 자동으로 처리됩니다. 매 턴(turn)이 시작되기 전, 에이전트의 대화 컨텍스트(conversation context)를 채우기 위해 동일한 세션으로부터 최근 이벤트들을 로드합니다. 단기 메모리는 가공되지 않은 이벤트 스트림(raw event stream)입니다. 에이전트는 슬라이딩 윈도우 대화 관리자(Sliding Window Conversation Manager) 설정에 따라 컨텍스트 윈도우(context window) 내에서 마지막 20개의 턴을 확인하게 됩니다. 에이전트 호출(invocation) 중 및 호출 후에 새로운 대화 메시지는 AgentCore 메모리에 자동으로 저장(persist)됩니다.
이 과정을 통해 에이전트에 장기 메모리를 성공적으로 추가하였으며, 사용자의 선호도에 따라 각 사용자별 브리핑을 개인화할 수 있게 되었습니다.
Biography
Biography
영국 IBM의 수석 AWS 아키텍트 (Chief AWS Architect)로서, 저는 전 세계에서 가장 빠르게 성장하는 AWS 컨설팅 파트너 중 하나인 이곳에서 AWS 역량과 커뮤니티를 확장하는 책임을 맡고 있습니다. 덕분에 최신 기능들이 정식 출시 (General Availability)되기 전에 프리뷰 (Preview) 단계에서 미리 테스트해 볼 수 있는 기회를 얻고 있습니다. 저는 종종 저의 경험을 블로그에 기록하곤 하지만, 더 알고 싶은 서비스가 있다면 언제든 문의해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
