@@ -426,3 +426,185 @@ async def test_document_deletion(api_client):
426426 f"/v1/default/banks/{ test_bank_id } /documents/sales-report-q1-2024"
427427 )
428428 assert response .status_code == 404
429+
430+
431+ @pytest .mark .asyncio
432+ async def test_async_retain (api_client ):
433+ """Test asynchronous retain functionality.
434+
435+ When async=true is passed, the retain endpoint should:
436+ 1. Return immediately with success and async_=true
437+ 2. Process the content in the background
438+ 3. Eventually store the memories
439+ """
440+ import asyncio
441+
442+ test_bank_id = f"async_retain_test_{ datetime .now ().timestamp ()} "
443+
444+ # Store memory with async=true
445+ response = await api_client .post (
446+ f"/v1/default/banks/{ test_bank_id } /memories" ,
447+ json = {
448+ "async" : True ,
449+ "items" : [
450+ {
451+ "content" : "Alice is a senior engineer at TechCorp. She has been working on the authentication system for 5 years." ,
452+ "context" : "team introduction"
453+ }
454+ ]
455+ }
456+ )
457+ assert response .status_code == 200
458+ result = response .json ()
459+ assert result ["success" ] is True
460+ assert result ["async" ] is True , "Response should indicate async processing"
461+ assert result ["items_count" ] == 1
462+
463+ # Check operations endpoint to see the pending operation
464+ response = await api_client .get (f"/v1/default/banks/{ test_bank_id } /operations" )
465+ assert response .status_code == 200
466+ ops_result = response .json ()
467+ assert "operations" in ops_result
468+
469+ # Wait for async processing to complete (poll with timeout)
470+ max_wait_seconds = 30
471+ poll_interval = 0.5
472+ elapsed = 0
473+ memories_found = False
474+
475+ while elapsed < max_wait_seconds :
476+ # Check if memories are stored
477+ response = await api_client .get (
478+ f"/v1/default/banks/{ test_bank_id } /memories/list" ,
479+ params = {"limit" : 10 }
480+ )
481+ assert response .status_code == 200
482+ items = response .json ()["items" ]
483+
484+ if len (items ) > 0 :
485+ memories_found = True
486+ break
487+
488+ await asyncio .sleep (poll_interval )
489+ elapsed += poll_interval
490+
491+ assert memories_found , f"Async retain did not complete within { max_wait_seconds } seconds"
492+
493+ # Verify we can recall the stored memory
494+ response = await api_client .post (
495+ f"/v1/default/banks/{ test_bank_id } /memories/recall" ,
496+ json = {
497+ "query" : "Who works at TechCorp?" ,
498+ "thinking_budget" : 30
499+ }
500+ )
501+ assert response .status_code == 200
502+ search_results = response .json ()
503+ assert len (search_results ["results" ]) > 0 , "Should find the asynchronously stored memory"
504+
505+ # Verify Alice is mentioned
506+ found_alice = any ("Alice" in r ["text" ] for r in search_results ["results" ])
507+ assert found_alice , "Should find Alice in search results"
508+
509+
510+ @pytest .mark .asyncio
511+ async def test_async_retain_parallel (api_client ):
512+ """Test multiple async retain operations running in parallel.
513+
514+ Verifies that:
515+ 1. Multiple async operations can be submitted concurrently
516+ 2. All operations complete successfully
517+ 3. The exact number of documents are processed
518+ """
519+ import asyncio
520+
521+ test_bank_id = f"async_parallel_test_{ datetime .now ().timestamp ()} "
522+ num_documents = 5
523+
524+ # Prepare multiple documents to retain
525+ documents = [
526+ {
527+ "content" : f"Document { i } : This is test content about Person{ i } who works at Company{ i } ." ,
528+ "context" : f"test document { i } " ,
529+ "document_id" : f"doc_{ i } "
530+ }
531+ for i in range (num_documents )
532+ ]
533+
534+ # Submit all async retain operations in parallel
535+ async def submit_async_retain (doc ):
536+ return await api_client .post (
537+ f"/v1/default/banks/{ test_bank_id } /memories" ,
538+ json = {
539+ "async" : True ,
540+ "items" : [doc ]
541+ }
542+ )
543+
544+ # Run all submissions concurrently
545+ responses = await asyncio .gather (* [submit_async_retain (doc ) for doc in documents ])
546+
547+ # Verify all submissions succeeded
548+ for i , response in enumerate (responses ):
549+ assert response .status_code == 200 , f"Document { i } submission failed"
550+ result = response .json ()
551+ assert result ["success" ] is True
552+ assert result ["async" ] is True
553+
554+ # Check operations endpoint - should show pending operations
555+ response = await api_client .get (f"/v1/default/banks/{ test_bank_id } /operations" )
556+ assert response .status_code == 200
557+
558+ # Wait for all async operations to complete (poll with timeout)
559+ max_wait_seconds = 60
560+ poll_interval = 1.0
561+ elapsed = 0
562+ all_docs_processed = False
563+
564+ while elapsed < max_wait_seconds :
565+ # Check document count
566+ response = await api_client .get (f"/v1/default/banks/{ test_bank_id } /documents" )
567+ assert response .status_code == 200
568+ docs = response .json ()["items" ]
569+
570+ if len (docs ) >= num_documents :
571+ all_docs_processed = True
572+ break
573+
574+ await asyncio .sleep (poll_interval )
575+ elapsed += poll_interval
576+
577+ assert all_docs_processed , f"Expected { num_documents } documents, but only { len (docs )} were processed within { max_wait_seconds } seconds"
578+
579+ # Verify exact document count
580+ response = await api_client .get (f"/v1/default/banks/{ test_bank_id } /documents" )
581+ assert response .status_code == 200
582+ final_docs = response .json ()["items" ]
583+ assert len (final_docs ) == num_documents , f"Expected exactly { num_documents } documents, got { len (final_docs )} "
584+
585+ # Verify each document exists
586+ doc_ids = {doc ["id" ] for doc in final_docs }
587+ for i in range (num_documents ):
588+ assert f"doc_{ i } " in doc_ids , f"Document doc_{ i } not found"
589+
590+ # Verify memories were created for all documents
591+ response = await api_client .get (
592+ f"/v1/default/banks/{ test_bank_id } /memories/list" ,
593+ params = {"limit" : 100 }
594+ )
595+ assert response .status_code == 200
596+ memories = response .json ()["items" ]
597+ assert len (memories ) >= num_documents , f"Expected at least { num_documents } memories, got { len (memories )} "
598+
599+ # Verify we can recall content from different documents
600+ for i in [0 , num_documents - 1 ]: # Check first and last
601+ response = await api_client .post (
602+ f"/v1/default/banks/{ test_bank_id } /memories/recall" ,
603+ json = {
604+ "query" : f"Who works at Company{ i } ?" ,
605+ "thinking_budget" : 30
606+ }
607+ )
608+ assert response .status_code == 200
609+ results = response .json ()["results" ]
610+ assert len (results ) > 0 , f"Should find memories for document { i } "
0 commit comments