Metadata-Version: 2.4
Name: sovant
Version: 1.3.3
Summary: Sovant Python SDK — governed AI memory layer for AI agents and applications
Author: Sovant
License-Expression: MIT
Project-URL: Documentation, https://sovant.ai/docs
Project-URL: Source, https://github.com/hechin91/sovant-ai
Project-URL: Tracker, https://github.com/hechin91/sovant-ai/issues
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.8.2
Dynamic: license-file

# Sovant Python SDK

**Sovant is a governed AI memory layer for AI agents and applications.**
Use it to store, search, and recall memories with profile awareness and enterprise-grade control over how memory is captured and used.

[![PyPI version](https://img.shields.io/pypi/v/sovant.svg)](https://pypi.org/project/sovant/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Installation

```bash
pip install sovant
```

## Quick Start

```python
import os
from sovant import Sovant, MemoryCreate, SearchQuery

client = Sovant(api_key=os.environ["SOVANT_API_KEY"])

# Create a memory
mem = client.memory_create(MemoryCreate(
    data="User prefers dark mode",
    type="preference",
    tags=["ui", "settings"],
))

# Search memories
results = client.memory_search(SearchQuery(
    query="user preferences",
    limit=10,
))

# Update a memory
updated = client.memory_update(mem["id"], {
    "tags": ["ui", "settings", "theme"],
})

# Delete a memory
client.memory_delete(mem["id"])
```

## Recall vs Search

Sovant provides two ways to query memories:

- **`memory_recall()`** -- Hybrid recall, profile-aware.
  Use for conversational queries: "What do you know about me?", "What happened on Project X?".
  Uses Sovant's hybrid pipeline (profile fast-path + thread-scoped lexical + vector semantic search) and prioritizes profile facts (name/age/location) when available.

- **`memory_search()`** -- Semantic search.
  Use for topic lookup and discovery. Pure vector similarity search without profile logic.

```python
# Recall for conversational queries (returns { "results": [...], "total": N })
recall = client.memory_recall(
    query="what do you know about me?",
    limit=10,
)
for mem in recall.get("results", []):
    print(mem["content"])

# Search for topic discovery
topics = client.memory_search(SearchQuery(
    query="project updates",
    limit=5,
))
```

## Working with Threads

Threads let you organize related memories into conversations or sessions. Each thread has a `title` and can contain multiple memories.

```python
import os
from sovant import Sovant, MemoryCreate

client = Sovant(api_key=os.environ["SOVANT_API_KEY"])

# Create a new thread
thread = client.threads_create(
    title="Project Alpha Discussion",
    description="Q1 planning meeting notes",
    metadata={"project": "alpha", "quarter": "Q1"},
)

# Store memories in the thread
mem1 = client.memory_create(MemoryCreate(
    data="Decided to launch in March",
    type="journal",
    thread_id=thread["id"],
))

mem2 = client.memory_create(MemoryCreate(
    data="Budget approved: $50k",
    type="insight",
    thread_id=thread["id"],
))

# Recall memories from this specific thread
thread_recall = client.memory_recall(
    query="launch date",
    thread_id=thread["id"],
    limit=10,
)
# thread_recall["results"] contains the matching memories

# Get thread with all its memories
thread_with_memories = client.threads_get(
    thread["id"],
    include_memories=True,
    limit=50,
)

# List all threads
threads = client.threads_list(limit=20, offset=0)

# Update thread
updated = client.threads_update(
    thread["id"],
    title="Project Alpha - Q1 Launch",
    status="completed",
)

# Delete thread (keeps memories by default)
client.threads_delete(thread["id"])

# Delete thread AND all its memories
client.threads_delete(thread["id"], delete_memories=True)
```

## Configuration

```python
from sovant import Sovant

client = Sovant(
    api_key="sk_live_...",           # Required (or set SOVANT_API_KEY env var)
    base_url="https://sovant.ai",    # Optional, defaults to https://sovant.ai
    timeout=30.0,                    # Optional, request timeout in seconds (default: 30.0)
    max_retries=3,                   # Optional, max retry attempts (default: 3)
)
```

The SDK handles authentication via the `Authorization: Bearer` header.

## API Reference

### Memory Operations

#### Create Memory

```python
from sovant import MemoryCreate

memory = client.memory_create(MemoryCreate(
    data="Customer contacted support about billing",
    type="observation",          # 'journal' | 'insight' | 'observation' | 'task' | 'preference'
    tags=["support", "billing"],
    metadata={"ticket_id": "12345"},
    thread_id="thread_abc123",   # Optional thread association
))
```

#### Get Memory by ID

```python
memory = client.memory_get("mem_123abc")
```

#### Update Memory

```python
updated = client.memory_update("mem_123abc", {
    "tags": ["support", "billing", "resolved"],
})
```

#### Delete Memory

```python
client.memory_delete("mem_123abc")
```

#### Search Memories

```python
from sovant import SearchQuery

# Semantic search
results = client.memory_search(SearchQuery(
    query="customer preferences about notifications",
    limit=10,
    type="preference",
))

# Filter-based search
results = client.memory_search(SearchQuery(
    tags=["settings", "notifications"],
    from_date="2024-01-01",
    to_date="2024-12-31",
    limit=20,
))
```

#### Recall Memories

Recall returns `{ "results": [...], "total": N, "query_type": "hybrid" }`.

```python
# Hybrid recall with profile awareness
recall = client.memory_recall(
    query="what do you know about me?",
    limit=10,
)
for mem in recall.get("results", []):
    print(mem["content"], mem.get("relevance"))

# Thread-scoped recall
recall = client.memory_recall(
    query="launch timeline",
    thread_id="thread_abc123",
    limit=5,
)
```

#### Batch Create

```python
results = client.memory_create_batch([
    {"data": "First memory", "type": "journal"},
    {"data": "Second memory", "type": "insight", "tags": ["important"]},
])
```

#### List Memories

Fetch memories with filtering and pagination. Use `memory_list()` to retrieve recent memories or filter by criteria — it's more efficient than `memory_search()` when you don't need vector similarity.

```python
# List recent memories
result = client.memory_list(limit=20, offset=0)
for mem in result["memories"]:
    print(mem["content"])

# Filter by thread
thread_mems = client.memory_list(thread_id="thread-uuid", limit=50)

# Filter by type and tags
prefs = client.memory_list(
    type="preference",
    tags=["settings"],
    sort_by="updated_at",
    sort_order="desc",
)

# Pinned memories only
pinned = client.memory_list(is_pinned=True)
```

**Available parameters:**
- `limit` — max results (default: 20, max: 100)
- `offset` — pagination offset
- `thread_id` — filter by thread
- `type` — filter by memory type
- `tags` — filter by tags (must have all)
- `is_pinned` — filter by pinned status
- `is_archived` — filter by archived status
- `sort_by` — `created_at`, `updated_at`, `importance`, or `type`
- `sort_order` — `asc` or `desc`

### Thread Management

```python
# Create a thread
thread = client.threads_create(
    title="Customer Support Session",
    metadata={"user_id": "user_123"},
)

# List threads
threads = client.threads_list(limit=10, offset=0)

# Get thread by ID (with memories)
thread = client.threads_get("thread_abc123", include_memories=True)

# Update thread
updated = client.threads_update(
    "thread_abc123",
    title="Resolved: Billing Issue",
    status="completed",
)

# Delete thread
client.threads_delete("thread_abc123")
```

## Memory Types

- **journal** -- Chronological entries and logs
- **insight** -- Derived patterns and conclusions
- **observation** -- Factual, observed information
- **task** -- Action items and todos
- **preference** -- User preferences and settings

## Error Handling

The SDK raises `SovantError` for all API errors:

```python
from sovant import Sovant, SovantError

try:
    memory = client.memory_get("invalid_id")
except SovantError as e:
    print(f"Error: {e}")
    print(f"Code: {e.code}")      # e.g. "NOT_FOUND", "HTTP_401"
    print(f"Status: {e.status}")  # e.g. 404, 401

    if e.status == 404:
        print("Memory not found")
    elif e.status == 401:
        print("Invalid API key")
    elif e.status == 429:
        print("Rate limited -- SDK retries automatically")
```

The SDK automatically retries on rate limit (429) and server errors (5xx) with exponential backoff.

## Examples

### Customer Support Integration

```python
from sovant import MemoryCreate

# Track customer interaction
interaction = client.memory_create(MemoryCreate(
    data="Customer reported slow dashboard loading",
    type="observation",
    thread_id=f"ticket_{ticket_id}",
    tags=["support", "performance", "dashboard"],
    metadata={
        "ticket_id": ticket_id,
        "priority": "high",
    },
))

# Record resolution
resolution = client.memory_create(MemoryCreate(
    data="Resolved by clearing cache and upgrading plan",
    type="insight",
    thread_id=f"ticket_{ticket_id}",
    tags=["support", "resolved"],
))
```

### User Preference Tracking

```python
from sovant import MemoryCreate, SearchQuery

# Store preference
client.memory_create(MemoryCreate(
    data="User prefers email notifications over SMS",
    type="preference",
    tags=["notifications", "email"],
))

# Query preferences
preferences = client.memory_search(SearchQuery(
    query="notification preferences",
    type="preference",
))
```

## Rate Limiting

The API enforces 60 requests per minute. The SDK automatically retries rate-limited requests with exponential backoff. Rate limit headers are included in responses:

- `X-RateLimit-Limit` -- Request limit per window
- `X-RateLimit-Remaining` -- Remaining requests
- `X-RateLimit-Reset` -- Reset timestamp

## Support

- Documentation: [https://sovant.ai/docs](https://sovant.ai/docs)
- Issues: [GitHub Issues](https://github.com/hechin91/sovant-ai/issues)

## License & Use

- This SDK is MIT-licensed for integration convenience.
- The Sovant API and platform are proprietary to Sovant Technologies Sdn. Bhd.
- You may use this SDK to integrate with Sovant's hosted API.
- Hosting/redistributing the Sovant backend or any proprietary components is not permitted.

## License

MIT -- See [LICENSE](LICENSE) file for details.
