Skip to content

Commit fb2b030

Browse files
Aayush KatariaAayush Kataria
authored andcommitted
making some code improvements
1 parent 4512359 commit fb2b030

34 files changed

Lines changed: 1634 additions & 242 deletions

Docs/azure_testing.md

Lines changed: 52 additions & 37 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
@@ -111,11 +113,17 @@ az functionapp config appsettings set \
111113
AI_FOUNDRY_ENDPOINT="https://<openai-account-name>.openai.azure.com/" \
112114
AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME="text-embedding-3-large" \
113115
AI_FOUNDRY_EMBEDDING_DIMENSIONS="1536" \
114-
AI_FOUNDRY_CHAT_DEPLOYMENT_NAME="gpt-5-mini"
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

@@ -179,8 +189,9 @@ AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME=text-embedding-3-large
179189
AI_FOUNDRY_EMBEDDING_DIMENSIONS=1536
180190
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"),
211222
embedding_deployment_name=os.getenv("AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME", "text-embedding-3-large"),
212-
adf_endpoint=os.getenv("ADF_ENDPOINT"),
213-
adf_key=os.getenv("ADF_KEY", ""),
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"),
241251
embedding_deployment_name=os.getenv("AI_FOUNDRY_EMBEDDING_DEPLOYMENT_NAME", "text-embedding-3-large"),
242-
adf_endpoint=os.getenv("ADF_ENDPOINT"),
243-
adf_key=os.getenv("ADF_KEY", ""),
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: 7 additions & 15 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(
16+
mem = AsyncCosmosMemoryClient(
1817
cosmos_endpoint=COSMOS_DB_ENDPOINT,
19-
cosmos_database="memory",
18+
cosmos_database="ai_memory",
2019
cosmos_container="memories",
21-
ai_foundry_endpoint=AOAI_ENDPOINT,
20+
ai_foundry_endpoint=AI_FOUNDRY_ENDPOINT,
2221
embedding_deployment_name="text-embedding-3-large",
23-
adf_endpoint=ADF_ENDPOINT,
24-
adf_key=ADF_KEY,
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_DB_ENDPOINT,
30-
database="memory",
31-
container="memories",
32-
credential=AsyncDefaultAzureCredential(),
3324
)
25+
await mem.connect_cosmos()
3426

3527
THREAD_ID = "thread-abc-123"
3628

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,33 @@ high_conf_facts = memory.get_memories(user_id="u1", memory_type="fact", min_conf
207207

208208
### Auto-trigger (per-turn extraction)
209209

210-
By default, the **InProcess processor** runs fact extraction after **every turn** (`FACT_EXTRACTION_EVERY_N=1`) and a thread summary every **10 turns** (`THREAD_SUMMARY_EVERY_N=10`). The trigger fires inside `push_to_cosmos()` after the turn is durably written, so calling `process_now()` is normally redundant — it remains as an explicit "flush now" hook. The Durable backend uses the same defaults via the change-feed function app.
210+
By default, the **InProcess processor** runs each pipeline step independently as its own threshold trips inside `push_to_cosmos()`:
211+
212+
| Env var | Default | Step that fires | Async behavior |
213+
|---|---|---|---|
214+
| `FACT_EXTRACTION_EVERY_N` | `1` (every turn) | `process_extract_memories` (extract + dedup) | scheduled via `asyncio.create_task` |
215+
| `THREAD_SUMMARY_EVERY_N` | `10` | `process_thread_summary` | scheduled via `asyncio.create_task` |
216+
| `USER_SUMMARY_EVERY_N` | `20` | `process_user_summary` | scheduled via `asyncio.create_task` |
217+
218+
Each `*_EVERY_N=0` disables only that step. The Durable backend uses the same defaults via the change-feed function app, so the in-process and durable backends fire on the same turn boundaries — only the *where* differs. Calling `process_now()` is normally redundant — it remains as an explicit "process now" hook for tests, manual workflows, and operators who set every threshold to `0`.
219+
220+
The async client (`AsyncCosmosMemoryClient.push_to_cosmos`) does **not** await the auto-trigger; it schedules it as a background `asyncio.Task` so the write call returns as soon as the Cosmos upserts complete. Background failures are surfaced via `logger.warning` (search for `"Background auto-trigger task failed"`).
221+
222+
#### Backend exclusivity (`MEMORY_PROCESSOR_OWNER`)
223+
224+
Both the SDK auto-trigger and the function-app change-feed processor write into the same `counter` container. If you accidentally point an `InProcessProcessor` at a Cosmos container that already has a function app attached, both backends will run the pipeline on the same writes — double extraction, double dedup, double counters.
225+
226+
Set the env var on **both sides** to make ownership explicit:
227+
228+
| `MEMORY_PROCESSOR_OWNER` | SDK behavior | Function-app behavior |
229+
|---|---|---|
230+
| _unset_ (default) | runs auto-trigger | runs orchestrator (today's behavior) |
231+
| `inprocess` | runs auto-trigger | change-feed trigger skips batch + logs |
232+
| `durable` | auto-trigger logs warning + skips | runs orchestrator |
233+
234+
The default (unset) preserves backward compatibility. For any production deployment we recommend setting it on both sides so a misconfiguration produces a loud log line instead of silent double-work.
235+
236+
> **Advisory, not enforced.** `MEMORY_PROCESSOR_OWNER` is operator-configured exclusivity, not a server-side lock. Each backend reads its own env var; if the SDK is set to `inprocess` but the FA forgets to set `durable` (or vice versa), both still run. As a backstop, every counter write stamps `last_owner=<this backend>` on the doc — when the SDK observes a counter previously written by `durable` (or vice versa), it logs a one-shot `WARN` so misconfiguration surfaces in logs without spamming. Treat this as a configuration audit signal, not a hard guarantee.
211237
212238
---
213239

agent_memory_toolkit/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@
2525
ProcessThreadResult,
2626
UserSummaryResult,
2727
)
28+
from agent_memory_toolkit.thresholds import (
29+
DEFAULT_FACT_EXTRACTION_EVERY_N,
30+
DEFAULT_THREAD_SUMMARY_EVERY_N,
31+
DEFAULT_USER_SUMMARY_EVERY_N,
32+
PROCESSOR_OWNER_DURABLE,
33+
PROCESSOR_OWNER_INPROCESS,
34+
get_fact_extraction_every_n,
35+
get_processor_owner,
36+
get_thread_summary_every_n,
37+
get_user_summary_every_n,
38+
)
2839

2940
__all__ = [
3041
"CosmosMemoryClient",
@@ -51,4 +62,13 @@
5162
"OrchestrationTimeoutError",
5263
"ProcessingError",
5364
"ValidationError",
65+
"DEFAULT_FACT_EXTRACTION_EVERY_N",
66+
"DEFAULT_THREAD_SUMMARY_EVERY_N",
67+
"DEFAULT_USER_SUMMARY_EVERY_N",
68+
"PROCESSOR_OWNER_DURABLE",
69+
"PROCESSOR_OWNER_INPROCESS",
70+
"get_fact_extraction_every_n",
71+
"get_processor_owner",
72+
"get_thread_summary_every_n",
73+
"get_user_summary_every_n",
5474
]

0 commit comments

Comments
 (0)