Produktives Deployment und Engineering von AI-Agenten
Der Produktionseinsatz umfasst mehrere Aspekte wie Zuverlässigkeit, Skalierbarkeit und Kostenkontrolle.
Engineering-Praktiken gewährleisten einen stabilen Systembetrieb und einfache Wartung.
Architektur für den Produktionseinsatz
Produktionsumgebungen stellen höhere Anforderungen an das System.
Mehrere Aspekte wie Zuverlässigkeit, Skalierbarkeit, Monitoring und Alarmierung müssen berücksichtigt werden.
Bereitstellungsmodi
Einzelmaschinen-Bereitstellung
Geeignet für Entwicklungs- und Testumgebungen.
Einfach und leicht bereitzustellen, kann aber keinen Produktionsverkehr bewältigen.
Verteilte Bereitstellung
Geeignet für Produktionsumgebungen; mehrere Aspekte müssen berücksichtigt werden.
Lastverteilung: Anfragen auf mehrere Instanzen verteilen.
Service-Discovery: Die Liste der Instanzen dynamisch verwalten.
Zustandsverwaltung: Konsistenzprobleme verteilter Zustände behandeln.
Fehlertoleranz: Ein einzelner Ausfallpunkt beeinträchtigt den Gesamtservice nicht.
Containerisierte Bereitstellung
Dockerfile-Beispiel
FROM python:3.11-slim
# Arbeitsverzeichnis festlegen
WORKDIR /app
# Abhängigkeiten installieren
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Anwendungscode kopieren
COPY . .
# Nicht-Root-Benutzer erstellen (Sicherheitsaspekt)
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
# Port freigeben
EXPOSE 8000
# Startbefehl
CMD ["python", "agent.py"]
Kubernetes Deployment
Kubernetes Deployment-Konfiguration
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-agent
labels:
app: ai-agent
spec:
# Anzahl der Replikate
replicas: 3
selector:
matchLabels:
app: ai-agent
template:
metadata:
labels:
app: ai-agent
spec:
containers:
- name: agent
image: your-registry/ai-agent:latest
ports:
- containerPort: 8000
# Ressourcenlimits
Ressourcen:
limits:
memory: "2Gi"
cpu: "1"
requests:
memory: "1Gi"
cpu: "0.5"
# Health-Check
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: ai-agent-service
spec:
selector:
app: ai-agent
ports:
- port: 80
targetPort: 8000
type: LoadBalancer
Kostenkontrolle
Die Kosten eines Agent-Systems stammen hauptsächlich aus LLM-API-Aufrufen.
Die Kostenoptimierung ist ein wichtiges Thema für den Produktiveinsatz.
Token-Optimierungsstrategien
Prompt-Komprimierung: Den Prompt vereinfachen und unnötigen Text reduzieren.
Kontextkürzung: Nur die wesentlichen Kontextinhalte behalten.
Antwort-Caching: Antworten für identische oder ähnliche Anfragen zwischenspeichern.
Code-Implementierung
Implementierung eines Kostenoptimierungs-Agenten
import json
from functools import lru_cache
class CostOptimizedAgent:
"""
Kostenoptimierter Agent
API-Aufrufkosten durch Caching und Komprimierung reduzieren
"""
def __init__(self, base_agent, cache, embedder=Keine):
# Basis-Agent
self.base_agent = base_agent
# Cache-Speicher
self.cache = cache
# Embedder (für Ähnlichkeits-Cache-Abgleich)
self.embedder = embedder
def process(self, request):
"""
Anfragen verarbeiten, einschließlich Kostenoptimierungslogik
"""
# Anfrage-Fingerabdruck generieren
request_fingerprint = self.generate_fingerprint(request)
# Exakten Cache prüfen
cached_result = self.cache.get(request_fingerprint)
if cached_result:
return {
**cached_result,
"from_cache": True
}
# Wenn ein Embedder vorhanden ist, semantischen Ähnlichkeits-Cache prüfen
if self.embedder:
similar = self.find_similar_cached(request)
if similar:
return {
**similar,
"from_cache": True,
"similar_to": similar.get("request_id")
}
# Anfrage ausführen
result = self.base_agent.process(request)
# Ergebnis cachen
self.cache.set(
request_fingerprint,
result,
ttl=3600 # Läuft in 1 Stunde ab
)
return {
**result,
"from_cache": False
}
def generate_fingerprint(self, request):
"""
Anfrage-Fingerabdruck generieren
Für exakten Cache-Abgleich
"""
content = json.dumps(request, sort_keys=True)
return hashlib.sha256(content.encode()).hexdigest()
def find_similar_cached(self, request, threshold=0.95):
"""
Semantisch ähnliche zwischengespeicherte Ergebnisse finden
Unter Verwendung der Kosinusähnlichkeit von Embedding-Vektoren
"""
if not self.embedder:
return None
# Anfrage vektorisieren
request_vector = self.embedder.embed([str(request)])[0]
# Im Cache nach ähnlichen Anfragen suchen
best_match = None
best_similarity = 0
for cached in self.cache.get_all():
cached_vector = cached["vector"]
similarity = cosine_similarity(request_vector, cached_vector)
if similarity > best_similarity and similarity >= threshold:
best_similarity = similarity
best_match = cached
return best_match
class ResponseCache:
"""
Antwort-Cache
Einfache In-Memory-Cache-Implementierung
Redis kann in der Produktion verwendet werden
"""
def __init__(self):
self.cache = {}
self.timestamps = {}
def get(self, key):
"""Cache abrufen"""
if key in self.cache:
# Prüfen, ob abgelaufen
if self.is_expired(key):
self.delete(key)
return None
return self.cache[key]
return None
def set(self, key, value, ttl=3600):
"""Cache setzen"""
self.cache[key] = value
self.timestamps[key] = time.time() + ttl
def delete(self, key):
"""Cache löschen"""
if key in self.cache:
del self.cache[key]
if key in self.timestamps:
del self.timestamps[key]
def is_expired(self, key):
"""Prüfen, ob abgelaufen"""
if key not in self.timestamps:
return True
return time.time() > self.timestamps[key]
def get_all(self):
"""Alle Cache-Einträge abrufen"""
return list(self.cache.values())
def cosine_similarity(vec1, vec2):
"""Kosinusähnlichkeit berechnen"""
dot_product = sum(a * b for a, b in zip(vec1, vec2))
norm1 = math.sqrt(sum(a * a for a in vec1))
norm2 = math.sqrt(sum(b * b for b in vec2))
return dot_product / (norm1 * norm2)
Streaming und asynchron
Streaming-Antwort
Streaming-Antworten reduzieren die Wartezeit und verbessern die Benutzererfahrung.
Sie müssen nicht auf die vollständige Antwort warten; Sie können generierten Inhalt schrittweise anzeigen.
Code-Implementierung
Streaming-Antwort-Agent
class StreamingAgent:
"""
Agent, der Streaming-Antworten unterstützt
Während der Generierung zurückgeben, wodurch die Wartezeit reduziert wird
"""
def __init__(self, llm):
self.llm = llm
async def stream_generate(self, prompt):
"""
Generierung von Streaming-Antworten
Async-Generator-Muster verwenden
:yield: Das generierte Textfragment
"""
# Asynchrone Generierungsaufgabe starten
async for chunk in self.llm.stream_generate(prompt):
yield chunk
async def process_stream(self, request):
"""
Streaming-Anfragen verarbeiten
Async-Generator zurückgeben
"""
prompt = self.build_prompt(request)
# Generator zurückgeben
async def generate():
async for chunk in self.stream_generate(prompt):
yield chunk
return generate()
# Verwendungsbeispiel
async def main():
agent = StreamingAgent(llm)
# Streaming-Generierung starten
async for token in agent.stream_generate("Quantencomputing erklären"):
# Während der Generierung ausgeben
print(token, end="", flush=True)
print() # Zeilenumbruch
# ausführen
asyncio.run(main())
Asynchrone Aufgabenwarteschlange
Für lang andauernde Aufgaben verwenden Sie asynchrone Warteschlangenverarbeitung.
Geben Sie sofort zurück, nachdem der Benutzer eine Aufgabe übermittelt hat, und führen Sie sie asynchron im Hintergrund aus.
Implementierung einer asynchronen Aufgabenwarteschlange
from queue import Queue
from threading import Thread
import uuid
class AsyncAgent:
"""
Asynchroner Agent
Verwenden Sie eine Aufgabenwarteschlange, um lang andauernde Aufgaben zu bearbeiten
"""
def __init__(self, agent, task_queue):
# Basis-Agent
self.agent = agent
# Aufgabenwarteschlange
self.task_queue = task_queue
# Ergebnisspeicher
self.results = {}
# Hintergrund-Worker-Thread starten
self.worker_thread = Thread(target=self.process_queue, daemon=True)
self.worker_thread.start()
async def submit(self, task):
"""
Aufgabe übermitteln
Aufgaben-ID sofort zurückgeben
"""
# Eine eindeutige Aufgaben-ID generieren
task_id = str(uuid.uuid4())
# Zur Warteschlange hinzufügen
await self.task_queue.enqueue({
"id": task_id,
"task": task,
"status": "pending",
"created_at": time.time()
})
return task_id
async def get_result(self, task_id):
"""
Aufgabenergebnisse abrufen
nicht blockierend
"""
if task_id in self.results:
return self.results[task_id]
# Aufgabenstatus prüfen
status = await self.task_queue.get_status(task_id)
if status is None:
return None # Aufgabe existiert nicht
if status == "pending" or status == "processing":
return {
"status": status,
"result": None
}
return None
def process_queue(self):
"""
Hintergrund-Worker-Thread
Eine Aufgabe aus der Warteschlange entnehmen und ausführen
"""
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
while True:
task = loop.run_until_complete(self.task_queue.dequeue())
if task:
# Status auf Verarbeitung aktualisieren
loop.run_until_complete(
self.task_queue.update_status(task["id"], "processing")
)
try:
# Aufgabe ausführen
result = self.agent.process(task["task"])
# Ergebnis speichern
self.results[task["id"]] = {
"status": "completed",
"result": result,
"completed_at": time.time()
}
loop.run_until_complete(
self.task_queue.update_status(task["id"], "completed")
)
except Exception as e:
self.results[task["id"]] = {
"status": "failed",
"error": str(e)
}
loop.run_until_complete(
self.task_queue.update_status(task["id"], "failed")
)
class TaskQueue:
"""Einfache Implementierung einer Aufgabenwarteschlange"""
def __init__(self):
self.queue = Queue()
self.statuses = {}
async def enqueue(self, task):
"""Aufgabe zur Warteschlange hinzufügen"""
self.queue.put(task)
self.statuses[task["id"]] = "pending"
async def dequeue(self):
Eine Aufgabe abrufen
if not self.queue.empty():
return self.queue.get()
return None
async def get_status(self, task_id):
Aufgabenstatus abrufen
return self.statuses.get(task_id)
async def update_status(self, task_id, status):
Aufgabenstatus aktualisieren
self.statuses[task_id] = status
Microservices-Design
Teilen Sie das Agent-System in mehrere unabhängige Dienste auf, um Wartbarkeit und Skalierbarkeit zu verbessern.
Service-Partitionierung
| Dienste | Verantwortlichkeiten |
|---|---|
| API Gateway | Einheitlicher Einstiegspunkt, Authentifizierung, Ratenbegrenzung, Routenweiterleitung. |
| Agent-Dienst | Kernlogik, Schlussfolgerung und Planung. |
| Tool-Dienst | Externe API-Integration, Tool-Aufruf. |
| Speicherdienst | Wissensdatenbank, Zustandsverwaltung, Caching. |
| Monitoring-Dienst | Protokollsammlung, Metriküberwachung, Alarmierung. |
Dienstübergreifende Kommunikation
synchrone Kommunikation: HTTP/gRPC, verwendet für Echtzeit-Anfrage-Antwort.
asynchrone Kommunikation: Nachrichtenwarteschlange, verwendet für zeitaufwändige Operationen und Ereignisbenachrichtigungen.
Kapitelzusammenfassung
Dieses Kapitel stellt wichtige Praktiken für Produktionsbereitstellung und Engineering vor.
ProduktionsbereitstellungsarchitekturVon Standalone zu verteilt, schrittweise skalieren.
Containerisierte BereitstellungDocker und Kubernetes verwenden.
KostenkontrolleKosten durch Caching und Token-Optimierung senken.
Streaming und AsynchronitätBenutzererfahrung und Systemdurchsatz verbessern.
Microservice-orientiertes DesignDie Wartbarkeit und Skalierbarkeit des Systems verbessern.
Die Produktionsbereitstellung erfordert eine umfassende Berücksichtigung mehrerer Aspekte wie Zuverlässigkeit, Leistung und Kosten.
Es wird empfohlen, mit einem kleinen Umfang zu beginnen und die Komplexität schrittweise zu erhöhen.
Weitere Erweiterungen