Skip to content

Commit

Permalink
Merge branch 'main' into run-response-default-dict-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
dirkbrnd authored Feb 7, 2025
2 parents 74a2e3b + cd5e38a commit 53ce529
Show file tree
Hide file tree
Showing 25 changed files with 526 additions and 157 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Agno is designed with three core principles:

Here's why you should build Agents with Agno:

- **Lightning Fast**: Agent creation is 6000x faster than LangGraph (see [performance](#performance)).
- **Lightning Fast**: Agent creation is ~10,000x faster than LangGraph (see [performance](#performance)).
- **Model Agnostic**: Use any model, any provider, no lock-in.
- **Multi Modal**: Native support for text, image, audio and video.
- **Multi Agent**: Delegate tasks across a team of specialized agents.
Expand Down Expand Up @@ -222,12 +222,12 @@ python agent_team.py

Agno is designed for high performance agentic systems:

- Agent instantiation: <5μs on average (5000x faster than LangGraph).
- Memory footprint: <0.01Mib on average (50x less memory than LangGraph).
- Agent instantiation: <5μs on average (~10,000x faster than LangGraph).
- Memory footprint: <0.01Mib on average (~50x less memory than LangGraph).

> Tested on an Apple M4 Mackbook Pro.
While an Agent's performance is bottlenecked by inference, we must do everything possible to minimize execution time, reduce memory usage, and parallelize tool calls. These numbers are may seem minimal, but they add up even at medium scale.
While an Agent's performance is bottlenecked by inference, we must do everything possible to minimize execution time, reduce memory usage, and parallelize tool calls. These numbers are may seem trivial, but they add up even at medium scale.

### Instantiation time

Expand Down
5 changes: 3 additions & 2 deletions cookbook/models/openai/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
tools=[DuckDuckGoTools()],
add_history_to_messages=True,
)
agent.print_response("How many people live in Canada?")
agent.print_response("What is their national anthem called?")
agent.cli_app()
# agent.print_response("How many people live in Canada?")
# agent.print_response("What is their national anthem called?")
1 change: 1 addition & 0 deletions cookbook/models/perplexity/basic_stream.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Iterator # noqa
from agno.agent import Agent, RunResponse # noqa
from agno.models.perplexity import Perplexity

agent = Agent(model=Perplexity(id="sonar"), markdown=True)

# Get the response in a variable
Expand Down
22 changes: 16 additions & 6 deletions libs/agno/agno/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2124,19 +2124,29 @@ def get_run_messages(

# 3. Add history to run_messages
if self.add_history_to_messages:
from copy import deepcopy

history: List[Message] = self.memory.get_messages_from_last_n_runs(
last_n=self.num_history_responses, skip_role=self.get_system_message_role()
)
if len(history) > 0:
logger.debug(f"Adding {len(history)} messages from history")
# Create a deep copy of the history messages to avoid modifying the original messages
history_copy = [deepcopy(msg) for msg in history]

# Tag each message as coming from history
for _msg in history_copy:
_msg.from_history = True

logger.debug(f"Adding {len(history_copy)} messages from history")

if self.run_response.extra_data is None:
self.run_response.extra_data = RunResponseExtraData(history=history)
self.run_response.extra_data = RunResponseExtraData(history=history_copy)
else:
if self.run_response.extra_data.history is None:
self.run_response.extra_data.history = history
self.run_response.extra_data.history = history_copy
else:
self.run_response.extra_data.history.extend(history)
run_messages.messages += history
self.run_response.extra_data.history.extend(history_copy)
run_messages.messages += history_copy

# 4.Add user message to run_messages
user_message: Optional[Message] = None
Expand Down Expand Up @@ -2501,7 +2511,7 @@ def update_run_response_with_reasoning(
def aggregate_metrics_from_messages(self, messages: List[Message]) -> Dict[str, Any]:
aggregated_metrics: Dict[str, Any] = defaultdict(list)

# Use a defaultdict(list) to collect all values for each assisntant message
# Use a defaultdict(list) to collect all values for each assistant message
for m in messages:
if m.role == "assistant" and m.metrics is not None:
for k, v in m.metrics.items():
Expand Down
50 changes: 24 additions & 26 deletions libs/agno/agno/memory/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,39 +134,37 @@ def get_messages(self) -> List[Dict[str, Any]]:
def get_messages_from_last_n_runs(
self, last_n: Optional[int] = None, skip_role: Optional[str] = None
) -> List[Message]:
"""Returns the messages from the last_n runs
"""Returns the messages from the last_n runs, excluding previously tagged history messages.
Args:
last_n: The number of runs to return from the end of the conversation.
skip_role: Skip messages with this role.
Returns:
A list of Messages in the last_n runs.
A list of Messages from the specified runs, excluding history messages.
"""
if last_n is None:
logger.debug("Getting messages from all previous runs")
messages_from_all_history = []
for prev_run in self.runs:
if prev_run.response and prev_run.response.messages:
if skip_role:
prev_run_messages = [m for m in prev_run.response.messages if m.role != skip_role]
else:
prev_run_messages = prev_run.response.messages
messages_from_all_history.extend(prev_run_messages)
logger.debug(f"Messages from previous runs: {len(messages_from_all_history)}")
return messages_from_all_history

logger.debug(f"Getting messages from last {last_n} runs")
messages_from_last_n_history = []
for prev_run in self.runs[-last_n:]:
if prev_run.response and prev_run.response.messages:
if skip_role:
prev_run_messages = [m for m in prev_run.response.messages if m.role != skip_role]
else:
prev_run_messages = prev_run.response.messages
messages_from_last_n_history.extend(prev_run_messages)
logger.debug(f"Messages from last {last_n} runs: {len(messages_from_last_n_history)}")
return messages_from_last_n_history
if not self.runs:
return []

runs_to_process = self.runs if last_n is None else self.runs[-last_n:]
messages_from_history = []

for run in runs_to_process:
if not (run.response and run.response.messages):
continue

for message in run.response.messages:
# Skip messages with specified role
if skip_role and message.role == skip_role:
continue
# Skip messages that were tagged as history in previous runs
if hasattr(message, "from_history") and message.from_history:
continue

messages_from_history.append(message)

logger.debug(f"Getting messages from previous runs: {len(messages_from_history)}")
return messages_from_history

def get_message_pairs(
self, user_role: str = "user", assistant_role: Optional[List[str]] = None
Expand Down
2 changes: 2 additions & 0 deletions libs/agno/agno/models/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class Message(BaseModel):
stop_after_tool_call: bool = False
# When True, the message will be added to the agent's memory.
add_to_agent_memory: bool = True
# This flag is enabled when a message is fetched from the agent's memory.
from_history: bool = False
# Metrics for the message.
metrics: Dict[str, Any] = Field(default_factory=dict)
# The references added to the message for RAG
Expand Down
1 change: 0 additions & 1 deletion libs/agno/agno/models/perplexity/perplexity.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class Perplexity(OpenAILike):
max_tokens (int): The maximum number of tokens. Defaults to 1024.
"""


id: str = "sonar"
name: str = "Perplexity"
provider: str = "Perplexity: " + id
Expand Down
122 changes: 64 additions & 58 deletions libs/agno/agno/playground/async_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,24 +125,20 @@ async def create_agent_run(
session_id: Optional[str] = Form(None),
user_id: Optional[str] = Form(None),
files: Optional[List[UploadFile]] = File(None),
image: Optional[UploadFile] = File(None),
):
logger.debug(f"AgentRunRequest: {message} {session_id} {user_id} {agent_id}")
agent = get_agent_by_id(agent_id, agents)
if agent is None:
raise HTTPException(status_code=404, detail="Agent not found")

if files:
if agent.knowledge is None:
raise HTTPException(status_code=404, detail="KnowledgeBase not found")

if session_id is not None:
logger.debug(f"Continuing session: {session_id}")
else:
logger.debug("Creating new session")

# Create a new instance of this agent
new_agent_instance = agent.deep_copy(update={"session_id": session_id})
new_agent_instance.session_name = None
if user_id is not None:
new_agent_instance.user_id = user_id

Expand All @@ -151,72 +147,82 @@ async def create_agent_run(
else:
new_agent_instance.monitoring = False

base64_image: Optional[Image] = None
if image:
base64_image = await process_image(image)
base64_images: List[Image] = []

if files:
for file in files:
if file.content_type == "application/pdf":
from agno.document.reader.pdf_reader import PDFReader

contents = await file.read()
pdf_file = BytesIO(contents)
pdf_file.name = file.filename
file_content = PDFReader().read(pdf_file)
if agent.knowledge is not None:
agent.knowledge.load_documents(file_content)
elif file.content_type == "text/csv":
from agno.document.reader.csv_reader import CSVReader

contents = await file.read()
csv_file = BytesIO(contents)
csv_file.name = file.filename
file_content = CSVReader().read(csv_file)
if agent.knowledge is not None:
agent.knowledge.load_documents(file_content)
elif file.content_type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
from agno.document.reader.docx_reader import DocxReader

contents = await file.read()
docx_file = BytesIO(contents)
docx_file.name = file.filename
file_content = DocxReader().read(docx_file)
if agent.knowledge is not None:
agent.knowledge.load_documents(file_content)
elif file.content_type == "text/plain":
from agno.document.reader.text_reader import TextReader

contents = await file.read()
text_file = BytesIO(contents)
text_file.name = file.filename
file_content = TextReader().read(text_file)
if agent.knowledge is not None:
agent.knowledge.load_documents(file_content)

elif file.content_type == "application/json":
from agno.document.reader.json_reader import JSONReader

contents = await file.read()
json_file = BytesIO(contents)
json_file.name = file.filename
file_content = JSONReader().read(json_file)
if agent.knowledge is not None:
agent.knowledge.load_documents(file_content)
if file.content_type in ["image/png", "image/jpeg", "image/jpg", "image/webp"]:
try:
base64_image = await process_image(file)
base64_images.append(base64_image)
except Exception as e:
logger.error(f"Error processing image {file.filename}: {e}")
continue
else:
raise HTTPException(status_code=400, detail="Unsupported file type")
# Check for knowledge base before processing documents
if new_agent_instance.knowledge is None:
raise HTTPException(status_code=404, detail="KnowledgeBase not found")

if file.content_type == "application/pdf":
from agno.document.reader.pdf_reader import PDFReader

contents = await file.read()
pdf_file = BytesIO(contents)
pdf_file.name = file.filename
file_content = PDFReader().read(pdf_file)
if new_agent_instance.knowledge is not None:
new_agent_instance.knowledge.load_documents(file_content)
elif file.content_type == "text/csv":
from agno.document.reader.csv_reader import CSVReader

contents = await file.read()
csv_file = BytesIO(contents)
csv_file.name = file.filename
file_content = CSVReader().read(csv_file)
if new_agent_instance.knowledge is not None:
new_agent_instance.knowledge.load_documents(file_content)
elif file.content_type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
from agno.document.reader.docx_reader import DocxReader

contents = await file.read()
docx_file = BytesIO(contents)
docx_file.name = file.filename
file_content = DocxReader().read(docx_file)
if new_agent_instance.knowledge is not None:
new_agent_instance.knowledge.load_documents(file_content)
elif file.content_type == "text/plain":
from agno.document.reader.text_reader import TextReader

contents = await file.read()
text_file = BytesIO(contents)
text_file.name = file.filename
file_content = TextReader().read(text_file)
if new_agent_instance.knowledge is not None:
new_agent_instance.knowledge.load_documents(file_content)

elif file.content_type == "application/json":
from agno.document.reader.json_reader import JSONReader

contents = await file.read()
json_file = BytesIO(contents)
json_file.name = file.filename
file_content = JSONReader().read(json_file)
if new_agent_instance.knowledge is not None:
new_agent_instance.knowledge.load_documents(file_content)
else:
raise HTTPException(status_code=400, detail="Unsupported file type")

if stream:
return StreamingResponse(
chat_response_streamer(new_agent_instance, message, images=[base64_image] if base64_image else None),
chat_response_streamer(new_agent_instance, message, images=base64_images if base64_images else None),
media_type="text/event-stream",
)
else:
run_response = cast(
RunResponse,
await new_agent_instance.arun(
message,
images=[base64_image] if base64_image else None,
message=message,
images=base64_images if base64_images else None,
stream=False,
),
)
Expand Down
Loading

0 comments on commit 53ce529

Please sign in to comment.