Metadata-Version: 2.4
Name: personiq
Version: 0.2.1
Summary: Personalization memory connector for LangChain and LangGraph applications
Author-email: Ashwini Bhardwaj <ashwini.10521@gmail.com>
License: MIT
Keywords: langchain,langgraph,memory,personalization,chatbot,ai,llm,rag,user-memory
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: langchain>=0.2.0
Requires-Dist: langchain-core>=0.2.0
Requires-Dist: langgraph>=0.1.0
Requires-Dist: sentence-transformers>=3.0.0
Requires-Dist: sqlite-vec>=0.1.1
Requires-Dist: pydantic>=2.0.0
Requires-Dist: numpy>=1.26.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: tenacity>=8.2.0
Provides-Extra: groq
Requires-Dist: langchain-groq>=0.1.0; extra == "groq"
Provides-Extra: openai
Requires-Dist: langchain-openai>=0.1.0; extra == "openai"
Provides-Extra: anthropic
Requires-Dist: langchain-anthropic>=0.1.0; extra == "anthropic"
Provides-Extra: ollama
Requires-Dist: langchain-ollama>=0.1.0; extra == "ollama"
Provides-Extra: huggingface
Requires-Dist: langchain-huggingface>=0.0.3; extra == "huggingface"
Provides-Extra: mistral
Requires-Dist: langchain-mistralai>=0.1.0; extra == "mistral"
Provides-Extra: all
Requires-Dist: langchain-groq>=0.1.0; extra == "all"
Requires-Dist: langchain-openai>=0.1.0; extra == "all"
Requires-Dist: langchain-anthropic>=0.1.0; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
Requires-Dist: ruff>=0.4.0; extra == "dev"
Dynamic: license-file

# personiq

**Personalization memory connector for LangChain and LangGraph.**

One adapter. Any LLM. Works alongside any other memory system.

personiq automatically extracts what matters about each user from their conversations, stores it with hybrid search (BM25 + semantic + recency), and injects it into future prompts — so your chatbot, recommendation engine, or AI application feels like it genuinely knows the user.

```python
from langchain_groq import ChatGroq
from personiq import PersoniqAdapter

piq = PersoniqAdapter(llm=ChatGroq(model="llama-3.1-8b-instant"))

# LangGraph — two lines:
builder.add_node("personiq_load", piq.load_node)
builder.add_node("personiq_save", piq.save_node)

# Direct — three lines:
ctx    = piq.persona("alice", user_message)
system = f"{ctx}\n{base_system}" if ctx else base_system
await piq.learn("alice", messages)
```

---

## Installation

```bash
pip install personiq

# Then install your LLM provider:
pip install personiq[groq]        # Groq  (llama, gemma, mixtral)
pip install personiq[openai]      # OpenAI
pip install personiq[anthropic]   # Anthropic (Claude)
pip install personiq[ollama]      # Ollama — local, no API key needed
pip install personiq[mistral]     # Mistral
```

---

## What personiq does

1. **Extracts** durable facts from conversations using your LLM (skills, goals, preferences, context, style, personal info)
2. **Stores** them in a local SQLite database with vector embeddings and BM25 keyword indexes
3. **Retrieves** the most relevant memories per query using hybrid search (BM25 + cosine similarity + recency decay) fused with Reciprocal Rank Fusion
4. **Injects** them into your system prompt — either as a structured bullet list or a natural-language persona paragraph

---

## Integration styles

### Style 1 — LangGraph nodes (recommended)

```python
from langchain_groq import ChatGroq
from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import END, StateGraph
from personiq import PersoniqAdapter, PersoniqState

piq = PersoniqAdapter(llm=ChatGroq(model="llama-3.1-8b-instant"), mode="persona")

async def chat(state: PersoniqState) -> dict:
    mem_ctx  = state.get("memory_context", "")
    system   = f"{mem_ctx}\nYou are a helpful assistant." if mem_ctx else "You are a helpful assistant."
    messages = [SystemMessage(content=system)] + list(state["messages"])
    response = await llm.ainvoke(messages)
    return {"messages": [response]}

builder = StateGraph(PersoniqState)
builder.add_node("personiq_load", piq.load_node)   # retrieves memories
builder.add_node("chat",          chat)
builder.add_node("personiq_save", piq.save_node)   # saves memories

builder.set_entry_point("personiq_load")
builder.add_edge("personiq_load", "chat")
builder.add_edge("chat",          "personiq_save")
builder.add_edge("personiq_save", END)
graph = builder.compile()

result = await graph.ainvoke({
    "user_id":  "alice",
    "messages": [HumanMessage(content="I'm a backend engineer using Go and Postgres.")],
})
```

### Style 2 — Direct usage (any framework)

```python
piq = PersoniqAdapter(llm=llm)

# Before your LLM call — get personalization context:
ctx    = piq.context("alice", user_message)   # structured bullets
# or:
ctx    = piq.persona("alice", user_message)   # natural-language paragraph (better UX)

system = f"{ctx}\n{base_system}" if ctx else base_system

# After your LLM call — save new memories (fire-and-forget):
await piq.learn("alice", messages)
```

### Style 3 — Inject into messages list

```python
messages = piq.inject(messages, user_id="alice", query=user_message)
response = await llm.ainvoke(messages)
```

---

## Two context modes

### `mode="context"` (default) — structured bullets

```
[personiq: what I know about this user]

Technical background:
  • User is an experienced Go developer
  • User works with PostgreSQL and Redis

Goals & current work:
  • User is building a distributed payment service

[end of personiq context]
```

### `mode="persona"` — natural language

```
You already know this user. Here is what you know about them:
They work with Go and PostgreSQL. They're currently focused on: building
a distributed payment service. They prefer concise, direct technical answers.
Use this naturally — don't recite it back, just let it shape how you respond.
```

---

## Works alongside other memory systems

personiq is fully independent. It stores memories in its own SQLite database and does not interfere with Mem0, Zep, LangMem, or any other memory setup.

```python
piq  = PersoniqAdapter(llm=llm)   # personiq — personalization memory
mem0 = MemoryClient(...)          # Mem0 — conversation history

# Both work independently on the same conversation
ctx  = piq.persona("alice", message)
hist = mem0.get_history("alice")
```

---

## Supported LLM providers

| Provider | Install | Import |
|---|---|---|
| Groq | `pip install personiq[groq]` | `from langchain_groq import ChatGroq` |
| OpenAI | `pip install personiq[openai]` | `from langchain_openai import ChatOpenAI` |
| Anthropic | `pip install personiq[anthropic]` | `from langchain_anthropic import ChatAnthropic` |
| Ollama (local) | `pip install personiq[ollama]` | `from langchain_ollama import ChatOllama` |
| Mistral | `pip install personiq[mistral]` | `from langchain_mistralai import ChatMistralAI` |

Any `BaseChatModel` from LangChain works.

---

## Memory categories

| Category | What gets stored |
|---|---|
| `technical` | Languages, frameworks, tools, platforms |
| `goal` | Objectives, projects, purchase intent, problems to solve |
| `preference` | Likes, dislikes, favourites — products, approaches, content |
| `context` | Occupation, location, education, life stage |
| `style` | Communication style, verbosity, tone preference |
| `personal` | Name, relationships, values, life events |

---

## Configuration

```python
from personiq import PersoniqAdapter, PersoniqConfig

config = PersoniqConfig(
    db_path              = "./myapp.db",
    embedding_backend    = "local",      # "local" (default) or "openai"
    top_k                = 5,
    similarity_threshold = 0.30,
    semantic_weight      = 0.60,
    bm25_weight          = 0.30,
    recency_weight       = 0.10,
    async_extraction     = True,         # fire-and-forget (default)
    mode                 = "persona",    # "context" or "persona"
    debug                = False,
)

piq = PersoniqAdapter(llm=llm, config=config)
```

All settings are also configurable via environment variables (prefix `PERSONIQ_`).

---

## Memory management

```python
# Inspect stored memories
for m in piq.memories("alice"):
    print(f"[{m.category:12}] {m.content}  (importance: {m.importance_score:.2f})")

# Filter by category
goals = piq.memories("alice", category="goal")
tech  = piq.memories("alice", category="technical")

# Count
print(piq.count("alice"))

# Delete all memories (GDPR / account deletion)
piq.forget("alice")
```

---

## Running tests

```bash
pip install -e ".[dev]"
pytest tests/ -v
```

No API key required — all LLM calls are mocked in tests.

---

## Use cases

personiq is designed for any application where knowing the user improves outcomes:

- **Personalised chatbots** — assistant remembers user's name, job, preferences across sessions
- **Recommendation engines** — surface products/content matching stored preferences and goals
- **AI sales agents** — know the prospect's industry, pain points, and purchase intent
- **Content personalisation** — adapt tone, complexity, and topics to each user's profile
- **Ad targeting** — build rich user profiles from natural conversation signals
- **Customer support** — remember past issues, preferences, and technical environment
