Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Docs/azure_testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,9 @@ for i in range(10):

# Wait for the change-feed processor to catch up, then read derived memories.
import time; time.sleep(15)
print(memory.get_memories(user_id="user-1", thread_id="thread-1", memory_type="summary"))
print(memory.get_memories(user_id="user-1", memory_type="fact"))
print(memory.get_memories(user_id="user-1", memory_type="user_summary"))
print(memory.get_memories(user_id="user-1", thread_id="thread-1", memory_types=["summary"]))
print(memory.get_memories(user_id="user-1", memory_types=["fact"]))
print(memory.get_memories(user_id="user-1", memory_types=["user_summary"]))
```

### Change feed auto-processing
Expand All @@ -339,7 +339,7 @@ for i in range(3):
# Wait a few seconds for the change feed to trigger, then check:
import time
time.sleep(10)
results = memory.get_memories(user_id="user-1", thread_id=thread_id, memory_type="summary")
results = memory.get_memories(user_id="user-1", thread_id=thread_id, memory_types=["summary"])
print(results) # Should contain an auto-generated summary
```

Expand All @@ -348,9 +348,9 @@ Check the Function App logs to confirm the `on_memory_change` trigger fired and
### Verify stored results

```python
print(memory.get_memories(user_id="user-1", memory_type="summary"))
print(memory.get_memories(user_id="user-1", memory_type="fact"))
print(memory.get_memories(user_id="user-1", memory_type="user_summary"))
print(memory.get_memories(user_id="user-1", memory_types=["summary"]))
print(memory.get_memories(user_id="user-1", memory_types=["fact"]))
print(memory.get_memories(user_id="user-1", memory_types=["user_summary"]))
```

---
Expand Down
12 changes: 6 additions & 6 deletions Docs/design_patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ Search across all memories (or filter by type) to find the most relevant context
facts = await mem.search_cosmos(
search_terms="database migration requirements",
user_id="user-1",
memory_type="fact",
memory_types=["fact"],
top_k=10,
)

Expand All @@ -185,10 +185,10 @@ profile = await mem.get_user_summary(user_id="user-1")

```python
# All summaries for a user
summaries = await mem.get_memories(user_id="user-1", memory_type="summary")
summaries = await mem.get_memories(user_id="user-1", memory_types=["summary"])

# All facts
facts = await mem.get_memories(user_id="user-1", memory_type="fact")
facts = await mem.get_memories(user_id="user-1", memory_types=["fact"])

# Filter by thread_id
thread_turns = await mem.get_memories(user_id="user-1", thread_id=THREAD_ID)
Expand All @@ -204,7 +204,7 @@ A typical chat application lifecycle looks like this:
New session starts
├─ Retrieve user summary (get_user_summary)
├─ Semantic search for prior facts (search_cosmos, type="fact")
├─ Semantic search for prior facts (search_cosmos, memory_types=["fact"])
│ ┌── Conversation loop ──┐
│ │ Store each turn │ (add_cosmos)
Expand All @@ -221,7 +221,7 @@ New session starts
```python
# --- Session start ---
profile = await mem.get_user_summary(user_id="user-1")
relevant = await mem.search_cosmos("topic of interest", user_id="user-1", memory_type="fact", top_k=5)
relevant = await mem.search_cosmos("topic of interest", user_id="user-1", memory_types=["fact"], top_k=5)

# Build system prompt with profile and relevant facts
system_prompt = build_prompt(profile, relevant)
Expand Down Expand Up @@ -280,7 +280,7 @@ await mem.extract_facts(user_id="user-1", thread_id="research-thread")
facts = await mem.search_cosmos(
search_terms="source database schema foreign keys",
user_id="user-1",
memory_type="fact",
memory_types=["fact"],
top_k=10,
)

Expand Down
2 changes: 1 addition & 1 deletion Docs/local_testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ for i in range(3):
5. Verify the summary was created:

```python
result = memory.get_memories(user_id="user-001", thread_id=thread_id, memory_type="summary")
result = memory.get_memories(user_id="user-001", thread_id=thread_id, memory_types=["summary"])
print(result)
```

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ Filter at retrieval time:

```python
results = memory.search_cosmos("user preferences", user_id="u1", min_confidence=0.7)
high_conf_facts = memory.get_memories(user_id="u1", memory_type="fact", min_confidence=0.7)
high_conf_facts = memory.get_memories(user_id="u1", memory_types=["fact"], min_confidence=0.7)
```

### Memory Reconciliation
Expand Down
6 changes: 3 additions & 3 deletions Samples/Demo.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@
" print(f\" [{m['id'][:8]}...] {m['content'][:60]}\")\n",
"\n",
"# Filter by type\n",
"facts = memory.get_local(memory_type=\"fact\")\n",
"facts = memory.get_local(memory_types=[\"fact\"])\n",
"print(f\"\\nFact memories: {len(facts)}\")\n",
"for m in facts:\n",
" print(f\" [{m['id'][:8]}...] {m['content']}\")\n",
Expand Down Expand Up @@ -2739,7 +2739,7 @@
"source": [
"# Inspect the persisted derived memories across BOTH threads for this user.\n",
"for mt in (\"fact\", \"episodic\", \"procedural\"):\n",
" docs = memory.get_memories(user_id=USER_ID, memory_type=mt)\n",
" docs = memory.get_memories(user_id=USER_ID, memory_types=[mt])\n",
" print(f\"\\n{mt.upper()}S ({len(docs)}):\")\n",
" for d in docs:\n",
" print(f\" • {d['content'][:100]} [salience={d.get('salience')}]\")\n"
Expand Down Expand Up @@ -2879,7 +2879,7 @@
"stored = memory.get_user_summary(user_id=user_id)\n",
"if stored:\n",
" print(\"User Summary for\", user_id)\n",
" print(stored[0][\"content\"])\n",
" print(stored[\"content\"])\n",
"else:\n",
" print(\"No user summary found — run the generate_user_summary cell first.\")\n"
]
Expand Down
6 changes: 3 additions & 3 deletions Samples/Demo_async.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@
" print(f\" [{m['id'][:8]}...] {m['content'][:60]}\")\n",
"\n",
"# Filter by type\n",
"facts = memory.get_local(memory_type=\"fact\")\n",
"facts = memory.get_local(memory_types=[\"fact\"])\n",
"print(f\"\\nFact memories: {len(facts)}\")\n",
"for m in facts:\n",
" print(f\" [{m['id'][:8]}...] {m['content']}\")\n",
Expand Down Expand Up @@ -2631,7 +2631,7 @@
"source": [
"# Inspect the persisted derived memories across BOTH threads for this user.\n",
"for mt in (\"fact\", \"episodic\", \"procedural\"):\n",
" docs = await memory.get_memories(user_id=USER_ID, memory_type=mt)\n",
" docs = await memory.get_memories(user_id=USER_ID, memory_types=[mt])\n",
" print(f\"\\n{mt.upper()}S ({len(docs)}):\")\n",
" for d in docs:\n",
" print(f\" • {d['content'][:100]} [salience={d.get('salience')}]\")\n"
Expand Down Expand Up @@ -2765,7 +2765,7 @@
"stored = await memory.get_user_summary(user_id=user_id)\n",
"if stored:\n",
" print(\"User Summary for\", user_id)\n",
" print(stored[0][\"content\"])\n",
" print(stored[\"content\"])\n",
"else:\n",
" print(\"No user summary found — run the generate_user_summary cell first.\")\n"
]
Expand Down
8 changes: 4 additions & 4 deletions Samples/Demo_function_app.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@
"## 5. Wait for the Function App to produce a summary\n",
"\n",
"`process_now_and_wait` polls Cosmos for the thread's summary doc until `timeout` seconds. This is **RU-costly**\n",
"(repeated `get_memories(memory_type='summary')` queries) — only use it for demos and tests.\n",
"(repeated `get_memories(memory_types=['summary'])` queries) — only use it for demos and tests.\n",
"\n",
"> If this returns `False`, check that:\n",
"> * The Function App is deployed and running.\n",
Expand Down Expand Up @@ -594,7 +594,7 @@
"def _show():\n",
" counts = {}\n",
" for mt in (\"summary\", \"fact\", \"episodic\", \"procedural\", \"user_summary\"):\n",
" docs = memory.get_memories(user_id=USER_ID, memory_type=mt)\n",
" docs = memory.get_memories(user_id=USER_ID, memory_types=[mt])\n",
" counts[mt] = len(docs)\n",
" print(f\"\\n{mt.upper()}S ({len(docs)}):\")\n",
" for d in docs:\n",
Expand All @@ -610,7 +610,7 @@
" break\n",
" print(f\"\\n... fact / procedural extraction still in flight (attempt {attempt+1}/6, sleeping 15s)\")\n",
" time.sleep(15)\n",
" counts = {mt: len(memory.get_memories(user_id=USER_ID, memory_type=mt))\n",
" counts = {mt: len(memory.get_memories(user_id=USER_ID, memory_types=[mt]))\n",
" for mt in (\"summary\", \"fact\", \"episodic\", \"procedural\", \"user_summary\")}\n",
"\n",
"print(\"\\n=== Final state ===\")\n",
Expand All @@ -627,7 +627,7 @@
"* **Per-turn embedding**: the SDK auto-embeds non-`turn` documents you `add_cosmos` directly. Raw `turn`\n",
" records are intentionally not embedded — the function app does that during summary/extraction.\n",
"* **Search across function-app-produced memories**: works exactly as in the in-process demo:\n",
" `memory.search_cosmos(search_terms=\"…\", memory_type=\"fact\", user_id=USER_ID)`.\n",
" `memory.search_cosmos(search_terms=\"…\", memory_types=[\"fact\"], user_id=USER_ID)`.\n",
"* **Switch back to in-process** for ad-hoc work: instantiate the client without the `processor=` kwarg\n",
" (or pass `InProcessProcessor()` explicitly) and use `generate_thread_summary` / `extract_memories`\n",
" directly."
Expand Down
4 changes: 2 additions & 2 deletions Samples/Demo_function_app_async.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@
"async def _show():\n",
" counts = {}\n",
" for mt in (\"summary\", \"fact\", \"episodic\", \"procedural\", \"user_summary\"):\n",
" docs = await memory.get_memories(user_id=USER_ID, memory_type=mt)\n",
" docs = await memory.get_memories(user_id=USER_ID, memory_types=[mt])\n",
" counts[mt] = len(docs)\n",
" print(f\"\\n{mt.upper()}S ({len(docs)}):\")\n",
" for d in docs:\n",
Expand All @@ -554,7 +554,7 @@
" await asyncio.sleep(15)\n",
" counts = {}\n",
" for mt in (\"summary\", \"fact\", \"episodic\", \"procedural\", \"user_summary\"):\n",
" docs = await memory.get_memories(user_id=USER_ID, memory_type=mt)\n",
" docs = await memory.get_memories(user_id=USER_ID, memory_types=[mt])\n",
" counts[mt] = len(docs)\n",
"\n",
"print(\"\\n=== Final state ===\")\n",
Expand Down
2 changes: 1 addition & 1 deletion Samples/advanced_memory_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def main() -> None:

_header(5, "Archive: delete raw turns, keep derived memories")
deleted = 0
for m in mem.get_memories(user_id=user_id, thread_id=thread_id, memory_type="turn"):
for m in mem.get_memories(user_id=user_id, thread_id=thread_id, memory_types=["turn"]):
mem.delete_cosmos(memory_id=m["id"], thread_id=thread_id, user_id=user_id)
deleted += 1
print(f" deleted {deleted} raw turn(s)")
Expand Down
2 changes: 1 addition & 1 deletion Samples/processing_fact_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def main() -> None:
print(f"Extraction stats: {json.dumps(stats, indent=2)}\n")

for kind in ("fact", "procedural", "episodic"):
items = mem.get_memories(user_id=user_id, memory_type=kind)
items = mem.get_memories(user_id=user_id, memory_types=[kind])
print(f"{kind.upper()}S ({len(items)}):")
for it in items:
tags = ", ".join(it.get("tags") or [])
Expand Down
2 changes: 1 addition & 1 deletion Samples/processing_thread_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def main() -> None:
print(f"incremental_update flag: {doc2.get('metadata', {}).get('incremental_update')}")

_banner("STEP 4 – read summaries from Cosmos")
for s in mem.get_memories(user_id=user_id, thread_id=thread_id, memory_type="summary"):
for s in mem.get_memories(user_id=user_id, thread_id=thread_id, memory_types=["summary"]):
print(f" • {s['id']} :: {s['content'][:120]}…")

print("\nDone.")
Expand Down
7 changes: 5 additions & 2 deletions Samples/processing_user_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,11 @@ def main() -> None:
print(json.dumps(doc.get("metadata", {}).get("structured_summary"), indent=2))

_banner("Read profile back from Cosmos")
for s in mem.get_user_summary(user_id):
print(f" • {s['content'][:200]}…")
summary = mem.get_user_summary(user_id)
if summary:
print(f" • {summary['content'][:200]}…")
else:
print(" (no summary stored yet)")

print("\nDone.")

Expand Down
4 changes: 2 additions & 2 deletions Samples/scenario_customer_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ def _run_ticket_3_greeting(mem: CosmosMemoryClient, user_id: str, ticket: str) -

# Build context from the user profile + extracted facts to personalise the greeting.
profile = mem.get_user_summary(user_id)
facts = mem.get_memories(user_id=user_id, memory_type="fact")
facts = mem.get_memories(user_id=user_id, memory_types=["fact"])
print("Profile content (truncated):")
if profile:
print(f" {profile[0]['content'][:200]}…")
print(f" {profile['content'][:200]}…")
print("\nKnown facts:")
for f in facts[:8]:
print(f" • {f['content']}")
Expand Down
4 changes: 2 additions & 2 deletions Samples/scenario_memory_reconciliation.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,15 @@ def main() -> None:
print(f" + {content}")

banner("3. Active facts before reconcile")
before = mem.get_memories(user_id=unique_user_id, memory_type="fact")
before = mem.get_memories(user_id=unique_user_id, memory_types=["fact"])
print_facts(before)

banner("4. Running reconcile_memories")
stats = mem.reconcile(user_id=unique_user_id)
print(f" stats: {dict(stats)}")

banner("5. Active facts after reconcile (duplicates merged, contradictions resolved)")
after = mem.get_memories(user_id=unique_user_id, memory_type="fact")
after = mem.get_memories(user_id=unique_user_id, memory_types=["fact"])
print_facts(after)

banner("6. Audit trail (soft-deleted records)")
Expand Down
7 changes: 3 additions & 4 deletions Samples/scenario_rag_with_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,8 @@ def run_demo() -> None:

summaries = mem.get_user_summary(user_id=user_id)
if summaries:
print(f" Found {len(summaries)} summary document(s):")
for s in summaries:
print(f" • {s.get('content', '')[:120]}")
print(" Found user summary:")
print(f" • {summaries.get('content', '')[:120]}")
else:
Comment on lines 183 to 187
print(" No summary available yet (generate one via generate_thread_summary).")

Expand All @@ -204,7 +203,7 @@ def run_demo() -> None:

# 4c. User summary context
summary_context = (
summaries[0].get("content", "") if summaries else "No summary available."
summaries.get("content", "") if summaries else "No summary available."
)

augmented_prompt = textwrap.dedent(f"""\
Expand Down
11 changes: 11 additions & 0 deletions agent_memory_toolkit/_query_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ def add_array_contains_any(self, field: str, param_base: str, values: list[Any])
self._parameters.append({"name": pname, "value": val})
self._conditions.append("(" + " OR ".join(parts) + ")")

def add_in_filter(self, field: str, param_base: str, values: list[Any]) -> None:
"""Add a ``field IN (@p_0, @p_1, ...)`` filter."""
if not values:
return
parts: list[str] = []
for i, val in enumerate(values):
pname = f"{param_base}{i}"
parts.append(pname)
self._parameters.append({"name": pname, "value": val})
self._conditions.append(f"{field} IN ({', '.join(parts)})")

def add_is_null_or_undefined(self, field: str) -> None:
"""Add ``(NOT IS_DEFINED(field) OR IS_NULL(field))`` filter."""
self._conditions.append(f"(NOT IS_DEFINED({field}) OR IS_NULL({field}))")
Expand Down
11 changes: 8 additions & 3 deletions agent_memory_toolkit/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,16 +312,21 @@ def _build_memory_query_builder(
user_id: Optional[str] = None,
thread_id: Optional[str] = None,
role: Optional[str] = None,
memory_type: Optional[str] = None,
memory_types: Optional[list[str]] = None,
min_confidence: Optional[float] = None,
) -> _QueryBuilder:
"""Return a :class:`_QueryBuilder` pre-loaded with the standard filters."""
"""Return a :class:`_QueryBuilder` pre-loaded with the standard filters.

``memory_types`` is a list of types (e.g. ``["fact", "procedural",
"episodic"]``); when ``None`` or empty no type filter is applied.
"""
qb = _QueryBuilder()
qb.add_filter("c.id", "@memory_id", memory_id)
qb.add_filter("c.user_id", "@user_id", user_id)
qb.add_filter("c.thread_id", "@thread_id", thread_id)
qb.add_filter("c.role", "@role", role)
qb.add_filter("c.type", "@memory_type", memory_type)
if memory_types:
qb.add_in_filter("c.type", "@memory_type_", list(memory_types))
if min_confidence is not None and min_confidence > 0:
qb.add_gte("c.confidence", "@min_confidence", min_confidence)
return qb
Expand Down
Loading
Loading