장기 기억

·3분 읽기

원문: Koog Documentation — long-term-memory 이 글은 Koog 공식 문서의 long-term-memory 페이지를 한국어로 옮긴 번역본입니다. 문서 구조와 링크 의미를 유지하되, MkDocs 전용 UI 문법은 블로그에서 읽기 좋도록 정리했습니다.

장기 기억

기능(실험적)

LongTermMemory 기능은 두 개의 독립적인 설정 그룹을 통해 Koog AI 에이전트에 영구 메모리를 추가합니다.

  • 검색 — 메모리 저장소의 관련 컨텍스트로 LLM 프롬프트를 강화합니다(검색 증강 생성 또는 RAG).
  • 수집 — 향후 검색을 위해 대화 메시지를 메모리 저장소에 유지합니다.

빠른 시작

참고: LongTermMemory은 실험적인 API입니다. 코드에 @OptIn(ExperimentalAgentsApi::class) 주석을 달거나 파일 상단에 @file:OptIn(ExperimentalAgentsApi::class)를 추가하세요.

코틀린

1@OptIn(ExperimentalAgentsApi::class)2val myStorage = InMemoryRecordStorage() // or your vector DB adapter34@OptIn(ExperimentalAgentsApi::class)5val agent = AIAgent(6    promptExecutor = executor,7    strategy = singleRunStrategy(),8    agentConfig = agentConfig,9    toolRegistry = ToolRegistry.EMPTY10) {11    install(LongTermMemory) {12        retrieval {13            storage = myStorage14            searchStrategy = SimilaritySearchStrategy(topK = 5)15        }16    }17}1819agent.run("What did we discuss yesterday?")

자바

1InMemoryRecordStorage myStorage = new InMemoryRecordStorage();23AIAgent agent = AIAgent.builder()4    .promptExecutor(executor)5    .llmModel(OpenAIModels.Chat.GPT4o)6    .systemPrompt("You are a helpful assistant.")7    .install(LongTermMemory.Feature, config -> {8        config.retrieval(9            new LongTermMemory.RetrievalSettingsBuilder()10                .withStorage(myStorage)11                .withSearchStrategy(12                    SearchStrategy.builder().similarity().withTopK(5).build()13                )14                .build()15        );16    })17    .build();1819Object result = agent.run("What did we discuss yesterday?");

검색 전용(RAG)

기술 자료가 미리 채워진 경우 수집 없이 검색을 사용합니다.

코틀린

1@OptIn(ExperimentalAgentsApi::class)2install(LongTermMemory) {3    retrieval {4        storage = myVectorDbStorage5        namespace = "my-collection"  // optional: scope to a specific namespace/collection6        searchStrategy = SimilaritySearchStrategy(topK = 3, similarityThreshold = 0.7)7        promptAugmenter = SystemPromptAugmenter()8    }9}

자바

1var retrievalSettings = new LongTermMemory.RetrievalSettingsBuilder()2    .withStorage(myVectorDbStorage)3    .withSearchStrategy(4        SearchStrategy.builder().similarity().withTopK(3).withSimilarityThreshold(0.7).build()5    )6    .withPromptAugmenter(PromptAugmenter.builder().system().build())7    .build();

프롬프트 증강기

증강기 행동
SystemPromptAugmenter() 프롬프트 시작 시 시스템 메시지로 컨텍스트를 삽입합니다(시스템 메시지가 없으면 작동하지 않음).
UserPromptAugmenter() 마지막 사용자 메시지 앞에 별도의 사용자 메시지로 컨텍스트를 삽입합니다.
PromptAugmenter { prompt, context -> ... } 람다를 통한 사용자 정의 증대

쿼리 추출기

기본적으로 검색 흐름에서는 마지막 사용자 메시지를 검색 쿼리로 사용합니다. QueryExtractor을 제공하여 이를 사용자 정의할 수 있습니다.

추출기 행동
LastUserMessageQueryExtractor() 마지막 사용자 메시지의 내용을 사용합니다(기본값)
QueryExtractor { prompt -> ... } 람다를 통한 사용자 정의 추출

코틀린

1@OptIn(ExperimentalAgentsApi::class)2install(LongTermMemory) {3    retrieval {4        storage = myStorage5        queryExtractor = QueryExtractor { prompt ->6            // Combine the last two user messages as the search query7            prompt.messages8                .filter { it.role == Message.Role.User }9                .takeLast(2)10                .joinToString(" ") { it.content }11                .ifEmpty { null }12        }13    }14}

자바

1var retrievalSettings = new LongTermMemory.RetrievalSettingsBuilder()2    .withStorage(myStorage)3    .withQueryExtractor(prompt -> {4        var userMessages = prompt.getMessages().stream()5            .filter(m -> m.getRole() == Message.Role.User)6            .toList();7        if (userMessages.isEmpty()) return null;8        return userMessages.get(userMessages.size() - 1).getContent();9    })10    .build();

검색 전략

전략 행동
SimilaritySearchStrategy() 벡터 유사성 의미 검색 — 기본값
query -> new SimilaritySearchRequest(query, 20, 0, 0.0, null) 람다를 통한 맞춤 검색

섭취 전용

시간이 지남에 따라 메모리 저장소를 구축하려면 검색 없이 수집을 사용합니다.

코틀린

1@OptIn(ExperimentalAgentsApi::class)2install(LongTermMemory) {3    ingestion {4        storage = myVectorDbStorage5        namespace = "my-collection"  // optional: scope to a specific namespace/collection6        extractionStrategy = FilteringExtractionStrategy(7            messageRolesToExtract = setOf(Message.Role.User, Message.Role.Assistant)8        )9        timing = IngestionTiming.ON_LLM_CALL10    }11}

자바

1var ingestionSettings = new LongTermMemory.IngestionSettingsBuilder()2    .withStorage(myVectorDbStorage)3    .withExtractionStrategy(4        ExtractionStrategy.builder()5            .filtering()6            .withExtractRoles(new HashSet<>(Arrays.asList(Message.Role.User, Message.Role.Assistant)))7            .withLastMessageOnly(false)8            .build()9    )10    .withTiming(IngestionTiming.ON_LLM_CALL)11    .build();

수집 시기

타이밍 행동
ON_LLM_CALL 각 LLM 통화가 시작되기 전에 프롬프트 메시지가 수집됩니다. 보조 출력은 완료 또는 스트림 완료 후에 수집됩니다. 세션 내 RAG를 활성화합니다.
ON_AGENT_COMPLETION 최종 누적 세션 프롬프트/기록은 에이전트 완료 시 한 번 수집됩니다.

자동 동작 비활성화

기본적으로 검색 및 수집은 자동으로 실행됩니다(각각 LLM 호출 전후). 전략 노드 내에서 구성된 스토리지 및 전략에 계속 액세스하면서 자동 동작을 비활성화할 수 있습니다.

코틀린

1@OptIn(ExperimentalAgentsApi::class)2install(LongTermMemory) {3    retrieval {4        storage = myStorage5        enableAutomaticRetrieval = false  // no automatic prompt augmentation6    }7    ingestion {8        storage = myStorage9        enableAutomaticIngestion = false  // no automatic message persistence10    }11}

자바

1config.retrieval(2    new LongTermMemory.RetrievalSettingsBuilder()3        .withStorage(myStorage)4        .withEnableAutomaticRetrieval(false)5        .build()6);7config.ingestion(8    new LongTermMemory.IngestionSettingsBuilder()9        .withStorage(myStorage)10        .withEnableAutomaticIngestion(false)11        .build()12);

이렇게 하면 세 가지 정리 모드가 제공됩니다.

  1. 완전 자동(기본값): 기능을 설치하고 스토리지를 구성합니다. 검색 및 수집이 자동으로 작동합니다.
  2. 수동 전용: enableAutomaticRetrieval = false / enableAutomaticIngestion = false을 설정하고 그래프 전략 노드에서 스토리지 및 전략을 사용합니다.
  3. 하이브리드: 자동 수집과 수동 검색을 결합합니다(또는 그 반대).

전략 노드에서 장기 메모리에 액세스

전략 노드 내에서 withLongTermMemory { }을 사용하여 레코드를 직접 검색하거나 추가합니다.

1@OptIn(ExperimentalAgentsApi::class)2val myNode by node<String, Unit> {3    withLongTermMemory {4        // Manually add records5        val record = MemoryRecord(content = "important fact")6        ingestionStorage?.add(listOf(record), namespace = "my-namespace")78        // Manually search9        val request = SimilaritySearchRequest(queryText = input, limit = 5)10        val results = retrievalStorage?.search(request, namespace = "my-namespace")11    }12}

기능 인스턴스를 직접 가져오려면 longTermMemory()을 사용하세요.

1@OptIn(ExperimentalAgentsApi::class)2val myNode by node<String, Unit> {3    val memory = longTermMemory()4    val storage = memory.ingestionStorage5}

사용자 정의 추출 전략

ExtractionStrategy을 구현하여 저장 전에 메시지가 변환되는 방식을 제어합니다.

1@OptIn(ExperimentalAgentsApi::class)2val summarizingExtractor = ExtractionStrategy { messages ->3    messages4        .filter { it.role == Message.Role.Assistant }5        .map { MemoryRecord(content = summarize(it.content)) }6}78install(LongTermMemory) {9    ingestion {10        storage = myStorage11        extractionStrategy = summarizingExtractor12    }13}

맞춤형 스토리지 구현

벡터 데이터베이스에 연결하려면 SearchStorage 및/또는 WriteStorage을 구현하십시오.

1class MyVectorDbStorage : SearchStorage<TextDocument, SearchRequest>, WriteStorage<TextDocument> {2    override suspend fun search(3        request: SearchRequest, namespace: String?4    ): List<SearchResult<TextDocument>> {5        // Query your vector DB6    }78    override suspend fun add(9        records: List<TextDocument>, namespace: String?10    ) {11        // Upsert into your vector DB12    }13}

테스트를 위해 메모리에 기록을 보관하는 내장 InMemoryRecordStorage을 사용하세요. KeywordSearchRequestSimilaritySearchRequest를 모두 허용하지만 둘 다 단순한 대소문자 구분 하위 문자열 일치(벡터 임베딩 없음)로 구현합니다.