Skip to content

Commit 18162ff

Browse files
committed
agno
1 parent b723bcb commit 18162ff

File tree

9 files changed

+241
-11
lines changed

9 files changed

+241
-11
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { test, expect } from "@playwright/test";
2+
3+
test("[Agno] Backend Tool Rendering displays weather cards", async ({ page }) => {
4+
// Set shorter default timeout for this test
5+
test.setTimeout(30000); // 30 seconds total
6+
7+
await page.goto("/agno/feature/backend_tool_rendering");
8+
9+
// Verify suggestion buttons are visible
10+
await expect(page.getByRole("button", { name: "Weather in San Francisco" })).toBeVisible({
11+
timeout: 5000,
12+
});
13+
14+
// Click first suggestion and verify weather card appears
15+
await page.getByRole("button", { name: "Weather in San Francisco" }).click();
16+
17+
// Wait for either test ID or fallback to "Current Weather" text
18+
const weatherCard = page.getByTestId("weather-card");
19+
const currentWeatherText = page.getByText("Current Weather");
20+
21+
// Try test ID first, fallback to text
22+
try {
23+
await expect(weatherCard).toBeVisible({ timeout: 10000 });
24+
} catch (e) {
25+
// Fallback to checking for "Current Weather" text
26+
await expect(currentWeatherText.first()).toBeVisible({ timeout: 10000 });
27+
}
28+
29+
// Verify weather content is present (use flexible selectors)
30+
const hasHumidity = await page
31+
.getByText("Humidity")
32+
.isVisible()
33+
.catch(() => false);
34+
const hasWind = await page
35+
.getByText("Wind")
36+
.isVisible()
37+
.catch(() => false);
38+
const hasCityName = await page
39+
.locator("h3")
40+
.filter({ hasText: /San Francisco/i })
41+
.isVisible()
42+
.catch(() => false);
43+
44+
// At least one of these should be true
45+
expect(hasHumidity || hasWind || hasCityName).toBeTruthy();
46+
47+
// Click second suggestion
48+
await page.getByRole("button", { name: "Weather in New York" }).click();
49+
await page.waitForTimeout(2000);
50+
51+
// Verify at least one weather-related element is still visible
52+
const weatherElements = await page.getByText(/Weather|Humidity|Wind|Temperature/i).count();
53+
expect(weatherElements).toBeGreaterThan(0);
54+
});

typescript-sdk/apps/dojo/src/agents.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [
265265
tool_based_generative_ui: new AgnoAgent({
266266
url: `${envVars.agnoUrl}/tool_based_generative_ui/agui`,
267267
}),
268+
backend_tool_rendering: new AgnoAgent({
269+
url: `${envVars.agnoUrl}/backend_tool_rendering/agui`,
270+
}),
268271
};
269272
},
270273
},

typescript-sdk/apps/dojo/src/app/[integrationId]/feature/backend_tool_rendering/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ const Chat = () => {
3131
available: "disabled",
3232
parameters: [{ name: "location", type: "string", required: true }],
3333
render: ({ args, result, status }) => {
34-
console.log(result, args);
3534
if (status !== "complete") {
3635
return (
3736
<div className=" bg-[#667eea] text-white p-4 rounded-lg max-w-md">

typescript-sdk/apps/dojo/src/files.json

Lines changed: 27 additions & 7 deletions
Large diffs are not rendered by default.

typescript-sdk/integrations/agno/examples/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ readme = "README.md"
1010
requires-python = ">=3.12,<4.0"
1111
dependencies = [
1212
"agno>=1.7.7",
13+
"httpx>=0.27.0",
1314
"openai>=1.99.1",
1415
"yfinance>=0.2.63",
1516
"fastapi>=0.116.1",
@@ -23,4 +24,4 @@ authors = [
2324

2425

2526
[project.scripts]
26-
dev = "server:main"
27+
dev = "server:main"

typescript-sdk/integrations/agno/examples/server/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
from .api import (
1717
agentic_chat_app,
1818
tool_based_generative_ui_app,
19+
backend_tool_rendering_app,
1920
)
2021

2122
app = FastAPI(title='Agno AG-UI server')
2223
app.mount('/agentic_chat', agentic_chat_app, 'Agentic Chat')
2324
app.mount('/tool_based_generative_ui', tool_based_generative_ui_app, 'Tool-based Generative UI')
25+
app.mount('/backend_tool_rendering', backend_tool_rendering_app, 'Backend Tool Rendering')
2426

2527
def main():
2628
"""Main function to start the FastAPI server."""

typescript-sdk/integrations/agno/examples/server/api/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
from .agentic_chat import app as agentic_chat_app
66
from .tool_based_generative_ui import app as tool_based_generative_ui_app
7+
from .backend_tool_rendering import app as backend_tool_rendering_app
78

89
__all__ = [
910
'agentic_chat_app',
1011
'tool_based_generative_ui_app',
11-
]
12+
'backend_tool_rendering_app',
13+
]
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""Example: Agno Agent with Finance tools
2+
3+
This example shows how to create an Agno Agent with tools (YFinanceTools) and expose it in an AG-UI compatible way.
4+
"""
5+
6+
from agno.agent.agent import Agent
7+
from agno.app.agui.app import AGUIApp
8+
from agno.models.openai import OpenAIChat
9+
from agno.tools.yfinance import YFinanceTools
10+
from agno.tools import tool
11+
import httpx
12+
import json
13+
14+
15+
def get_weather_condition(code: int) -> str:
16+
"""Map weather code to human-readable condition.
17+
18+
Args:
19+
code: WMO weather code.
20+
21+
Returns:
22+
Human-readable weather condition string.
23+
"""
24+
conditions = {
25+
0: "Clear sky",
26+
1: "Mainly clear",
27+
2: "Partly cloudy",
28+
3: "Overcast",
29+
45: "Foggy",
30+
48: "Depositing rime fog",
31+
51: "Light drizzle",
32+
53: "Moderate drizzle",
33+
55: "Dense drizzle",
34+
56: "Light freezing drizzle",
35+
57: "Dense freezing drizzle",
36+
61: "Slight rain",
37+
63: "Moderate rain",
38+
65: "Heavy rain",
39+
66: "Light freezing rain",
40+
67: "Heavy freezing rain",
41+
71: "Slight snow fall",
42+
73: "Moderate snow fall",
43+
75: "Heavy snow fall",
44+
77: "Snow grains",
45+
80: "Slight rain showers",
46+
81: "Moderate rain showers",
47+
82: "Violent rain showers",
48+
85: "Slight snow showers",
49+
86: "Heavy snow showers",
50+
95: "Thunderstorm",
51+
96: "Thunderstorm with slight hail",
52+
99: "Thunderstorm with heavy hail",
53+
}
54+
return conditions.get(code, "Unknown")
55+
56+
57+
@tool(external_execution=False)
58+
async def get_weather(location: str) -> str:
59+
"""Get current weather for a location.
60+
61+
Args:
62+
location: City name.
63+
64+
Returns:
65+
A json string with weather information including temperature, feels like,
66+
humidity, wind speed, wind gust, conditions, and location name.
67+
"""
68+
async with httpx.AsyncClient() as client:
69+
# Geocode the location
70+
geocoding_url = (
71+
f"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1"
72+
)
73+
geocoding_response = await client.get(geocoding_url)
74+
geocoding_data = geocoding_response.json()
75+
76+
if not geocoding_data.get("results"):
77+
raise ValueError(f"Location '{location}' not found")
78+
79+
result = geocoding_data["results"][0]
80+
latitude = result["latitude"]
81+
longitude = result["longitude"]
82+
name = result["name"]
83+
84+
# Get weather data
85+
weather_url = (
86+
f"https://api.open-meteo.com/v1/forecast?"
87+
f"latitude={latitude}&longitude={longitude}"
88+
f"&current=temperature_2m,apparent_temperature,relative_humidity_2m,"
89+
f"wind_speed_10m,wind_gusts_10m,weather_code"
90+
)
91+
weather_response = await client.get(weather_url)
92+
weather_data = weather_response.json()
93+
94+
current = weather_data["current"]
95+
96+
return json.dumps(
97+
{
98+
"temperature": current["temperature_2m"],
99+
"feelsLike": current["apparent_temperature"],
100+
"humidity": current["relative_humidity_2m"],
101+
"windSpeed": current["wind_speed_10m"],
102+
"windGust": current["wind_gusts_10m"],
103+
"conditions": get_weather_condition(current["weather_code"]),
104+
"location": name,
105+
}
106+
)
107+
108+
109+
agent = Agent(
110+
model=OpenAIChat(id="gpt-4o"),
111+
tools=[
112+
get_weather,
113+
],
114+
description="You are a helpful weather assistant that provides accurate weather information.",
115+
instructions="""
116+
Your primary function is to help users get weather details for specific locations. When responding:
117+
- Always ask for a location if none is provided
118+
- If the location name isn’t in English, please translate it
119+
- If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York")
120+
- Include relevant details like humidity, wind conditions, and precipitation
121+
- Keep responses concise but informative
122+
123+
Use the get_weather tool to fetch current weather data.
124+
""",
125+
)
126+
127+
agui_app = AGUIApp(
128+
agent=agent,
129+
name="Weather Agent",
130+
app_id="backend_tool_rendering",
131+
description="A helpful weather assistant that provides accurate weather information.",
132+
)
133+
134+
app = agui_app.get_app()

typescript-sdk/integrations/agno/examples/uv.lock

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)