장기 기억
원문: 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);이렇게 하면 세 가지 정리 모드가 제공됩니다.
- 완전 자동(기본값): 기능을 설치하고 스토리지를 구성합니다. 검색 및 수집이 자동으로 작동합니다.
- 수동 전용:
enableAutomaticRetrieval = false/enableAutomaticIngestion = false을 설정하고 그래프 전략 노드에서 스토리지 및 전략을 사용합니다. - 하이브리드: 자동 수집과 수동 검색을 결합합니다(또는 그 반대).
전략 노드에서 장기 메모리에 액세스
전략 노드 내에서 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을 사용하세요. KeywordSearchRequest 및 SimilaritySearchRequest를 모두 허용하지만 둘 다 단순한 대소문자 구분 하위 문자열 일치(벡터 임베딩 없음)로 구현합니다.