AI Agent Programmierung mit Agno
Dieses Beispiel wird auf einem MacOS durchgeführt im Code Editor VS Code. Verwendet wird das Agno Framework.
Voraussetzungen für das Tutorial sind:
- Lokale Python Installation
- VS-Code oder ein anderer Code Editor
Python Package Manager installieren (uv)
uv installieren (macOS & Windows)
macOS (Homebrew)
brew install uv
uv --version
Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
uv --version
Projektverzeichnis und uv-Projekt erstellen
Erstellen des Projektverzeichnisses
mkdir ai-agent-project
In das Projekt navigieren
cd ai-agent-project
Projekt initialisieren
uv init
Nun befinden sich im Projektverzeichnis folgende Dateien:
- main.py
- pyproject.toml
- README.md
Das kann man kurz überprüfen indem man folgendes Kommando in das aktuelle Terminal eingibt:
ls
Erhalten wir die gewünschte Projektstruktur beginnen wir damit die notwendigen Pakete zu installieren:
uv add agno openai
Sofern python auf dem System vorhanden ist, wird die vorhandene Installation verwendet um ein Virtual Environment zu erstellen.
Im Projektverzeichnis sollten sich nun zwei Änderungen ergeben haben:
- .venv (Neuer Ordner mit allen Abhängigkeiten und Paketen für das Projekt)
- uv.lock (Datei, welche die Abhängigkeiten/ Pakete und Versionen fixiert)
Zusätzlich erstellen wir eine .env Datei, die all unsere Secret Keys wie den API Key für OpenAI oder Gemini beinhaltet.
nano .env
Dort erstellst du die Variable OPENAI_API_KEY:
OPENAI_API_KEY=<dein-key-kommt-hier-ohne-die-klammern-rein>
Nun haben wir alles um mit einem einfachen Agenten Beispiel zu beginnen. Hierfür öffnen wir VS-Code durch folgendes Kommando (oder regulär über die VS-Code Oberfläche in den Projektordner navigieren):
Öffnen von VS-Code
code .
Programmierung des Agenten
Statischer AI Agent ohne "Gedächtnis"
Für einen sehr simplem AI Agent, der bei der Ausführung immer den hartkodierten Prompt hat können wir folgenden Code in die main.py-Datei einfügen:
# Imports
from dotenv import load_dotenv
from agno.agent import Agent
from agno.models.openai import OpenAIResponses
# Laden der Umgebungsvariablen
load_dotenv()
def main():
# Agent mit OpenAI Modell erstellen
agent = Agent(
model=OpenAIResponses(id="gpt-4o-mini"),
markdown=True,
)
# Anfrage an Agent senden und Antwort ausgeben
agent.print_response("Tell me a very funny software engineering joke", stream=True)
if __name__ == "__main__":
main()
Nach dem Ausführen des Codes ist das die Ausgabe auf der Konsole:
Message
> Tell me a very funny software engineering joke
Response (2.9s)
> Sure! Here's a classic:
>
> Why do programmers prefer dark mode?
>
> Because light attracts bugs! 🐛💻
Dynamischer KI Agent mit "Gedächtnis"
Unser bisheriger Agent hatte keine Möglichkeit sich den vorherigen Gesprächsverlauf zu "merken". Zudem gab es immer einen statischen Prompt, den wir im Code vor der Ausführung anpassen mussten.
Hierfür installieren wir uns folgendes Paket über uv:
uv add sqlalchemy
Um das zu verbessern nehmen wir hier eine temporäre Sqlite-Datenbank und starten den Agenten nun über eine CLI-App (CLI = Command Line Interface, also einer Kommunikation über die Kommandozeile).
Nach der ersten Ausführung des Codes ist im Projektverzeichnis der Ordner tmp mit der darin enthaltenen Datei agent.db.
Wir fügen dem Agent-Objekt folgende Attribute hinzu:
db(Zeigt welche Art der Datenbank wir nutzen und wo sie liegt)add_history_to_context(Sagt dem Agenten, dass der Gesprächsverlauf mitgenommen werden soll in jeden neuen Prompt)num_history_runs(Maximale Anzahl der letzten Gesprächsrunden, die in den Kontext mit rein sollen)
# Imports
from dotenv import load_dotenv
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
# Laden der Umgebungsvariablen
load_dotenv()
def main():
# Agent mit OpenAI Modell, Datenbank und Chat History erstellen
agent = Agent(
model=OpenAIResponses(id="gpt-4o-mini"),
db=SqliteDb(db_file="tmp/agent.db"),
add_history_to_context=True,
num_history_runs=5,
markdown=True,
stream=True
)
# Interaktive CLI starten
agent.cli_app(stream=True)
if __name__ == "__main__":
main()
Nachdem man die kleine Anwendung startet kann man bereit solche Konversationen führen:
Message
> Hi, ich bin Marios
Response (2.4s)
> Hallo Marios! Wie kann ich dir heute helfen?
Message
> Wie lautet mein Name?
Response (2.1s)
> Dein Name ist Marios. Wie kann ich dir weiterhelfen?
Message
> Erzähle mir einen lustigen Witz und verwende dabei meinen Namen
Response (1.7s)
> Klar, hier ist ein Witz mit deinem Namen:
> Warum hat Marios einen GPS-Tracker in die Küche gelegt?
> Weil er immer wieder in die "Maro-lokation" von Snacks verlorenging! 😂
> Hoffe, das hat dir ein Lächeln ins Gesicht gezaubert!
Tools für den AI Agent
DuckDuckGo-Tool
Damit unser Agent mehr Fertigkeiten hat als ein einfacher Chatbot, können wir über Agno sogenannte Tools einbinden.
Hier gibt es die gesamte Liste an Tools: https://docs.agno.com/tools/toolkits/overview
Für unser nächstes Beispiel wählen wir das Tool für die Suchmaschine DuckDuckGo.
Das installieren wir zunächst über uv:
uv add duckduckgo-search ddgs
Durch das Attribut tools übergeben wir dem Agenten die zur Verfügung stehenden Tools.
# Imports
from dotenv import load_dotenv
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
from agno.tools.duckduckgo import DuckDuckGoTools
# Laden der Umgebungsvariablen
load_dotenv()
def main():
# Agent mit OpenAI Modell, Datenbank, Chat History und Tools erstellen
agent = Agent(
model=OpenAIResponses(id="gpt-4o-mini"),
db=SqliteDb(db_file="tmp/agent.db"),
tools=[DuckDuckGoTools()],
add_history_to_context=True,
num_history_runs=5,
markdown=True,
)
agent.cli_app(stream=True)
if __name__ == "__main__":
main()
Nachfolgend sieht man wie nach Ausführung der App eine mögliche Konversation aussieht. Aus dem Verlauf kann man herauslesen, dass das KI Modell selbstständig entschieden hat das DuckDuckGo Tool zu nutzen, um die neusten Informationen zu bekommen.
😎 User: Welche Neuigkeiten gibt es aktuell zu OpenAI? Fasse kurz zusammen in 3 Sätzen.
Message
> Welche Neuigkeiten gibt es aktuell zu OpenAI? Fasse kurz zusammen in 3 Sätzen.
Tool Calls
- search_news(query="OpenAI", max_results=5)
Response (8.7s)
Hier sind die neuesten Entwicklungen zu OpenAI:
1. OpenAI hat eine neue Version seines Codex-Tools eingeführt, die auf speziellen Chips basiert, was als erster Meilenstein in der Zusammenarbeit mit einem Chiphersteller gilt. [Artikel lesen](https://www.msn.com/en-us/news/technology/a-new-version-of-openai-s-codex-is-powered-by-a-new-dedicated-chip/ar-AA1WepWX).
2. Greg Brockman, der Präsident von OpenAI, hat Millionen an Donald Trump gespendet, um die Mission von OpenAI zu unterstützen, was bei einigen Mitarbeitern auf Unmut stößt. [Artikel lesen](https://www.wired.com/story/openai-president-greg-brockman-political-donations-trump-humanity/).
3. OpenAI hat die Cerebras-Chips für das neue Modell GPT-5.3 Codex Spark eingesetzt, um die Abhängigkeit von Nvidia zu verringern und die Generierungsgeschwindigkeit um das 15-fache zu steigern. [Artikel lesen](https://venturebeat.com/technology/openai-deploys-cerebras-chips-for-15x-faster-code-generation-in-first-major).
Arxiv-Tool
Ein weiteres interessantes Tool, das vor allem im wissenschaftlichen Kontext verwendet werden kann, ist das Arxiv-Tool.
Hierfür installieren wir uns, wie gehabt, über uv unsere Abhängigkeiten/ Pakete:
uv add arxiv pypdf
Für einen verbesserten Kontext setzen wir die Rolle unseres Agents mit dem Attribut instructions. Zusätzlich fügen wir dem Tools-Array das Arxiv-Tools hinzu.
Hier ist die gesamte Dokumentation des Tools zu finden: https://docs.agno.com/tools/toolkits/search/arxiv
# Imports
from dotenv import load_dotenv
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
from agno.tools.duckduckgo import DuckDuckGoTools
from agno.tools.arxiv import ArxivTools
# Laden der Umgebungsvariablen
load_dotenv()
def main():
# Agent mit OpenAI Modell, Datenbank, Chat History und Tools erstellen
agent = Agent(
model=OpenAIResponses(id="gpt-4o-mini"),
db=SqliteDb(db_file="tmp/agent.db"),
tools=[DuckDuckGoTools(), ArxivTools()],
instructions=[
"Du bist ein Forschungsassistent.",
"Nutze ArxivTools um akademische Paper zu suchen und zusammenzufassen oder DuckDuckGo zum Suchen.",
],
add_history_to_context=True,
num_history_runs=5,
markdown=True,
)
agent.cli_app(stream=True)
if __name__ == "__main__":
main()
So kann eine Konversation aussehen:
😎 User: Suche nach Papers zum Thema LLMs und KI Agenten und fasse kurz zusammen.
Message
> Suche nach Papers zum Thema LLMs und KI Agenten und fasse kurz zusammen.
Tool Calls
- search_arxiv_and_return_articles(query="Large Language Models AI Agents", num_articles=5)
- search_arxiv_and_return_articles(query="LLMs AI Agents", num_articles=5)
Response (206.4s)
Hier sind einige aktuelle Papers zum Thema *Large Language Models (LLMs) und KI-Agenten*:
1. **Unmasking the Shadows of AI: Investigating Deceptive Capabilities in Large Language Models**
Autor: Linge Guo
Kurzfassung: Untersucht täuschendes Verhalten von LLMs und identifiziert vier Täuschungsarten (strategische Täuschung, Imitation, Schmeichelei, „untreues Denken“), inklusive sozialer Implikationen und möglicher Gegenmaßnahmen.
Links: http://arxiv.org/abs/2403.09676v1 · https://arxiv.org/pdf/2403.09676v1
2. **Is Self-knowledge and Action Consistent or Not: Investigating Large Language Model's Personality**
Autoren: Yiming Ai et al.
Kurzfassung: Prüft, ob klassische Persönlichkeitstests bei LLMs aussagekräftig sind, und ob „behauptete“ Persönlichkeitsmerkmale mit beobachtbarem Verhalten konsistent sind.
Links: http://arxiv.org/abs/2402.14679v2 · https://arxiv.org/pdf/2402.14679v2
3. **Enhancing Human-Like Responses in Large Language Models**
Autoren: Ethem Yağız Çalık, Talha Rüzgar Akkuş
Kurzfassung: Betrachtet Methoden, um LLM-Ausgaben menschlicher wirken zu lassen (u.a. natürlichere Sprache, emotionale Intelligenz) und nutzt Feinabstimmung zur Verbesserung der Mensch-KI-Interaktion.
Links: http://arxiv.org/abs/2501.05032v2 · https://arxiv.org/pdf/2501.05032v2
4. **Knowledge-Driven Agentic Scientific Corpus Distillation Framework for Biomedical Large Language Models Training**
Autoren: Meng Xiao et al.
Kurzfassung: Schlägt einen agentischen Multi-Agent-Ansatz vor, um hochwertige Trainingsdaten aus biomedizinischer Literatur zu destillieren und die Abhängigkeit von teuren/knappen Annotationen zu reduzieren.
Links: http://arxiv.org/abs/2504.19565v3 · https://arxiv.org/pdf/2504.19565v3
5. **Head-Specific Intervention Can Induce Misaligned AI Coordination in Large Language Models**
Autoren: Paul Darm, Annalisa Riccardi
Kurzfassung: Zeigt, dass gezielte Eingriffe zur Inferenzzeit (z.B. auf bestimmte Attention-Heads) Sicherheitsmechanismen beeinflussen/umgehen und unerwünschte Koordination bzw. Verhaltensänderungen auslösen können.
Links: http://arxiv.org/abs/2502.05945v3 · https://arxiv.org/pdf/2502.05945v3
Custom-Tools für den Agent
Oftmals reichen die fertigen Tools nicht aus für die spezifischen Business/ Anwendungsfälle. Zu den bereits vordefinierten Tools, die von Agno bereitstestellt werden, haben wir die Möglichkeit eigene Tools zu erstellen und dem Agenten zu übergeben. Dieser entscheidet dann selbstständig, ob er diese nutzt oder nicht.
In der Folge erstellen wir ein eigenes Tool, das speziell auf die HackerNews zugreift, um die neusten TechTrend für uns zu finden und zusammenzufassen.
Hierfür benötigen wir keine neuen Pakete, müssen allerdings json und httpx für die API-Calls und tool importieren.
Für das Custom-Tool gibt es die spezielle Annotation @tool().
Weitere Informationen sind hier in der Dokumentation zu finden: https://docs.agno.com/cookbook/tools/custom-tools
Diese Funktion implementieren wir in unseren Code:
# Imports
from dotenv import load_dotenv
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
from agno.tools.duckduckgo import DuckDuckGoTools
from agno.tools.arxiv import ArxivTools
import httpx
import json
from agno.tools import tool
# Laden der Umgebungsvariablen
load_dotenv()
@tool()
def get_top_hackernews_stories(num_stories: int = 5) -> str:
"""Fetch top stories from Hacker News.
Args:
num_stories (int): Number of stories to retrieve.
"""
response = httpx.get("https://hacker-news.firebaseio.com/v0/topstories.json")
story_ids = response.json()
stories = []
for story_id in story_ids[:num_stories]:
story_response = httpx.get(
f"https://hacker-news.firebaseio.com/v0/item/{story_id}.json"
)
story = story_response.json()
story.pop("text", None)
stories.append(story)
return json.dumps(stories)
def main():
# Agent mit OpenAI Modell, Datenbank, Chat History und Tools erstellen
agent = Agent(
model=OpenAIResponses(id="gpt-4o-mini"),
db=SqliteDb(db_file="tmp/agent.db"),
tools=[DuckDuckGoTools(), ArxivTools(), get_top_hackernews_stories],
instructions=["Du bist ein Tech-News Assistent. Nutze das HackerNews Tool um aktuelle Stories zu finden."],
add_history_to_context=True,
num_history_runs=5,
markdown=True,
)
agent.cli_app(stream=True)
if __name__ == "__main__":
main()
Wir starten die App und chatten mit dem Agenten:
😎 User: Welche Tech News gibt es? Fasse ganz kurz zusammen
Message
> Welche Tech News gibt es? Fasse ganz kurz zusammen
Tool Calls
- get_top_hackernews_stories(num_stories=5)
Response (11.9s)
Hier sind die aktuellen Tech-News von Hacker News:
1. **Zed Editor wechselt Grafiklibraries von blade zu wgpu**
Kurz: Größeres Rendering-Update, das Performance und Kompatibilität verbessern soll.
Link: https://github.com/zed-industries/zed/pull/46758
2. **Monosketch**
Kurz: Neue, simple Sketching-/Entwurfsplattform mit Fokus auf unkompliziertes Zeichnen.
Link: https://monosketch.io/
3. **Open Source Is Not About You (2018)**
Kurz: Essay/Reminder zur Philosophie und Haltung hinter Open Source.
Link: https://gist.github.com/richhickey/1563cddea1002958f96e7ba9519972d9
4. **Green’s Dictionary of Slang – 500 Jahre vulgäre Sprache**
Kurz: Umfangreiche Online-Referenz für Slang-Begriffe und deren Geschichte.
Link: https://greensdictofslang.com/
5. **Schneller als Dijkstra?**
Kurz: Diskussion über alternative Shortest-Path-Ansätze, die Dijkstra ggf. schlagen könnten.
Link: https://systemsapproach.org/2026/02/09/faster-than-dijkstra/
Agent über eine API verfügbar machen
Um den Agenten über eine Schnittstelle zu erreichen, nutzen wir von Agno das AgentOS Framework, das uns automatisch die Endpoints generiert. Für eine strukturierte Rückgabe nutzen wir zusätzlich FastAPI mit einem Response Schema.
Über die AgentOS-Dokumentation gibt es mehr Infos zum Vorgehen:
https://docs.agno.com/agent-os/run-your-os
Zunächst installieren wir uns die Abgängigkeiten mit uv:
uv add "fastapi[standard]" sqlalchemy PyJWT
Dann nutzen wir folgenden Code:
# Imports
from dotenv import load_dotenv
from fastapi import FastAPI
from pydantic import BaseModel
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
from agno.tools.duckduckgo import DuckDuckGoTools
from agno.tools.arxiv import ArxivTools
from agno.os import AgentOS
from agno.tools import tool
import httpx
import json
# Laden der Umgebungsvariablen
load_dotenv()
@tool()
def get_top_hackernews_stories(num_stories: int = 5) -> str:
"""Fetch top stories from Hacker News.
Args:
num_stories (int): Number of stories to retrieve.
"""
response = httpx.get("https://hacker-news.firebaseio.com/v0/topstories.json")
story_ids = response.json()
stories = []
for story_id in story_ids[:num_stories]:
story_response = httpx.get(
f"https://hacker-news.firebaseio.com/v0/item/{story_id}.json"
)
story = story_response.json()
story.pop("text", None)
stories.append(story)
return json.dumps(stories)
# Agent erstellen
agent = Agent(
id="tech-agent",
model=OpenAIResponses(id="gpt-4o-mini"),
db=SqliteDb(db_file="tmp/agent.db"),
tools=[DuckDuckGoTools(), ArxivTools(), get_top_hackernews_stories],
instructions=["Du bist ein Tech-News Assistent. Nutze das HackerNews Tool um aktuelle Stories zu finden."],
add_history_to_context=True,
num_history_runs=5,
markdown=True,
)
# Custom FastAPI App
custom_app = FastAPI(title="Tech Agent API")
# Request Schema
class ChatRequest(BaseModel):
message: str
session_id: str | None = None
# Sauberer Chat Endpoint
@custom_app.post("/chat")
def chat(request: ChatRequest):
response = agent.run(request.message, session_id=request.session_id)
return {
"response": response.content,
"session_id": response.session_id,
}
# AgentOS mit Custom App als base_app
agent_os = AgentOS(
agents=[agent],
base_app=custom_app,
)
app = agent_os.get_app()
if __name__ == "__main__":
agent_os.serve(app="main:app", reload=True)
Anschließend starten wir den Server mit:
python main.py
Der Agent ist unter folgenden URLs lokal verfügbar:
POST http://localhost:7777/agents/tech-agent/runs
Die automatisch generierte API Dokumentation ist aufrufbar unter:
http://localhost:7777/docs
Die Session lässt sich über diese URL verwalten:
GET http://localhost:7777/sessions
Zusätzlicher Endpoint /chat:
POST http://localhost:7777/chat
Durch AgentOS haben wir automatisch Zugriff auf:
- Runs
- Sessions
- Memory
- uvm.
Nun testen wir den Zugriff auf unseren Agent. Hierfür gibt es zwei Möglichkeiten:
- HTTP Client (zum Beispiel Postman)
- curl
Wir machen das nun am Beispiel von curl und senden folgende Anfrage:
curl -X POST http://localhost:7777/chat \
-H "Content-Type: application/json" \
-d '{"message": "Was sind die Top HackerNews Stories?"}'
Von unserer API erhalten wir folgende Antwort:
{"response":"Hier sind die aktuellen Top-Stories von HackerNews:\n\n1. **[GPT-5.2 derives a new result in theoretical physics](https://openai.com/index/new-result-theoretical-physics/)** \n von davidbarker \n - Punkte: 28 \n - Kommentare: 2 \n - Zeit: 2023-12-12\n\n2. **[Apple, fix my keyboard before the timer ends or I'm leaving iPhone](https://ios-countdown.win/)** \n von ozzyphantom \n - Punkte: 783 \n - Kommentare: 402 \n - Zeit: 2023-12-12\n\n3. **[Monosketch](https://monosketch.io/)** \n von penguin_booze \n - Punkte: 539 \n - Kommentare: 108 \n - Zeit: 2023-12-12\n\n4. **[Sandwich Bill of Materials](https://nesbitt.io/2026/02/08/sandwich-bill-of-materials.html)** \n von zdw \n - Punkte: 92 \n - Kommentare: 6 \n - Zeit: 2023-12-12\n\n5. **[Open Source Is Not About You (2018)](https://gist.github.com/richhickey/1563cddea1002958f96e7ba9519972d9)** \n von doubleg \n - Punkte: 154 \n - Kommentare: 100 \n - Zeit: 2023-12-12\n\nFalls du mehr Informationen zu einer spezifischen Story möchtest, lass es mich wissen!",
"session_id":"b6212889-e8d1-450f-bc9d-5f4b344fd405"}
Agent Frontend
Damit wir die Daten nicht immer über die Konsole eingeben und auf Rohdaten in Form von JSON.
Hierfür nutzen wir das leichtgewichtige und schnell eingerichtete Frontend von Streamlit.
In das bestehende Terminal geben wir folgende Kommandos ein:
uv add streamlit requests python-dotenv
Wir erstellen den Ordner frontend und darin die Datei app.py:
mkdir frontend
nano app.py
Dort fügen wir folgenden Code ein, der als Chat Interface dient:
import streamlit as st
import requests
import json
import uuid
# Konfiguration
st.set_page_config(
page_title="AI Agent Chat",
page_icon="🤖",
layout="wide"
)
st.title("🤖 AI Agent Chat")
st.markdown("---")
# Backend URL
API_URL = "http://localhost:7777"
# Initialize session state
if "messages" not in st.session_state:
st.session_state.messages = []
if "session_id" not in st.session_state:
st.session_state.session_id = str(uuid.uuid4())
# Zeige Chat Historie
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Chat input
if user_input := st.chat_input("Type your message..."):
# Hinzufüger der User Nachricht zur Historie
st.session_state.messages.append({
"role": "user",
"content": user_input
})
# Zeige Nachricht des Users
with st.chat_message("user"):
st.markdown(user_input)
# Erhalte die Antwort vom Backend
with st.chat_message("assistant"):
try:
response = requests.post(
f"{API_URL}/chat",
json={
"message": user_input,
"session_id": st.session_state.session_id
},
timeout=60
)
response.raise_for_status()
result = response.json()
# Zeige nur den Inhalt der Response
response_text = result.get("response", "No response")
st.markdown(response_text)
# Füge zur Historie hinzu
st.session_state.messages.append({
"role": "assistant",
"content": response_text
})
except requests.exceptions.ConnectionError:
st.error(f"❌ Cannot connect to backend at {API_URL}")
except requests.exceptions.Timeout:
st.error("⏱️ Request timed out")
except Exception as e:
st.error(f"❌ Error: {str(e)}")
Nun müssen wir beide Anwendungen starten, wofür wir zwei Terminals benötigen.
Für unsere API:
python main.py
Für das Frontend:
cd frontend
streamlit run app.py
Multi Agent System
Nun haben wir einen Agenten inkl. Frontend der sehr spezialisiert ist auf TechNews. Wir hätten aber gerne ein Agente, der in der Lage ist selber zu entscheiden welche anderen spezialisierten Agenten aufgerufen werden. Also eine Art Orchestrator, der entscheidet wer am besten geeignet ist.
Hierfür bietet Agno die Möglichkeit von Teams an, wobei ein zentraler Agent als Koordinator fungiert.
Zunächst installieren wir uns eine weitere Abhängigkeit/ Paket über uv:
uv add yfinance
Anschließend ersetzen wir den Code der main.py durch folgenden:
# Imports
from dotenv import load_dotenv
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
from agno.tools.duckduckgo import DuckDuckGoTools
from agno.tools.arxiv import ArxivTools
from agno.tools.yfinance import YFinanceTools
from agno.tools.hackernews import HackerNewsTools
from agno.team import Team
from agno.os import AgentOS
# Laden der Umgebungsvariablen
load_dotenv()
db = SqliteDb(db_file="tmp/team.db")
# === Agenten erstellen ===
news_agent = Agent(
name="News Agent",
role="Du findest aktuelle Tech-News auf HackerNews.",
model=OpenAIResponses(id="gpt-4o-mini"),
tools=[HackerNewsTools()],
instructions=["Finde aktuelle News und fasse sie zusammen."],
)
research_agent = Agent(
name="Research Agent",
role="Du recherchierst akademische Paper auf arXiv.",
model=OpenAIResponses(id="gpt-4o-mini"),
tools=[ArxivTools()],
instructions=["Suche nach relevanten akademischen Papern und fasse die Ergebnisse zusammen."],
)
stock_agent = Agent(
name="Stock Agent",
role="Du analysierst Aktienkurse und Finanzdaten.",
model=OpenAIResponses(id="gpt-4o-mini"),
tools=[YFinanceTools()],
instructions=["Liefere aktuelle Aktienkurse, Analysten-Empfehlungen und Finanzdaten."],
)
web_agent = Agent(
name="Web Search Agent",
role="Du durchsuchst das Web nach aktuellen Informationen.",
model=OpenAIResponses(id="gpt-4o-mini"),
tools=[DuckDuckGoTools()],
instructions=["Suche im Web nach relevanten Informationen."],
)
# === Team erstellen ===
team = Team(
id="research-team",
name="Research Team",
mode="coordinate",
model=OpenAIResponses(id="gpt-4o-mini"),
members=[news_agent, research_agent, stock_agent, web_agent],
instructions=[
"Du bist ein Koordinator, der Anfragen an die passenden Team-Mitglieder delegiert.",
"Nutze den News Agent für aktuelle Tech-News.",
"Nutze den Research Agent für akademische Recherchen.",
"Nutze den Stock Agent für Aktien- und Finanzdaten.",
"Nutze den Web Search Agent für allgemeine Websuchen.",
"Fasse die Ergebnisse aller beteiligten Agenten zusammen.",
],
db=db,
add_history_to_context=True,
num_history_runs=5,
store_member_responses=True,
share_member_interactions=True,
markdown=True,
)
# === Custom FastAPI App ===
custom_app = FastAPI(title="Research Team API")
class ChatRequest(BaseModel):
message: str
session_id: str | None = None
class AgentInfo(BaseModel):
name: str
role: str
content: str
class ChatResponse(BaseModel):
response: str
session_id: Optional[str]
agents_used: List[AgentInfo]
@custom_app.post("/chat", response_model=ChatResponse)
def chat(request: ChatRequest):
response = team.run(request.message, session_id=request.session_id)
# Beteiligte Agenten aus member_responses extrahieren
agents_used = []
if response.member_responses:
for member_response in response.member_responses:
agent_name = getattr(member_response, "agent_id", None) or "Unknown"
agents_used.append(AgentInfo(
name=agent_name,
role="Team Member",
content=member_response.content or "",
))
return ChatResponse(
response=response.content,
session_id=response.session_id,
agents_used=agents_used,
)
# === AgentOS mit Custom App ===
agent_os = AgentOS(
agents=[news_agent, research_agent, stock_agent, web_agent],
teams=[team],
base_app=custom_app,
)
app = agent_os.get_app()
if __name__ == "__main__":
agent_os.serve(app="main:app", reload=True)
Nun passen wir noch das Frontend entsprechend an:
import streamlit as st
import requests
import json
import uuid
# Page config
st.set_page_config(
page_title="AI Agent Chat",
page_icon="🤖",
layout="wide"
)
st.title("🤖 AI Agent Chat")
st.markdown("---")
# Backend URL
API_URL = "http://localhost:7777"
# Initialize session state
if "messages" not in st.session_state:
st.session_state.messages = []
if "session_id" not in st.session_state:
st.session_state.session_id = str(uuid.uuid4())
# Display chat history
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Show agents if available
if "agents" in message and message["agents"]:
with st.expander("👥 Agents involved"):
for agent in message["agents"]:
st.markdown(f"**{agent['name']}** ({agent['role']})")
# Chat input
if user_input := st.chat_input("Type your message..."):
# Add user message to history
st.session_state.messages.append({
"role": "user",
"content": user_input,
"agents": []
})
# Display user message
with st.chat_message("user"):
st.markdown(user_input)
# Get response from backend
with st.chat_message("assistant"):
try:
response = requests.post(
f"{API_URL}/chat",
json={
"message": user_input,
"session_id": st.session_state.session_id
},
timeout=60
)
response.raise_for_status()
result = response.json()
# Display the response content
response_text = result.get("response", "No response")
st.markdown(response_text)
# Display agents used
agents_used = result.get("agents_used", [])
if agents_used:
with st.expander("👥 Agents involved"):
for agent in agents_used:
st.markdown(f"**{agent['name']}** - {agent['role']}")
# Add to history
st.session_state.messages.append({
"role": "assistant",
"content": response_text,
"agents": agents_used
})
except requests.exceptions.ConnectionError:
st.error(f"❌ Cannot connect to backend at {API_URL}")
except requests.exceptions.Timeout:
st.error("⏱️ Request timed out")
except Exception as e:
st.error(f"❌ Error: {str(e)}")
Nun können wir wieder beide Anwendungen starten und entsprechende Prompts stellen. Nach jeder Antwort kommt ein Dropdown Menü, das anzeigt welche der vorhandenen Agenten verwendet wurde.
Implementieren von STT und TTS
Damit man mit den Agenten auch über natürliche Sprache reden und nicht nur schreiben kann, ist hier noch eine letzte Implementierung im Frontend.
import streamlit as st
import requests
import json
import uuid
import os
from pathlib import Path
from dotenv import load_dotenv
from openai import OpenAI
# Load .env from parent directory
env_path = Path(__file__).parent.parent / ".env"
load_dotenv(env_path)
# Page config
st.set_page_config(
page_title="AI Agent Chat",
page_icon="🤖",
layout="wide"
)
st.title("🤖 AI Agent Chat")
st.markdown("---")
# Backend URL
API_URL = "http://localhost:7777"
# OpenAI Client
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# Initialize session state
if "messages" not in st.session_state:
st.session_state.messages = []
if "session_id" not in st.session_state:
st.session_state.session_id = str(uuid.uuid4())
if "input_mode" not in st.session_state:
st.session_state.input_mode = "text"
if "last_audio_played" not in st.session_state:
st.session_state.last_audio_played = None
# Display chat history (WITHOUT audio - audio will be shown separately)
for i, message in enumerate(st.session_state.messages):
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Show agents if available
if "agents" in message and message["agents"]:
with st.expander("👥 Agents involved"):
for agent in message["agents"]:
st.markdown(f"**{agent['name']}** ({agent['role']})")
# Input mode toggle
st.markdown("### 📝 Input Mode")
col1, col2 = st.columns(2)
with col1:
if st.button("⌨️ Text Input", use_container_width=True,
type="primary" if st.session_state.input_mode == "text" else "secondary"):
st.session_state.input_mode = "text"
st.rerun()
with col2:
if st.button("🎤 Voice Input", use_container_width=True,
type="primary" if st.session_state.input_mode == "voice" else "secondary"):
st.session_state.input_mode = "voice"
st.rerun()
st.markdown("---")
# Display selected input method
user_input = None
if st.session_state.input_mode == "text":
text_input = st.chat_input("Type your message...")
user_input = text_input
else: # voice mode
audio_input = st.audio_input("Record your message", key="audio_input")
if audio_input:
try:
# Convert audio to text with Whisper
with st.spinner("🎯 Transcribing..."):
transcript = client.audio.transcriptions.create(
model="whisper-1",
file=("audio.wav", audio_input, "audio/wav")
)
user_input = transcript.text
st.success(f"Transcribed: {user_input}")
except Exception as e:
st.error(f"❌ Transcription failed: {type(e).__name__}: {str(e)}")
user_input = None
# Process input
if user_input:
# Add user message to history
st.session_state.messages.append({
"role": "user",
"content": user_input,
"agents": []
})
# Display user message
with st.chat_message("user"):
st.markdown(user_input)
# Get response from backend
with st.chat_message("assistant"):
try:
response = requests.post(
f"{API_URL}/chat",
json={
"message": user_input,
"session_id": st.session_state.session_id
},
timeout=180
)
response.raise_for_status()
result = response.json()
# Display the response content
response_text = result.get("response", "No response")
st.markdown(response_text)
# Display agents used
agents_used = result.get("agents_used", [])
if agents_used:
with st.expander("👥 Agents involved"):
for agent in agents_used:
st.markdown(f"**{agent['name']}** - {agent['role']}")
# Convert response to speech
with st.spinner("🔊 Generating audio..."):
audio_response = client.audio.speech.create(
model="gpt-4o-mini-tts-2025-12-15",
voice="echo",
input=response_text
)
audio_bytes = audio_response.content
st.audio(audio_bytes, format="audio/mpeg", autoplay=True)
# Add to history
st.session_state.messages.append({
"role": "assistant",
"content": response_text,
"agents": agents_used
})
except requests.exceptions.ConnectionError:
st.error(f"❌ Cannot connect to backend at {API_URL}")
except requests.exceptions.Timeout:
st.error("⏱️ Request timed out")
except Exception as e:
st.error(f"❌ Error: {type(e).__name__}: {str(e)}")
# Show continue input if chat exists
if st.session_state.messages:
st.markdown("---")
st.write("💬 Continue the conversation:")
if st.session_state.input_mode == "text":
continue_input = st.chat_input("Type your next message...")
else: # voice mode
continue_input = None
continue_audio = st.audio_input("Record your next message", key="continue_audio")
if continue_audio:
try:
with st.spinner("🎯 Transcribing..."):
transcript = client.audio.transcriptions.create(
model="whisper-1",
file=("audio.wav", continue_audio, "audio/wav")
)
continue_input = transcript.text
except Exception as e:
st.error(f"❌ Transcription failed: {str(e)}")
# Process continue input
if continue_input:
# Add user message
st.session_state.messages.append({
"role": "user",
"content": continue_input,
"agents": []
})
# Display user message
with st.chat_message("user"):
st.markdown(continue_input)
# Get response
with st.chat_message("assistant"):
try:
response = requests.post(
f"{API_URL}/chat",
json={
"message": continue_input,
"session_id": st.session_state.session_id
},
timeout=180
)
response.raise_for_status()
result = response.json()
# Display response
response_text = result.get("response", "No response")
st.markdown(response_text)
# Display agents
agents_used = result.get("agents_used", [])
if agents_used:
with st.expander("👥 Agents involved"):
for agent in agents_used:
st.markdown(f"**{agent['name']}** - {agent['role']}")
# Generate and play audio
with st.spinner("🔊 Generating audio..."):
audio_response = client.audio.speech.create(
model="gpt-4o-mini-tts-2025-12-15",
voice="echo",
input=response_text
)
audio_bytes = audio_response.content
st.audio(audio_bytes, format="audio/mpeg", autoplay=True)
# Add to history
st.session_state.messages.append({
"role": "assistant",
"content": response_text,
"agents": agents_used
})
except Exception as e:
st.error(f"❌ Error: {type(e).__name__}: {str(e)}")
Ich hoffe, dass jeder der den Beitrag gelesen hat oder sogar mitprogrammiert hat spaß dran hatte und vor allem etwas neues dazu gelernt hat. AI Agents sind noch ein relativ neues Feld, was uns in Zukunft aber immer weiter begleiten wird, weshalb es wichtig ist am Ball zu bleiben und sich immer wieder neues wissen anzueignen.
Für diejenigen, die nicht Schritt für Schritt mitprogrammieren wollen/ können, hier das gesamte Repository: https://github.com/DFT-IT/agno-ai-agent-project.git
Bis zum nächsten mal! :) -- Marios Tzialidis