Personalization memory connector for LangChain and LangGraph applications
Project description
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.
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
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
- Extracts durable facts from conversations using your LLM (skills, goals, preferences, context, style, personal info)
- Stores them in a local SQLite database with vector embeddings and BM25 keyword indexes
- Retrieves the most relevant memories per query using hybrid search (BM25 + cosine similarity + recency decay) fused with Reciprocal Rank Fusion
- 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)
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)
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
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.
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
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
# 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
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file personiq-0.2.1.tar.gz.
File metadata
- Download URL: personiq-0.2.1.tar.gz
- Upload date:
- Size: 35.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b59864ab8894f9d4683ec4f23d31fda585dbcba85914c6ead2d989f66754dc66
|
|
| MD5 |
d37ae606b574e691e73d85771eecb06c
|
|
| BLAKE2b-256 |
ae16735a82b5cc2e79ad780c798223c410f8deab67897b349a2af5cf3ce5c4ea
|
File details
Details for the file personiq-0.2.1-py3-none-any.whl.
File metadata
- Download URL: personiq-0.2.1-py3-none-any.whl
- Upload date:
- Size: 28.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
12bb86e027bc4db26b84c4b9f732abdd6e85d26083ca1d966e5a51e3c54efe32
|
|
| MD5 |
1c863f34c687ab835a2867853d4e97b9
|
|
| BLAKE2b-256 |
fe1ed34b51497ef1440ac454f080eec46ba64d1281563819531bcf866131a473
|