Metadata-Version: 2.4
Name: smara-langchain
Version: 0.1.0
Summary: LangChain integration for Smara Memory API — persistent memory for AI agents
Project-URL: Homepage, https://smara.io
Project-URL: Documentation, https://api.smara.io/docs/
Project-URL: Repository, https://github.com/smara-io/smara-langchain
Author-email: Smara <hello@smara.io>
License-Expression: MIT
Keywords: agents,ai,langchain,memory,smara
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.9
Requires-Dist: httpx>=0.25.0
Requires-Dist: langchain-core>=0.2.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# smara-langchain

LangChain integration for the [Smara Memory API](https://smara.io) -- persistent, decay-aware memory for AI agents.

## Installation

```bash
pip install smara-langchain
```

## Quick start

### SmaraMemory -- conversation memory

Drop-in replacement for LangChain's built-in memory classes. Stores every
human/AI turn as a memory in Smara and retrieves the most relevant memories
on each new turn via semantic search with Ebbinghaus decay ranking.

```python
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from smara_langchain import SmaraMemory

memory = SmaraMemory(
    api_key="sm_...",
    user_id="user-42",
    top_k=5,               # retrieve top 5 memories per turn
)

chain = ConversationChain(
    llm=ChatOpenAI(),
    memory=memory,
)

chain.invoke({"input": "I'm allergic to peanuts"})
# Memory stored in Smara automatically

chain.invoke({"input": "What should I avoid eating?"})
# Smara retrieves the peanut allergy memory as context
```

### SmaraRetriever -- RAG over memories

Use Smara as a retriever in any LangChain RAG pipeline. Each memory is
returned as a `Document` with the fact text and full metadata (importance,
decay score, similarity, etc.).

```python
from smara_langchain import SmaraRetriever

retriever = SmaraRetriever(
    api_key="sm_...",
    user_id="user-42",
    top_k=10,
    score_threshold=0.3,    # optional: filter low-relevance results
)

# Use with invoke()
docs = retriever.invoke("What are the user's dietary preferences?")
for doc in docs:
    print(f"{doc.page_content}  (score: {doc.metadata['score']})")

# Use in a RAG chain
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_template(
    "Use these memories about the user to answer their question.\n\n"
    "Memories:\n{context}\n\n"
    "Question: {question}"
)

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | ChatOpenAI()
    | StrOutputParser()
)

answer = chain.invoke("What food does the user like?")
```

### SmaraClient -- direct API access

For lower-level control you can use the client directly.

```python
from smara_langchain import SmaraClient

client = SmaraClient(api_key="sm_...")

# Store a memory
client.store(user_id="user-42", fact="User prefers dark mode", importance=0.7)

# Search memories
results = client.search(user_id="user-42", query="UI preferences")

# Get formatted context for an LLM prompt
ctx = client.get_context(user_id="user-42", query="UI preferences", top_n=5)
print(ctx["context"])

# Delete a memory
client.delete(memory_id="some-uuid")
```

### Async support

All methods have async counterparts -- prefix with `a`:

```python
await client.astore(user_id="user-42", fact="...")
await client.asearch(user_id="user-42", query="...")
docs = await retriever.ainvoke("query")
```

## Configuration

| Parameter | Default | Description |
|---|---|---|
| `api_key` | *required* | Your Smara API key (`sm_...`) |
| `user_id` | *required* | End-user identifier for scoping memories |
| `base_url` | `https://api.smara.io` | API base URL |
| `top_k` | 5 (memory) / 10 (retriever) | Number of memories to retrieve |
| `score_threshold` | `None` | Minimum blended score to include (retriever only) |
| `memory_key` | `"memory"` | Variable name in the chain context (memory only) |
| `return_memories_raw` | `False` | Return raw dicts instead of formatted string (memory only) |

## How Smara ranking works

Smara combines **semantic similarity** with **Ebbinghaus decay** (memories
naturally fade over time but are refreshed on access) and **importance scoring**
to produce a blended relevance score. This means frequently accessed, important,
and semantically relevant memories surface first -- mimicking how human memory
works.

## Links

- [Smara website](https://smara.io)
- [API documentation](https://api.smara.io/docs/)
- [GitHub](https://github.com/smara-io/smara-langchain)
