Build a shopping response with Microsoft Agent Framework memory
We will store an explicit shopping preference in AuraDB, attach a Neo4j context provider to a Microsoft Agent Framework agent, and verify the persisted turn and recorded trace. The product catalog is a small fictional list in the instructions; this lesson does not promise graph algorithms or real inventory queries.
|
Preview. This adapter is a preview integration. It targets the Microsoft Agent Framework 1.x GA line ( |
This lesson uses neo4j-agent-memory 0.7.0 from PyPI and the complete programs on this page in a POSIX shell. Create the local files shown below; the checks below verify the results in your environment.
Before you begin
-
Python 3.10 or newer and a POSIX shell.
-
A Neo4j Aura account for a dedicated lesson instance.
-
An OpenAI API key with access to the chat model selected for this lesson and
text-embedding-3-small.
This path uses the OpenAI client. The SDK’s Microsoft integration targets agent-framework-core>=1.13,<2; the integration guide owns the compatibility details.
1. Install the current integration
Create a local folder and virtual environment for the examples on this page. The commands reuse an existing environment without changing its files:
mkdir -p ~/agent-memory-tutorials
cd ~/agent-memory-tutorials
if [ -e .venv ]; then
printf '%s\n' 'Using the existing virtual environment.'
else
python3 -m venv .venv
fi
source .venv/bin/activate
Expected: ~/agent-memory-tutorials is your working directory and its virtual environment is active. Install the published SDK with the command below.
Each complete code block labelled Save as names a file to create in this folder using your editor. Copy the entire block, including imports and the entry point. Expand each helper disclosure and use Copy code to copy its full source. Keep all files together so their imports resolve.
When continuing from another tutorial or guide, retain the existing environment, configuration, session files, and .tutorial-state/. Reuse unchanged helper files; compare an existing file before replacing it, and finish any pending cleanup or recovery before changing the code that owns its state.
python -m pip install 'neo4j-agent-memory[microsoft-agent,openai]==0.7.0'
python -c "from importlib.metadata import version; \
import neo4j_agent_memory; \
assert version('neo4j-agent-memory') == '0.7.0'; \
print('SDK 0.7.0 import verified')"
Expected: SDK 0.7.0 import verified. Keep agent-memory-tutorials as your working directory for every command below.
python -m pip install 'agent-framework-openai>=1.13,<2'
python -c "from agent_framework.openai import OpenAIChatClient; \
print('OpenAI client import verified')"
Expected: OpenAI client import verified. The optional framework client is installed explicitly rather than assuming the memory extra includes it.
Create and check the local files
Create each file below in ~/agent-memory-tutorials. All complete sources follow this manifest. On a first visit every file is new; when returning, reuse unchanged helpers and compare their contents before replacing them.
| File | Purpose | Returning reader |
|---|---|---|
|
Read explicit Aura credentials; the database defaults to |
Reuse unchanged |
|
Probe the selected database and close the driver. |
Reuse unchanged |
|
Run the shopping agent and verify framework persistence and its trace. This is the lesson entry point. |
New for this lesson; retain it with unfinished state |
The manifest above names this lesson’s entry point and its helpers. Save every file it lists, then run the offline assembly checks that follow the sources before the lesson commands.
Open the helper below and save its complete source as aura_connection.py in ~/agent-memory-tutorials.
Show aura_connection.py
aura_connection.py"""Read the Aura connection exported by the documentation setup commands."""
import os
class AuraConfigurationError(ValueError):
"""Missing or invalid tutorial settings, with no credential values in errors."""
def aura_config():
required = ("NEO4J_URI", "NEO4J_USERNAME", "NEO4J_PASSWORD")
missing = [name for name in required if not os.environ.get(name, "").strip()]
if missing:
raise AuraConfigurationError("Export the Aura connection settings: " + ", ".join(missing))
uri = os.environ["NEO4J_URI"]
if not uri.startswith("neo4j+s://"):
raise AuraConfigurationError("NEO4J_URI must use the Aura neo4j+s:// connection scheme")
database = os.environ.get("NEO4J_DATABASE", "neo4j")
if not database.strip():
raise AuraConfigurationError("NEO4J_DATABASE must not be empty")
return {
"uri": uri,
"username": os.environ["NEO4J_USERNAME"],
"password": os.environ["NEO4J_PASSWORD"],
"database": database,
}
Open the helper below and save its complete source as wait_for_tutorial_neo4j.py in ~/agent-memory-tutorials.
Show wait_for_tutorial_neo4j.py
wait_for_tutorial_neo4j.py"""Check the exported Aura connection, waiting at most three minutes."""
import asyncio
import sys
from aura_connection import AuraConfigurationError, aura_config
from neo4j import AsyncGraphDatabase
from neo4j.exceptions import DriverError, Neo4jError, ServiceUnavailable, SessionExpired
async def wait_until_ready(timeout=180):
if timeout <= 0:
raise ValueError("Readiness timeout must be positive")
config = aura_config()
async def probe(driver):
while True:
try:
await driver.verify_connectivity()
records, _, _ = await driver.execute_query(
"RETURN 1 AS ready", database_=config["database"], routing_="r"
)
if len(records) != 1 or records[0]["ready"] != 1:
raise RuntimeError("Unexpected readiness query result")
return
except (ServiceUnavailable, SessionExpired):
await asyncio.sleep(2)
async with AsyncGraphDatabase.driver(
config["uri"],
auth=(config["username"], config["password"]),
connection_timeout=min(10, timeout),
connection_acquisition_timeout=min(10, timeout),
) as driver:
await asyncio.wait_for(probe(driver), timeout=timeout)
def main():
try:
asyncio.run(wait_until_ready())
except AuraConfigurationError as error:
print(str(error), file=sys.stderr)
raise SystemExit(1) from None
except (asyncio.TimeoutError, DriverError, Neo4jError) as error:
# Driver exceptions can include connection details. Report the category
# and next action without echoing credentials or a raw server message.
print(
f"Neo4j Aura readiness failed ({type(error).__name__}). "
"Check that the instance is Running, the exported NEO4J_* values "
"match its credentials, and your network allows the connection.",
file=sys.stderr,
)
raise SystemExit(1) from None
print("Verified: Neo4j Aura answered the readiness query")
if __name__ == "__main__":
main()
microsoft_shopping_tutorial.py"""Small Microsoft Agent Framework shopping exercise; no GDS or hidden catalog."""
import asyncio
import os
from uuid import uuid4
from aura_connection import aura_config
async def main():
model = os.environ.get("OPENAI_MODEL", "").strip()
if not model or model.startswith("replace-with-"):
raise ValueError("Export an accessible OPENAI_MODEL before storing the preference")
from agent_framework.openai import OpenAIChatClient
from neo4j_agent_memory import BoltSettings, MemoryClient
from neo4j_agent_memory.integrations.microsoft_agent import (
Neo4jMicrosoftMemory,
record_agent_trace,
)
settings = BoltSettings(
neo4j=aura_config(),
embedding="openai/text-embedding-3-small",
extraction={"extractor_type": "none"},
)
session_id = f"shopping-docs-{uuid4().hex[:8]}"
async with MemoryClient(settings) as client:
preference = await client.long_term.add_preference(
preference="Maya prefers Northstar running shoes under 150 dollars.",
category="shopping",
)
print(f"Stored explicit preference: {preference.id}")
memory = Neo4jMicrosoftMemory(
client,
session_id,
extract_entities=False,
include_reasoning=False,
similarity_threshold=0.0,
)
chat = OpenAIChatClient(model=model, api_key=os.environ["OPENAI_API_KEY"])
agent = chat.as_agent(
name="TutorialShoppingAssistant",
instructions=(
"Use the provided memory to recommend one item from this fictional catalog: "
"Northstar Trail Runner costs 120 dollars; Harbor Road Runner costs 180 dollars. "
"Explain the match. These are tutorial products, not real inventory."
),
context_providers=[memory.context_provider],
)
prompt = "Which running shoe fits Maya's preferences and budget?"
try:
response = await agent.run(prompt)
except Exception as exc:
await record_agent_trace(
memory,
messages=[{"role": "user", "content": prompt}],
task=prompt,
outcome=f"Agent call failed: {type(exc).__name__}",
success=False,
)
raise
text = response.text or ""
if not text.strip():
raise RuntimeError("The agent returned no text")
print(f"Assistant: {text}")
history = (await client.short_term.get_conversation(session_id)).messages
if not any(message.content == prompt for message in history):
raise RuntimeError("Context-provider hook did not persist the user turn")
print("Verified: context-provider hook persisted the user turn")
trace = await record_agent_trace(
memory,
messages=[{"role": "user", "content": prompt}, {"role": "assistant", "content": text}],
task=prompt,
outcome="Shopping response recorded",
success=True,
)
recorded = await client.reasoning.get_session_traces(session_id)
if not any(item.id == trace.id for item in recorded):
raise RuntimeError("The recorded trace was missing from readback")
print(f"Verified: trace read back; session={session_id}")
if __name__ == "__main__":
asyncio.run(main())
Check the copied files before exporting credentials or making a service request:
python -m py_compile aura_connection.py wait_for_tutorial_neo4j.py microsoft_shopping_tutorial.py
python -c "import aura_connection, wait_for_tutorial_neo4j, microsoft_shopping_tutorial; assert all(callable(f) for f in [aura_connection.aura_config, wait_for_tutorial_neo4j.wait_until_ready, microsoft_shopping_tutorial.main]); print('Lesson imports verified')"
Expected: compilation exits without errors, then Lesson imports verified. These checks import the local files without calling their entry points, downloading models, or contacting Aura/providers. Compilation alone does not check imports. If a file or function is missing, recopy its entire source and repeat the checks; leave existing session/state files intact. Provider/framework calls are checked when the lesson runs.
2. Create and connect to the Aura lesson instance
This starts a new lesson with a new empty database. To return to an unfinished lesson, use Resume this lesson later instead.
-
Sign in to the Neo4j Aura console and create an AuraDB Free instance named
agent-memory-tutorial. Use an empty instance dedicated to this lesson; do not load a sample dataset. -
Download the generated credentials and keep them outside the tutorial folder. Wait until the instance shows Running.
-
Copy its connection URI, username, password and database name into the exports below. Keep the
neo4j+s://scheme supplied by Aura.
Aura permits one Free instance per account. This lesson needs that slot for a dedicated tutorial instance; do not delete an existing database containing other work to make room. See Aura instance creation for account and tier requirements.
The files from the preceding assembly checkpoint read these exports and verify the selected database.
export NEO4J_URI='neo4j+s://<instance-id>.databases.neo4j.io'
export NEO4J_USERNAME='neo4j'
export NEO4J_PASSWORD='replace-with-the-generated-password'
export NEO4J_DATABASE='neo4j'
python wait_for_tutorial_neo4j.py
Expected: Verified: Neo4j Aura answered the readiness query. The helper checks connectivity and executes RETURN 1 in the selected database, retrying temporary connection failures for up to three minutes. Authentication failures stop immediately. If the check fails, confirm the instance is Running and recopy its connection settings.
The examples read these exported variables through aura_connection.py. They require the Aura URI, username and password and never fall back to a local instance. When NEO4J_DATABASE is unset, they use the neo4j database, which is the Aura default. Keep this shell and virtual environment active for the remaining commands. See Aura connection instructions for help locating the settings.
In the Aura console, open Query, select this instance and database in the connection bar, and connect using the same credentials. Run:
MATCH (n) RETURN count(n) AS node_count
Expected: 0. This confirms that the lesson starts with an empty database. The Python SDK still uses backend="bolt": Aura hosts Neo4j and accepts encrypted Bolt connections.
3. Configure credentials and read the complete script
Before filling in OPENAI_MODEL, open the OpenAI model catalog, select a text model, and check its model page for Chat Completions support. Copy its API model ID, not its display name. In the API project that owns your key, confirm the model is permitted and that usage/billing limits allow this exercise. A ChatGPT subscription or a key-presence check does not establish that project’s API access.
The Models retrieve endpoint provides an optional authenticated metadata check for the chosen ID before lesson writes. That is a real provider request but does not generate a completion or embed text; finding metadata does not prove inference permission or available quota. The lesson’s embedding/chat requests are separate operations and can incur usage charges.
Resolve missing or blank exports locally. If a provider request fails, distinguish authentication failure (check the project/key), model not found or access denied (check the exact ID and project permissions), and quota/rate limits (check billing/limits or retry guidance). Do not keep rerunning a state-changing lesson to test access; follow its inspection/reset instructions after a partial write.
Unlike the other OpenAI lessons, this one uses OpenAIChatClient, which calls the OpenAI Responses API rather than Chat Completions. On the model page, check for Responses support instead.
export OPENAI_API_KEY="replace-with-your-key"
export OPENAI_MODEL="replace-with-your-accessible-OpenAI-model-id"
python -c "import os; \
assert os.environ['OPENAI_API_KEY'].strip(); \
model = os.environ['OPENAI_MODEL']; \
assert model.strip() and not model.startswith('replace-with-'); \
print('Provider variables present')"
Expected: Provider variables present. This checks configuration presence; the model request in the program verifies access. The program repeats the OPENAI_MODEL check before it stores the preference, so a missing model ID stops the run before any write.
The complete file is microsoft_shopping_tutorial.py. It reads the exported Aura connection variables through the shared aura_config() helper and contains the fictional catalog, explicit preference write, agent configuration, trace handling, and client cleanup.
The context-provider hook owns conversation writes; application code does not also save the same turn. Entity extraction is disabled for this lesson. The program records both successful calls and provider-call failures as trace outcomes without inventing an unsupported error argument.
4. Run and verify the result
python microsoft_shopping_tutorial.py
Expected milestones:
-
Stored explicit preference:with its real ID. This is an application write, not an inference that the agent learned a preference automatically. -
An assistant response recommending the fictional Northstar Trail Runner at 120 dollars, consistent with the stored brand and budget. Wording can vary; a different recommendation needs investigation.
-
Verified: context-provider hook persisted the user turn. -
Verified: trace read back; session=…with the new session ID.
The last two checks read records back from the lesson’s Aura instance. A plausible answer alone is not proof that the integration persisted the turn or trace.
What we learned
The application explicitly stored a preference, the framework hook supplied memory context and persisted the exchange, and the trace helper recorded the result. Those are separate actors and operations. Nothing in this script creates hidden reasoning, a product knowledge graph, or guaranteed cross-user isolation.
Resume this lesson later
A new Python process keeps the same shell exports; a new terminal does not. If this lesson’s Aura instance still exists, resume it without creating another instance or asserting that its database is empty:
-
Return to the existing folder and activate its environment:
cd ~/agent-memory-tutorials source .venv/bin/activate -
Re-export the original instance’s
NEO4J_URI,NEO4J_USERNAME,NEO4J_PASSWORD, andNEO4J_DATABASEfrom its saved credentials. Restore this page’s provider key/model/region settings too. Keep secrets out of the Python files. -
Check the same database:
python wait_for_tutorial_neo4j.py -
Use the readback or inspection step described below. Do not repeat the seed/record/write command just to recover context. Preserve existing IDs and state until you know which operations completed.
If the instance was already destroyed, its stored results cannot be resumed; complete local cleanup and start a new empty lesson instance. A different lesson also starts with a fresh instance, while keeping the folder and environment.
Inspect the stored session=… and trace output from the completed run in Aura Query. The main program creates a new session and preference on every run; rerunning it makes a new model call and writes another exercise.
In this dedicated instance, the following read-only query locates the lesson’s sessions even if a failed run never printed its final session ID:
MATCH (conversation:Conversation)-[:HAS_MESSAGE]->(message:Message)
WHERE conversation.session_id STARTS WITH 'shopping-docs-'
RETURN conversation.session_id, message.id, message.role, message.content
ORDER BY conversation.session_id, message.timestamp
Compare the stored prompt and response with the completed run’s output. No rows does not prove that the earlier preference write was absent; inspect preferences or follow full instance cleanup before restarting a partially completed exercise.
Cleanup and next steps
Cleanup ends this lesson and removes its stored results. To keep working with those results later, follow Resume this lesson later before cleanup.
In the Aura console, select only the agent-memory-tutorial instance created for this lesson. Use its trashcan action, enter its exact name and confirm Destroy.
Expected: the tutorial instance disappears from the instance list. This removes its data and snapshots, so verify the instance name before confirming. See Aura instance deletion for the console procedure.
Remove the downloaded credentials for that deleted instance and clear its connection variables:
unset NEO4J_URI NEO4J_USERNAME NEO4J_PASSWORD NEO4J_DATABASE
Keep the tutorial folder and virtual environment. Start each Aura tutorial with a new empty lesson instance and its new credentials; this also avoids carrying over vector indexes from a lesson using a different embedding dimension.
For a larger application, see the existing retail assistant example and Microsoft integration guide. Review their separate prerequisites before adding graph algorithms or another deployment.