Personalized Search Agent with Hindsight Memory
Run this notebook
This recipe is available as an interactive Jupyter notebook. Open in GitHub →
A search assistant that learns your preferences, location, dietary needs, and lifestyle to provide contextually relevant search results.
Features
- Learns location, dietary restrictions, and lifestyle
- Personalizes search queries based on context
- Remembers past searches and preferences
- Integrates with Tavily for real web search (optional)
Prerequisites
- OpenAI API key
- Hindsight running locally via Docker (see setup below)
- Tavily API key (optional, for real web search)
Start Hindsight Locally
Before running this notebook, start Hindsight in a terminal:
export OPENAI_API_KEY="your-openai-api-key"
docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
-e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
-e HINDSIGHT_API_LLM_MODEL=gpt-4o-mini \
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
ghcr.io/vectorize-io/hindsight:latest
1. Install Dependencies
# Tavily is optional - demo works with simulated results if not installed
!pip install -q hindsight-client openai tavily-python nest-asyncio
2. Configure API Keys
Enter your API keys when prompted. Tavily is optional - press Enter to skip for simulated search results.
import getpass
import os
# Set OpenAI API key (used by both Hindsight and the demo)
if not os.getenv("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")
# Tavily is optional - for real web search
if not os.getenv("TAVILY_API_KEY"):
tavily_key = getpass.getpass("Enter your Tavily API key (or press Enter to skip): ")
if tavily_key:
os.environ["TAVILY_API_KEY"] = tavily_key
print("API keys configured!")
3. Initialize Clients
import nest_asyncio
nest_asyncio.apply()
from openai import OpenAI
from hindsight_client import Hindsight
# Initialize Hindsight client (connects to local Docker instance)
hindsight = Hindsight(
base_url=os.getenv("HINDSIGHT_BASE_URL", "http://localhost:8888"),
)
openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# Optional: Tavily for real web search
try:
from tavily import TavilyClient
tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
HAS_TAVILY = True
print("Tavily configured - using real web search!")
except (ImportError, Exception) as e:
HAS_TAVILY = False
print("Note: Using simulated search results (Tavily not configured)")
USER_ID = "search-user-demo"
print("Clients initialized!")
4. Define Helper Functions
def store_preference(preference: str) -> str:
"""Store a user preference."""
hindsight.retain(
bank_id=USER_ID,
content=f"User preference: {preference}",
metadata={"category": "preference"},
)
return f"Learned: {preference}"
def store_interaction(query: str, response: str) -> None:
"""Store a search interaction."""
hindsight.retain(
bank_id=USER_ID,
content=f"Search query: {query}\nResult highlights: {response[:200]}",
metadata={"category": "search_history"},
)
def get_user_context(query: str) -> str:
"""Retrieve relevant user context."""
memories = hindsight.recall(
bank_id=USER_ID,
query=f"preferences location dietary lifestyle {query}",
budget="mid",
)
if memories and memories.results:
return "\n".join(f"- {m.text}" for m in memories.results[:6])
return ""
def personalized_search(query: str) -> str:
"""Perform a personalized search."""
user_context = get_user_context(query)
enhancement_prompt = f"""Given this user's preferences and the search query, suggest how to enhance the search.
User preferences:
{user_context if user_context else "No preferences recorded yet."}
Search query: {query}
Return a JSON object with:
- "enhanced_query": The improved search query incorporating relevant preferences
- "filters": Any specific filters to apply (e.g., "vegetarian", "within 5 miles")
- "reasoning": Brief explanation of personalizations applied"""
enhancement = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": enhancement_prompt}],
temperature=0.3,
max_tokens=300,
)
enhanced_info = enhancement.choices[0].message.content
# Perform the search
if HAS_TAVILY:
search_results = tavily.search(
query=query,
search_depth="advanced",
max_results=5,
)
results_text = "\n".join(
f"- {r['title']}: {r['content'][:150]}..."
for r in search_results.get('results', [])
)
else:
results_text = f"[Simulated search results for: {query}]"
response_prompt = f"""Based on the search results and user preferences, provide a personalized summary.
User preferences:
{user_context if user_context else "No preferences recorded yet."}
Query: {query}
Search enhancement applied:
{enhanced_info}
Search results:
{results_text}
Provide a helpful, personalized response that takes into account their preferences."""
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": response_prompt}],
temperature=0.7,
max_tokens=500,
)
answer = response.choices[0].message.content
store_interaction(query, answer)
return answer
def get_preference_profile() -> str:
"""Get a summary of the user's preference profile."""
profile = hindsight.reflect(
bank_id=USER_ID,
query="""Summarize what we know about this user:
- Location and neighborhood
- Dietary preferences and restrictions
- Work style and schedule
- Hobbies and interests
- Family situation
- Shopping preferences""",
budget="high",
)
return profile.text if hasattr(profile, 'text') else str(profile)
print("Helper functions defined!")
5. Build User Profile
print("Learning user preferences...")
preferences = [
"Lives in San Francisco, Mission District",
"Works remotely as a software engineer",
"Vegetarian, prefers organic food when possible",
"Has a 5-year-old daughter named Emma",
"Enjoys hiking and outdoor activities on weekends",
"Prefers quiet coffee shops for remote work",
"Lactose intolerant, uses oat milk",
"Interested in sustainable and eco-friendly products",
"Usually free on Tuesday and Thursday afternoons",
"Husband is allergic to nuts",
]
for pref in preferences:
result = store_preference(pref)
print(f" {result}")
6. Personalized Search Results
import time
print("=" * 60)
print(" Personalized Search Results")
print("=" * 60)
searches = [
"Find a good coffee shop for working remotely",
"Restaurant recommendations for a family dinner",
"Birthday gift ideas for a 5-year-old",
]
for query in searches:
print(f"\nSearch: {query}")
print("-" * 40)
result = personalized_search(query)
print(result)
time.sleep(1)
7. View Preference Profile
print("=" * 60)
print(" User Preference Profile")
print("=" * 60)
print(get_preference_profile())
8. Try Your Own Search
your_search = "Best hiking trails near me" # Change this!
print(f"Search: {your_search}")
print("-" * 40)
print(personalized_search(your_search))
9. Cleanup
hindsight.close()
print("Client connection closed.")