mirror of
https://github.com/agno-agi/agno.git
synced 2026-10-11 22:09:58 +00:00
chore: release 2.3.3 (#5524)
# Changelog ## New Feature: - **Context Compression**: Introducing compression of tool call results in a running context. Context Compression allows you to manage your agent context while it is running, helping the agent stay within its context window and avoid rate limits or decreases in response quality. This is still a **beta** feature. See the [docs](https://docs.agno.com/basics/context-compression/overview) - **Memory Optimisation:** Added the ability to summarize a user’s memories. This adds `optimize_memories` and `aoptimize_memories` to the `MemoryManager` as a way to summarize memories outside of agent runs. Only one strategy is currently supported, but this will be extended in future. Thie is still a **beta** feature. See the [docs](https://docs.agno.com/basics/memory/working-with-memories/memory-optimization). - **Gemini File Search Support**: Added support for [Gemini File Search](https://blog.google/technology/developers/file-search-gemini-api/). - File Search Store Management: Create, list, get, and delete document stores - File Upload & Import: Direct upload with custom chunking configuration and metadata - Document Management: List, get, delete, and update documents with metadata filtering - Citation Extraction: Helper methods to extract and format source citations from responses - Async Support: Full async/await support for production workloads - Docs coming soon! In the mean time check out these cookbooks: - `cookbook/models/google/gemini/file_search_basic.py` - `cookbook/models/google/gemini/file_search_advanced.py` - `cookbook/models/google/gemini/file_search_rag_pipeline.py` ## Improvements: - **`output_schema` on run/arun**: override `output_schema` at runtime via the `run()` and `arun()` methods for both `Agent` and `Team`, as well as AgentOS API endpoints. - **API Key for AWS Bedrock**: Added `api_key` support for AWS Bedrock. See the [documentation](https://docs.agno.com/integrations/models/cloud/aws-claude/overview). - **AWS Bedrock Claude**: It is now more compatible with native Claude, which adds support for thinking models and caching. - **VertexAI Claude**: It is now more compatible with native Claude, which adds support for thinking models and caching. - **Gemini 3 Thinking Level**: Added support for `thinking_level` on `Gemini` ## Bug Fixes: - **Async Knowledge Retriever:** Fixed a bug where async knowledge retrieving would break due to missing async versions of `get_user_message` functions on Agent and Team - **Team Member Events**: Ensure that member events are streamed if `stream_member_events=True` but `stream_events=False` on `Team`. - **MigrationManager Improvements:** Fixed an issue preventing migrations from being applied for versions older than 2.3.0. Also adds support for a force migration flag - **StopAgentRun**: Fixed the `StopAgentRun` exception from not correctly ending the agent run. - **PDF Image Reader:** Fixed PDF Image Reader generating ID’s based on filename instead of using UUID. - **MCP `tool_name_prefix`:** Made the `tool_name_prefix` parameter `None` by default to prevent it from adding `_` by default as a prefix. - **AGUI:** - Duplicate value for `tool_call_id` fixed - Missing assistant messages for tool calls fixed --------- Co-authored-by: Dirk Brand <[email protected]> Co-authored-by: Mustafa Esoofally <[email protected]>
This commit is contained in:
co-authored by
Dirk Brand
Mustafa Esoofally
parent
bc1667d1e2
commit
bce27a9ef0
@@ -119,11 +119,11 @@ jobs:
|
||||
set -e
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
- name: Run dev setup
|
||||
- name: Run test setup
|
||||
working-directory: .
|
||||
run: |
|
||||
set -e
|
||||
./scripts/dev_setup.sh
|
||||
./scripts/test_setup.sh
|
||||
- name: Run tests for Agno
|
||||
working-directory: .
|
||||
env:
|
||||
|
||||
@@ -626,7 +626,7 @@ jobs:
|
||||
- name: Run dev setup
|
||||
working-directory: .
|
||||
run: |
|
||||
./scripts/dev_setup.sh
|
||||
./scripts/test_setup.sh
|
||||
- name: Run agent integration tests
|
||||
working-directory: .
|
||||
env:
|
||||
@@ -658,7 +658,7 @@ jobs:
|
||||
- name: Run dev setup
|
||||
working-directory: .
|
||||
run: |
|
||||
./scripts/dev_setup.sh
|
||||
./scripts/test_setup.sh
|
||||
- name: Run teams integration tests
|
||||
working-directory: .
|
||||
env:
|
||||
@@ -688,7 +688,7 @@ jobs:
|
||||
- name: Run dev setup
|
||||
working-directory: .
|
||||
run: |
|
||||
./scripts/dev_setup.sh
|
||||
./scripts/test_setup.sh
|
||||
- name: Run workflows integration tests
|
||||
working-directory: .
|
||||
env:
|
||||
@@ -719,7 +719,7 @@ jobs:
|
||||
- name: Run dev setup
|
||||
working-directory: .
|
||||
run: |
|
||||
./scripts/dev_setup.sh
|
||||
./scripts/test_setup.sh
|
||||
- name: Run embedder tests
|
||||
working-directory: .
|
||||
env:
|
||||
@@ -750,7 +750,7 @@ jobs:
|
||||
- name: Run dev setup
|
||||
working-directory: .
|
||||
run: |
|
||||
./scripts/dev_setup.sh
|
||||
./scripts/test_setup.sh
|
||||
- name: Run knowledge integration tests
|
||||
working-directory: .
|
||||
env:
|
||||
@@ -786,7 +786,7 @@ jobs:
|
||||
- name: Run dev setup
|
||||
working-directory: .
|
||||
run: |
|
||||
./scripts/dev_setup.sh
|
||||
./scripts/test_setup.sh
|
||||
- name: Run knowledge integration tests
|
||||
working-directory: .
|
||||
env:
|
||||
@@ -886,14 +886,14 @@ jobs:
|
||||
- name: Run dev setup
|
||||
working-directory: .
|
||||
run: |
|
||||
./scripts/dev_setup.sh
|
||||
./scripts/test_setup.sh
|
||||
- name: Run AgentOS integration tests
|
||||
working-directory: .
|
||||
env:
|
||||
AGNO_TELEMETRY: false
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
python -m pytest --ignore=./libs/agno/tests/integration/os/interfaces/a2a --ignore=./libs/agno/tests/integration/os/interfaces/agui ./libs/agno/tests/integration/os
|
||||
python -m pytest --ignore=./libs/agno/tests/integration/os/interfaces/test_a2a.py ./libs/agno/tests/integration/os
|
||||
|
||||
|
||||
test-clean-os:
|
||||
@@ -952,7 +952,7 @@ jobs:
|
||||
- name: Run dev setup
|
||||
working-directory: .
|
||||
run: |
|
||||
./scripts/dev_setup.sh
|
||||
./scripts/test_setup.sh
|
||||
- name: Run remaining integration tests
|
||||
working-directory: .
|
||||
env:
|
||||
@@ -996,8 +996,8 @@ jobs:
|
||||
- name: Verify agno_infra installation
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
pip show agno-infra || exit 1
|
||||
uv pip show agno-infra || exit 1
|
||||
- name: List installed packages
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
pip list
|
||||
uv pip list
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
Integrate Agno with Traceloop to send traces and gain insights into your agent's performance.
|
||||
|
||||
Prerequisites:
|
||||
- Traceloop account
|
||||
- Traceloop API key
|
||||
|
||||
Steps:
|
||||
1. `pip install agno openai traceloop-sdk`
|
||||
2. Set the Traceloop API key as an environment variable: `export TRACELOOP_API_KEY=<your-api-key>`
|
||||
"""
|
||||
|
||||
from agno.agent import Agent
|
||||
from agno.models.openai import OpenAIChat
|
||||
from traceloop.sdk import Traceloop
|
||||
from traceloop.sdk.decorators import workflow
|
||||
|
||||
Traceloop.init(app_name="agno_workflows")
|
||||
|
||||
agent = Agent(
|
||||
name="AnalysisAgent",
|
||||
model=OpenAIChat(id="gpt-4o-mini"),
|
||||
debug_mode=True,
|
||||
)
|
||||
|
||||
|
||||
@workflow(name="data_analysis_pipeline")
|
||||
def analyze_data(query: str) -> str:
|
||||
"""Custom workflow that wraps agent execution."""
|
||||
response = agent.run(query)
|
||||
return response.content
|
||||
|
||||
|
||||
# The workflow decorator creates a parent span
|
||||
result = analyze_data("Analyze the benefits of observability in AI systems")
|
||||
print(result)
|
||||
@@ -10,7 +10,7 @@ from agno.agent import Agent
|
||||
from agno.models.google import Gemini
|
||||
|
||||
agent = Agent(
|
||||
model=Gemini(id="gemini-2.0-flash-exp", search=True),
|
||||
model=Gemini(id="gemini-2.0-flash", search=True),
|
||||
markdown=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@ from agno.session.summary import SessionSummary
|
||||
from agno.tools import Toolkit
|
||||
from agno.tools.function import Function
|
||||
from agno.utils.agent import (
|
||||
aexecute_instructions,
|
||||
aexecute_system_message,
|
||||
aget_last_run_output_util,
|
||||
aget_run_output_util,
|
||||
aget_session_metrics_util,
|
||||
@@ -87,6 +89,8 @@ from agno.utils.agent import (
|
||||
collect_joint_files,
|
||||
collect_joint_images,
|
||||
collect_joint_videos,
|
||||
execute_instructions,
|
||||
execute_system_message,
|
||||
get_last_run_output_util,
|
||||
get_run_output_util,
|
||||
get_session_metrics_util,
|
||||
@@ -6907,7 +6911,9 @@ class Agent:
|
||||
if isinstance(self.system_message, str):
|
||||
sys_message_content = self.system_message
|
||||
elif callable(self.system_message):
|
||||
sys_message_content = self.system_message(agent=self)
|
||||
sys_message_content = execute_system_message(
|
||||
agent=self, system_message=self.system_message, session_state=session_state, run_context=run_context
|
||||
)
|
||||
if not isinstance(sys_message_content, str):
|
||||
raise Exception("system_message must return a string")
|
||||
|
||||
@@ -6936,25 +6942,9 @@ class Agent:
|
||||
if self.instructions is not None:
|
||||
_instructions = self.instructions
|
||||
if callable(self.instructions):
|
||||
import inspect
|
||||
|
||||
signature = inspect.signature(self.instructions)
|
||||
instruction_args: Dict[str, Any] = {}
|
||||
|
||||
# Check for agent parameter
|
||||
if "agent" in signature.parameters:
|
||||
instruction_args["agent"] = self
|
||||
|
||||
# Check for session_state parameter
|
||||
if "session_state" in signature.parameters:
|
||||
instruction_args["session_state"] = session_state or {}
|
||||
|
||||
# Check for run_context parameter
|
||||
if "run_context" in signature.parameters:
|
||||
instruction_args["run_context"] = run_context or None
|
||||
|
||||
# Run the instructions function
|
||||
_instructions = self.instructions(**instruction_args)
|
||||
_instructions = execute_instructions(
|
||||
agent=self, instructions=self.instructions, session_state=session_state, run_context=run_context
|
||||
)
|
||||
|
||||
if isinstance(_instructions, str):
|
||||
instructions.append(_instructions)
|
||||
@@ -7264,7 +7254,9 @@ class Agent:
|
||||
if isinstance(self.system_message, str):
|
||||
sys_message_content = self.system_message
|
||||
elif callable(self.system_message):
|
||||
sys_message_content = self.system_message(agent=self)
|
||||
sys_message_content = await aexecute_system_message(
|
||||
agent=self, system_message=self.system_message, session_state=session_state, run_context=run_context
|
||||
)
|
||||
if not isinstance(sys_message_content, str):
|
||||
raise Exception("system_message must return a string")
|
||||
|
||||
@@ -7294,20 +7286,9 @@ class Agent:
|
||||
if self.instructions is not None:
|
||||
_instructions = self.instructions
|
||||
if callable(self.instructions):
|
||||
import inspect
|
||||
|
||||
signature = inspect.signature(self.instructions)
|
||||
instruction_args: Dict[str, Any] = {}
|
||||
|
||||
# Check for agent parameter
|
||||
if "agent" in signature.parameters:
|
||||
instruction_args["agent"] = self
|
||||
|
||||
# Check for session_state parameter
|
||||
if "session_state" in signature.parameters:
|
||||
instruction_args["session_state"] = session_state or {}
|
||||
|
||||
_instructions = self.instructions(**instruction_args)
|
||||
_instructions = await aexecute_instructions(
|
||||
agent=self, instructions=self.instructions, session_state=session_state, run_context=run_context
|
||||
)
|
||||
|
||||
if isinstance(_instructions, str):
|
||||
instructions.append(_instructions)
|
||||
|
||||
@@ -237,7 +237,7 @@ class MySQLDb(BaseDb):
|
||||
]
|
||||
|
||||
for table_name, table_type in tables_to_create:
|
||||
self._create_table(table_name=table_name, table_type=table_type)
|
||||
self._get_or_create_table(table_name=table_name, table_type=table_type, create_table_if_not_found=True)
|
||||
|
||||
def _get_table(self, table_type: str, create_table_if_not_found: Optional[bool] = False) -> Optional[Table]:
|
||||
if table_type == "sessions":
|
||||
|
||||
@@ -138,7 +138,7 @@ class AsyncPostgresDb(AsyncBaseDb):
|
||||
]
|
||||
|
||||
for table_name, table_type in tables_to_create:
|
||||
await self._create_table(table_name=table_name, table_type=table_type)
|
||||
await self._get_or_create_table(table_name=table_name, table_type=table_type)
|
||||
|
||||
async def _create_table(self, table_name: str, table_type: str) -> Table:
|
||||
"""
|
||||
|
||||
@@ -134,7 +134,7 @@ class PostgresDb(BaseDb):
|
||||
]
|
||||
|
||||
for table_name, table_type in tables_to_create:
|
||||
self._create_table(table_name=table_name, table_type=table_type)
|
||||
self._get_or_create_table(table_name=table_name, table_type=table_type, create_table_if_not_found=True)
|
||||
|
||||
def _create_table(self, table_name: str, table_type: str) -> Table:
|
||||
"""
|
||||
|
||||
@@ -181,7 +181,7 @@ class SingleStoreDb(BaseDb):
|
||||
]
|
||||
|
||||
for table_name, table_type in tables_to_create:
|
||||
self._create_table(table_name=table_name, table_type=table_type)
|
||||
self._get_or_create_table(table_name=table_name, table_type=table_type, create_table_if_not_found=True)
|
||||
|
||||
def _create_table(self, table_name: str, table_type: str) -> Table:
|
||||
"""
|
||||
@@ -366,42 +366,6 @@ class SingleStoreDb(BaseDb):
|
||||
|
||||
raise ValueError(f"Unknown table type: {table_type}")
|
||||
|
||||
def get_latest_schema_version(self, table_name: str) -> str:
|
||||
"""Get the latest version of the database schema."""
|
||||
table = self._get_table(table_type="versions", create_table_if_not_found=True)
|
||||
if table is None:
|
||||
return "2.0.0"
|
||||
with self.Session() as sess:
|
||||
stmt = select(table)
|
||||
# Latest version for the given table
|
||||
stmt = stmt.where(table.c.table_name == table_name)
|
||||
stmt = stmt.order_by(table.c.version.desc()).limit(1)
|
||||
result = sess.execute(stmt).fetchone()
|
||||
if result is None:
|
||||
return "2.0.0"
|
||||
version_dict = dict(result._mapping)
|
||||
return version_dict.get("version") or "2.0.0"
|
||||
|
||||
def upsert_schema_version(self, table_name: str, version: str) -> None:
|
||||
"""Upsert the schema version into the database."""
|
||||
table = self._get_table(table_type="versions", create_table_if_not_found=True)
|
||||
if table is None:
|
||||
return
|
||||
current_datetime = datetime.now().isoformat()
|
||||
with self.Session() as sess, sess.begin():
|
||||
stmt = mysql.insert(table).values(
|
||||
table_name=table_name,
|
||||
version=version,
|
||||
created_at=current_datetime, # Store as ISO format string
|
||||
updated_at=current_datetime,
|
||||
)
|
||||
# Update version if table_name already exists
|
||||
stmt = stmt.on_duplicate_key_update(
|
||||
version=version,
|
||||
updated_at=current_datetime,
|
||||
)
|
||||
sess.execute(stmt)
|
||||
|
||||
def _get_or_create_table(
|
||||
self,
|
||||
table_name: str,
|
||||
@@ -450,6 +414,42 @@ class SingleStoreDb(BaseDb):
|
||||
log_error(f"Error loading existing table {table_ref}: {e}")
|
||||
raise
|
||||
|
||||
def get_latest_schema_version(self, table_name: str) -> str:
|
||||
"""Get the latest version of the database schema."""
|
||||
table = self._get_table(table_type="versions", create_table_if_not_found=True)
|
||||
if table is None:
|
||||
return "2.0.0"
|
||||
with self.Session() as sess:
|
||||
stmt = select(table)
|
||||
# Latest version for the given table
|
||||
stmt = stmt.where(table.c.table_name == table_name)
|
||||
stmt = stmt.order_by(table.c.version.desc()).limit(1)
|
||||
result = sess.execute(stmt).fetchone()
|
||||
if result is None:
|
||||
return "2.0.0"
|
||||
version_dict = dict(result._mapping)
|
||||
return version_dict.get("version") or "2.0.0"
|
||||
|
||||
def upsert_schema_version(self, table_name: str, version: str) -> None:
|
||||
"""Upsert the schema version into the database."""
|
||||
table = self._get_table(table_type="versions", create_table_if_not_found=True)
|
||||
if table is None:
|
||||
return
|
||||
current_datetime = datetime.now().isoformat()
|
||||
with self.Session() as sess, sess.begin():
|
||||
stmt = mysql.insert(table).values(
|
||||
table_name=table_name,
|
||||
version=version,
|
||||
created_at=current_datetime, # Store as ISO format string
|
||||
updated_at=current_datetime,
|
||||
)
|
||||
# Update version if table_name already exists
|
||||
stmt = stmt.on_duplicate_key_update(
|
||||
version=version,
|
||||
updated_at=current_datetime,
|
||||
)
|
||||
sess.execute(stmt)
|
||||
|
||||
# -- Session methods --
|
||||
def delete_session(self, session_id: str) -> bool:
|
||||
"""
|
||||
|
||||
@@ -140,7 +140,7 @@ class AsyncSqliteDb(AsyncBaseDb):
|
||||
]
|
||||
|
||||
for table_name, table_type in tables_to_create:
|
||||
await self._create_table(table_name=table_name, table_type=table_type)
|
||||
await self._get_or_create_table(table_name=table_name, table_type=table_type)
|
||||
|
||||
async def _create_table(self, table_name: str, table_type: str) -> Table:
|
||||
"""
|
||||
|
||||
@@ -141,7 +141,7 @@ class SqliteDb(BaseDb):
|
||||
]
|
||||
|
||||
for table_name, table_type in tables_to_create:
|
||||
self._create_table(table_name=table_name, table_type=table_type)
|
||||
self._get_or_create_table(table_name=table_name, table_type=table_type, create_table_if_not_found=True)
|
||||
|
||||
def _create_table(self, table_name: str, table_type: str) -> Table:
|
||||
"""
|
||||
|
||||
@@ -140,7 +140,6 @@ class TavilyReader(Reader):
|
||||
documents.extend(self.chunk_document(Document(name=name or url, id=url, content=content)))
|
||||
else:
|
||||
documents.append(Document(name=name or url, id=url, content=content))
|
||||
|
||||
return documents
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -61,7 +61,7 @@ class CerebrasOpenAI(OpenAILike):
|
||||
log_debug(f"Calling {self.provider} with request parameters: {request_params}", log_level=2)
|
||||
return request_params
|
||||
|
||||
def _format_message(self, message: Message) -> Dict[str, Any]:
|
||||
def _format_message(self, message: Message, compress_tool_results: bool = False) -> Dict[str, Any]:
|
||||
"""
|
||||
Format a message into the format expected by the Cerebras API.
|
||||
|
||||
@@ -71,6 +71,7 @@ class CerebrasOpenAI(OpenAILike):
|
||||
Returns:
|
||||
Dict[str, Any]: The formatted message.
|
||||
"""
|
||||
|
||||
# Basic message content
|
||||
message_dict: Dict[str, Any] = {
|
||||
"role": message.role,
|
||||
@@ -100,10 +101,11 @@ class CerebrasOpenAI(OpenAILike):
|
||||
|
||||
# Handle tool responses
|
||||
if message.role == "tool" and message.tool_call_id:
|
||||
content = message.get_content(use_compressed_content=compress_tool_results)
|
||||
message_dict = {
|
||||
"role": "tool",
|
||||
"tool_call_id": message.tool_call_id,
|
||||
"content": message.content if message.content is not None else "",
|
||||
"content": content if message.content is not None else "",
|
||||
}
|
||||
|
||||
# Ensure no None values in the message
|
||||
|
||||
@@ -24,6 +24,7 @@ from agno.run.agent import RunOutput
|
||||
from agno.run.team import TeamRunOutput
|
||||
from agno.session import AgentSession, TeamSession, WorkflowSession
|
||||
from agno.team.team import Team
|
||||
from agno.utils.agent import aexecute_instructions, aexecute_system_message
|
||||
from agno.workflow.agent import WorkflowAgent
|
||||
from agno.workflow.workflow import Workflow
|
||||
|
||||
@@ -337,12 +338,20 @@ class AgentResponse(BaseModel):
|
||||
"read_tool_call_history": agent.read_tool_call_history,
|
||||
}
|
||||
|
||||
instructions = agent.instructions if agent.instructions else None
|
||||
if instructions and callable(instructions):
|
||||
instructions = await aexecute_instructions(instructions=instructions, agent=agent)
|
||||
|
||||
system_message = agent.system_message if agent.system_message else None
|
||||
if system_message and callable(system_message):
|
||||
system_message = await aexecute_system_message(system_message=system_message, agent=agent)
|
||||
|
||||
system_message_info = {
|
||||
"system_message": str(agent.system_message) if agent.system_message else None,
|
||||
"system_message": str(system_message) if system_message else None,
|
||||
"system_message_role": agent.system_message_role,
|
||||
"build_context": agent.build_context,
|
||||
"description": agent.description,
|
||||
"instructions": agent.instructions if agent.instructions else None,
|
||||
"instructions": instructions,
|
||||
"expected_output": agent.expected_output,
|
||||
"additional_context": agent.additional_context,
|
||||
"markdown": agent.markdown,
|
||||
@@ -560,12 +569,18 @@ class TeamResponse(BaseModel):
|
||||
"get_member_information_tool": team.get_member_information_tool,
|
||||
}
|
||||
|
||||
team_instructions = (
|
||||
team.instructions() if team.instructions and callable(team.instructions) else team.instructions
|
||||
)
|
||||
team_instructions = team.instructions if team.instructions else None
|
||||
if team_instructions and callable(team_instructions):
|
||||
team_instructions = await aexecute_instructions(instructions=team_instructions, agent=team, team=team)
|
||||
|
||||
team_system_message = team.system_message if team.system_message else None
|
||||
if team_system_message and callable(team_system_message):
|
||||
team_system_message = await aexecute_system_message(
|
||||
system_message=team_system_message, agent=team, team=team
|
||||
)
|
||||
|
||||
system_message_info = {
|
||||
"system_message": str(team.system_message) if team.system_message else None,
|
||||
"system_message": team_system_message,
|
||||
"system_message_role": team.system_message_role,
|
||||
"description": team.description,
|
||||
"instructions": team_instructions,
|
||||
|
||||
+33
-22
@@ -67,6 +67,8 @@ from agno.session.summary import SessionSummary
|
||||
from agno.tools import Toolkit
|
||||
from agno.tools.function import Function
|
||||
from agno.utils.agent import (
|
||||
aexecute_instructions,
|
||||
aexecute_system_message,
|
||||
aget_last_run_output_util,
|
||||
aget_run_output_util,
|
||||
aget_session_metrics_util,
|
||||
@@ -80,6 +82,8 @@ from agno.utils.agent import (
|
||||
collect_joint_files,
|
||||
collect_joint_images,
|
||||
collect_joint_videos,
|
||||
execute_instructions,
|
||||
execute_system_message,
|
||||
get_last_run_output_util,
|
||||
get_run_output_util,
|
||||
get_session_metrics_util,
|
||||
@@ -5465,7 +5469,13 @@ class Team:
|
||||
if isinstance(self.system_message, str):
|
||||
sys_message_content = self.system_message
|
||||
elif callable(self.system_message):
|
||||
sys_message_content = self.system_message(agent=self)
|
||||
sys_message_content = execute_system_message(
|
||||
system_message=self.system_message,
|
||||
agent=self,
|
||||
team=self,
|
||||
session_state=session_state,
|
||||
run_context=run_context,
|
||||
)
|
||||
if not isinstance(sys_message_content, str):
|
||||
raise Exception("system_message must return a string")
|
||||
|
||||
@@ -5489,15 +5499,13 @@ class Team:
|
||||
if self.instructions is not None:
|
||||
_instructions = self.instructions
|
||||
if callable(self.instructions):
|
||||
import inspect
|
||||
|
||||
signature = inspect.signature(self.instructions)
|
||||
if "team" in signature.parameters:
|
||||
_instructions = self.instructions(team=self)
|
||||
elif "agent" in signature.parameters:
|
||||
_instructions = self.instructions(agent=self)
|
||||
else:
|
||||
_instructions = self.instructions()
|
||||
_instructions = execute_instructions(
|
||||
instructions=self.instructions,
|
||||
agent=self,
|
||||
team=self,
|
||||
session_state=session_state,
|
||||
run_context=run_context,
|
||||
)
|
||||
|
||||
if isinstance(_instructions, str):
|
||||
instructions.append(_instructions)
|
||||
@@ -5773,7 +5781,13 @@ class Team:
|
||||
if isinstance(self.system_message, str):
|
||||
sys_message_content = self.system_message
|
||||
elif callable(self.system_message):
|
||||
sys_message_content = self.system_message(agent=self)
|
||||
sys_message_content = await aexecute_system_message(
|
||||
system_message=self.system_message,
|
||||
agent=self,
|
||||
team=self,
|
||||
session_state=session_state,
|
||||
run_context=run_context,
|
||||
)
|
||||
if not isinstance(sys_message_content, str):
|
||||
raise Exception("system_message must return a string")
|
||||
|
||||
@@ -5797,15 +5811,13 @@ class Team:
|
||||
if self.instructions is not None:
|
||||
_instructions = self.instructions
|
||||
if callable(self.instructions):
|
||||
import inspect
|
||||
|
||||
signature = inspect.signature(self.instructions)
|
||||
if "team" in signature.parameters:
|
||||
_instructions = self.instructions(team=self)
|
||||
elif "agent" in signature.parameters:
|
||||
_instructions = self.instructions(agent=self)
|
||||
else:
|
||||
_instructions = self.instructions()
|
||||
_instructions = await aexecute_instructions(
|
||||
instructions=self.instructions,
|
||||
agent=self,
|
||||
team=self,
|
||||
session_state=session_state,
|
||||
run_context=run_context,
|
||||
)
|
||||
|
||||
if isinstance(_instructions, str):
|
||||
instructions.append(_instructions)
|
||||
@@ -8544,8 +8556,7 @@ class Team:
|
||||
|
||||
session = self.get_session(session_id=session_id) # type: ignore
|
||||
if session is None:
|
||||
log_warning(f"Session {session_id} not found")
|
||||
return []
|
||||
raise Exception("Session not found")
|
||||
|
||||
return session.get_messages(
|
||||
team_id=self.id,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from asyncio import Future, Task
|
||||
from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, Iterator, List, Optional, Sequence, Union
|
||||
from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Optional, Sequence, Union
|
||||
|
||||
from agno.media import Audio, File, Image, Video
|
||||
from agno.models.message import Message
|
||||
from agno.models.metrics import Metrics
|
||||
from agno.models.response import ModelResponse
|
||||
from agno.run import RunContext
|
||||
from agno.run.agent import RunEvent, RunInput, RunOutput, RunOutputEvent
|
||||
from agno.run.team import RunOutputEvent as TeamRunOutputEvent
|
||||
from agno.run.team import TeamRunOutput
|
||||
@@ -818,3 +819,120 @@ async def aget_chat_history_util(entity: Union["Agent", "Team"], session_id: str
|
||||
raise Exception("Session not found")
|
||||
|
||||
return session.get_chat_history() # type: ignore
|
||||
|
||||
|
||||
def execute_instructions(
|
||||
instructions: Callable,
|
||||
agent: Optional[Union["Agent", "Team"]] = None,
|
||||
team: Optional["Team"] = None,
|
||||
session_state: Optional[Dict[str, Any]] = None,
|
||||
run_context: Optional[RunContext] = None,
|
||||
) -> Union[str, List[str]]:
|
||||
"""Execute the instructions function."""
|
||||
import inspect
|
||||
|
||||
signature = inspect.signature(instructions)
|
||||
instruction_args: Dict[str, Any] = {}
|
||||
|
||||
# Check for agent parameter
|
||||
if "agent" in signature.parameters:
|
||||
instruction_args["agent"] = agent
|
||||
|
||||
if "team" in signature.parameters:
|
||||
instruction_args["team"] = team
|
||||
|
||||
# Check for session_state parameter
|
||||
if "session_state" in signature.parameters:
|
||||
instruction_args["session_state"] = session_state or {}
|
||||
|
||||
# Check for run_context parameter
|
||||
if "run_context" in signature.parameters:
|
||||
instruction_args["run_context"] = run_context or None
|
||||
|
||||
# Run the instructions function, await if it's awaitable, otherwise run directly (in thread)
|
||||
if inspect.iscoroutinefunction(instructions):
|
||||
raise Exception("Instructions function is async, use `agent.arun()` instead")
|
||||
|
||||
# Run the instructions function
|
||||
return instructions(**instruction_args)
|
||||
|
||||
|
||||
def execute_system_message(
|
||||
system_message: Callable,
|
||||
agent: Optional[Union["Agent", "Team"]] = None,
|
||||
team: Optional["Team"] = None,
|
||||
session_state: Optional[Dict[str, Any]] = None,
|
||||
run_context: Optional[RunContext] = None,
|
||||
) -> str:
|
||||
"""Execute the system message function."""
|
||||
import inspect
|
||||
|
||||
signature = inspect.signature(system_message)
|
||||
system_message_args: Dict[str, Any] = {}
|
||||
|
||||
# Check for agent parameter
|
||||
if "agent" in signature.parameters:
|
||||
system_message_args["agent"] = agent
|
||||
if "team" in signature.parameters:
|
||||
system_message_args["team"] = team
|
||||
if inspect.iscoroutinefunction(system_message):
|
||||
raise ValueError("System message function is async, use `agent.arun()` instead")
|
||||
|
||||
return system_message(**system_message_args)
|
||||
|
||||
|
||||
async def aexecute_instructions(
|
||||
instructions: Callable,
|
||||
agent: Optional[Union["Agent", "Team"]] = None,
|
||||
team: Optional["Team"] = None,
|
||||
session_state: Optional[Dict[str, Any]] = None,
|
||||
run_context: Optional[RunContext] = None,
|
||||
) -> Union[str, List[str]]:
|
||||
"""Execute the instructions function."""
|
||||
import inspect
|
||||
|
||||
signature = inspect.signature(instructions)
|
||||
instruction_args: Dict[str, Any] = {}
|
||||
|
||||
# Check for agent parameter
|
||||
if "agent" in signature.parameters:
|
||||
instruction_args["agent"] = agent
|
||||
if "team" in signature.parameters:
|
||||
instruction_args["team"] = team
|
||||
|
||||
# Check for session_state parameter
|
||||
if "session_state" in signature.parameters:
|
||||
instruction_args["session_state"] = session_state or {}
|
||||
|
||||
# Check for run_context parameter
|
||||
if "run_context" in signature.parameters:
|
||||
instruction_args["run_context"] = run_context or None
|
||||
|
||||
if inspect.iscoroutinefunction(instructions):
|
||||
return await instructions(**instruction_args)
|
||||
else:
|
||||
return instructions(**instruction_args)
|
||||
|
||||
|
||||
async def aexecute_system_message(
|
||||
system_message: Callable,
|
||||
agent: Optional[Union["Agent", "Team"]] = None,
|
||||
team: Optional["Team"] = None,
|
||||
session_state: Optional[Dict[str, Any]] = None,
|
||||
run_context: Optional[RunContext] = None,
|
||||
) -> str:
|
||||
import inspect
|
||||
|
||||
signature = inspect.signature(system_message)
|
||||
system_message_args: Dict[str, Any] = {}
|
||||
|
||||
# Check for agent parameter
|
||||
if "agent" in signature.parameters:
|
||||
system_message_args["agent"] = agent
|
||||
if "team" in signature.parameters:
|
||||
system_message_args["team"] = team
|
||||
|
||||
if inspect.iscoroutinefunction(system_message):
|
||||
return await system_message(**system_message_args)
|
||||
else:
|
||||
return system_message(**system_message_args)
|
||||
|
||||
+22
-19
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "agno"
|
||||
version = "2.3.2"
|
||||
version = "2.3.3"
|
||||
description = "Agno: a lightweight library for building Multi-Agent Systems"
|
||||
requires-python = ">=3.7,<4"
|
||||
readme = "README.md"
|
||||
@@ -47,19 +47,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["mypy==1.18.2", "ruff==0.14.3", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "timeout-decorator", "types-pyyaml", "types-aiofiles", "fastapi", "uvicorn"]
|
||||
dev = ["mypy==1.18.2", "ruff==0.14.3", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "timeout-decorator", "types-pyyaml", "types-aiofiles", "fastapi", "uvicorn", "PyJWT", "mcp", "openai"]
|
||||
|
||||
os = ["fastapi", "uvicorn", "PyJWT"]
|
||||
|
||||
# Models integration test dependencies
|
||||
integration-tests = [
|
||||
"exa_py",
|
||||
"ddgs",
|
||||
"yfinance",
|
||||
"sqlalchemy",
|
||||
"Pillow",
|
||||
"fastmcp"
|
||||
]
|
||||
|
||||
# Dependencies for Telemetry
|
||||
opentelemetry = ["opentelemetry-sdk", "opentelemetry-exporter-otlp"]
|
||||
@@ -100,15 +91,15 @@ elevenlabs = ["elevenlabs"]
|
||||
evm = ["web3"]
|
||||
exa = ["exa_py"]
|
||||
fal = ["fal_client"]
|
||||
firecrawl = ["firecrawl-py"]
|
||||
firecrawl = ["firecrawl-py==3.4.0"]
|
||||
tavily = ["tavily-python"]
|
||||
crawl4ai= ["crawl4ai"]
|
||||
crawl4ai = ["crawl4ai>=0.6.3"]
|
||||
github = ["PyGithub"]
|
||||
gmail = ["google-api-python-client", "google-auth-httplib2", "google-auth-oauthlib"]
|
||||
google_bigquery = ["google-cloud-bigquery"]
|
||||
googlemaps = ["googlemaps", "google-maps-places"]
|
||||
matplotlib = ["matplotlib"]
|
||||
mcp = ["mcp"]
|
||||
mcp = ["mcp>=1.9.2"]
|
||||
mem0 = ["mem0ai"]
|
||||
memori = ["memorisdk"]
|
||||
newspaper = ["newspaper4k", "lxml_html_clean"]
|
||||
@@ -124,7 +115,7 @@ webex = ["webexpythonsdk"]
|
||||
yfinance = ["yfinance"]
|
||||
youtube = ["youtube_transcript_api"]
|
||||
zep = ["zep-cloud"]
|
||||
daytona=["daytona"]
|
||||
daytona = ["daytona"]
|
||||
oxylabs = ["oxylabs"]
|
||||
trafilatura = ["trafilatura"]
|
||||
neo4j = ["neo4j"]
|
||||
@@ -142,7 +133,7 @@ redis = ["redis", "redisvl"]
|
||||
# Dependencies for Vector databases
|
||||
pgvector = ["pgvector"]
|
||||
chromadb = ["chromadb"]
|
||||
lancedb = ["lancedb>=0.24.0", "tantivy"]
|
||||
lancedb = ["lancedb==0.24.0", "tantivy"]
|
||||
pylance = ["pylance"]
|
||||
qdrant = ["qdrant-client"]
|
||||
couchbase = ["couchbase"]
|
||||
@@ -200,7 +191,6 @@ models = [
|
||||
"agno[google]",
|
||||
"agno[groq]",
|
||||
"agno[ibm]",
|
||||
"agno[infinity]",
|
||||
"agno[litellm]",
|
||||
"agno[meta]",
|
||||
"agno[mistral]",
|
||||
@@ -213,7 +203,7 @@ models = [
|
||||
tools = [
|
||||
"agno[apify]",
|
||||
"agno[arxiv]",
|
||||
"agno[brave]",
|
||||
# "agno[brave]", # It clashes with other dependencies
|
||||
"agno[exa]",
|
||||
"agno[cartesia]",
|
||||
"agno[ddg]",
|
||||
@@ -239,7 +229,7 @@ tools = [
|
||||
"agno[parallel]",
|
||||
"agno[scrapegraph]",
|
||||
"agno[valyu]",
|
||||
# "agno[yfinance]", # Broken right now
|
||||
"agno[yfinance]",
|
||||
"agno[confluence]",
|
||||
"agno[notion]",
|
||||
"agno[oxylabs]",
|
||||
@@ -281,6 +271,7 @@ vectordbs = [
|
||||
"agno[pinecone]",
|
||||
"agno[surrealdb]",
|
||||
"agno[upstash]",
|
||||
"agno[pylance]",
|
||||
]
|
||||
|
||||
# All knowledge
|
||||
@@ -302,6 +293,7 @@ embedders = [
|
||||
|
||||
# All libraries for testing
|
||||
tests = [
|
||||
"agno[a2a]",
|
||||
"agno[dev]",
|
||||
"agno[models]",
|
||||
"agno[tools]",
|
||||
@@ -312,10 +304,21 @@ tests = [
|
||||
"agno[performance]",
|
||||
"agno[cookbooks]",
|
||||
"agno[agui]",
|
||||
"agno[integration-tests]",
|
||||
"twine",
|
||||
"build",
|
||||
]
|
||||
|
||||
# Models integration test dependencies
|
||||
integration-tests = [
|
||||
"exa_py",
|
||||
"ddgs",
|
||||
"yfinance",
|
||||
"sqlalchemy",
|
||||
"Pillow",
|
||||
"fastmcp"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://agno.com"
|
||||
documentation = "https://docs.agno.com"
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import pytest
|
||||
|
||||
from agno.agent import Agent
|
||||
from agno.team import Team
|
||||
|
||||
|
||||
def test_callable_instructions():
|
||||
def instructions(agent: Agent, team: Team) -> str:
|
||||
return "You are a helpful assistant."
|
||||
|
||||
agent = Agent(instructions=instructions)
|
||||
team = Team(instructions=instructions, members=[])
|
||||
|
||||
agent.run("Hello")
|
||||
team.run("Hello")
|
||||
|
||||
assert True, "No Errors"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_callable_instructions():
|
||||
async def instructions(agent: Agent, team: Team) -> str:
|
||||
return "You are a helpful assistant."
|
||||
|
||||
agent = Agent(instructions=instructions)
|
||||
team = Team(instructions=instructions, members=[])
|
||||
|
||||
await agent.arun("Hello")
|
||||
await team.arun("Hello")
|
||||
|
||||
|
||||
def test_callable_system_message():
|
||||
def instructions(agent: Agent, team: Team) -> str:
|
||||
return "You are a helpful assistant."
|
||||
|
||||
agent = Agent(system_message=instructions)
|
||||
team = Team(system_message=instructions, members=[])
|
||||
|
||||
agent.run("Hello")
|
||||
team.run("Hello")
|
||||
|
||||
assert True, "No Errors"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_callable_system_message():
|
||||
async def instructions(agent: Agent, team: Team) -> str:
|
||||
return "You are a helpful assistant."
|
||||
|
||||
agent = Agent(system_message=instructions)
|
||||
team = Team(system_message=instructions, members=[])
|
||||
|
||||
await agent.arun("Hello")
|
||||
await team.arun("Hello")
|
||||
@@ -5,18 +5,6 @@ from agno.agent import Agent, RunOutput
|
||||
from agno.models.openai import OpenAIChat
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_async_client():
|
||||
"""Reset global async HTTP client between tests to avoid event loop conflicts."""
|
||||
import agno.utils.http as http_utils
|
||||
|
||||
# Reset before test
|
||||
http_utils._global_async_client = None
|
||||
yield
|
||||
# Reset after test
|
||||
http_utils._global_async_client = None
|
||||
|
||||
|
||||
class PersonSchema(BaseModel):
|
||||
name: str = Field(..., description="Person's name")
|
||||
age: int = Field(..., description="Person's age")
|
||||
|
||||
@@ -12,6 +12,18 @@ from agno.db.sqlite import AsyncSqliteDb, SqliteDb
|
||||
from agno.session import Session
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_async_client():
|
||||
"""Reset global async HTTP client between tests to avoid event loop conflicts."""
|
||||
import agno.utils.http as http_utils
|
||||
|
||||
# Reset before test
|
||||
http_utils._global_async_client = None
|
||||
yield
|
||||
# Reset after test
|
||||
http_utils._global_async_client = None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_storage_db_file():
|
||||
"""Create a temporary SQLite database file for agent storage testing."""
|
||||
|
||||
@@ -6,14 +6,15 @@ import pytest
|
||||
from agno.agent import Agent
|
||||
from agno.db.sqlite.sqlite import SqliteDb
|
||||
from agno.knowledge.knowledge import Knowledge
|
||||
from agno.vectordb.chroma import ChromaDb
|
||||
from agno.vectordb.lancedb.lance_db import LanceDb
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def setup_vector_db():
|
||||
"""Setup a temporary vector DB for testing."""
|
||||
table_name = f"docx_test_{os.urandom(4).hex()}"
|
||||
vector_db = LanceDb(table_name=table_name, uri="tmp/lancedb")
|
||||
path = f"tmp/chromadb_{os.urandom(4).hex()}"
|
||||
vector_db = ChromaDb(collection="vectors", path=path, persistent_client=True)
|
||||
yield vector_db
|
||||
# Clean up after test
|
||||
vector_db.drop()
|
||||
|
||||
@@ -64,6 +64,7 @@ def test_simple_function_tool():
|
||||
"units": {"type": "string", "description": "Temperature units (celsius or fahrenheit)"},
|
||||
},
|
||||
"required": ["location", "units"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
@@ -19,7 +19,7 @@ def _assert_metrics(response: RunOutput):
|
||||
|
||||
|
||||
def test_basic():
|
||||
agent = Agent(model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"), markdown=True, telemetry=False)
|
||||
agent = Agent(model=CerebrasOpenAI(id="gpt-oss-120b"), markdown=True, telemetry=False)
|
||||
|
||||
response: RunOutput = agent.run("Share a 2 sentence horror story")
|
||||
|
||||
@@ -31,7 +31,7 @@ def test_basic():
|
||||
|
||||
|
||||
def test_basic_stream():
|
||||
agent = Agent(model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"), markdown=True, telemetry=False)
|
||||
agent = Agent(model=CerebrasOpenAI(id="gpt-oss-120b"), markdown=True, telemetry=False)
|
||||
|
||||
response_stream = agent.run("Share a 2 sentence horror story", stream=True)
|
||||
|
||||
@@ -46,7 +46,7 @@ def test_basic_stream():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_basic():
|
||||
agent = Agent(model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"), markdown=True, telemetry=False)
|
||||
agent = Agent(model=CerebrasOpenAI(id="gpt-oss-120b"), markdown=True, telemetry=False)
|
||||
|
||||
response = await agent.arun("Share a 2 sentence horror story")
|
||||
|
||||
@@ -58,7 +58,7 @@ async def test_async_basic():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_basic_stream():
|
||||
agent = Agent(model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"), markdown=True, telemetry=False)
|
||||
agent = Agent(model=CerebrasOpenAI(id="gpt-oss-120b"), markdown=True, telemetry=False)
|
||||
|
||||
async for response in agent.arun("Share a 2 sentence horror story", stream=True):
|
||||
assert response.content is not None
|
||||
@@ -67,7 +67,7 @@ async def test_async_basic_stream():
|
||||
def test_with_memory():
|
||||
agent = Agent(
|
||||
db=SqliteDb(db_file="tmp/test_with_memory.db"),
|
||||
model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=CerebrasOpenAI(id="gpt-oss-120b"),
|
||||
add_history_to_context=True,
|
||||
num_history_runs=5,
|
||||
markdown=True,
|
||||
@@ -99,7 +99,7 @@ def test_structured_output():
|
||||
plot: str = Field(..., description="Brief plot summary")
|
||||
|
||||
agent = Agent(
|
||||
model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=CerebrasOpenAI(id="gpt-oss-120b"),
|
||||
output_schema=MovieScript,
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ class MovieScript(BaseModel):
|
||||
|
||||
def test_structured_response():
|
||||
structured_output_agent = Agent(
|
||||
model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=CerebrasOpenAI(id="gpt-oss-120b"),
|
||||
description="You help people write movie scripts.",
|
||||
output_schema=MovieScript,
|
||||
)
|
||||
@@ -52,7 +52,7 @@ def test_structured_response_with_enum_fields():
|
||||
rating: Grade
|
||||
|
||||
structured_output_agent = Agent(
|
||||
model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=CerebrasOpenAI(id="gpt-oss-120b"),
|
||||
description="You help generate recipe names and ratings.",
|
||||
output_schema=Recipe,
|
||||
)
|
||||
@@ -65,7 +65,7 @@ def test_structured_response_with_enum_fields():
|
||||
def test_structured_response_strict_output_false():
|
||||
"""Test structured response with strict_output=False (guided mode)"""
|
||||
guided_output_agent = Agent(
|
||||
model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct", strict_output=False),
|
||||
model=CerebrasOpenAI(id="gpt-oss-120b", strict_output=False),
|
||||
description="You write movie scripts.",
|
||||
output_schema=MovieScript,
|
||||
)
|
||||
|
||||
@@ -2,12 +2,13 @@ import pytest
|
||||
|
||||
from agno.agent import Agent
|
||||
from agno.models.cerebras import CerebrasOpenAI
|
||||
from agno.tools.duckduckgo import DuckDuckGoTools
|
||||
from agno.tools.yfinance import YFinanceTools
|
||||
|
||||
|
||||
def test_tool_use():
|
||||
agent = Agent(
|
||||
model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=CerebrasOpenAI(id="gpt-oss-120b"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
telemetry=False,
|
||||
)
|
||||
@@ -18,12 +19,11 @@ def test_tool_use():
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages)
|
||||
assert response.content is not None
|
||||
assert "TSLA" in response.content
|
||||
|
||||
|
||||
def test_tool_use_stream():
|
||||
agent = Agent(
|
||||
model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=CerebrasOpenAI(id="gpt-oss-120b"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
@@ -44,14 +44,13 @@ def test_tool_use_stream():
|
||||
|
||||
assert len(responses) > 0
|
||||
assert tool_call_seen, "No tool calls observed in stream"
|
||||
assert any("TSLA" in r.content for r in responses if r.content)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_tool_use():
|
||||
agent = Agent(
|
||||
model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
model=CerebrasOpenAI(id="gpt-oss-120b"),
|
||||
tools=[DuckDuckGoTools(cache_results=True)],
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
@@ -61,13 +60,12 @@ async def test_async_tool_use():
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages if msg.role == "assistant")
|
||||
assert response.content is not None
|
||||
assert "France" in response.content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_tool_use_stream():
|
||||
agent = Agent(
|
||||
model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=CerebrasOpenAI(id="gpt-oss-120b"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
telemetry=False,
|
||||
)
|
||||
@@ -92,8 +90,8 @@ async def test_async_tool_use_stream():
|
||||
|
||||
def test_tool_use_with_content():
|
||||
agent = Agent(
|
||||
model=CerebrasOpenAI(id="llama-4-scout-17b-16e-instruct"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
model=CerebrasOpenAI(id="gpt-oss-120b"),
|
||||
tools=[DuckDuckGoTools(cache_results=True)],
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
@@ -103,4 +101,3 @@ def test_tool_use_with_content():
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages if msg.tool_calls is not None)
|
||||
assert response.content is not None
|
||||
assert "France" in response.content
|
||||
|
||||
@@ -9,7 +9,7 @@ from agno.models.cerebras import Cerebras
|
||||
@pytest.fixture(scope="module")
|
||||
def cerebras_model():
|
||||
"""Fixture that provides a Cerebras model and reuses it across all tests in the module."""
|
||||
return Cerebras(id="llama-4-scout-17b-16e-instruct")
|
||||
return Cerebras(id="gpt-oss-120b")
|
||||
|
||||
|
||||
def _assert_metrics(response: RunOutput):
|
||||
|
||||
@@ -7,7 +7,7 @@ from agno.tools.duckduckgo import DuckDuckGoTools
|
||||
|
||||
def test_tool_use():
|
||||
agent = Agent(
|
||||
model=Cerebras(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=Cerebras(id="gpt-oss-120b"),
|
||||
tools=[DuckDuckGoTools(cache_results=True)],
|
||||
telemetry=False,
|
||||
)
|
||||
@@ -23,7 +23,7 @@ def test_tool_use():
|
||||
|
||||
def test_tool_use_stream():
|
||||
agent = Agent(
|
||||
model=Cerebras(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=Cerebras(id="gpt-oss-120b"),
|
||||
tools=[DuckDuckGoTools(cache_results=True)],
|
||||
telemetry=False,
|
||||
)
|
||||
@@ -48,7 +48,7 @@ def test_tool_use_stream():
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_tool_use():
|
||||
agent = Agent(
|
||||
model=Cerebras(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=Cerebras(id="gpt-oss-120b"),
|
||||
tools=[DuckDuckGoTools(cache_results=True)],
|
||||
telemetry=False,
|
||||
)
|
||||
@@ -65,7 +65,7 @@ async def test_async_tool_use():
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_tool_use_stream():
|
||||
agent = Agent(
|
||||
model=Cerebras(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=Cerebras(id="gpt-oss-120b"),
|
||||
tools=[DuckDuckGoTools(cache_results=True)],
|
||||
telemetry=False,
|
||||
)
|
||||
@@ -90,7 +90,7 @@ async def test_async_tool_use_stream():
|
||||
|
||||
def test_tool_use_with_content():
|
||||
agent = Agent(
|
||||
model=Cerebras(id="llama-4-scout-17b-16e-instruct"),
|
||||
model=Cerebras(id="gpt-oss-120b"),
|
||||
tools=[DuckDuckGoTools(cache_results=True)],
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
@@ -4,13 +4,12 @@ import pytest
|
||||
|
||||
from agno.agent import Agent, RunOutput # noqa
|
||||
from agno.models.deepinfra import DeepInfra
|
||||
from agno.tools.exa import ExaTools
|
||||
from agno.tools.yfinance import YFinanceTools
|
||||
|
||||
|
||||
def test_tool_use():
|
||||
agent = Agent(
|
||||
model=DeepInfra(id="meta-llama/Llama-2-70b-chat-hf"),
|
||||
model=DeepInfra(id="openai/gpt-oss-120b"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
@@ -27,7 +26,7 @@ def test_tool_use():
|
||||
|
||||
def test_tool_use_stream():
|
||||
agent = Agent(
|
||||
model=DeepInfra(id="meta-llama/Llama-2-70b-chat-hf"),
|
||||
model=DeepInfra(id="openai/gpt-oss-120b"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
@@ -57,7 +56,7 @@ def test_tool_use_stream():
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_tool_use():
|
||||
agent = Agent(
|
||||
model=DeepInfra(id="meta-llama/Llama-2-70b-chat-hf"),
|
||||
model=DeepInfra(id="openai/gpt-oss-120b"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
@@ -75,7 +74,7 @@ async def test_async_tool_use():
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_tool_use_stream():
|
||||
agent = Agent(
|
||||
model=DeepInfra(id="meta-llama/Llama-2-70b-chat-hf"),
|
||||
model=DeepInfra(id="openai/gpt-oss-120b"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
@@ -104,7 +103,7 @@ async def test_async_tool_use_stream():
|
||||
|
||||
def test_parallel_tool_calls():
|
||||
agent = Agent(
|
||||
model=DeepInfra(id="meta-llama/Llama-2-70b-chat-hf"),
|
||||
model=DeepInfra(id="openai/gpt-oss-120b"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
@@ -131,7 +130,7 @@ def test_tool_call_custom_tool_no_parameters():
|
||||
return "It is currently 70 degrees and cloudy in Tokyo"
|
||||
|
||||
agent = Agent(
|
||||
model=DeepInfra(id="meta-llama/Llama-2-70b-chat-hf"),
|
||||
model=DeepInfra(id="openai/gpt-oss-120b"),
|
||||
tools=[get_the_weather_in_tokyo],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
@@ -159,7 +158,7 @@ def test_tool_call_custom_tool_optional_parameters():
|
||||
return f"It is currently 70 degrees and cloudy in {city}"
|
||||
|
||||
agent = Agent(
|
||||
model=DeepInfra(id="meta-llama/Llama-2-70b-chat-hf"),
|
||||
model=DeepInfra(id="openai/gpt-oss-120b"),
|
||||
tools=[get_the_weather],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
@@ -169,32 +168,6 @@ def test_tool_call_custom_tool_optional_parameters():
|
||||
|
||||
# Verify tool usage
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages if msg.tool_calls is not None)
|
||||
assert response.content is not None
|
||||
assert "70" in response.content
|
||||
|
||||
|
||||
def test_tool_call_list_parameters():
|
||||
agent = Agent(
|
||||
model=DeepInfra(id="meta-llama/Llama-2-70b-chat-hf"),
|
||||
tools=[ExaTools()],
|
||||
instructions="Use a single tool call if possible",
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
response = agent.run(
|
||||
"What are the papers at https://arxiv.org/pdf/2307.06435 and https://arxiv.org/pdf/2502.09601 about?"
|
||||
)
|
||||
|
||||
# Verify tool usage
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages if msg.tool_calls is not None)
|
||||
tool_calls = []
|
||||
for msg in response.messages:
|
||||
if msg.tool_calls:
|
||||
tool_calls.extend(msg.tool_calls)
|
||||
for call in tool_calls:
|
||||
if call.get("type", "") == "function":
|
||||
assert call["function"]["name"] in ["search_exa", "get_contents", "exa_answer"]
|
||||
assert response.content is not None
|
||||
|
||||
@@ -155,7 +155,6 @@ def test_tool_call_custom_tool_no_parameters():
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages if msg.tool_calls is not None)
|
||||
assert response.content is not None
|
||||
assert "70" in response.content
|
||||
|
||||
|
||||
def test_tool_call_custom_tool_optional_parameters():
|
||||
@@ -184,7 +183,6 @@ def test_tool_call_custom_tool_optional_parameters():
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages if msg.tool_calls is not None)
|
||||
assert response.content is not None
|
||||
assert "70" in response.content
|
||||
|
||||
|
||||
def test_tool_call_list_parameters():
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
import pytest
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from agno.agent import Agent, RunOutput
|
||||
from agno.db.sqlite import SqliteDb
|
||||
from agno.models.ollama import OllamaTools
|
||||
|
||||
|
||||
def _assert_metrics(response: RunOutput):
|
||||
assert response.metrics is not None
|
||||
input_tokens = response.metrics.input_tokens
|
||||
output_tokens = response.metrics.output_tokens
|
||||
total_tokens = response.metrics.total_tokens
|
||||
|
||||
assert input_tokens > 0
|
||||
assert output_tokens > 0
|
||||
assert total_tokens > 0
|
||||
assert total_tokens == input_tokens + output_tokens
|
||||
|
||||
|
||||
def test_basic():
|
||||
agent = Agent(model=OllamaTools(id="llama3.2:latest"), markdown=True, telemetry=False)
|
||||
|
||||
response: RunOutput = agent.run("Share a 2 sentence horror story")
|
||||
|
||||
assert response.content is not None
|
||||
assert response.messages is not None
|
||||
assert len(response.messages) == 3
|
||||
assert [m.role for m in response.messages] == ["system", "user", "assistant"]
|
||||
|
||||
_assert_metrics(response)
|
||||
|
||||
|
||||
def test_basic_stream():
|
||||
agent = Agent(model=OllamaTools(id="llama3.2:latest"), markdown=True, telemetry=False)
|
||||
|
||||
for response in agent.run("Share a 2 sentence horror story", stream=True):
|
||||
assert response.content is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_basic():
|
||||
agent = Agent(model=OllamaTools(id="llama3.2:latest"), markdown=True, telemetry=False)
|
||||
|
||||
response = await agent.arun("Share a 2 sentence horror story")
|
||||
|
||||
assert response.content is not None
|
||||
assert response.messages is not None
|
||||
assert len(response.messages) == 3
|
||||
assert [m.role for m in response.messages] == ["system", "user", "assistant"]
|
||||
_assert_metrics(response)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_basic_stream():
|
||||
agent = Agent(model=OllamaTools(id="llama3.2:latest"), markdown=True, telemetry=False)
|
||||
|
||||
async for response in agent.arun("Share a 2 sentence horror story", stream=True):
|
||||
assert response.content is not None
|
||||
|
||||
|
||||
def test_with_memory():
|
||||
agent = Agent(
|
||||
db=SqliteDb(db_file="tmp/test_with_memory.db"),
|
||||
model=OllamaTools(id="llama3.2:latest"),
|
||||
add_history_to_context=True,
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
# First interaction
|
||||
response1 = agent.run("My name is John Smith")
|
||||
assert response1.content is not None
|
||||
|
||||
# Second interaction should remember the name
|
||||
response2 = agent.run("What's my name?")
|
||||
assert response2.content is not None
|
||||
assert "John Smith" in response2.content # type: ignore
|
||||
|
||||
# Verify memories were created
|
||||
messages = agent.get_session_messages()
|
||||
assert len(messages) == 5
|
||||
assert [m.role for m in messages] == ["system", "user", "assistant", "user", "assistant"]
|
||||
|
||||
# Test metrics structure and types
|
||||
_assert_metrics(response2)
|
||||
|
||||
|
||||
def test_output_schema():
|
||||
class MovieScript(BaseModel):
|
||||
title: str = Field(..., description="Movie title")
|
||||
genre: str = Field(..., description="Movie genre")
|
||||
plot: str = Field(..., description="Brief plot summary")
|
||||
|
||||
agent = Agent(model=OllamaTools(id="llama3.2:latest"), markdown=True, telemetry=False, output_schema=MovieScript)
|
||||
|
||||
response = agent.run("Create a movie about time travel")
|
||||
|
||||
# Verify structured output
|
||||
assert isinstance(response.content, MovieScript)
|
||||
assert response.content.title is not None
|
||||
assert response.content.genre is not None
|
||||
assert response.content.plot is not None
|
||||
|
||||
|
||||
def test_json_response_mode():
|
||||
class MovieScript(BaseModel):
|
||||
title: str = Field(..., description="Movie title")
|
||||
genre: str = Field(..., description="Movie genre")
|
||||
plot: str = Field(..., description="Brief plot summary")
|
||||
|
||||
agent = Agent(
|
||||
model=OllamaTools(id="llama3.2:latest"),
|
||||
use_json_mode=True,
|
||||
telemetry=False,
|
||||
output_schema=MovieScript,
|
||||
)
|
||||
|
||||
response = agent.run("Create a movie about time travel")
|
||||
|
||||
# Verify structured output
|
||||
assert isinstance(response.content, MovieScript)
|
||||
assert response.content.title is not None
|
||||
assert response.content.genre is not None
|
||||
assert response.content.plot is not None
|
||||
|
||||
|
||||
def test_history():
|
||||
agent = Agent(
|
||||
model=OllamaTools(id="llama3.2:latest"),
|
||||
db=SqliteDb(db_file="tmp/ollama_tools/test_basic.db"),
|
||||
add_history_to_context=True,
|
||||
telemetry=False,
|
||||
)
|
||||
run_output = agent.run("Hello")
|
||||
assert run_output.messages is not None
|
||||
assert len(run_output.messages) == 3
|
||||
run_output = agent.run("Hello 2")
|
||||
assert run_output.messages is not None
|
||||
assert len(run_output.messages) == 5
|
||||
run_output = agent.run("Hello 3")
|
||||
assert run_output.messages is not None
|
||||
assert len(run_output.messages) == 7
|
||||
run_output = agent.run("Hello 4")
|
||||
assert run_output.messages is not None
|
||||
assert len(run_output.messages) == 9
|
||||
@@ -1,182 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
|
||||
from agno.agent import Agent
|
||||
from agno.models.ollama import OllamaTools
|
||||
from agno.tools.duckduckgo import DuckDuckGoTools
|
||||
from agno.tools.exa import ExaTools
|
||||
from agno.tools.yfinance import YFinanceTools
|
||||
|
||||
|
||||
def test_tool_use():
|
||||
agent = Agent(
|
||||
model=OllamaTools(id="llama3.2:latest"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
response = agent.run("What is the current price of TSLA?")
|
||||
|
||||
# Verify tool usage
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages)
|
||||
assert response.content is not None
|
||||
assert "TSLA" in response.content
|
||||
|
||||
|
||||
def test_tool_use_stream():
|
||||
agent = Agent(
|
||||
model=OllamaTools(id="llama3.2:latest"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
for chunk in agent.run("What is the current price of TSLA?", stream=True, stream_events=True):
|
||||
if chunk.event in ["ToolCallStarted", "ToolCallCompleted"] and hasattr(chunk, "tool") and chunk.tool: # type: ignore
|
||||
if chunk.tool.tool_name: # type: ignore
|
||||
tool_call_seen = True
|
||||
if chunk.content is not None and "TSLA" in chunk.content:
|
||||
keyword_seen_in_response = True
|
||||
|
||||
assert tool_call_seen, "No tool calls observed in stream"
|
||||
assert keyword_seen_in_response, "Keyword not found in response"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_tool_use():
|
||||
agent = Agent(
|
||||
model=OllamaTools(id="llama3.2:latest"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
response = await agent.arun("What is the current price of TSLA?")
|
||||
|
||||
# Verify tool usage
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages if msg.role == "assistant")
|
||||
assert response.content is not None
|
||||
assert "TSLA" in response.content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_tool_use_stream():
|
||||
agent = Agent(
|
||||
model=OllamaTools(id="llama3.2:latest"),
|
||||
tools=[YFinanceTools(cache_results=True)],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
async for chunk in agent.arun("What is the current price of TSLA?", stream=True, stream_events=True):
|
||||
if chunk.event in ["ToolCallStarted", "ToolCallCompleted"] and hasattr(chunk, "tool") and chunk.tool: # type: ignore
|
||||
if chunk.tool.tool_name: # type: ignore
|
||||
tool_call_seen = True
|
||||
if chunk.content is not None and "TSLA" in chunk.content:
|
||||
keyword_seen_in_response = True
|
||||
|
||||
assert tool_call_seen, "No tool calls observed in stream"
|
||||
assert keyword_seen_in_response, "Keyword not found in response"
|
||||
|
||||
|
||||
def test_multiple_tool_calls():
|
||||
agent = Agent(
|
||||
model=OllamaTools(id="llama3.2:latest"),
|
||||
tools=[YFinanceTools(cache_results=True), DuckDuckGoTools(cache_results=True)],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
response = agent.run("What is the current price of TSLA and what is the latest news about it?")
|
||||
|
||||
# Verify tool usage
|
||||
assert response.messages is not None
|
||||
tool_calls = []
|
||||
for msg in response.messages:
|
||||
if msg.tool_calls:
|
||||
tool_calls.extend(msg.tool_calls)
|
||||
assert len([call for call in tool_calls if call.get("type", "") == "function"]) >= 2
|
||||
assert response.content is not None
|
||||
assert "TSLA" in response.content and "latest news" in response.content.lower()
|
||||
|
||||
|
||||
def test_tool_call_custom_tool_no_parameters():
|
||||
def get_the_weather_in_tokyo():
|
||||
"""
|
||||
Get the weather in Tokyo
|
||||
"""
|
||||
return "It is currently 70 degrees and cloudy in Tokyo"
|
||||
|
||||
agent = Agent(
|
||||
model=OllamaTools(id="llama3.2:latest"),
|
||||
tools=[get_the_weather_in_tokyo],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
response = agent.run("What is the weather in Tokyo?")
|
||||
|
||||
# Verify tool usage
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages)
|
||||
assert response.content is not None
|
||||
assert "70" in response.content
|
||||
|
||||
|
||||
def test_tool_call_custom_tool_optional_parameters():
|
||||
def get_the_weather(city: Optional[str] = None):
|
||||
"""
|
||||
Get the weather in a city
|
||||
|
||||
Args:
|
||||
city: The city to get the weather for
|
||||
"""
|
||||
if city is None:
|
||||
return "It is currently 70 degrees and cloudy in Tokyo"
|
||||
else:
|
||||
return f"It is currently 70 degrees and cloudy in {city}"
|
||||
|
||||
agent = Agent(
|
||||
model=OllamaTools(id="llama3.2:latest"),
|
||||
tools=[get_the_weather],
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
response = agent.run("What is the weather in Paris?")
|
||||
|
||||
# Verify tool usage
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages)
|
||||
assert response.content is not None
|
||||
assert "70" in response.content
|
||||
|
||||
|
||||
def test_tool_call_list_parameters():
|
||||
agent = Agent(
|
||||
model=OllamaTools(id="llama3.2:latest"),
|
||||
tools=[ExaTools()],
|
||||
instructions="Use a single tool call if possible",
|
||||
markdown=True,
|
||||
telemetry=False,
|
||||
)
|
||||
|
||||
response = agent.run(
|
||||
"What are the papers at https://arxiv.org/pdf/2307.06435 and https://arxiv.org/pdf/2502.09601 about?"
|
||||
)
|
||||
|
||||
# Verify tool usage
|
||||
assert response.messages is not None
|
||||
assert any(msg.tool_calls for msg in response.messages)
|
||||
tool_calls = []
|
||||
for msg in response.messages:
|
||||
if msg.tool_calls:
|
||||
tool_calls.extend(msg.tool_calls)
|
||||
for call in tool_calls:
|
||||
if call.get("type", "") == "function":
|
||||
assert call["function"]["name"] in ["get_contents", "exa_answer", "search_exa"]
|
||||
assert response.content is not None
|
||||
@@ -204,19 +204,6 @@ def test_history(openai_model):
|
||||
assert len(run_output.messages) == 8
|
||||
|
||||
|
||||
def test_cache_read_tokens(openai_model):
|
||||
"""Assert cache_read_tokens is populated correctly and returned in the metrics"""
|
||||
agent = Agent(model=openai_model, markdown=True, telemetry=False)
|
||||
|
||||
# Multiple + one large prompt to ensure token caching is triggered
|
||||
agent.run("Share a 2 sentence horror story" * 250)
|
||||
response = agent.run("Share a 2 sentence horror story" * 250)
|
||||
|
||||
assert response.metrics is not None
|
||||
assert response.metrics.cache_read_tokens is not None
|
||||
assert response.metrics.cache_read_tokens > 0
|
||||
|
||||
|
||||
def test_reasoning_tokens():
|
||||
"""Assert reasoning_tokens is populated correctly and returned in the metrics"""
|
||||
agent = Agent(model=OpenAIChat(id="o3-mini"), markdown=True, telemetry=False)
|
||||
|
||||
@@ -112,7 +112,8 @@ def test_kwargs_propagate_to_run_context(test_os_client, test_agent: Agent):
|
||||
def assert_run_context(run_context: RunContext):
|
||||
assert run_context.user_id == "test-user-123"
|
||||
assert run_context.session_id == "test-session-123"
|
||||
assert run_context.session_state == {"test_session_state": "test-session-state"}
|
||||
assert "test_session_state" in run_context.session_state
|
||||
assert run_context.session_state["test_session_state"] == "test-session-state"
|
||||
assert run_context.dependencies == {"test_dependencies": "test-dependencies"}
|
||||
assert run_context.metadata == {"test_metadata": "test-metadata"}
|
||||
assert run_context.knowledge_filters == {"test_knowledge_filters": "test-knowledge-filters"}
|
||||
|
||||
@@ -57,7 +57,7 @@ def test_list_as_input():
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg",
|
||||
"url": "https://www.exp1.com/wp-content/uploads/sites/7/2018/08/Golden-Gate-Bridge.jpg",
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -143,11 +143,7 @@ def test_empty_string_with_image():
|
||||
|
||||
response = team.run(
|
||||
input="",
|
||||
images=[
|
||||
Image(
|
||||
url="https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
|
||||
)
|
||||
],
|
||||
images=[Image(url="https://www.exp1.com/wp-content/uploads/sites/7/2018/08/Golden-Gate-Bridge.jpg")],
|
||||
)
|
||||
assert response.content is not None
|
||||
assert len(response.content) > 0
|
||||
@@ -176,11 +172,7 @@ def test_none_input_with_image():
|
||||
|
||||
response = team.run(
|
||||
input=None,
|
||||
images=[
|
||||
Image(
|
||||
url="https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
|
||||
)
|
||||
],
|
||||
images=[Image(url="https://www.exp1.com/wp-content/uploads/sites/7/2018/08/Golden-Gate-Bridge.jpg")],
|
||||
)
|
||||
assert response.content is not None
|
||||
assert len(response.content) > 0
|
||||
@@ -209,11 +201,7 @@ def test_empty_string_with_multiple_media():
|
||||
|
||||
response = team.run(
|
||||
input="",
|
||||
images=[
|
||||
Image(
|
||||
url="https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
|
||||
)
|
||||
],
|
||||
images=[Image(url="https://www.exp1.com/wp-content/uploads/sites/7/2018/08/Golden-Gate-Bridge.jpg")],
|
||||
)
|
||||
assert response.content is not None
|
||||
assert len(response.content) > 0
|
||||
@@ -248,11 +236,7 @@ def test_empty_string_with_image_and_user_memories():
|
||||
|
||||
response = team.run(
|
||||
input="",
|
||||
images=[
|
||||
Image(
|
||||
url="https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
|
||||
)
|
||||
],
|
||||
images=[Image(url="https://www.exp1.com/wp-content/uploads/sites/7/2018/08/Golden-Gate-Bridge.jpg")],
|
||||
)
|
||||
assert response.content is not None
|
||||
assert len(response.content) > 0
|
||||
@@ -287,11 +271,7 @@ def test_none_input_with_image_and_user_memories():
|
||||
|
||||
response = team.run(
|
||||
input=None,
|
||||
images=[
|
||||
Image(
|
||||
url="https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
|
||||
)
|
||||
],
|
||||
images=[Image(url="https://www.exp1.com/wp-content/uploads/sites/7/2018/08/Golden-Gate-Bridge.jpg")],
|
||||
)
|
||||
assert response.content is not None
|
||||
assert len(response.content) > 0
|
||||
@@ -326,11 +306,7 @@ def test_empty_string_with_image_and_session_summaries():
|
||||
|
||||
response = team.run(
|
||||
input="",
|
||||
images=[
|
||||
Image(
|
||||
url="https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
|
||||
)
|
||||
],
|
||||
images=[Image(url="https://www.exp1.com/wp-content/uploads/sites/7/2018/08/Golden-Gate-Bridge.jpg")],
|
||||
)
|
||||
assert response.content is not None
|
||||
assert len(response.content) > 0
|
||||
@@ -365,11 +341,7 @@ def test_none_input_with_image_and_session_summaries():
|
||||
|
||||
response = team.run(
|
||||
input=None,
|
||||
images=[
|
||||
Image(
|
||||
url="https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
|
||||
)
|
||||
],
|
||||
images=[Image(url="https://www.exp1.com/wp-content/uploads/sites/7/2018/08/Golden-Gate-Bridge.jpg")],
|
||||
)
|
||||
assert response.content is not None
|
||||
assert len(response.content) > 0
|
||||
|
||||
@@ -59,7 +59,7 @@ async def test_multi_step_background_execution(multi_step_workflow):
|
||||
assert response.run_id is not None
|
||||
|
||||
# Poll for completion with step tracking
|
||||
max_polls = 45 # Longer timeout for multi-step
|
||||
max_polls = 60 # Longer timeout for multi-step
|
||||
poll_count = 0
|
||||
seen_running = False
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@ def test_extract_with_custom_name():
|
||||
}
|
||||
|
||||
# Create reader and call read with custom name
|
||||
reader = TavilyReader()
|
||||
reader = TavilyReader(chunk=False)
|
||||
documents = reader.read("https://example.com", name="Custom Name")
|
||||
|
||||
# Verify custom name was used
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "agno-infra"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
description = "A lightweight framework and CLI for managing Agentic Infrastructure"
|
||||
requires-python = ">=3.7,<4"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -33,8 +33,8 @@ CALL :print_heading "Installing agno"
|
||||
ECHO [INFO] Installing dependencies from %AGNO_DIR%\requirements.txt
|
||||
pip install -r "%AGNO_DIR%\requirements.txt"
|
||||
|
||||
CALL :print_heading "Installing agno in editable mode with tests dependencies"
|
||||
pip install -e "%AGNO_DIR%[tests]"
|
||||
CALL :print_heading "Installing agno in editable mode with dev dependencies"
|
||||
pip install -e "%AGNO_DIR%[dev]"
|
||||
|
||||
CALL :print_heading "Installing agno-os"
|
||||
ECHO [INFO] Installing dependencies from %AGNO_INFRA_DIR%\requirements.txt
|
||||
|
||||
+5
-18
@@ -31,32 +31,19 @@ print_heading "Installing agno"
|
||||
print_info "VIRTUAL_ENV=${VENV_DIR} uv pip install -r ${AGNO_DIR}/requirements.txt"
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install -r ${AGNO_DIR}/requirements.txt
|
||||
|
||||
print_heading "Installing agno in editable mode with tests dependencies"
|
||||
# Install dev dependencies first (includes pytest-cov)
|
||||
print_heading "Installing agno in editable mode with dev dependencies"
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install -U -e ${AGNO_DIR}[dev]
|
||||
# Then install tests dependencies (includes all models, tools, etc.)
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install -U -e ${AGNO_DIR}[tests]
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install yfinance
|
||||
#TODO: Improve the dev setup to handle conflicts which results in missing dependencies
|
||||
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install google-genai==1.52.0
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install mcp==1.9.2
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install fastmcp
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install crawl4ai==0.6.3
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install firecrawl-py==3.4.0
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install chonkie[st] chonkie
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install pylance
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install psycopg-binary psycopg psycopg2
|
||||
|
||||
|
||||
print_heading "Installing agno-os"
|
||||
print_heading "Installing agno-infra"
|
||||
print_info "VIRTUAL_ENV=${VENV_DIR} uv pip install -r ${AGNO_INFRA_DIR}/requirements.txt"
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install -r ${AGNO_INFRA_DIR}/requirements.txt
|
||||
|
||||
print_heading "Installing agno-os in editable mode with dev dependencies"
|
||||
print_heading "Installing agno-infra in editable mode with dev dependencies"
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install -e ${AGNO_INFRA_DIR}[dev]
|
||||
|
||||
print_heading "uv pip list"
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip list
|
||||
|
||||
print_heading "Development setup complete"
|
||||
print_heading "Activate venv using: source .venv/bin/activate"
|
||||
print_heading "Activate venv using: source .venv/bin/activate"
|
||||
@@ -0,0 +1,68 @@
|
||||
@echo off
|
||||
REM ###########################################################################
|
||||
REM # Agno Development Setup (Windows)
|
||||
REM # - Create a virtual environment and install libraries in editable mode.
|
||||
REM # - Please deactivate the existing virtual environment before running.
|
||||
REM # Usage: scripts\dev_setup.bat
|
||||
REM ###########################################################################
|
||||
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
REM Get current directory
|
||||
SET "CURR_DIR=%~dp0"
|
||||
SET "REPO_ROOT=%CURR_DIR%\.."
|
||||
SET "AGNO_DIR=%REPO_ROOT%\libs\agno"
|
||||
SET "AGNO_INFRA_DIR=%REPO_ROOT%\libs\agno_infra"
|
||||
SET "VENV_DIR=%REPO_ROOT%\.venv"
|
||||
|
||||
REM Function to print headings
|
||||
CALL :print_heading "Development setup..."
|
||||
|
||||
CALL :print_heading "Removing virtual env"
|
||||
ECHO [INFO] Removing %VENV_DIR%
|
||||
rmdir /s /q "%VENV_DIR%" 2>nul
|
||||
|
||||
CALL :print_heading "Creating virtual env"
|
||||
ECHO [INFO] Creating virtual environment in %VENV_DIR%
|
||||
python -m venv "%VENV_DIR%"
|
||||
|
||||
REM Activate virtual environment
|
||||
CALL :activate_venv
|
||||
|
||||
CALL :print_heading "Installing agno"
|
||||
ECHO [INFO] Installing dependencies from %AGNO_DIR%\requirements.txt
|
||||
pip install -r "%AGNO_DIR%\requirements.txt"
|
||||
|
||||
CALL :print_heading "Installing agno in editable mode with tests dependencies"
|
||||
pip install -e "%AGNO_DIR%[tests]"
|
||||
|
||||
CALL :print_heading "Installing agno-os"
|
||||
ECHO [INFO] Installing dependencies from %AGNO_INFRA_DIR%\requirements.txt
|
||||
pip install -r "%AGNO_INFRA_DIR%\requirements.txt"
|
||||
|
||||
CALL :print_heading "Installing agno-os in editable mode with dev dependencies"
|
||||
pip install -e "%AGNO_INFRA_DIR%[dev]"
|
||||
|
||||
CALL :print_heading "pip list"
|
||||
pip list
|
||||
|
||||
CALL :print_heading "Development setup complete"
|
||||
ECHO.
|
||||
ECHO Activate venv using: .\.venv\Scripts\activate
|
||||
ECHO.
|
||||
EXIT /B
|
||||
|
||||
REM Function to print headings
|
||||
:print_heading
|
||||
ECHO.
|
||||
ECHO ##################################################
|
||||
ECHO # %1
|
||||
ECHO ##################################################
|
||||
ECHO.
|
||||
EXIT /B
|
||||
|
||||
REM Function to activate virtual environment
|
||||
:activate_venv
|
||||
ECHO [INFO] Activating virtual environment...
|
||||
CALL "%VENV_DIR%\Scripts\activate"
|
||||
EXIT /B
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
############################################################################
|
||||
# Agno Development Setup
|
||||
# - Create a virtual environment and install libraries in editable mode.
|
||||
# - Please install uv before running this script.
|
||||
# - Please deactivate the existing virtual environment before running.
|
||||
# Usage: ./scripts/dev_setup.sh
|
||||
############################################################################
|
||||
|
||||
CURR_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(dirname "${CURR_DIR}")"
|
||||
AGNO_DIR="${REPO_ROOT}/libs/agno"
|
||||
AGNO_INFRA_DIR="${REPO_ROOT}/libs/agno_infra"
|
||||
source "${CURR_DIR}/_utils.sh"
|
||||
|
||||
VENV_DIR="${REPO_ROOT}/.venv"
|
||||
PYTHON_VERSION=$(python3 --version)
|
||||
|
||||
print_heading "Development setup..."
|
||||
|
||||
print_heading "Removing virtual env"
|
||||
print_info "rm -rf ${VENV_DIR}"
|
||||
rm -rf ${VENV_DIR}
|
||||
|
||||
print_heading "Creating virtual env"
|
||||
print_info "VIRTUAL_ENV=${VENV_DIR} uv venv --python 3.12"
|
||||
VIRTUAL_ENV=${VENV_DIR} uv venv --python 3.12
|
||||
|
||||
print_heading "Installing agno"
|
||||
print_info "VIRTUAL_ENV=${VENV_DIR} uv pip install -r ${AGNO_DIR}/requirements.txt"
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install -r ${AGNO_DIR}/requirements.txt
|
||||
|
||||
print_heading "Installing agno in editable mode with tests dependencies"
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install -U -e ${AGNO_DIR}[tests]
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install brave-search
|
||||
|
||||
|
||||
print_heading "Installing agno-infra"
|
||||
print_info "VIRTUAL_ENV=${VENV_DIR} uv pip install -r ${AGNO_INFRA_DIR}/requirements.txt"
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install -r ${AGNO_INFRA_DIR}/requirements.txt
|
||||
|
||||
print_heading "Installing agno-infra in editable mode with dev dependencies"
|
||||
VIRTUAL_ENV=${VENV_DIR} uv pip install -e ${AGNO_INFRA_DIR}[dev]
|
||||
|
||||
print_heading "Development setup complete"
|
||||
print_heading "Activate venv using: source .venv/bin/activate"
|
||||
Reference in New Issue
Block a user