Metadata-Version: 2.2
Name: gllm-memory-binary
Version: 0.2.0
Summary: A flexible memory system for Gen AI applications
Author-email: Budi Kurniawan <budi.kurniawan1@gdplabs.id>
Requires-Python: <3.14,>=3.11
Description-Content-Type: text/markdown
Requires-Dist: filetype<2.0.0,>=1.2.0
Requires-Dist: gllm-core-binary>=0.4.0
Requires-Dist: gllm-datastore-binary<0.6.0,>=0.5.0
Requires-Dist: gllm-inference-binary<0.7.0,>=0.6.0
Requires-Dist: httpx<1.0.0,>=0.28.0
Requires-Dist: langchain-community<2.0.0,>=0.4.0
Requires-Dist: langchain-core<2.0.0,>=1.0.0
Requires-Dist: pydantic<3.0.0,>=2.11.4
Requires-Dist: python-dotenv<2.0.0,>=1.0.0
Requires-Dist: python-json-logger<5.0.0,>=3.3.0
Requires-Dist: python-magic<0.5.0,>=0.4.27; sys_platform != "win32"
Requires-Dist: python-magic-bin<0.5.0,>=0.4.14; sys_platform == "win32"
Requires-Dist: pyyaml<7.0.0,>=6.0
Requires-Dist: scipy<2.0.0,>=1.15.1
Requires-Dist: typing-extensions<5.0.0,>=4.8.0
Requires-Dist: cryptography>=46.0.5
Provides-Extra: dev
Requires-Dist: coverage<8.0.0,>=7.4.4; extra == "dev"
Requires-Dist: mypy<2.0.0,>=1.15.0; extra == "dev"
Requires-Dist: pre-commit<5.0.0,>=3.7.0; extra == "dev"
Requires-Dist: pytest<10.0.0,>=9.0.0; extra == "dev"
Requires-Dist: pytest-asyncio<2.0.0,>=1.3.0; extra == "dev"
Requires-Dist: pytest-cov<8.0.0,>=6.0.0; extra == "dev"
Requires-Dist: ruff<1.0.0,>=0.6.7; extra == "dev"
Provides-Extra: mem0ai
Requires-Dist: mem0ai<3.0.0,>=0.1.117; extra == "mem0ai"
Requires-Dist: spacy<4.0.0,>=3.7.0; extra == "mem0ai"
Provides-Extra: vector-stores
Requires-Dist: elasticsearch<10.0.0,>=9.0.0; extra == "vector-stores"

# GLLM Memory

## Description

Memory layer for AI agents.

The public API is `MemoryManager`.

You can use it in two ways:

1. **HTTP mode**: use `api_key` and optional `host`
2. **SDK mode**: use `MemoryManagerConfig` and pass `config=...`

In SDK mode, you can register your own LLM, embedding model, memory store, and
optional reranker without exposing backend-specific config to application code.

## Prerequisites

### Mandatory

1. **Python 3.11+** — [Install here](https://www.python.org/downloads/)
2. **pip** — [Install here](https://pip.pypa.io/en/stable/installation/)
3. **uv** — [Install here](https://docs.astral.sh/uv/getting-started/installation/)
4. **gcloud CLI** (for authentication) — [Install here](https://cloud.google.com/sdk/docs/install), then log in using:
   ```bash
   gcloud auth login
   ```

### Mem0 Configuration

- **Mem0 API key** (HTTP client): from [Mem0 dashboard](https://app.mem0.ai/dashboard/api-keys).
- **Self-hosted URL**: set `MEM0_HOST` if the API is not Mem0 cloud.

**Environment variables** (typical):

| Variable          | Role                                                                                      |
|-------------------|-------------------------------------------------------------------------------------------|
| `MEM0_API_KEY`    | Required for the HTTP client when not passed in code.                                     |
| `MEM0_HOST`       | Optional; base URL for self-hosted Mem0 API.                                              |
| `MEMORY_PROVIDER` | Optional; default is Mem0 (`mem0`).                                                       |
| `TIMEOUT_SEC`     | Optional; request timeout in seconds (default `30`). Used when building clients from env. |

**Two ways to connect**

1. **HTTP API** — pass `api_key` and optionally `host` to `MemoryManager`. Same as setting `MEM0_API_KEY` / `MEM0_HOST`
   and using defaults.
2. **SDK mode** — pass `config=MemoryManagerConfig(...)` to `MemoryManager`. This path uses the local SDK integration
   and lets you register LLM, embedding, memory store, and reranker through the builder API. See
   `examples/example_mem0_sdk_client.py`.

Do not commit secrets to git.

---

## 📦 Installation

### Install from Artifact Registry

This requires authentication via the `gcloud` CLI.

```bash
uv pip install \
  --extra-index-url "https://oauth2accesstoken:$(gcloud auth print-access-token)@glsdk.gdplabs.id/gen-ai-internal/simple/" \
  gllm-memory
```

---

## 🔧 Local Development Setup

### Prerequisites

1. **Python 3.11+** — [Install here](https://www.python.org/downloads/)
2. **pip** — [Install here](https://pip.pypa.io/en/stable/installation/)
3. **uv** — [Install here](https://docs.astral.sh/uv/getting-started/installation/)
4. **gcloud CLI** — [Install here](https://cloud.google.com/sdk/docs/install), then log in using:
   ```bash
   gcloud auth login
   ```
5. **Git** — [Install here](https://git-scm.com/downloads)
6. **Access** to the [GDP Labs SDK GitHub repository](https://github.com/GDP-ADMIN/gl-sdk)

---

### 1. Clone Repository

```bash
git clone git@github.com:GDP-ADMIN/gl-sdk.git
cd gl-sdk/libs/gllm-memory
```

---

### 2. Setup Authentication

Set the following environment variables to authenticate with internal package indexes:

```bash
export UV_INDEX_GEN_AI_INTERNAL_USERNAME=oauth2accesstoken
export UV_INDEX_GEN_AI_INTERNAL_PASSWORD="$(gcloud auth print-access-token)"
export UV_INDEX_GEN_AI_USERNAME=oauth2accesstoken
export UV_INDEX_GEN_AI_PASSWORD="$(gcloud auth print-access-token)"
```

---

### 3. Quick Setup

Run:

```bash
make setup
```

---

### 4. Activate Virtual Environment

```bash
source .venv/bin/activate
```

---

## 🚀 Quick Start

### For Using the Library

1. **Install the package**:
   ```bash
   uv pip install gllm-memory
   ```

2. **Set your Mem0 API key**:
   ```bash
   export MEM0_API_KEY="your_api_key_here"
   ```

3. **For Self-Hosted Mem0** (Optional):
   ```bash
   export MEM0_API_KEY="your_api_key_here"
   export MEM0_HOST="https://your-mem0-server.com"
   ```

### For Development

1. **Complete setup** (this will install all dependencies, setup pre-commit, and activate the environment):
   ```bash
   make setup
   source .venv/bin/activate
   ```

2. **Set your Mem0 API key**:
   ```bash
   export MEM0_API_KEY="your_api_key_here"
   ```

3. **Run an example**:
   ```bash
   # HTTP API (add, search, list, delete_by_user_query, delete)
   python examples/simple_usage.py
   # SDK mode with MemoryManagerConfig
   python examples/example_mem0_sdk_client.py
   ```

## Architecture

The system follows a layered architecture below:

```
┌──────────────────────────────────────────────────────────────┐
│                    Application Layer                         │
├──────────────────────────────────────────────────────────────┤
│                    Memory Manager                            │
├──────────────────────────────────────────────────────────────┤
│                    Memory Client (Base)                      │
├──────────────────────────────────────────────────────────────┤
│                    Provider Layer (Mem0)                     │
├──────────────────────────────────────────────────────────────┤
│                    Mem0 Platform (HTTP client or Python SDK) │
└──────────────────────────────────────────────────────────────┘
```

## 🌐 HTTP Mode

Use this mode if you want to connect to the HTTP API directly.

Point the client at your own server:

```python
from gllm_memory import MemoryManager

manager = MemoryManager(
    api_key="your-api-key",
    host="https://your-mem0-server.com",
)
```

If you want local SDK mode, use `MemoryManager(config=...)` instead of `api_key` and `host`.

## 🧩 SDK Mode With `MemoryManagerConfig`

Use this mode if you want to:

1. register your own LM Invoker
2. register your own EM Invoker
3. choose the memory store from config
4. configure an optional reranker
5. keep application code independent from backend-specific config shape

`gllm-memory` does not create provider-specific invokers for you in normal SDK usage.
You create the invoker instances in your application, then register them in
`MemoryManagerConfig`.

### SDK Mode Example

```python
from gllm_memory import MemoryManager, MemoryManagerConfig
from gllm_inference.lm_invoker.openai_lm_invoker import OpenAILMInvoker

lm_invoker = OpenAILMInvoker(
    model_name="gpt-5-nano",
    api_key="your_openai_api_key",
)


def build_em_invoker():
    from gllm_inference.em_invoker.openai_em_invoker import OpenAIEMInvoker

    return OpenAIEMInvoker(
        model_name="text-embedding-3-small",
        api_key="your_openai_api_key",
    )


em_invoker = build_em_invoker()

config = (
    MemoryManagerConfig.builder()
    .memory_store.elasticsearch(
        host="localhost",
        port=9200,
        collection_name="memories",
        embedding_model_dims=1536,
    )
    .embedding.register(
        em_invoker,
        model="text-embedding-3-small",
        embedding_dims=1536,
    )
    .llm.register(lm_invoker, model="gpt-5-nano")
    .reranker.llm_reranker(
        model="gpt-5-nano",
        api_key="your_openai_api_key",
        top_k=5,
    )
    .build()
)

memory_manager = MemoryManager(config=config)
```

`gllm-memory` does not require a provider-specific helper import for this step.
You only need to pass an LM Invoker instance and an EM Invoker instance. The
reranker is optional; when configured with `llm_reranker`, the builder emits the
Mem0-compatible reranker section for SDK search calls that use `rerank=True`.
If your installed `gllm_inference` version still has a circular import on
`OpenAIEMInvoker`, instantiate the EM invoker with a local lazy import like the
example above.

### SDK Mode With Default Config

If you want to use the default SDK setup, you can build an empty config:

```python
from gllm_memory import MemoryManager, MemoryManagerConfig

config = MemoryManagerConfig.builder().build()
memory_manager = MemoryManager(config=config)
```

Default SDK behavior:

1. memory store uses Elasticsearch
2. embedding uses `gllm-inference: EM Invoker` with OpenAI defaults
3. llm uses `gllm-inference: LM Invoker` with OpenAI defaults
4. reranker is omitted unless configured explicitly

Required environment variables for the default SDK config:

1. `ELASTICSEARCH_HOST`
2. `ELASTICSEARCH_PORT`
3. `ELASTICSEARCH_COLLECTION_NAME`
4. `ELASTICSEARCH_EMBEDDING_MODEL_DIMS`
5. `OPENAI_API_KEY`

Optional environment variables:

1. `ELASTICSEARCH_USER`
2. `ELASTICSEARCH_PASSWORD`
3. `OPENAI_BASE_URL`
4. `OPENAI_MODEL_NAME` (default SDK LLM model override)
5. `OPENAI_EMBEDDING_MODEL` (used by `examples/example_mem0_sdk_client.py`)

### SDK Mode With Another Memory Store

You can register another memory store with the same builder style:

```python
config = (
    MemoryManagerConfig.builder()
    .memory_store.register(
        "pgvector",
        {
            "host": "localhost",
            "port": 5432,
            "dbname": "postgres",
            "user": "postgres",
            "password": "postgres",
            "collection_name": "memories",
        },
    )
    .embedding.register(em_invoker, embedding_dims=1536)
    .llm.register(lm_invoker)
    .build()
)
```

Notes:

1. `memory_store` is the public config name
2. you do not need to know the backend-native config structure for the built-in builder helpers
3. non-Elasticsearch stores use the backend's native behavior unless `gllm-memory` adds custom handling for them

## Core API methods

`MemoryManager` exposes async methods; `query` is required where noted.

### Methods

- **`add(user_id, agent_id, messages, scopes, metadata, infer, is_important)`** - Add new memories from message objects
- **`search(query, user_id, agent_id, scopes, metadata, threshold, top_k, include_important, rerank)`** - Search and
  retrieve memories by query (query is required)
- **`list_memories(user_id, agent_id, scopes, metadata, keywords, page, page_size)`** - Get all memories with pagination
  and keywords filtering
- **`update(memory_id, new_content, metadata, user_id, agent_id, scopes, is_important)`** - Update an existing memory by
  ID
- **`delete(memory_ids, user_id, agent_id, scopes, metadata)`** - Delete memories by IDs or by user/agent identifiers
- **`delete_by_user_query(query, user_id, agent_id, scopes, metadata, threshold, top_k)`** - Delete memories by query (
  query is required)

### Example (HTTP API)

```python
from gllm_memory import MemoryManager
from gllm_inference.schema.message import Message
from gllm_memory.enums import MemoryScope

memory_manager = MemoryManager(api_key="...", host="...")  # host optional

messages = [
    Message.user("I love pizza"),
    Message.assistant("Noted."),
]
await memory_manager.add(
    user_id="user_123",
    agent_id="agent_456",
    messages=messages,
    scopes={MemoryScope.USER},
    metadata={"conversation_id": "chat_001"},  # Optional
    infer=True,  # Optional, defaults to True
    is_important=False,  # Optional, defaults to False
)

memories = await memory_manager.search(
    query="What does the user like?",
    user_id="user_123",
    scopes={MemoryScope.USER},
    metadata=None,  # Optional
    threshold=0.3,  # Optional, defaults to 0.3
    top_k=10,  # Optional, defaults to 10
    include_important=False,  # Optional, defaults to False
    rerank=False,  # Optional, defaults to False; if True, applies re-ranking to results
)

await memory_manager.list_memories(
    user_id="user_123",
    scopes={MemoryScope.USER},
    metadata=None,  # Optional
    keywords="food",  # Optional
    page=1,  # Optional, defaults to 1
    page_size=100  # Optional, defaults to 100
)

await memory_manager.update(
    memory_id="memory_uuid_123",
    new_content="Updated text",
    user_id="user_123",
    agent_id="agent_456",
    scopes={MemoryScope.USER, MemoryScope.ASSISTANT},  # Optional
    is_important=None,  # Optional; None leaves existing flag unchanged
)

await memory_manager.delete_by_user_query(
    query="food preferences",
    user_id="user_123",
    scopes={MemoryScope.USER, MemoryScope.ASSISTANT},
    metadata=None,  # Optional
    threshold=0.3,  # Optional, defaults to 0.3
    top_k=10  # Optional, defaults to 10
)

# Delete memories by identifiers
delete_result = await memory_manager.delete(
    memory_ids=None,  # Optional
    user_id="user_123",
    scopes={MemoryScope.USER, MemoryScope.ASSISTANT},
    metadata=None  # Optional
)
# Then use await manager.add(...), search(...), etc.
```

### Example (SDK Mode)

```python
from gllm_memory import MemoryManager, MemoryManagerConfig
from gllm_memory.enums import MemoryScope
from gllm_inference.lm_invoker.openai_lm_invoker import OpenAILMInvoker
from gllm_inference.schema.message import Message

lm_invoker = OpenAILMInvoker(model_name="gpt-5-nano", api_key="...")


def build_em_invoker():
    from gllm_inference.em_invoker.openai_em_invoker import OpenAIEMInvoker

    return OpenAIEMInvoker(model_name="text-embedding-3-small", api_key="...")


em_invoker = build_em_invoker()

config = (
    MemoryManagerConfig.builder()
    .memory_store.elasticsearch(
        host="localhost",
        port=9200,
        collection_name="memories",
        embedding_model_dims=1536,
    )
    .embedding.register(em_invoker, embedding_dims=1536)
    .llm.register(lm_invoker)
    .reranker.llm_reranker(model="gpt-5-nano", api_key="...", top_k=5)
    .build()
)

memory_manager = MemoryManager(config=config)

messages = [
    Message.user("I love pizza"),
    Message.assistant("Noted."),
]

await memory_manager.add(
    user_id="user_123",
    agent_id="agent_456",
    messages=messages,
    scopes={MemoryScope.USER},
)
```

### 🔧 Code Quality

```bash
# Format code with ruff
ruff format gllm_memory/ tests/

# Check code quality
ruff check gllm_memory/ tests/

# Fix auto-fixable issues
ruff check gllm_memory/ tests/ --fix
```

---

## Local Development Utilities

The following Makefile commands are available for quick operations:

### Install uv

```bash
make install-uv
```

### Install Pre-Commit

```bash
make install-pre-commit
```

### Install Dependencies

```bash
make install
```

### Update Dependencies

```bash
make update
```

### Run Tests

```bash
make test
```

---

## Contributing

Please refer to
the [Python Style Guide](https://docs.google.com/document/d/1uRggCrHnVfDPBnG641FyQBwUwLoFw0kTzNqRm92vUwM/edit?usp=sharing)
for information about code style, documentation standards, and SCA requirements.

### Contributing Steps

1. **Fork and clone** the repository
2. **Set up development environment**:
   ```bash
   # Complete setup: installs uv, configures auth, installs packages, sets up pre-commit
   make setup
   ```

3. **Activate virtual environment**:
   ```bash
   source .venv/bin/activate
   ```

4. **Run tests** to ensure everything works:
   ```bash
   make test
   ```

5. **Make your changes** and ensure tests pass:
   ```bash
   # Make your changes
   # Ensure tests pass
   make test
   ```

6. **Submit a pull request**:
   ```bash
   # Submit a pull request
   git push origin your-branch
   ```
