Resolve issues pertaining to main module reloading mechanisms internal to Agno/agent-ui

Fixes #8
This commit is contained in:
Dominik Jain committed 2025-04-03 15:26:02 +02:00
1 parent fbfb0cc7ff
commit e88c1e6fac
3 files changed
+101 -67

No files matched your search

+6 -67
View File
@@ -1,82 +1,21 @@
import argparse
import os
from logging import Logger
from pathlib import Path
from agno.agent.agent import Agent
from agno.memory.agent import AgentMemory
from agno.models.anthropic.claude import Claude
from agno.models.google.gemini import Gemini
from agno.playground.playground import Playground
from agno.playground.serve import serve_playground_app
from agno.storage.sqlite import SqliteStorage
from dotenv import load_dotenv
from sensai.util import logging
from sensai.util.helper import mark_used
from serena.agent import SerenaAgent
from serena.agno import SerenaAgnoToolkit
from serena.agno import SerenaAgnoAgentProvider
mark_used(Gemini, Claude)
os.chdir(Path(__file__).parent)
Logger.root.setLevel(logging.INFO)
logging.configure(level=logging.INFO)
load_dotenv()
parser = argparse.ArgumentParser(description="Serena coding assistant")
parser.add_argument("--project-file", required=True, help="Path to the project file, either absolute or relative to the root directory")
args = parser.parse_args()
project_file = Path(args.project_file).resolve()
# If project file path is relative, make it absolute by joining with project root
if not project_file.is_absolute():
# Get the project root directory (parent of scripts directory)
project_root = Path(__file__).parent.parent
project_file = project_root / args.project_file
# Ensure the path is normalized and absolute
project_file = project_file.resolve()
serena_agent = SerenaAgent(str(project_file), start_language_server=True)
# Even though we don't want to keep history between sessions,
# for the agno ui to work as a conversation, we a persistent storage on disk
# This storage should be deleted between sessions.
# Note that this might collide with custom options for the agent, like adding vector-search based tools.
# See here for an explanation: https://www.reddit.com/r/agno/comments/1jk6qea/regarding_the_built_in_memory/
sql_db_path = Path("tmp") / "agent_storage.db"
sql_db_path.parent.mkdir(exist_ok=True)
# delete the db file if it exists
if sql_db_path.exists():
sql_db_path.unlink()
# model = Claude(id="claude-3-7-sonnet-20250219")
# Or use any other model supported by agno, e.g. the newest Gemini:
model = Gemini(id="gemini-2.5-pro-exp-03-25")
agno_agent = Agent(
name="Serena",
model=model,
# See explanation above on why storage is needed
storage=SqliteStorage(table_name="serena_agent_sessions", db_file=str(sql_db_path)),
description="A fully-featured coding assistant",
tools=[SerenaAgnoToolkit(serena_agent)], # type: ignore
# The tool calls will be shown in the UI anyway since whether to show them is configurable per tool
# To see detailed logs, you should use the serena logger (configure it in the project file path)
show_tool_calls=False,
markdown=True,
system_message=serena_agent.prompt_factory.create_system_prompt(),
telemetry=False,
memory=AgentMemory(),
add_history_to_messages=True,
num_history_responses=100, # you might want to adjust this (expense vs. history awareness)
)
app = Playground(agents=[agno_agent]).get_app()
# Define the model to use (see Agno documentation for supported models; these are just examples)
model = Claude(id="claude-3-7-sonnet-20250219")
# model = Gemini(id="gemini-2.5-pro-exp-03-25")
app = Playground(agents=[SerenaAgnoAgentProvider.get_agent(model)]).get_app()
if __name__ == "__main__":
serve_playground_app("agno_agent:app", reload=False)
+6
View File
@@ -1 +1,7 @@
__version__ = "0.1.0-dev1"
def serena_root_path() -> str:
from pathlib import Path
return str(Path(__file__).parent.parent.absolute())
+89
View File
@@ -1,9 +1,20 @@
import argparse
import logging
import os
import threading
from pathlib import Path
from typing import Any
from agno.agent import Agent
from agno.memory import AgentMemory
from agno.models.base import Model
from agno.storage.sqlite import SqliteStorage
from agno.tools.function import Function
from agno.tools.toolkit import Toolkit
from dotenv import load_dotenv
from sensai.util.logging import LogTime
from serena import serena_root_path
from serena.agent import SerenaAgent, Tool
log = logging.getLogger(__name__)
@@ -89,3 +100,81 @@ class SerenaAgnoToolkit(Toolkit):
function.entrypoint = entrypoint
function.skip_entrypoint_processing = True
return function
class SerenaAgnoAgentProvider:
_agent: Agent | None = None
_lock = threading.Lock()
@classmethod
def get_agent(cls, model: Model) -> Agent:
"""
Returns the singleton instance of the Serena agent or creates it with the given parameters if it doesn't exist.
NOTE: This is very ugly with poor separation of concerns, but the way in which the Agno UI works (reloading the
module that defines the `app` variable) essentially forces us to do something like this.
:param model: the large language model to use for the agent
:return: the agent instance
"""
with cls._lock:
if cls._agent is not None:
return cls._agent
# change to Serena root
os.chdir(serena_root_path())
load_dotenv()
parser = argparse.ArgumentParser(description="Serena coding assistant")
parser.add_argument(
"--project-file", required=True, help="Path to the project file, either absolute or relative to the root directory"
)
args = parser.parse_args()
project_file = Path(args.project_file).resolve()
# If project file path is relative, make it absolute by joining with project root
if not project_file.is_absolute():
# Get the project root directory (parent of scripts directory)
project_root = Path(serena_root_path())
project_file = project_root / args.project_file
# Ensure the path is normalized and absolute
project_file = project_file.resolve()
with LogTime("Loading Serena agent"):
serena_agent = SerenaAgent(str(project_file), start_language_server=True)
# Even though we don't want to keep history between sessions,
# for agno-ui to work as a conversation, we use a persistent storage on disk.
# This storage should be deleted between sessions.
# Note that this might collide with custom options for the agent, like adding vector-search based tools.
# See here for an explanation: https://www.reddit.com/r/agno/comments/1jk6qea/regarding_the_built_in_memory/
sql_db_path = (Path("temp") / "agno_agent_storage.db").absolute()
sql_db_path.parent.mkdir(exist_ok=True)
# delete the db file if it exists
log.info(f"Deleting DB from PID {os.getpid()}")
if sql_db_path.exists():
sql_db_path.unlink()
agno_agent = Agent(
name="Serena",
model=model,
# See explanation above on why storage is needed
storage=SqliteStorage(table_name="serena_agent_sessions", db_file=str(sql_db_path)),
description="A fully-featured coding assistant",
tools=[SerenaAgnoToolkit(serena_agent)],
# The tool calls will be shown in the UI anyway since whether to show them is configurable per tool
# To see detailed logs, you should use the serena logger (configure it in the project file path)
show_tool_calls=False,
markdown=True,
system_message=serena_agent.prompt_factory.create_system_prompt(),
telemetry=False,
memory=AgentMemory(),
add_history_to_messages=True,
num_history_responses=100, # you might want to adjust this (expense vs. history awareness)
)
cls._agent = agno_agent
log.info(f"Agent instantiated: {agno_agent}")
return agno_agent