From 9b1e0bb0a39ae19f747e0d2394452f63c8eff3a7 Mon Sep 17 00:00:00 2001 From: Ashpreet Bedi Date: Tue, 22 Oct 2024 16:00:27 +0100 Subject: [PATCH] v2.5.14 --- ....py => memories_and_summaries_postgres.py} | 0 ...ry.py => memories_and_summaries_sqlite.py} | 30 +++++-- phi/agent/agent.py | 8 +- phi/memory/db/postgres.py | 1 + phi/memory/db/sqlite.py | 87 +++++++++++++++---- phi/storage/agent/sqlite.py | 13 ++- 6 files changed, 103 insertions(+), 36 deletions(-) rename cookbook/agents/{personalized_memories_and_summaries.py => memories_and_summaries_postgres.py} (100%) rename cookbook/agents/{personalized_memories_and_summaries_sqlite_memory.py => memories_and_summaries_sqlite.py} (52%) diff --git a/cookbook/agents/personalized_memories_and_summaries.py b/cookbook/agents/memories_and_summaries_postgres.py similarity index 100% rename from cookbook/agents/personalized_memories_and_summaries.py rename to cookbook/agents/memories_and_summaries_postgres.py diff --git a/cookbook/agents/personalized_memories_and_summaries_sqlite_memory.py b/cookbook/agents/memories_and_summaries_sqlite.py similarity index 52% rename from cookbook/agents/personalized_memories_and_summaries_sqlite_memory.py rename to cookbook/agents/memories_and_summaries_sqlite.py index ce51cd192..0cbf296fa 100644 --- a/cookbook/agents/personalized_memories_and_summaries_sqlite_memory.py +++ b/cookbook/agents/memories_and_summaries_sqlite.py @@ -2,8 +2,8 @@ This recipe shows how to use personalized memories and summaries in an agent. Steps: 1. Run: `./cookbook/run_pgvector.sh` to start a postgres and pgvector instance -2. Run: `pip install openai sqlalchemy 'psycopg[binary]' pgvector` to install the dependencies -3. Run: `python cookbook/agents/personalized_memories_and_summaries_sqlite_memory.py` to run the agent +2. Run: `pip install openai sqlalchemy` to install dependencies +3. Run: `python cookbook/agents/memories_and_summaries_sqlite.py` to run the agent """ from rich.pretty import pprint @@ -11,17 +11,33 @@ from phi.agent import Agent, AgentMemory from phi.model.openai import OpenAIChat from phi.memory.db.sqlite import SqliteMemoryDb -from phi.storage.agent.postgres import PgAgentStorage +from phi.storage.agent.sqlite import SqlAgentStorage + +agent_memory_file: str = "tmp/agent_memory.db" +agent_storage_file: str = "tmp/agent_storage.db" -db_url = "postgresql+psycopg://ai:ai@localhost:5532/ai" agent = Agent( model=OpenAIChat(id="gpt-4o"), - # Store the memories and summary in a database + # The memories are personalized for this user + user_id="john_billings", + # Store the memories and summary in a table: agent_memory memory=AgentMemory( - db=SqliteMemoryDb(table_name="agent_memory"), create_user_memories=True, create_session_summary=True + db=SqliteMemoryDb( + table_name="agent_memory", + db_file=agent_memory_file, + ), + # Create and store personalized memories for this user + create_user_memories=True, + # Update memories for the user after each run + update_user_memories_after_run=True, + # Create and store session summaries + create_session_summary=True, + # Update session summaries after each run + update_session_summary_after_run=True, ), # Store agent sessions in a database - storage=PgAgentStorage(table_name="personalized_agent_sessions", db_url=db_url), + storage=SqlAgentStorage(table_name="agent_sessions", db_file=agent_storage_file), + description="You are a helpful assistant that always responds in a polite, upbeat and positive manner.", # Show debug logs so you can see the memory being created # debug_mode=True, ) diff --git a/phi/agent/agent.py b/phi/agent/agent.py index a14789a28..4faab5231 100644 --- a/phi/agent/agent.py +++ b/phi/agent/agent.py @@ -873,7 +873,7 @@ def get_system_message(self) -> Optional[Message]: if self.memory.create_user_memories: if self.memory.memories and len(self.memory.memories) > 0: system_message_lines.append( - "You have access to memory from previous interactions with the user that you can use:" + "You have access to memories from previous interactions with the user that you can use:" ) system_message_lines.append("### Memories from previous interactions") system_message_lines.append("\n".join([f"- {memory.memory}" for memory in self.memory.memories])) @@ -884,10 +884,12 @@ def get_system_message(self) -> Optional[Message]: system_message_lines.append("If you need to update the long-term memory, use the `update_memory` tool.") else: system_message_lines.append( - "You also have access to memory from previous interactions with the user but the user has no memories yet." + "You have the capability to retain memories from previous interactions with the user, " + "but have not had any interactions with the user yet." ) system_message_lines.append( - "If the user asks about memories, you can let them know that you dont have any memory about the yet, but can add new memories using the `update_memory` tool." + "If the user asks about previous memories, you can let them know that you dont have any memory about the user yet because you have not had any interactions with them yet, " + "but can add new memories using the `update_memory` tool." ) system_message_lines.append( "If you use the `update_memory` tool, remember to pass on the response to the user.\n" diff --git a/phi/memory/db/postgres.py b/phi/memory/db/postgres.py index 21cc3ddcf..f356b8242 100644 --- a/phi/memory/db/postgres.py +++ b/phi/memory/db/postgres.py @@ -48,6 +48,7 @@ def __init__( self.schema: Optional[str] = schema self.db_url: Optional[str] = db_url self.db_engine: Engine = _engine + self.inspector = inspect(self.db_engine) self.metadata: MetaData = MetaData(schema=self.schema) self.Session: scoped_session = scoped_session(sessionmaker(bind=self.db_engine)) self.table: Table = self.get_table() diff --git a/phi/memory/db/sqlite.py b/phi/memory/db/sqlite.py index 8601ef350..4d0356cf2 100644 --- a/phi/memory/db/sqlite.py +++ b/phi/memory/db/sqlite.py @@ -1,8 +1,24 @@ +from pathlib import Path from typing import Optional, List -from sqlalchemy import create_engine, MetaData, Table, Column, String, DateTime, text, select, delete, inspect -from sqlalchemy.orm import sessionmaker, scoped_session -from sqlalchemy.exc import SQLAlchemyError +try: + from sqlalchemy import ( + create_engine, + MetaData, + Table, + Column, + String, + DateTime, + text, + select, + delete, + inspect, + Engine, + ) + from sqlalchemy.orm import sessionmaker, scoped_session + from sqlalchemy.exc import SQLAlchemyError +except ImportError: + raise ImportError("`sqlalchemy` not installed. Please install it with `pip install sqlalchemy`") from phi.memory.db import MemoryDb from phi.memory.row import MemoryRow @@ -13,23 +29,51 @@ class SqliteMemoryDb(MemoryDb): def __init__( self, table_name: str = "memory", - db_path: str = "memory.db", + db_url: Optional[str] = None, + db_file: Optional[str] = None, + db_engine: Optional[Engine] = None, ): """ - This class provides a memory store backed by a SQLite table using SQLAlchemy. + This class provides a memory store backed by a SQLite table. + + The following order is used to determine the database connection: + 1. Use the db_engine if provided + 2. Use the db_url + 3. Use the db_file + 4. Create a new in-memory database Args: - table_name (str): The name of the table to store memory rows. - db_path (str): The path to the SQLite database file. Defaults to ':memory:' for in-memory database. + table_name: The name of the table to store Agent sessions. + db_url: The database URL to connect to. + db_file: The database file to connect to. + db_engine: The database engine to use. """ + _engine: Optional[Engine] = db_engine + if _engine is None and db_url is not None: + _engine = create_engine(db_url) + elif _engine is None and db_file is not None: + # Use the db_file to create the engine + db_path = Path(db_file).resolve() + # Ensure the directory exists + db_path.parent.mkdir(parents=True, exist_ok=True) + _engine = create_engine(f"sqlite:///{db_path}") + else: + _engine = create_engine("sqlite://") + + if _engine is None: + raise ValueError("Must provide either db_url, db_file or db_engine") + + # Database attributes self.table_name: str = table_name - self.db_path: str = db_path - self.engine = create_engine(f"sqlite:///{self.db_path}") - self.inspector = inspect(self.engine) - self.metadata = MetaData() - self.Session = scoped_session(sessionmaker(bind=self.engine)) - self.table = self.get_table() - self.create() + self.db_url: Optional[str] = db_url + self.db_engine: Engine = _engine + self.metadata: MetaData = MetaData() + self.inspector = inspect(self.db_engine) + + # Database session + self.Session = scoped_session(sessionmaker(bind=self.db_engine)) + # Database table for memories + self.table: Table = self.get_table() def get_table(self) -> Table: return Table( @@ -49,8 +93,8 @@ def create(self) -> None: if not self.table_exists(): try: logger.debug(f"Creating table: {self.table_name}") - self.metadata.create_all(self.engine) - except SQLAlchemyError as e: + self.table.create(self.db_engine, checkfirst=True) + except Exception as e: logger.error(f"Error creating table '{self.table_name}': {e}") raise @@ -103,7 +147,7 @@ def upsert_memory(self, memory: MemoryRow, create_and_retry: bool = True) -> Non ) else: # Insert new memory - stmt = self.table.insert().values(id=memory.id, user_id=memory.user_id, memory=str(memory.memory)) + stmt = self.table.insert().values(id=memory.id, user_id=memory.user_id, memory=str(memory.memory)) # type: ignore session.execute(stmt) session.commit() @@ -127,10 +171,15 @@ def delete_memory(self, id: str) -> None: def drop_table(self) -> None: if self.table_exists(): logger.debug(f"Deleting table: {self.table_name}") - self.table.drop(self.engine) + self.table.drop(self.db_engine) def table_exists(self) -> bool: - return self.inspector.has_table(self.table_name) + logger.debug(f"Checking if table exists: {self.table.name}") + try: + return self.inspector.has_table(self.table.name) + except Exception as e: + logger.error(e) + return False def clear(self) -> bool: with self.Session() as session: diff --git a/phi/storage/agent/sqlite.py b/phi/storage/agent/sqlite.py index 868f63be8..7018959ac 100644 --- a/phi/storage/agent/sqlite.py +++ b/phi/storage/agent/sqlite.py @@ -1,9 +1,7 @@ import time -import os +from pathlib import Path from typing import Optional, Any, List -from phi.agent import AgentSession - try: from sqlalchemy.dialects import sqlite from sqlalchemy.engine import create_engine, Engine @@ -14,8 +12,9 @@ from sqlalchemy.sql.expression import select from sqlalchemy.types import String except ImportError: - raise ImportError("`sqlalchemy` not installed") + raise ImportError("`sqlalchemy` not installed. Please install it with `pip install sqlalchemy`") +from phi.agent import AgentSession from phi.storage.agent.base import AgentStorage from phi.utils.log import logger @@ -49,10 +48,10 @@ def __init__( if _engine is None and db_url is not None: _engine = create_engine(db_url) elif _engine is None and db_file is not None: - # Use an absolute path for the database file - db_path = os.path.abspath(db_file) + # Use the db_file to create the engine + db_path = Path(db_file).resolve() # Ensure the directory exists - os.makedirs(os.path.dirname(db_path), exist_ok=True) + db_path.parent.mkdir(parents=True, exist_ok=True) _engine = create_engine(f"sqlite:///{db_path}") else: _engine = create_engine("sqlite://")