Skip to content

Commit b25448f

Browse files
authored
Release 1.1.5 (#2202)
# Changelog ## New Features: - **Audio Responses:** Agents can now deliver audio responses (both with streaming and non-streaming). - The audio is in the `agent.run_response.response_audio`. - This only works with `OpenAIChat` with the `gpt-4o-audio-preview` model. See [their docs](https://platform.openai.com/docs/guides/audio) for more on how it works. For example ```python from agno.agent import Agent from agno.models.openai import OpenAIChat from agno.utils.audio import write_audio_to_file agent = Agent( model=OpenAIChat( id="gpt-4o-audio-preview", modalities=["text", "audio"], # Both text and audio responses are provided. audio={"voice": "alloy", "format": "wav"}, ), ) agent.print_response( "Tell me a 5 second story" ) if agent.run_response.response_audio is not None: write_audio_to_file( audio=agent.run_response.response_audio.base64_audio, filename=str(filename) ) ``` - See the [audio_conversation_agent cookbook](https://github.com/agno-agi/agno/blob/main/cookbook/playground/audio_conversation_agent.py) to test it out on the Agent Playground. - **Image understanding support for [Together.ai](http://together.ai/) and XAi**: You can now give images to agents using models from XAi and [Together.ai](http://together.ai/). - **Webex Tool**: Added a tool for sending messages on [Webex](https://www.webex.com/). - **Upstash Vector DB:** Added support for [Upstash](https://upstash.com/). ## Improvements: - **Automated Tests:** Added integration tests for all models. Most of these will be run on each pull request, with a suite of integration tests run before a new release is published. - **Grounding and Search with Gemini:** [Grounding and Search](https://ai.google.dev/gemini-api/docs/grounding?lang=python) can be used to improve the accuracy and recency of responses from the Gemini models. ## Bug Fixes: - **Structured output updates**: Fixed various cases where native structured output was not used on models - **Ollama tool parsing**: Fixed cases for Ollama with tools with optional parameters - **Gemini Memory Summariser**: Fixed cases where Gemini models were used as the memory summariser. - **Gemini Auto Tool Calling:** Enabled automatic tool calling when tools are provided, aligning behavior with other models. - **FixedSizeChunking issue with overlap:** Fixed issue where chunking would fail if overlap was set. - **Claude Tools with Multiple Types:** Fixed an issue where Claude tools would break when handling a union of types in parameters. - **JSON Response Parsing:** Fixed cases where JSON model responses returned quoted strings within dictionary values.
1 parent 6ac44df commit b25448f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+235
-356
lines changed

.github/workflows/main_test.yml

+20-7
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ name: Main Validation
33
on:
44
push:
55
branches:
6-
- 'main' # Run on all branches
7-
pull_request:
8-
branches:
9-
- 'release*' # Run on PRs targeting release branches
6+
- 'main' # Run on main
7+
- '*release*' # Run on release
108

119
jobs:
1210
style-check:
@@ -53,6 +51,7 @@ jobs:
5351
fail-fast: true # Stop all matrix jobs if one fails
5452
env:
5553
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
54+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
5655
steps:
5756
- uses: actions/checkout@v3
5857
- name: Set up Python ${{ matrix.python-version }}
@@ -86,6 +85,8 @@ jobs:
8685
fail-fast: true # Stop all matrix jobs if one fails
8786
env:
8887
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
88+
GOOGLE_CLOUD_PROJECT: ${{ secrets.GOOGLE_CLOUD_PROJECT }}
89+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
8990
steps:
9091
- uses: actions/checkout@v3
9192
- name: Set up Python ${{ matrix.python-version }}
@@ -119,6 +120,7 @@ jobs:
119120
fail-fast: true # Stop all matrix jobs if one fails
120121
env:
121122
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
123+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
122124
steps:
123125
- uses: actions/checkout@v3
124126
- name: Set up Python ${{ matrix.python-version }}
@@ -151,8 +153,10 @@ jobs:
151153
python-version: ["3.12"]
152154
fail-fast: true
153155
env:
154-
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
155-
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
156+
AWS_ACCESS_KEY_ID: ${{ secrets.BEDROCK_AWS_ACCESS_KEY_ID }}
157+
AWS_SECRET_ACCESS_KEY: ${{ secrets.BEDROCK_AWS_SECRET_ACCESS_KEY }}
158+
AWS_REGION: "us-east-1"
159+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
156160
steps:
157161
- uses: actions/checkout@v3
158162
- name: Set up Python ${{ matrix.python-version }}
@@ -186,6 +190,7 @@ jobs:
186190
fail-fast: true
187191
env:
188192
CO_API_KEY: ${{ secrets.CO_API_KEY }}
193+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
189194
steps:
190195
- uses: actions/checkout@v3
191196
- name: Set up Python ${{ matrix.python-version }}
@@ -252,6 +257,7 @@ jobs:
252257
fail-fast: true
253258
env:
254259
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
260+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
255261
steps:
256262
- uses: actions/checkout@v3
257263
- name: Set up Python ${{ matrix.python-version }}
@@ -285,6 +291,7 @@ jobs:
285291
fail-fast: true
286292
env:
287293
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
294+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
288295
steps:
289296
- uses: actions/checkout@v3
290297
- name: Set up Python ${{ matrix.python-version }}
@@ -318,6 +325,7 @@ jobs:
318325
fail-fast: true
319326
env:
320327
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
328+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
321329
steps:
322330
- uses: actions/checkout@v3
323331
- name: Set up Python ${{ matrix.python-version }}
@@ -344,6 +352,7 @@ jobs:
344352
345353
# Run tests for Nvidia
346354
test-nvidia:
355+
if: false # Disable nvidia tests until we get a test account
347356
runs-on: ubuntu-latest
348357
strategy:
349358
matrix:
@@ -374,16 +383,17 @@ jobs:
374383
run: |
375384
source .venv/bin/activate
376385
python -m pytest ./libs/agno/tests/integration/models/nvidia
377-
378386
# Run tests for OpenRouter
379387
test-openrouter:
388+
if: false # Disable openrouter tests until we get a test account
380389
runs-on: ubuntu-latest
381390
strategy:
382391
matrix:
383392
python-version: ["3.12"]
384393
fail-fast: true
385394
env:
386395
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
396+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
387397
steps:
388398
- uses: actions/checkout@v3
389399
- name: Set up Python ${{ matrix.python-version }}
@@ -476,13 +486,15 @@ jobs:
476486
477487
# Run tests for Together
478488
test-together:
489+
if: false # The tests take too long to run
479490
runs-on: ubuntu-latest
480491
strategy:
481492
matrix:
482493
python-version: ["3.12"]
483494
fail-fast: true
484495
env:
485496
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
497+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
486498
steps:
487499
- uses: actions/checkout@v3
488500
- name: Set up Python ${{ matrix.python-version }}
@@ -516,6 +528,7 @@ jobs:
516528
fail-fast: true
517529
env:
518530
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
531+
EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
519532
steps:
520533
- uses: actions/checkout@v3
521534
- name: Set up Python ${{ matrix.python-version }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import base64
2+
import wave
3+
from pathlib import Path
4+
from typing import Iterator
5+
6+
from agno.agent import Agent, RunResponse # noqa
7+
from agno.models.openai import OpenAIChat
8+
9+
# Audio Configuration
10+
SAMPLE_RATE = 24000 # Hz (24kHz)
11+
CHANNELS = 1 # Mono (Change to 2 if Stereo)
12+
SAMPLE_WIDTH = 2 # Bytes (16 bits)
13+
14+
# Provide the agent with the audio file and audio configuration and get result as text + audio
15+
agent = Agent(
16+
model=OpenAIChat(
17+
id="gpt-4o-audio-preview",
18+
modalities=["text", "audio"],
19+
audio={
20+
"voice": "alloy",
21+
"format": "pcm16",
22+
}, # Only pcm16 is supported with streaming
23+
),
24+
)
25+
output_stream: Iterator[RunResponse] = agent.run(
26+
"Tell me a 10 second story", stream=True
27+
)
28+
29+
filename = "tmp/response_stream.wav"
30+
31+
# Open the file once in append-binary mode
32+
with wave.open(str(filename), "wb") as wav_file:
33+
wav_file.setnchannels(CHANNELS)
34+
wav_file.setsampwidth(SAMPLE_WIDTH)
35+
wav_file.setframerate(SAMPLE_RATE)
36+
37+
# Iterate over generated audio
38+
for response in output_stream:
39+
if response.response_audio:
40+
if response.response_audio.transcript:
41+
print(response.response_audio.transcript, end="", flush=True)
42+
if response.response_audio.content:
43+
try:
44+
pcm_bytes = base64.b64decode(response.response_audio.content)
45+
wav_file.writeframes(pcm_bytes)
46+
except Exception as e:
47+
print(f"Error decoding audio: {e}")
48+
print()

libs/agno/agno/agent/agent.py

-2
Original file line numberDiff line numberDiff line change
@@ -2433,9 +2433,7 @@ def _transfer_task_to_agent(
24332433
True
24342434
if (
24352435
member_agent.response_model is not None
2436-
and member_agent.structured_outputs
24372436
and member_agent.model is not None
2438-
and member_agent.model.supports_structured_outputs
24392437
)
24402438
else False
24412439
)

libs/agno/pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "agno"
3-
version = "1.1.4"
3+
version = "1.1.5"
44
description = "Agno: a lightweight framework for building multi-modal Agents"
55
requires-python = ">=3.7,<4"
66
readme = "README.md"

libs/agno/tests/integration/__init__.py

Whitespace-only changes.

libs/agno/tests/integration/agent/__init__.py

Whitespace-only changes.

libs/agno/tests/integration/knowledge/__init__.py

Whitespace-only changes.

libs/agno/tests/integration/models/__init__.py

Whitespace-only changes.

libs/agno/tests/integration/models/anthropic/__init__.py

Whitespace-only changes.

libs/agno/tests/integration/models/anthropic/test_basic.py

+3-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from agno.agent import Agent, RunResponse # noqa
55
from agno.models.anthropic import Claude
6-
from agno.storage.agent.postgres import PostgresAgentStorage
6+
from agno.storage.agent.sqlite import SqliteAgentStorage
77

88

99
def _assert_metrics(response: RunResponse):
@@ -95,17 +95,7 @@ def test_with_memory():
9595
assert [m.role for m in agent.memory.messages] == ["system", "user", "assistant", "user", "assistant"]
9696

9797
# Test metrics structure and types
98-
input_tokens = response2.metrics["input_tokens"]
99-
output_tokens = response2.metrics["output_tokens"]
100-
total_tokens = response2.metrics["total_tokens"]
101-
102-
assert isinstance(input_tokens[0], int)
103-
assert input_tokens[0] > 0
104-
assert isinstance(output_tokens[0], int)
105-
assert output_tokens[0] > 0
106-
assert isinstance(total_tokens[0], int)
107-
assert total_tokens[0] > 0
108-
assert total_tokens[0] == input_tokens[0] + output_tokens[0]
98+
_assert_metrics(response2)
10999

110100

111101
def test_structured_output():
@@ -128,10 +118,9 @@ class MovieScript(BaseModel):
128118

129119

130120
def test_history():
131-
db_url = "postgresql+psycopg://ai:ai@localhost:5532/ai"
132121
agent = Agent(
133122
model=Claude(id="claude-3-5-haiku-20241022"),
134-
storage=PostgresAgentStorage(table_name="agent_sessions", db_url=db_url),
123+
storage=SqliteAgentStorage(table_name="agent_sessions", db_file="tmp/agent_storage.db"),
135124
add_history_to_messages=True,
136125
telemetry=False,
137126
monitoring=False,

libs/agno/tests/integration/models/anthropic/test_tool_use.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,5 +222,5 @@ def test_tool_call_list_parameters():
222222
tool_calls.extend(msg.tool_calls)
223223
for call in tool_calls:
224224
if call.get("type", "") == "function":
225-
assert call["function"]["name"] == "get_contents"
225+
assert call["function"]["name"] in ["get_contents", "exa_answer"]
226226
assert response.content is not None

libs/agno/tests/integration/models/aws/__init__.py

Whitespace-only changes.

libs/agno/tests/integration/models/aws/bedrock/__init__.py

Whitespace-only changes.

libs/agno/tests/integration/models/aws/bedrock/test_basic.py

+3-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from agno.agent import Agent, RunResponse # noqa
44
from agno.models.aws import AwsBedrock
5-
from agno.storage.agent.postgres import PostgresAgentStorage
5+
from agno.storage.agent.sqlite import SqliteAgentStorage
66

77

88
def _assert_metrics(response: RunResponse):
@@ -72,17 +72,7 @@ def test_with_memory():
7272
assert [m.role for m in agent.memory.messages] == ["system", "user", "assistant", "user", "assistant"]
7373

7474
# Test metrics structure and types
75-
input_tokens = response2.metrics["input_tokens"]
76-
output_tokens = response2.metrics["output_tokens"]
77-
total_tokens = response2.metrics["total_tokens"]
78-
79-
assert isinstance(input_tokens[0], int)
80-
assert input_tokens[0] > 0
81-
assert isinstance(output_tokens[0], int)
82-
assert output_tokens[0] > 0
83-
assert isinstance(total_tokens[0], int)
84-
assert total_tokens[0] > 0
85-
assert total_tokens[0] == input_tokens[0] + output_tokens[0]
75+
_assert_metrics(response2)
8676

8777

8878
def test_response_model():
@@ -109,10 +99,9 @@ class MovieScript(BaseModel):
10999

110100

111101
def test_history():
112-
db_url = "postgresql+psycopg://ai:ai@localhost:5532/ai"
113102
agent = Agent(
114103
model=AwsBedrock(id="anthropic.claude-3-sonnet-20240229-v1:0"),
115-
storage=PostgresAgentStorage(table_name="agent_sessions", db_url=db_url),
104+
storage=SqliteAgentStorage(table_name="agent_sessions", db_file="tmp/agent_storage.db"),
116105
add_history_to_messages=True,
117106
telemetry=False,
118107
monitoring=False,

libs/agno/tests/integration/models/aws/bedrock/test_tool_use.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -173,5 +173,5 @@ def test_tool_call_list_parameters():
173173
tool_calls.extend(msg.tool_calls)
174174
for call in tool_calls:
175175
if call.get("type", "") == "function":
176-
assert call["function"]["name"] == "get_contents"
176+
assert call["function"]["name"] in ["get_contents", "exa_answer"]
177177
assert response.content is not None

libs/agno/tests/integration/models/aws/claude/__init__.py

Whitespace-only changes.

libs/agno/tests/integration/models/aws/claude/test_basic.py

+3-15
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from agno.agent import Agent, RunResponse # noqa
55
from agno.models.aws import Claude
6-
from agno.storage.agent.postgres import PostgresAgentStorage
6+
from agno.storage.agent.sqlite import SqliteAgentStorage
77

88

99
def _assert_metrics(response: RunResponse):
@@ -101,17 +101,7 @@ def test_with_memory():
101101
assert [m.role for m in agent.memory.messages] == ["system", "user", "assistant", "user", "assistant"]
102102

103103
# Test metrics structure and types
104-
input_tokens = response2.metrics["input_tokens"]
105-
output_tokens = response2.metrics["output_tokens"]
106-
total_tokens = response2.metrics["total_tokens"]
107-
108-
assert isinstance(input_tokens[0], int)
109-
assert input_tokens[0] > 0
110-
assert isinstance(output_tokens[0], int)
111-
assert output_tokens[0] > 0
112-
assert isinstance(total_tokens[0], int)
113-
assert total_tokens[0] > 0
114-
assert total_tokens[0] == input_tokens[0] + output_tokens[0]
104+
_assert_metrics(response2)
115105

116106

117107
def test_structured_output():
@@ -137,14 +127,12 @@ class MovieScript(BaseModel):
137127

138128

139129
def test_history():
140-
db_url = "postgresql+psycopg://ai:ai@localhost:5532/ai"
141130
agent = Agent(
142131
model=Claude(id="anthropic.claude-3-sonnet-20240229-v1:0"),
143-
storage=PostgresAgentStorage(table_name="agent_sessions", db_url=db_url),
132+
storage=SqliteAgentStorage(table_name="agent_sessions", db_file="tmp/agent_storage.db"),
144133
add_history_to_messages=True,
145134
telemetry=False,
146135
monitoring=False,
147-
markdown=True,
148136
)
149137
agent.run("Hello")
150138
assert len(agent.run_response.messages) == 2

libs/agno/tests/integration/models/aws/claude/test_tool_use.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,5 +222,5 @@ def test_tool_call_list_parameters():
222222
tool_calls.extend(msg.tool_calls)
223223
for call in tool_calls:
224224
if call.get("type", "") == "function":
225-
assert call["function"]["name"] == "get_contents"
225+
assert call["function"]["name"] in ["get_contents", "exa_answer"]
226226
assert response.content is not None

libs/agno/tests/integration/models/azure/__init__.py

Whitespace-only changes.

libs/agno/tests/integration/models/azure/ai_foundry/__init__.py

Whitespace-only changes.

libs/agno/tests/integration/models/azure/ai_foundry/test_basic.py

+3-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from agno.agent import Agent, RunResponse
55
from agno.models.azure import AzureAIFoundry
6-
from agno.storage.agent.postgres import PostgresAgentStorage
6+
from agno.storage.agent.sqlite import SqliteAgentStorage
77

88

99
def _assert_metrics(response: RunResponse):
@@ -101,17 +101,7 @@ def test_with_memory():
101101
assert [m.role for m in agent.memory.messages] == ["system", "user", "assistant", "user", "assistant"]
102102

103103
# Test metrics structure and types
104-
input_tokens = response2.metrics["input_tokens"]
105-
output_tokens = response2.metrics["output_tokens"]
106-
total_tokens = response2.metrics["total_tokens"]
107-
108-
assert isinstance(input_tokens[0], int)
109-
assert input_tokens[0] > 0
110-
assert isinstance(output_tokens[0], int)
111-
assert output_tokens[0] > 0
112-
assert isinstance(total_tokens[0], int)
113-
assert total_tokens[0] > 0
114-
assert total_tokens[0] == input_tokens[0] + output_tokens[0]
104+
_assert_metrics(response2)
115105

116106

117107
def test_response_model():
@@ -137,10 +127,9 @@ class MovieScript(BaseModel):
137127

138128

139129
def test_history():
140-
db_url = "postgresql+psycopg://ai:ai@localhost:5532/ai"
141130
agent = Agent(
142131
model=AzureAIFoundry(id="Phi-4"),
143-
storage=PostgresAgentStorage(table_name="agent_sessions", db_url=db_url),
132+
storage=SqliteAgentStorage(table_name="agent_sessions", db_file="tmp/agent_storage.db"),
144133
add_history_to_messages=True,
145134
telemetry=False,
146135
monitoring=False,

libs/agno/tests/integration/models/azure/ai_foundry/test_tool_use.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,5 +222,5 @@ def test_tool_call_list_parameters():
222222
tool_calls.extend(msg.tool_calls)
223223
for call in tool_calls:
224224
if call.get("type", "") == "function":
225-
assert call["function"]["name"] == "get_contents"
225+
assert call["function"]["name"] in ["get_contents", "exa_answer"]
226226
assert response.content is not None

libs/agno/tests/integration/models/azure/openai/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)