Build a Personal AI Assistant: Complete Python Project with Memory and Tools
โก Quick Answer
Build a personal AI assistant in Python with persistent memory, web search, file access, and calendar integration โ a complete project from architecture to working prototype.
Get more content like this on Telegram!
Daily AI tips, notes & resources โ free
Advertisement
Build a Personal AI Assistant: Complete Python Project with Memory and Tools
A personal AI assistant is a script that remembers your context โ projects, preferences, files โ across sessions, unlike a stateless chatbot that forgets everything the moment you close the tab.
I've tried the commercial options โ Notion AI, ChatGPT plugins, various third-party assistants. What I actually use daily is a Python script I built myself, because it knows my projects, remembers my preferences, and can read the files I'm working on.
Commercial tools are built for the average user. A personal assistant you build is built for you โ this guide shows how, with persistent memory, web search, notes, and file access.
Architecture Overview
A personal assistant needs three layers: persistence (memory that survives a restart), a runtime (the active conversation), and tools (actions it can actually take). Think of it like a new employee: persistence is their notebook, runtime is the conversation happening right now, and tools are the systems they're allowed to touch.
Personal AI Assistant:
Persistence Layer:
- conversations.db (SQLite): full conversation history
- memories.json: explicit user facts and preferences
- notes/: directory of personal notes
Runtime Layer:
- Current session messages list
- Loaded system prompt (persona + memories + context)
- Available tools
Tools:
- web_search: search current information
- save_note: create/update notes
- read_note: read existing notes
- list_notes: show all notes
- calculate: precise math
- get_current_time: current date/time
Flow:
User input
โ Load relevant memories
โ Build context-aware system prompt
โ Tool call loop (agent decides which tools to use)
โ Stream response
โ Save to history
โ Extract and update memoriesPart 1: Database and Storage
The storage layer is what turns a chatbot into an assistant: a SQLite database for conversation history, a JSON file for memories, and a folder for notes. Without it, every session starts from zero โ like meeting a colleague who forgets you each morning.
# pip install openai sqlite-utils chromadb requests
import sqlite3
import json
from pathlib import Path
from datetime import datetime
class AssistantStorage:
def __init__(self, data_dir: str = "./assistant_data"):
self.data_dir = Path(data_dir)
self.data_dir.mkdir(exist_ok=True)
self.notes_dir = self.data_dir / "notes"
self.notes_dir.mkdir(exist_ok=True)
self.db_path = self.data_dir / "conversations.db"
self.memories_path = self.data_dir / "memories.json"
self.preferences_path = self.data_dir / "preferences.json"
self._init_db()
self._init_memories()
def _init_db(self):
conn = sqlite3.connect(self.db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS conversations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT,
role TEXT,
content TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_session ON conversations(session_id)")
conn.commit()
conn.close()
def _init_memories(self):
if not self.memories_path.exists():
self.memories_path.write_text(json.dumps({"facts": [], "preferences": []}))
if not self.preferences_path.exists():
self.preferences_path.write_text(json.dumps({
"response_style": "concise but complete",
"name": "User",
"timezone": "UTC"
}))
def save_message(self, session_id: str, role: str, content: str):
conn = sqlite3.connect(self.db_path)
conn.execute(
"INSERT INTO conversations (session_id, role, content) VALUES (?, ?, ?)",
(session_id, role, content)
)
conn.commit()
conn.close()
def get_recent_history(self, limit: int = 20) -> list[dict]:
conn = sqlite3.connect(self.db_path)
rows = conn.execute("""
SELECT role, content, timestamp
FROM conversations
ORDER BY timestamp DESC
LIMIT ?
""", (limit,)).fetchall()
conn.close()
return [
{"role": row[0], "content": row[1], "timestamp": row[2]}
for row in reversed(rows)
]
def get_memories(self) -> dict:
return json.loads(self.memories_path.read_text())
def add_memory(self, fact: str, category: str = "general"):
memories = self.get_memories()
memories["facts"].append({
"fact": fact,
"category": category,
"added": datetime.now().isoformat()
})
self.memories_path.write_text(json.dumps(memories, indent=2))
def get_preferences(self) -> dict:
return json.loads(self.preferences_path.read_text())
# Note management
def save_note(self, title: str, content: str) -> str:
filename = title.lower().replace(" ", "_").replace("/", "_") + ".md"
note_path = self.notes_dir / filename
note_path.write_text(f"# {title}\n\n{content}\n\nLast updated: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
return f"Note saved: {filename}"
def read_note(self, title: str) -> str:
filename = title.lower().replace(" ", "_") + ".md"
note_path = self.notes_dir / filename
if note_path.exists():
return note_path.read_text()
return f"Note not found: {title}"
def list_notes(self) -> list[str]:
return [p.stem.replace("_", " ") for p in self.notes_dir.glob("*.md")]Part 2: Tool Definitions
Tools are the functions the model can call to act instead of just talk โ function calling in the OpenAI API turns "I would search the web" into an actual HTTP request. Each tool below is a JSON schema plus a Python function that executes it.
import requests
from datetime import datetime
def create_tools(storage: AssistantStorage) -> list[dict]:
return [
{
"type": "function",
"function": {
"name": "web_search",
"description": "Search the web for current information",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "save_note",
"description": "Save a note for later reference",
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "Note title"},
"content": {"type": "string", "description": "Note content in markdown"}
},
"required": ["title", "content"]
}
}
},
{
"type": "function",
"function": {
"name": "read_note",
"description": "Read an existing note by title",
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "Note title to read"}
},
"required": ["title"]
}
}
},
{
"type": "function",
"function": {
"name": "list_notes",
"description": "List all saved notes",
"parameters": {"type": "object", "properties": {}}
}
},
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "Get the current date and time",
"parameters": {"type": "object", "properties": {}}
}
},
{
"type": "function",
"function": {
"name": "remember_fact",
"description": "Store an important fact about the user for future reference",
"parameters": {
"type": "object",
"properties": {
"fact": {"type": "string", "description": "The fact to remember"},
"category": {
"type": "string",
"enum": ["preference", "project", "contact", "schedule", "general"]
}
},
"required": ["fact"]
}
}
}
]
def execute_tool(tool_name: str, arguments: dict, storage: AssistantStorage) -> str:
"""Execute a tool and return the result."""
if tool_name == "web_search":
query = arguments["query"]
# Using DuckDuckGo's instant answer API (no key required)
try:
response = requests.get(
"https://api.duckduckgo.com/",
params={"q": query, "format": "json", "no_html": "1"},
timeout=5
)
data = response.json()
if data.get("Abstract"):
return f"Search result: {data['Abstract']}\nSource: {data['AbstractURL']}"
return f"Search for '{query}' completed. No instant answer available โ please provide context from your knowledge."
except Exception as e:
return f"Search error: {e}. Try again or answer from your training data."
elif tool_name == "save_note":
return storage.save_note(arguments["title"], arguments["content"])
elif tool_name == "read_note":
return storage.read_note(arguments["title"])
elif tool_name == "list_notes":
notes = storage.list_notes()
if notes:
return f"Your notes: {', '.join(notes)}"
return "No notes saved yet."
elif tool_name == "get_current_time":
return f"Current date/time: {datetime.now().strftime('%A, %B %d, %Y at %I:%M %p')}"
elif tool_name == "remember_fact":
storage.add_memory(arguments["fact"], arguments.get("category", "general"))
return f"Remembered: {arguments['fact']}"
return f"Unknown tool: {tool_name}"Part 3: Main Assistant Loop
The agent loop is the code that lets the model call tools repeatedly until it has enough information to answer โ the model decides what to do next, not a hardcoded script. This is what separates an "agent" from a single API call.
import json
import uuid
from openai import OpenAI
def build_system_prompt(storage: AssistantStorage) -> str:
prefs = storage.get_preferences()
memories = storage.get_memories()
memory_text = ""
if memories["facts"]:
recent_facts = memories["facts"][-10:] # Last 10 facts
memory_text = "\n\nWhat I know about you:\n" + \
"\n".join(f"- {m['fact']}" for m in recent_facts)
return f"""You are {prefs.get('name', 'User')}'s personal AI assistant.
Your style: {prefs.get('response_style', 'concise but helpful')}
Timezone: {prefs.get('timezone', 'UTC')}
{memory_text}
You have access to tools: web search, note-taking, and memory.
When you learn important things about the user (their projects, preferences, working style),
use the remember_fact tool to store them for future reference.
Be personal, helpful, and remember you're building a relationship with this user over time.
If you need to search for current information, use the web_search tool."""
class PersonalAssistant:
def __init__(self):
self.client = OpenAI()
self.storage = AssistantStorage()
self.session_id = str(uuid.uuid4())
self.messages = []
def _load_session(self):
"""Load recent history and build initial context."""
recent = self.storage.get_recent_history(limit=10)
for msg in recent:
if msg["role"] in ["user", "assistant"]:
self.messages.append({"role": msg["role"], "content": msg["content"]})
def chat(self, user_input: str) -> str:
self.messages.append({"role": "user", "content": user_input})
self.storage.save_message(self.session_id, "user", user_input)
tools = create_tools(self.storage)
system = build_system_prompt(self.storage)
# Agent loop
while True:
response = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "system", "content": system}] + self.messages,
tools=tools,
tool_choice="auto"
)
choice = response.choices[0]
if choice.finish_reason == "stop":
answer = choice.message.content
self.messages.append({"role": "assistant", "content": answer})
self.storage.save_message(self.session_id, "assistant", answer)
return answer
elif choice.finish_reason == "tool_calls":
self.messages.append(choice.message)
for tool_call in choice.message.tool_calls:
args = json.loads(tool_call.function.arguments)
result = execute_tool(tool_call.function.name, args, self.storage)
self.messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result
})
def run(self):
self._load_session()
print("Personal Assistant ready. Type 'quit' to exit.\n")
print("I can: search the web, take notes, remember things about you.")
print("-" * 50)
while True:
user_input = input("\nYou: ").strip()
if user_input.lower() in ["quit", "exit", "bye"]:
print("Goodbye!")
break
if not user_input:
continue
print("\nAssistant: ", end="", flush=True)
response = self.chat(user_input)
print(response)
if __name__ == "__main__":
assistant = PersonalAssistant()
assistant.run()Conclusion
A personal assistant built this way knows your projects after a week of real use. It remembers that you prefer concise responses, that you're building a React app, that you have a standing Tuesday meeting โ details no commercial tool tracks, because no commercial tool is built for you specifically.
The core logic is 50-100 lines. What compounds isn't the code โ it's the memory the assistant accumulates about how you actually work.
For expanding this assistant with agent capabilities for complex multi-step tasks, see our AI agents explained guide. For the LangChain framework that can replace the manual tool loop here, see our LangChain tutorial.
Further Reading
- AI API Cost Management: How to Cut LLM Costs by 80% Without Losing Quality
- Deploy AI Model to Production: FastAPI, Docker, and Cloud Deployment Guide
- Streamlit Tutorial: Build and Deploy AI Apps with Python in Minutes
- OpenAI API Integration: Complete Python Guide for Building AI Applications
- RAG System Tutorial: Build a Production Retrieval-Augmented Generation System
- Kaggle Competition Guide: How to Rank in the Top 10% Every Time
- NLP for Beginners: How Computers Learn to Understand Language
- Python for Absolute Beginners: Your First 30 Days Roadmap (2026)
Advertisement
๐ฌ DiscussionPowered by GitHub Discussions
Frequently Asked Questions

Software Testing Expert & Prompt Engineering
Ensures every release is bug-free through rigorous testing, and crafts high-precision prompts that power our AI-driven workflows. Abdullah Al Arman Emon leads QA and prompt engineering across AiTechWorlds.
Not sure yet? Ask AI about this article
Get an instant, unbiased AI summary of โBuild a Personal AI Assistant: Complete Python Project with Memory and Toolsโ.
Advertisement
Related Articles
AI API Cost Management: How to Cut LLM Costs by 80% Without Losing Quality
AI API cost management โ practical strategies to reduce OpenAI, Claude, and Gemini API costs by 80% using model selection, caching, RAG, prompt optimization, and batch processing.
Build an AI Chatbot with Python: Complete Guide from Scratch to Deployment
Build an AI chatbot with Python โ complete tutorial from OpenAI API integration to conversation memory, streaming responses, and deploying a production-ready chatbot application.
CrewAI Tutorial: Build Multi-Agent AI Systems That Work Together
CrewAI tutorial โ build multi-agent AI systems where specialized agents collaborate to complete complex tasks, with practical Python examples for research, coding, and content workflows.
Deploy AI Model to Production: FastAPI, Docker, and Cloud Deployment Guide
Deploy AI model to production โ complete guide using FastAPI, Docker, and cloud platforms with monitoring, scaling, CI/CD, and best practices for production ML systems.