Skip to content

Commit cb53239

Browse files
committed
Openai agent example
1 parent e1a6208 commit cb53239

File tree

3 files changed

+302
-0
lines changed

3 files changed

+302
-0
lines changed

examples/openai_agent/README.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
2+
# OpenAI Agents SDK + MemMachine (Python) Example
3+
4+
This example shows how to integrate **MemMachine** as an external memory store for an agent built with the **OpenAI Agents SDK** (the Python package that provides the `agents` module).
5+
6+
The agent is configured with two tools:
7+
8+
- `add_memory(memory: str)` — persists a memory string to MemMachine
9+
- `search_memory(query: str)` — queries MemMachine and returns a simplified list of relevant memories
10+
11+
The implementation is in `example.py`.
12+
13+
## What this demonstrates
14+
15+
- Creating (or reusing) a MemMachine **Project** via the Python SDK
16+
- Writing “explicit memory” items via `memory.add(...)`
17+
- Retrieving context via `memory.search(...)` and formatting results for the agent
18+
- Using an OpenAI-compatible chat model endpoint (this example uses **Qwen** via DashScope’s OpenAI-compatible API)
19+
20+
## Prerequisites
21+
22+
1. **Python 3.12+**
23+
2. A running **MemMachine server** (local Docker is recommended)
24+
3. An API key for an OpenAI-compatible LLM endpoint
25+
26+
## Start MemMachine
27+
28+
From the repository root, the quickest way is:
29+
30+
```bash
31+
./memmachine-compose.sh
32+
```
33+
34+
Then verify it is up:
35+
36+
```bash
37+
curl -s http://localhost:8080/health
38+
```
39+
40+
## Install dependencies
41+
42+
This example depends on:
43+
44+
- `memmachine` (this repo)
45+
- `openai` (Async client used for OpenAI-compatible endpoints)
46+
- the **OpenAI Agents SDK** (the package that provides the `agents` module)
47+
48+
If you are running from this repo checkout, you can typically install the repo plus the extra agent dependencies like this:
49+
50+
```bash
51+
pip install -e .
52+
pip install openai-agents
53+
```
54+
55+
Notes:
56+
57+
- The exact pip name for the OpenAI Agents SDK may differ depending on your environment. If `pip install openai-agents` does not provide the `agents` module, install the package recommended by the Agents SDK documentation.
58+
59+
## Configuration
60+
61+
### MemMachine environment variables
62+
63+
`example.py` reads the following variables (all optional):
64+
65+
| Variable | Purpose | Default |
66+
|---|---|---|
67+
| `MEMMACHINE_BASE_URL` | MemMachine server base URL | `http://localhost:8080` |
68+
| `MEMMACHINE_API_KEY` | MemMachine API key (if enabled on your server) | empty |
69+
| `MEMMACHINE_ORG_ID` | Organization ID (API v2 scoping) | `default_org` |
70+
| `MEMMACHINE_PROJECT_ID` | Project ID used for this demo | `openai_agent_demo` |
71+
72+
Example:
73+
74+
```bash
75+
export MEMMACHINE_BASE_URL="http://localhost:8080"
76+
export MEMMACHINE_ORG_ID="default_org"
77+
export MEMMACHINE_PROJECT_ID="openai_agent_demo"
78+
```
79+
80+
### LLM environment variables (Qwen / DashScope)
81+
82+
This example uses Qwen via an OpenAI-compatible endpoint using `openai.AsyncOpenAI(base_url=..., api_key=...)`.
83+
84+
| Variable | Purpose | Default |
85+
|---|---|---|
86+
| `QWEN_BASE_URL` | OpenAI-compatible base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
87+
| `QWEN_API_KEY` | API key for the endpoint | (required if not using `DASHSCOPE_API_KEY`) |
88+
| `DASHSCOPE_API_KEY` | Alternate key name supported by DashScope | (optional) |
89+
90+
Example:
91+
92+
```bash
93+
export QWEN_API_KEY="your-dashscope-api-key"
94+
```
95+
96+
## Run
97+
98+
From this directory:
99+
100+
```bash
101+
python example.py
102+
```
103+
104+
You should see two turns printed:
105+
106+
- Turn 1 stores a memory (e.g. “My name is Alice.”)
107+
- Turn 2 asks the agent to recall the stored memory via `search_memory`
108+
109+
## How the integration works
110+
111+
### 1) Create or reuse a Project
112+
113+
`_get_memmachine_project()` builds a `MemMachineClient` and calls:
114+
115+
- `get_or_create_project(org_id=..., project_id=...)`
116+
117+
This ensures the example can run repeatedly without manual setup.
118+
119+
### 2) Write memory
120+
121+
`add_memory()` calls:
122+
123+
```python
124+
mem.add(content=memory, role="user", metadata={"type": "explicit_memory"})
125+
```
126+
127+
### 3) Search memory
128+
129+
`search_memory()` calls:
130+
131+
```python
132+
result = mem.search(query=query, limit=10)
133+
```
134+
135+
and then formats key parts of the response:
136+
137+
- episodic memory (short/long-term buckets)
138+
- episode summaries
139+
- semantic memory items (if present)
140+
141+
## Important notes / customization
142+
143+
- **Memory scope**: this example uses `project.memory()` with no `user_id`, `agent_id`, or `session_id`, so all runs share a single default context. In a real app, pass identifiers to isolate memory per user/session:
144+
145+
- `project.memory(user_id=..., agent_id=..., session_id=...)`
146+
147+
- **Tracing**: `set_tracing_disabled(True)` is called so you don’t need extra tracing configuration.
148+
149+
- **Model choice**: the example uses `model="qwen3-max"` via an OpenAI-compatible endpoint. You can swap to other providers by changing `_get_qwen_client()` and the `OpenAIChatCompletionsModel` configuration.
150+
151+
## Troubleshooting
152+
153+
- `Connection error to MemMachine`: ensure `MEMMACHINE_BASE_URL` points to a running server and `curl http://localhost:8080/health` returns OK.
154+
- `Missing API key`: set `QWEN_API_KEY` (or `DASHSCOPE_API_KEY`).
155+
- `ModuleNotFoundError: agents`: install the OpenAI Agents SDK package that provides the `agents` module (see “Install dependencies”).
156+

examples/openai_agent/__init__.py

Whitespace-only changes.

examples/openai_agent/example.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import asyncio
2+
import os
3+
4+
from agents import (
5+
Agent,
6+
OpenAIChatCompletionsModel,
7+
Runner,
8+
function_tool,
9+
set_tracing_disabled,
10+
)
11+
from openai import AsyncOpenAI
12+
13+
from memmachine import MemMachineClient
14+
15+
_MEMMACHINE_CLIENT = None
16+
_MEMMACHINE_PROJECT = None
17+
18+
19+
def _get_memmachine_project():
20+
"""Create (or reuse) a MemMachine Project handle (global boundary)."""
21+
global _MEMMACHINE_CLIENT, _MEMMACHINE_PROJECT
22+
if _MEMMACHINE_PROJECT is not None:
23+
return _MEMMACHINE_PROJECT
24+
25+
base_url = os.getenv("MEMMACHINE_BASE_URL") or "http://localhost:8080"
26+
api_key = os.getenv("MEMMACHINE_API_KEY") or ""
27+
org_id = os.getenv("MEMMACHINE_ORG_ID") or "default_org"
28+
project_id = os.getenv("MEMMACHINE_PROJECT_ID") or "openai_agent_demo"
29+
30+
_MEMMACHINE_CLIENT = MemMachineClient(
31+
api_key=api_key, base_url=base_url, timeout=30
32+
)
33+
_MEMMACHINE_PROJECT = _MEMMACHINE_CLIENT.get_or_create_project(
34+
org_id=org_id,
35+
project_id=project_id,
36+
description="openai-agents tool memory integration",
37+
)
38+
return _MEMMACHINE_PROJECT
39+
40+
41+
def _get_memmachine_memory():
42+
project = _get_memmachine_project()
43+
return project.memory()
44+
45+
46+
@function_tool
47+
def add_memory(memory: str) -> str:
48+
"""Persist one memory string into MemMachine."""
49+
mem = _get_memmachine_memory()
50+
mem.add(
51+
content=memory,
52+
role="user",
53+
metadata={"type": "explicit_memory"},
54+
)
55+
return "ok"
56+
57+
58+
@function_tool
59+
def search_memory(query: str) -> list[str]:
60+
"""Search memories from MemMachine and return a simplified text list."""
61+
62+
mem = _get_memmachine_memory()
63+
result = mem.search(query=query, limit=10)
64+
content = result.content
65+
66+
lines: list[str] = []
67+
68+
episodic = content.get("episodic_memory") or {}
69+
long_term = episodic.get("long_term_memory") or {}
70+
short_term = episodic.get("short_term_memory") or {}
71+
72+
for bucket_name, bucket in (
73+
("long_term_memory", long_term),
74+
("short_term_memory", short_term),
75+
):
76+
episodes = bucket.get("episodes") or []
77+
if episodes:
78+
lines.append(f"{bucket_name}:")
79+
lines.extend(f"- {ep['content']}" for ep in episodes)
80+
81+
summaries = short_term.get("episode_summary") or []
82+
if summaries:
83+
lines.append("episode_summary:")
84+
lines.extend(f"- {s}" for s in summaries)
85+
86+
semantic = content.get("semantic_memory") or []
87+
if semantic:
88+
lines.append("semantic_memory:")
89+
lines.extend(
90+
f"- [{item.get('category')}/{item.get('tag')}] {item.get('feature_name')} = {item.get('value')}"
91+
for item in semantic
92+
)
93+
94+
return lines
95+
96+
97+
def _get_qwen_client() -> AsyncOpenAI:
98+
base_url = (
99+
os.getenv("QWEN_BASE_URL")
100+
or "https://dashscope.aliyuncs.com/compatible-mode/v1"
101+
)
102+
api_key = os.getenv("QWEN_API_KEY") or os.getenv("DASHSCOPE_API_KEY")
103+
if not api_key:
104+
raise ValueError(
105+
"Missing API key. Set QWEN_API_KEY (or DASHSCOPE_API_KEY) in your environment."
106+
)
107+
return AsyncOpenAI(base_url=base_url, api_key=api_key)
108+
109+
110+
async def main() -> None:
111+
# Disable tracing to avoid requiring an OpenAI key for trace export.
112+
set_tracing_disabled(True)
113+
114+
qwen_model = OpenAIChatCompletionsModel(
115+
model="qwen3-max",
116+
openai_client=_get_qwen_client(),
117+
)
118+
119+
agent = Agent(
120+
name="MemoryAgent",
121+
instructions=(
122+
"You are an assistant with an external memory.\n"
123+
"- When the user asks you to remember something, use add_memory to store the information.\n"
124+
"- When the user asks you to recall what was stored, use search_memory to retrieve the stored memories and answer based on them.\n"
125+
"- Use tools when helpful, but don't overuse them.\n"
126+
"- Keep answers concise and direct."
127+
),
128+
model=qwen_model,
129+
tools=[add_memory, search_memory],
130+
)
131+
132+
# ---- Test run ----
133+
info = "My name is Alice."
134+
135+
result1 = await Runner.run(agent, f"Please remember: {info}")
136+
print("[turn1]", result1.final_output)
137+
138+
result2 = await Runner.run(
139+
agent,
140+
"What did I ask you to remember earlier? Please recall it from your memory.",
141+
)
142+
print("[turn2]", result2.final_output)
143+
144+
145+
if __name__ == "__main__":
146+
asyncio.run(main())

0 commit comments

Comments
 (0)