Code refactoring, async code fixes and error handling#2
Merged
jcodella merged 2 commits intoApr 7, 2026
Conversation
There was a problem hiding this comment.
Pull request overview
Refactors the Agent Memory Toolkit into smaller sync/async client modules, fixes async implementation issues, and adds structured error handling + logging across Cosmos/LLM/processing paths while modernizing packaging and CI.
Changes:
- Split monolithic memory implementations into dedicated modules (models/exceptions/query builder + Cosmos/embeddings/processing clients, with async mirrors under
aio/). - Add more resilient Durable Functions activities (input validation, retries, batch embeddings, richer logging).
- Introduce
pyproject.tomlpackaging, CI (ruff + pytest/coverage matrix), and extensive offline unit test coverage.
Reviewed changes
Copilot reviewed 35 out of 40 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| agent_memory_toolkit/models.py | Adds Pydantic models/enums for validated memory and result envelopes. |
| agent_memory_toolkit/exceptions.py | Introduces a unified exception hierarchy for catchable, typed failures. |
| agent_memory_toolkit/_query_builder.py | Adds a small shared helper for building parameterized Cosmos WHERE clauses. |
| agent_memory_toolkit/_utils.py | Centralizes shared validation/query/policy helpers used by sync+async clients. |
| agent_memory_toolkit/cosmos_memory_client.py | New sync Cosmos client encapsulating CRUD + vector/hybrid search. |
| agent_memory_toolkit/embeddings.py | New sync Azure OpenAI embeddings client (lazy init + batch support). |
| agent_memory_toolkit/processing.py | New sync Durable Functions HTTP-start + polling client. |
| agent_memory_toolkit/memory.py | Sync orchestrator updated to compose the extracted sub-clients. |
| agent_memory_toolkit/aio/init.py | Exports async API surface mirroring sync package layout. |
| agent_memory_toolkit/aio/cosmos_memory_client.py | New async Cosmos client for CRUD + vector/hybrid search. |
| agent_memory_toolkit/aio/embeddings.py | New async embeddings client (lazy init + batch support). |
| agent_memory_toolkit/aio/processing.py | New async Durable Functions client using a reusable aiohttp session. |
| agent_memory_toolkit/aio/memory.py | New async orchestrator composing async sub-clients + lifecycle management. |
| agent_memory_toolkit/init.py | Re-exports key public types/exceptions and AsyncAgentMemory entrypoint. |
| azure_functions/activities.py | Adds validation, retries/backoff, batch embeddings, and improved logging/error handling. |
| tests/conftest.py | Adds shared fixtures for sample docs/embeddings/credentials. |
| tests/unit/test_models.py | Unit tests for models, validation, and Cosmos serialization round-trips. |
| tests/unit/test_exceptions.py | Unit tests for exception inheritance and structured attributes. |
| tests/unit/test_query_builder.py | Unit tests for query builder filter composition/copy semantics. |
| tests/unit/test_cosmos_memory_client.py | Unit tests for sync Cosmos store CRUD/search behavior (mocked). |
| tests/unit/test_embeddings.py | Unit tests for sync embeddings (auth modes, lazy init, ordering). |
| tests/unit/test_processing.py | Unit tests for sync Durable Functions polling and error paths. |
| tests/unit/test_memory.py | Unit tests for sync orchestrator delegation and local CRUD. |
| tests/unit/aio/test_cosmos_memory_client.py | Unit tests for async Cosmos store behavior (mocked + async iterators). |
| tests/unit/aio/test_embeddings.py | Unit tests for async embeddings client behavior and cleanup. |
| tests/unit/aio/test_processing.py | Unit tests for async processing client session reuse, polling, close/ctx mgr. |
| tests/unit/aio/test_memory.py | Unit tests for async orchestrator lifecycle and delegation. |
| .github/workflows/ci.yml | Adds CI jobs for ruff + pytest/coverage across Python 3.11–3.13. |
| pyproject.toml | Defines packaging metadata, dependencies, pytest/ruff/coverage config. |
| uv.lock | Adds a lockfile for reproducible dependency resolution. |
| README.md | Updates structure, install instructions, and async import guidance. |
| Docs/local_testing.md | Updates Python version guidance and install commands for new packaging. |
| Docs/azure_testing.md | Updates prerequisites and install commands for new packaging. |
| Docs/design_patterns.md | Updates async import path to agent_memory_toolkit.aio. |
| Samples/Demo_async.ipynb | Updates async demo import path to agent_memory_toolkit.aio. |
| requirements.txt | Removed in favor of pyproject.toml dependencies. |
| agent_memory_toolkit/async_memory.py | Deleted legacy async monolith in favor of aio/ package. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
urlliboropenaiexceptions with no context.NameError(_osundefined at line 62), deprecatedasyncio.get_event_loop()calls (3 sites), and a newaiohttp.ClientSessioncreated per request causing connection churn.What This PR Does
1 - Code Cleanup & Modularity
Broke the two monoliths into focused, single-responsibility modules:
models.pyMemoryRecord,MemoryRole/MemoryTypeenums,SearchResultdictconstruction via_make_memory()exceptions.pyAgentMemoryErrorValueError,KeyError,RuntimeError,TimeoutErrorscattered throughoutcosmos_memory_client.pyCosmosMemoryStore— sync Cosmos CRUD, vector search, container provisioningmemory.pyembeddings.pyEmbeddingsClient— sync Azure OpenAI embedding generationmemory.py,async_memory.py, andactivities.pyprocessing.pyProcessingClient— sync Durable Functions HTTP-start + pollmemory.py_query_builder.py_QueryBuilderhelper for Cosmos query constructionaio/subpackageAsyncCosmosMemoryStore,AsyncEmbeddingsClient,AsyncProcessingClient,AsyncAgentMemoryasync_memory.pymonolith (deleted)memory.pydropped from 1,069 → 574 lines. It's now a thin orchestrator composing the extracted modules.Key design decisions:
endpoint,credential,database, etc. directly — matching theazure-cosmosSDK convention.aio/subpackage for async. Mirrorsazure.cosmos/azure.cosmos.aio. Some code duplication is intentional for independent readability.from agent_memory_toolkit import AsyncAgentMemorystill works via re-export.2 - Async Fixes
_osNameErrorasync_memory.py:62asyncio.get_event_loop()AsyncProcessingClientusesasyncio.get_running_loop().time()aiohttp.ClientSessionper callclose()+async withsupport3 - Error Handling & Logging
Logging — Every module now uses
logging.getLogger(__name__):exc_info=TrueException hierarchy:
Activities (
azure_functions/activities.py):_call_llm_with_retry()— exponential backoff for 429/500/503 (3 attempts, 2s base)_validate_required()on all 6 activity functionsCosmosHttpResponseErrorhandling_build_transcript(),_load_prompt(),_get_llm_model()LLM_MODEL→AI_FOUNDRY_LLM→"gpt-4o"File Change Summary
New Files (11)
models.pyMemoryRecord(Pydantic), enums,SearchResult,OrchestrationResultexceptions.py_query_builder.py_QueryBuilderfor Cosmos queriescosmos_memory_client.pyCosmosMemoryStoreembeddings.pyEmbeddingsClientprocessing.pyProcessingClientaio/__init__.pyaio/cosmos_memory_client.pyAsyncCosmosMemoryStoreaio/embeddings.pyAsyncEmbeddingsClientaio/processing.pyAsyncProcessingClientaio/memory.pyAsyncAgentMemoryModified Files (4)
memory.py__init__.pyAsyncAgentMemoryfromaioactivities.pyREADME.mdSamples/Demo_async.ipynbfrom agent_memory_toolkit.aio import AsyncAgentMemoryDeleted Files (3)
async_memory.pyaio/memory.pyrequirements.txtpyproject.toml[project]dependenciesrequirements-dev.txtpyproject.toml[project.optional-dependencies]devNew: Packaging & CI
pyproject.toml[project]metadata (name, version,requires-python >= 3.11, deps, dev deps),[tool.pytest],[tool.ruff],[tool.coverage]uv.lock.github/workflows/ci.ymlNew Test Files (12)
tests/conftest.pytests/unit/test_models.pytests/unit/test_exceptions.pytests/unit/test_query_builder.pytests/unit/test_cosmos_memory_client.pytests/unit/test_embeddings.pytests/unit/test_processing.pytests/unit/test_memory.pytests/unit/aio/test_cosmos_memory_client.pytests/unit/aio/test_embeddings.pytests/unit/aio/test_processing.pytests/unit/aio/test_memory.pyBackward Compatibility
AgentMemory(cosmos_endpoint=..., ...)constructorfrom agent_memory_toolkit import AsyncAgentMemoryaio)list[dict])_make_memory(),VALID_ROLES,VALID_TYPESmemory.pyfunction_app.py)New preferred async import path:
Architecture
Dependencies
pydantic(v2)MemoryRecordvalidation and Cosmos serializationazure-cosmos,azure-identity,openai,aiohttpDev Dependencies (
pip install ".[dev]")pytest>= 8.0pytest-asyncio>= 0.23pytest-cov>= 5.0pytest-mock>= 3.12ruff>= 0.4CI/CD Pipeline (
.github/workflows/ci.yml)lintruff check+ruff format --checktestpytest tests/unit/with coverage, uploadscoverage.xmlper versionTriggers on push to
mainand all PRs tomain. 6 jobs total (3 versions × 2 jobs).Unit Tests — 184 Tests, 87% Coverage
All tests are fully mocked with zero cloud dependencies. Every test runs offline.
Sync Unit Tests (
tests/unit/)test_models.pyMemoryRecordcreation with defaults, enum validation (valid + invalid roles/types),to_cosmos_dict()key mapping + None omission,from_cosmos_dict()round-trip + Cosmos system field handling,SearchResult,OrchestrationResulttest_exceptions.pyAgentMemoryError,ConfigurationErrorauto-message fromparameterkwarg,MemoryNotFoundErrorcontext attributes,CosmosNotConnectedErrordefault message,OrchestrationTimeoutErrorattributes, base class catch-alltest_query_builder.pyNonevalue skipping,get_parameters()returns copytest_cosmos_memory_client.pyconnect()success + missing config errors,create_store()partition key + vector index + full-text policy,upsert/upsert_batch,get_memorieswith all filter combos +recent_k,get_thread,update/delete(success +MemoryNotFoundError),vector_search(vector-only + hybrid/RRF),get_user_summary,_require_connectedguardtest_embeddings.pygenerate()success + lazy init reuse, api_key vs credential auth paths, missing config errors, API failure →EmbeddingErrorwrapping,generate_batch()order preservation via index sort, empty batch, dimensions kwargtest_processing.pyinvoke_orchestrator()immediate completion + multi-poll +Failed→ProcessingError+ timeout →OrchestrationTimeoutError, missing endpoint, function key in URL, convenience method payloads (thread_summary_only,extract_facts_only,user_summary_only)test_memory.pyconnect,add,push,get,searchcomposing embeddings + vector_search), processing delegation,CosmosNotConnectedErrorguardAsync Unit Tests (
tests/unit/aio/)test_cosmos_memory_client.pyclose(),async withcontext manager,upsert_batchwithbatch_sizetest_embeddings.pyclose(),async withcontext managertest_processing.pyclose(),async withcontext manager, lazyaiohttp.ClientSessiontest_memory.pyclose()delegates to all sub-clients,async withcontext managerUncovered lines are primarily Cosmos SDK interaction paths (
create_storecontainer provisioning,update/deleteCosmos error branches) that require live Azure services — covered by integration tests in a future PR.Lint
ruff checkandruff format --checkboth pass with 0 errors across all source and test files.Suggested Review Order
models.py+exceptions.py— foundational types_query_builder.py— tiny shared helpercosmos_memory_client.py,embeddings.py,processing.pymemory.py— see how it composes the aboveaio/— async mirrorsactivities.py— error handling, retry, DRY helperstests/conftest.py→tests/unit/→tests/unit/aio/— test coverage