Skip to content

Commit 7a43443

Browse files
aayush3011Aayush KatariaCopilot
authored
Memory Types, Pluggable Processors, and Function App Hand-off (#7)
* Update CosmosMemoryClient with filtering, tagging, salience, procedural/episodic retrieval, and ProcessingPipeline - Replace ProcessingClient (Azure Durable Functions HTTP) with local ProcessingPipeline - Add LLMClient alongside EmbeddingsClient; remove adf_endpoint/adf_key params - Add tags, ttl, salience parameters to add_local() and add_cosmos() - Add tag filters (AND/OR/NOT), include_superseded, min_salience to get_memories(), search_cosmos(), and get_thread() - Add add_tags() and remove_tags() for tag management - Add get_procedural_memories() and search_episodic_memories() retrieval - Add build_procedural_context() and build_episodic_context() formatters - Add extract_memories(), deduplicate_facts() pipeline methods - Enable per-document TTL via default_ttl=-1 in create_memory_store() - Export LLMClient from __init__.py - Update tests for new default superseded_by filter and pipeline delegation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Mirror sync CosmosMemoryClient changes to AsyncCosmosMemoryClient - Remove adf_endpoint/adf_key params; add llm_model param with LLMClient - Add tags, ttl, salience params to add_local() and add_cosmos() - Add tags, any_tags, exclude_tags, include_superseded, min_salience to get_memories(), search_cosmos(), get_thread() - Add new methods: add_tags(), remove_tags(), get_procedural_memories(), search_episodic_memories(), build_procedural_context(), build_episodic_context(), extract_memories(), deduplicate_facts() - Replace ADF-based processing with ProcessingPipeline delegation (uses sync Cosmos container internally) - Pass default_ttl=-1 in create_memory_store() - Delete aio/processing.py (replaced by pipeline.py) - Update aio/__init__.py exports - Update async client tests to match new API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Adding a in processor and durable processor * Resolving comments and adding some code improvements * Resolving comments and adding some code improvements * Fixing builds * Fixing builds * Resolving comments and adding some code improvements * making some code improvements * making some code improvements * Fixing builds * code improvements * code improvements * code improvements * Initial commit for the policy changes for multi vector search * code improvements --------- Co-authored-by: Aayush Kataria <aayushkataria@Aayushs-MacBook-Pro-2.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 94ae8db commit 7a43443

126 files changed

Lines changed: 20260 additions & 5526 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.template

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ COSMOS_DB_ENDPOINT=https://<your-account>.documents.azure.com:443/
55
# COSMOS_DB__accountEndpoint is required for the Azure Functions change feed trigger
66
# (identity-based connection). Set it to the same value as COSMOS_DB_ENDPOINT.
77
COSMOS_DB__accountEndpoint=https://<your-account>.documents.azure.com:443/
8+
# Optional fallback when Cosmos control-plane RBAC is unavailable (private preview).
9+
# When set, the SDK uses the key for both data plane and control plane operations,
10+
# letting you create databases/containers without RBAC role assignments.
11+
# Leave blank to use DefaultAzureCredential / managed identity.
12+
COSMOS_DB_KEY=
813
COSMOS_DB_DATABASE=ai_memory
914
COSMOS_DB_CONTAINER=memories
1015
COSMOS_DB_COUNTERS_CONTAINER=counter
@@ -19,22 +24,18 @@ COSMOS_DB_LEASE_CONTAINER=leases
1924
COSMOS_DB_THROUGHPUT_MODE=serverless
2025
COSMOS_DB_AUTOSCALE_MAX_RU=1000
2126

22-
# ---- Change Feed Thresholds (set to 0 to disable) ----
23-
THREAD_SUMMARY_EVERY_N=0
24-
FACT_EXTRACTION_EVERY_N=0
25-
USER_SUMMARY_EVERY_N=0
27+
# ---- Processing thresholds (set to 0 to disable) ----
28+
THREAD_SUMMARY_EVERY_N=10
29+
FACT_EXTRACTION_EVERY_N=1
30+
USER_SUMMARY_EVERY_N=20
2631

2732
# ---- AI Foundry / Azure OpenAI ----
2833
AI_FOUNDRY_ENDPOINT=https://<your-account>.openai.azure.com/
2934
AI_FOUNDRY_API_KEY=
30-
EMBEDDING_MODEL=text-embedding-3-large
31-
EMBEDDING_DIMENSIONS=1536
32-
EMBEDDING_DATA_TYPE=float32
33-
EMBEDDING_DISTANCE_FUNCTION=cosine
34-
FULL_TEXT_LANGUAGE=en-US
35+
AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME=text-embedding-3-large
36+
AI_FOUNDRY_EMBEDDING_DIMENSIONS=1536
37+
AI_FOUNDRY_EMBEDDING_DATA_TYPE=float32
38+
AI_FOUNDRY_EMBEDDING_DISTANCE_FUNCTION=cosine
39+
COSMOS_DB_FULL_TEXT_LANGUAGE=en-US
3540

36-
LLM_MODEL=<your-model-deployment>
37-
38-
# ---- Azure Durable Functions ----
39-
ADF_ENDPOINT=http://localhost:7071/api
40-
ADF_KEY=
41+
AI_FOUNDRY_CHAT_DEPLOYMENT_NAME=<your-model-deployment>

Docs/azure_testing.md

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ You need:
2828

2929
```bash
3030
pip install -e ".[dev]"
31-
pip install -r azure_functions/requirements.txt
31+
pip install -r function_app/requirements.txt
3232
```
3333

34+
> The recommended way to provision **all** required Azure resources is `azd up` from the repo root, which uses the Bicep templates under `infra/`. See [`infra/README.md`](../infra/README.md) for details. The manual `az ...` commands below are kept as a reference for operators who can't use `azd`.
35+
3436
---
3537

3638
## 1. Create Azure Resources
@@ -109,13 +111,19 @@ az functionapp config appsettings set \
109111
COSMOS_DB_THROUGHPUT_MODE="serverless" \
110112
COSMOS_DB_AUTOSCALE_MAX_RU="1000" \
111113
AI_FOUNDRY_ENDPOINT="https://<openai-account-name>.openai.azure.com/" \
112-
EMBEDDING_MODEL="text-embedding-3-large" \
113-
EMBEDDING_DIMENSIONS="1536" \
114-
LLM_MODEL="gpt-5-mini"
114+
AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME="text-embedding-3-large" \
115+
AI_FOUNDRY_EMBEDDING_DIMENSIONS="1536" \
116+
AI_FOUNDRY_CHAT_DEPLOYMENT_NAME="gpt-5-mini" \
117+
THREAD_SUMMARY_EVERY_N="10" \
118+
FACT_EXTRACTION_EVERY_N="1" \
119+
USER_SUMMARY_EVERY_N="20" \
120+
MEMORY_PROCESSOR_OWNER="durable"
115121
```
116122

117123
`COSMOS_DB_THROUGHPUT_MODE=serverless` is the default and creates the `memories`, `counter`, and `leases` containers without specifying RU/s. Set `COSMOS_DB_THROUGHPUT_MODE=autoscale` to apply the shared `COSMOS_DB_AUTOSCALE_MAX_RU` cap to all required containers.
118124

125+
`MEMORY_PROCESSOR_OWNER=durable` tells the SDK that the deployed Function App owns processing, so any `CosmosMemoryClient` pointed at the same container will skip its in-process auto-trigger and avoid double-extraction. See the README's processor-ownership table for details.
126+
119127
### Change feed settings (optional)
120128

121129
To enable automatic processing via the change feed trigger, add these settings:
@@ -139,14 +147,16 @@ Set any threshold to `"0"` to disable that processing type.
139147

140148
The `leases` container is provisioned by `create_memory_store()` alongside the `memories` and `counter` containers, so the Function App should be configured to use that existing lease container.
141149

142-
If you use function-key auth for the HTTP trigger, keep the key for the client as `ADF_KEY`.
150+
The Function App authenticates to Cosmos DB and Azure OpenAI via its managed identity — there's no shared key or function-key handoff between the SDK and the Function App.
143151

144152
---
145153

146154
## 4. Deploy the Functions Project
147155

156+
The recommended path is `azd up` (which builds and deploys the `function_app/` service automatically). For manual deployment:
157+
148158
```bash
149-
cd azure_functions
159+
cd function_app
150160
func azure functionapp publish <function-app-name>
151161
```
152162

@@ -175,12 +185,13 @@ COSMOS_DB_THROUGHPUT_MODE=serverless
175185
COSMOS_DB_AUTOSCALE_MAX_RU=1000
176186
177187
AI_FOUNDRY_ENDPOINT=https://<openai-account-name>.openai.azure.com/
178-
EMBEDDING_MODEL=text-embedding-3-large
179-
EMBEDDING_DIMENSIONS=1536
180-
LLM_MODEL=gpt-5-mini
188+
AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME=text-embedding-3-large
189+
AI_FOUNDRY_EMBEDDING_DIMENSIONS=1536
190+
AI_FOUNDRY_CHAT_DEPLOYMENT_NAME=gpt-5-mini
181191
182-
ADF_ENDPOINT=https://<function-app-name>.azurewebsites.net/api
183-
ADF_KEY=<function-key-if-needed>
192+
# Tells the SDK that the deployed Function App owns auto-processing,
193+
# so this client skips its in-process auto-trigger.
194+
MEMORY_PROCESSOR_OWNER=durable
184195
```
185196

186197
---
@@ -195,22 +206,21 @@ Run once if the database and container do not already exist:
195206
import os
196207
from dotenv import load_dotenv
197208
from azure.identity import DefaultAzureCredential
198-
from agent_memory_toolkit import AgentMemory
209+
from agent_memory_toolkit import CosmosMemoryClient
199210

200211
load_dotenv()
201212

202-
memory = AgentMemory(
213+
memory = CosmosMemoryClient(
203214
cosmos_endpoint=os.getenv("COSMOS_DB_ENDPOINT"),
204-
cosmos_database=os.getenv("COSMOS_DB_DATABASE"),
205-
cosmos_container=os.getenv("COSMOS_DB_CONTAINER"),
215+
cosmos_database=os.getenv("COSMOS_DB_DATABASE", "ai_memory"),
216+
cosmos_container=os.getenv("COSMOS_DB_CONTAINER", "memories"),
206217
cosmos_counter_container=os.getenv("COSMOS_DB_COUNTERS_CONTAINER", "counter"),
207218
cosmos_lease_container=os.getenv("COSMOS_DB_LEASE_CONTAINER", "leases"),
208219
cosmos_throughput_mode=os.getenv("COSMOS_DB_THROUGHPUT_MODE", "serverless"),
209220
cosmos_autoscale_max_ru=int(os.getenv("COSMOS_DB_AUTOSCALE_MAX_RU", "1000")),
210221
ai_foundry_endpoint=os.getenv("AI_FOUNDRY_ENDPOINT"),
211-
embedding_model=os.getenv("EMBEDDING_MODEL", "text-embedding-3-large"),
212-
adf_endpoint=os.getenv("ADF_ENDPOINT"),
213-
adf_key=os.getenv("ADF_KEY", ""),
222+
embedding_deployment_name=os.getenv("AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME", "text-embedding-3-large"),
223+
chat_deployment_name=os.getenv("AI_FOUNDRY_CHAT_DEPLOYMENT_NAME", "gpt-5-mini"),
214224
use_default_credential=True,
215225
cosmos_credential=DefaultAzureCredential(),
216226
)
@@ -225,32 +235,26 @@ memory.connect_cosmos()
225235
import os
226236
from dotenv import load_dotenv
227237
from azure.identity.aio import DefaultAzureCredential as AsyncDefaultAzureCredential
228-
from agent_memory_toolkit.aio import AsyncAgentMemory
238+
from agent_memory_toolkit.aio import AsyncCosmosMemoryClient
229239

230240
load_dotenv()
231241

232-
memory = AsyncAgentMemory(
242+
memory = AsyncCosmosMemoryClient(
233243
cosmos_endpoint=os.getenv("COSMOS_DB_ENDPOINT"),
234-
cosmos_database=os.getenv("COSMOS_DB_DATABASE"),
235-
cosmos_container=os.getenv("COSMOS_DB_CONTAINER"),
244+
cosmos_database=os.getenv("COSMOS_DB_DATABASE", "ai_memory"),
245+
cosmos_container=os.getenv("COSMOS_DB_CONTAINER", "memories"),
236246
cosmos_counter_container=os.getenv("COSMOS_DB_COUNTERS_CONTAINER", "counter"),
237247
cosmos_lease_container=os.getenv("COSMOS_DB_LEASE_CONTAINER", "leases"),
238248
cosmos_throughput_mode=os.getenv("COSMOS_DB_THROUGHPUT_MODE", "serverless"),
239249
cosmos_autoscale_max_ru=int(os.getenv("COSMOS_DB_AUTOSCALE_MAX_RU", "1000")),
240250
ai_foundry_endpoint=os.getenv("AI_FOUNDRY_ENDPOINT"),
241-
embedding_model=os.getenv("EMBEDDING_MODEL", "text-embedding-3-large"),
242-
adf_endpoint=os.getenv("ADF_ENDPOINT"),
243-
adf_key=os.getenv("ADF_KEY", ""),
251+
embedding_deployment_name=os.getenv("AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME", "text-embedding-3-large"),
252+
chat_deployment_name=os.getenv("AI_FOUNDRY_CHAT_DEPLOYMENT_NAME", "gpt-5-mini"),
244253
use_default_credential=True,
245254
cosmos_credential=AsyncDefaultAzureCredential(),
246255
)
247256

248-
await memory.connect_cosmos(
249-
endpoint=os.getenv("COSMOS_DB_ENDPOINT"),
250-
database=os.getenv("COSMOS_DB_DATABASE"),
251-
container=os.getenv("COSMOS_DB_CONTAINER"),
252-
credential=AsyncDefaultAzureCredential(),
253-
)
257+
await memory.connect_cosmos()
254258
await memory.create_memory_store()
255259
```
256260

@@ -269,11 +273,11 @@ Bring the environment up in this order:
269273
5. test `add_cosmos()` / `push_to_cosmos()` / `get_memories()`
270274
6. test `get_memories(user_id=..., thread_id=...)` filtering
271275
7. test `search_cosmos()`
272-
8. deploy the Function App
273-
9. test `generate_thread_summary()`
274-
10. test `extract_facts()` — verify single-line fact output
275-
11. test `generate_user_summary()` / `get_user_summary()`
276-
12. (if change feed is enabled) test automatic processing — write turns and verify derived memories appear
276+
8. deploy the Function App (e.g., via `azd up`) so the change-feed processor is running
277+
9. write a few turns and verify a thread `summary` memory appears
278+
10. write more turns and verify `fact`, `procedural`, and `episodic` memories appear
279+
11. verify a per-user `user_summary` memory appears once `USER_SUMMARY_EVERY_N` turns have accumulated for that user
280+
12. test deduplication by writing two near-duplicate facts and confirming the dedup orchestrator merges them
277281

278282
This keeps failures isolated and easier to diagnose.
279283

@@ -294,15 +298,26 @@ print(memory.get_memories(user_id="user-1"))
294298
print(memory.search_cosmos("hello", user_id="user-1"))
295299
```
296300

297-
### Durable Functions
301+
### Durable processing (change-feed driven)
302+
303+
Processing is no longer invoked directly from the SDK — write turns with `add_cosmos()` / `push_to_cosmos()` and the deployed Function App's change-feed trigger fires the `extract_memories`, `thread_summary`, and `user_summary` orchestrators per the configured thresholds.
298304

299305
```python
300-
print(memory.generate_thread_summary(user_id="user-1", thread_id="thread-1"))
301-
print(memory.extract_facts(user_id="user-1", thread_id="thread-1"))
302-
print(memory.generate_user_summary(user_id="user-1"))
303-
```
306+
# Write enough turns to cross THREAD_SUMMARY_EVERY_N (default 10).
307+
for i in range(10):
308+
memory.add_cosmos(
309+
user_id="user-1",
310+
thread_id="thread-1",
311+
role="user",
312+
content=f"Turn {i+1}",
313+
)
304314

305-
Thread summaries and user summaries update incrementally: repeated calls merge only new memories into the existing derived document.
315+
# Wait for the change-feed processor to catch up, then read derived memories.
316+
import time; time.sleep(15)
317+
print(memory.get_memories(user_id="user-1", thread_id="thread-1", memory_type="summary"))
318+
print(memory.get_memories(user_id="user-1", memory_type="fact"))
319+
print(memory.get_memories(user_id="user-1", memory_type="user_summary"))
320+
```
306321

307322
### Change feed auto-processing
308323

@@ -335,7 +350,7 @@ Check the Function App logs to confirm the `on_memory_change` trigger fired and
335350
```python
336351
print(memory.get_memories(user_id="user-1", memory_type="summary"))
337352
print(memory.get_memories(user_id="user-1", memory_type="fact"))
338-
print(memory.get_user_summary(user_id="user-1"))
353+
print(memory.get_memories(user_id="user-1", memory_type="user_summary"))
339354
```
340355

341356
---
@@ -366,5 +381,5 @@ Recommended checks:
366381

367382
- enable Application Insights
368383
- confirm Function App managed identity roles
369-
- confirm `ADF_ENDPOINT` points to Azure
384+
- confirm `MEMORY_PROCESSOR_OWNER=durable` is set on any client pointed at a container that the Function App is also processing
370385
- confirm model deployment names are correct

Docs/design_patterns.md

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Design Patterns
22

3-
This guide shows when and how to use the toolkit's main operations in real applications. All examples use the async API (`AsyncAgentMemory`); the sync API (`AgentMemory`) has the same method signatures without `await`.
3+
This guide shows when and how to use the toolkit's main operations in real applications. All examples use the async API (`AsyncCosmosMemoryClient`); the sync API (`CosmosMemoryClient`) has the same method signatures without `await`.
44

55
---
66

@@ -11,26 +11,18 @@ This guide shows when and how to use the toolkit's main operations in real appli
1111
Write a turn memory every time a user or agent message is produced. If the application runs locally first and syncs later, use the local + bulk-upload pattern.
1212

1313
```python
14-
from azure.identity.aio import DefaultAzureCredential as AsyncDefaultAzureCredential
15-
from agent_memory_toolkit.aio import AsyncAgentMemory
14+
from agent_memory_toolkit.aio import AsyncCosmosMemoryClient
1615

17-
mem = AsyncAgentMemory(
18-
cosmos_endpoint=COSMOS_ENDPOINT,
19-
cosmos_database="memory",
16+
mem = AsyncCosmosMemoryClient(
17+
cosmos_endpoint=COSMOS_DB_ENDPOINT,
18+
cosmos_database="ai_memory",
2019
cosmos_container="memories",
21-
ai_foundry_endpoint=AOAI_ENDPOINT,
22-
embedding_model="text-embedding-3-large",
23-
adf_endpoint=ADF_ENDPOINT,
24-
adf_key=ADF_KEY,
20+
ai_foundry_endpoint=AI_FOUNDRY_ENDPOINT,
21+
embedding_deployment_name="text-embedding-3-large",
22+
chat_deployment_name="gpt-4o-mini",
2523
use_default_credential=True,
26-
cosmos_credential=AsyncDefaultAzureCredential(),
27-
)
28-
await mem.connect_cosmos(
29-
endpoint=COSMOS_ENDPOINT,
30-
database="memory",
31-
container="memories",
32-
credential=AsyncDefaultAzureCredential(),
3324
)
25+
await mem.connect_cosmos()
3426

3527
THREAD_ID = "thread-abc-123"
3628

Docs/local_testing.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ COSMOS_DB_THROUGHPUT_MODE=serverless
7878
COSMOS_DB_AUTOSCALE_MAX_RU=1000
7979
8080
AI_FOUNDRY_ENDPOINT=https://<your-project>.services.ai.azure.com/
81-
EMBEDDING_MODEL=text-embedding-3-large
82-
EMBEDDING_DIMENSIONS=1536
83-
LLM_MODEL=gpt-5-mini
81+
AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME=text-embedding-3-large
82+
AI_FOUNDRY_EMBEDDING_DIMENSIONS=1536
83+
AI_FOUNDRY_CHAT_DEPLOYMENT_NAME=gpt-5-mini
8484
8585
ADF_ENDPOINT=http://localhost:7071/api
8686
ADF_KEY=
@@ -166,7 +166,7 @@ memory = AgentMemory(
166166
cosmos_throughput_mode=os.getenv("COSMOS_DB_THROUGHPUT_MODE", "serverless"),
167167
cosmos_autoscale_max_ru=int(os.getenv("COSMOS_DB_AUTOSCALE_MAX_RU", "1000")),
168168
ai_foundry_endpoint=os.getenv("AI_FOUNDRY_ENDPOINT"),
169-
embedding_model=os.getenv("EMBEDDING_MODEL", "text-embedding-3-large"),
169+
embedding_deployment_name=os.getenv("AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME", "text-embedding-3-large"),
170170
adf_endpoint=os.getenv("ADF_ENDPOINT", "http://localhost:7071/api"),
171171
adf_key=os.getenv("ADF_KEY", ""),
172172
use_default_credential=True,
@@ -209,7 +209,7 @@ memory = AsyncAgentMemory(
209209
cosmos_throughput_mode=os.getenv("COSMOS_DB_THROUGHPUT_MODE", "serverless"),
210210
cosmos_autoscale_max_ru=int(os.getenv("COSMOS_DB_AUTOSCALE_MAX_RU", "1000")),
211211
ai_foundry_endpoint=os.getenv("AI_FOUNDRY_ENDPOINT"),
212-
embedding_model=os.getenv("EMBEDDING_MODEL", "text-embedding-3-large"),
212+
embedding_deployment_name=os.getenv("AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME", "text-embedding-3-large"),
213213
adf_endpoint=os.getenv("ADF_ENDPOINT", "http://localhost:7071/api"),
214214
adf_key=os.getenv("ADF_KEY", ""),
215215
use_default_credential=True,

0 commit comments

Comments
 (0)