Развёртывание и инженерия AI-агентов в продакшене
Развёртывание в продакшене включает несколько аспектов, таких как надёжность, масштабируемость и контроль затрат.
Инженерные практики обеспечивают стабильную работу системы и простоту сопровождения.
Архитектура развёртывания в продакшене
Продакшен-среды предъявляют повышенные требования к системе.
Необходимо учитывать несколько аспектов, таких как надёжность, масштабируемость, мониторинг и оповещения.
Режимы развёртывания
Развёртывание на одной машине
Подходит для сред разработки и тестирования.
Просто и легко развернуть, но не справляется с трафиком уровня продакшена.
Распределённое развёртывание
Подходит для продакшен-сред; необходимо учитывать множество аспектов.
Балансировка нагрузки: Распределение запросов между несколькими экземплярами.
Обнаружение сервисов: Динамическое управление списком экземпляров.
Управление состоянием: Обработка проблем согласованности распределённого состояния.
Отказоустойчивость: Единая точка отказа не влияет на работу сервиса в целом.
Контейнеризованное развёртывание
Пример Dockerfile
FROM python:3.11-slim
# Задать рабочий каталог
WORKDIR /app
# Установить зависимости
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Скопировать код приложения
COPY . .
# Создать пользователя без прав root (в целях безопасности)
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
# Открыть порт
EXPOSE 8000
# Команда запуска
CMD ["python", "agent.py"]
Развёртывание Kubernetes
Конфигурация развёртывания Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-agent
labels:
app: ai-agent
spec:
# Количество реплик
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
# Лимиты ресурсов
ресурсы:
limits:
memory: "2Gi"
cpu: "1"
requests:
memory: "1Gi"
cpu: "0.5"
# Проверка работоспособности
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
Контроль затрат
Затраты на агентную систему в основном связаны с вызовами LLM API.
Оптимизация затрат — важная тема для развёртывания в продакшене.
Стратегии оптимизации токенов
Сжатие промпта: упростите промпт и сократите ненужный текст.
Усечение контекста: сохранять только ключевое содержимое контекста.
Кэширование ответов: кэшировать ответы для идентичных или похожих запросов.
Реализация кода
Реализация агента оптимизации затрат
import json
from functools import lru_cache
class CostOptimizedAgent:
"""
Агент с оптимизацией затрат
Снижайте затраты на вызовы API с помощью кэширования и сжатия
"""
def __init__(self, base_agent, cache, embedder=Отсутствует):
# Базовый агент
self.base_agent = base_agent
# Хранилище кэша
self.cache = cache
# Embedder (для сопоставления кэша по сходству)
self.embedder = embedder
def process(self, request):
"""
Обработка запросов, включая логику оптимизации затрат
"""
# Сгенерировать отпечаток запроса
request_fingerprint = self.generate_fingerprint(request)
# Проверить точный кэш
cached_result = self.cache.get(request_fingerprint)
if cached_result:
return {
**cached_result,
"from_cache": True
}
# Если есть embedder, проверить кэш семантического сходства
if self.embedder:
similar = self.find_similar_cached(request)
if similar:
return {
**similar,
"from_cache": True,
"similar_to": similar.get("request_id")
}
# Выполнить запрос
result = self.base_agent.process(request)
# Кэшировать результат
self.cache.set(
request_fingerprint,
result,
ttl=3600 # Истекает через 1 час
)
return {
**result,
"from_cache": False
}
def generate_fingerprint(self, request):
"""
Сгенерировать отпечаток запроса
Для точного сопоставления с кэшем
"""
content = json.dumps(request, sort_keys=True)
return hashlib.sha256(content.encode()).hexdigest()
def find_similar_cached(self, request, threshold=0.95):
"""
Найти семантически похожие кэшированные результаты
Использование косинусного сходства векторов эмбеддингов
"""
if not self.embedder:
return None
# Векторизовать запрос
request_vector = self.embedder.embed([str(request)])[0]
# Поиск похожих запросов в кэше
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:
"""
Кэш ответов
Простая реализация кэша в памяти
Redis можно использовать в продакшене
"""
def __init__(self):
self.cache = {}
self.timestamps = {}
def get(self, key):
"""Получить кэш"""
if key in self.cache:
# Проверить, истёк ли срок действия
if self.is_expired(key):
self.delete(key)
return None
return self.cache[key]
return None
def set(self, key, value, ttl=3600):
"""Установить кэш"""
self.cache[key] = value
self.timestamps[key] = time.time() + ttl
def delete(self, key):
"""Удалить кэш"""
if key in self.cache:
del self.cache[key]
if key in self.timestamps:
del self.timestamps[key]
def is_expired(self, key):
"""Проверить, истёк ли срок"""
if key not in self.timestamps:
return True
return time.time() > self.timestamps[key]
def get_all(self):
"""Получить все элементы кэша"""
return list(self.cache.values())
def cosine_similarity(vec1, vec2):
"""Вычислить косинусное сходство"""
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)
Потоковая передача и асинхронность
Потоковый ответ
Потоковые ответы сокращают время ожидания и улучшают пользовательский опыт.
Не нужно ждать полного ответа; можно постепенно отображать сгенерированный контент.
Реализация кода
Агент потоковых ответов
class StreamingAgent:
"""
Агент, поддерживающий потоковые ответы
Возврат во время генерации, сокращая время ожидания
"""
def __init__(self, llm):
self.llm = llm
async def stream_generate(self, prompt):
"""
Генерация потокового ответа
Используйте шаблон асинхронного генератора
:yield: Сгенерированный фрагмент текста
"""
# Запуск задачи асинхронной генерации
async for chunk in self.llm.stream_generate(prompt):
yield chunk
async def process_stream(self, request):
"""
Обрабатывает потоковые запросы
Возвращает асинхронный генератор
"""
prompt = self.build_prompt(request)
# Возврат генератора
async def generate():
async for chunk in self.stream_generate(prompt):
yield chunk
return generate()
# Пример использования
async def main():
agent = StreamingAgent(llm)
# Начать потоковую генерацию
async for token in agent.stream_generate("Объясни квантовые вычисления"):
# Выводить по мере генерации
print(token, end="", flush=True)
print() # новая строка
# запуск
asyncio.run(main())
Асинхронная очередь задач
Для длительных задач используйте асинхронную обработку очередей.
Немедленно возвращайте управление после того, как пользователь отправит задачу, и выполняйте асинхронно в фоне.
Реализация асинхронной очереди задач
from queue import Queue
from threading import Thread
import uuid
class AsyncAgent:
"""
Асинхронный агент
Используйте очередь задач для обработки длительных задач
"""
def __init__(self, agent, task_queue):
# Базовый агент
self.agent = agent
# Очередь задач
self.task_queue = task_queue
# Хранилище результатов
self.results = {}
# Запустить фоновый рабочий поток
self.worker_thread = Thread(target=self.process_queue, daemon=True)
self.worker_thread.start()
async def submit(self, task):
"""
Отправить задачу
Вернуть ID задачи немедленно
"""
# Сгенерировать уникальный ID задачи
task_id = str(uuid.uuid4())
# Добавить в очередь
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):
"""
Получить результаты задачи
неблокирующий
"""
if task_id in self.results:
return self.results[task_id]
# Проверить статус задачи
status = await self.task_queue.get_status(task_id)
if status is None:
return None # Задача не существует
if status == "pending" or status == "processing":
return {
"status": status,
"result": None
}
return None
def process_queue(self):
"""
Фоновый рабочий поток
Взять задачу из очереди и выполнить её
"""
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
while True:
task = loop.run_until_complete(self.task_queue.dequeue())
if task:
# Обновить статус на processing
loop.run_until_complete(
self.task_queue.update_status(task["id"], "processing")
)
try:
# Выполнить задачу
result = self.agent.process(task["task"])
# Сохранить результат
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:
"""Простая реализация очереди задач"""
def __init__(self):
self.queue = Queue()
self.statuses = {}
async def enqueue(self, task):
"""Добавить задачу в очередь"""
self.queue.put(task)
self.statuses[task["id"]] = "pending"
async def dequeue(self):
Получить задачу
if not self.queue.empty():
return self.queue.get()
return None
async def get_status(self, task_id):
Получить статус задачи
return self.statuses.get(task_id)
async def update_status(self, task_id, status):
Обновить статус задачи
self.statuses[task_id] = status
Проектирование микросервисов
Разделите систему Agent на несколько независимых сервисов, чтобы повысить удобство сопровождения и масштабируемость.
Разделение на сервисы
| Сервисы | Обязанности |
|---|---|
| API-шлюз | Единая точка входа, ограничение частоты аутентификации, перенаправление маршрутов. |
| Сервис агента | Основная логика, рассуждения и планирование. |
| Сервис инструментов | Интеграция с внешними API, вызов инструментов. |
| Сервис хранения | База знаний, управление состоянием, кэширование. |
| Сервис мониторинга | Сбор логов, мониторинг метрик, оповещения. |
Межсервисное взаимодействие
синхронное взаимодействие: HTTP/gRPC, используется для обмена запросами и ответами в реальном времени.
асинхронное взаимодействие: Очередь сообщений, используется для длительных операций и уведомлений о событиях.
Краткое содержание главы
В этой главе представлены ключевые практики развертывания в производственной среде и инжиниринга.
Архитектура развёртывания в продакшенеОт автономного к распределённому, масштабируйтесь постепенно.
Контейнеризованное развёртываниеИспользуйте Docker и Kubernetes.
Контроль расходовСокращайте расходы за счёт кэширования и оптимизации токенов.
Потоковая передача и асинхронностьУлучшайте пользовательский опыт и пропускную способность системы.
Микросервисно-ориентированный дизайнПовышайте сопровождаемость и масштабируемость системы.
Развёртывание в продакшене требует комплексного учёта множества аспектов, таких как надёжность, производительность и стоимость.
Рекомендуется начинать с небольшого масштаба и постепенно повышать сложность.
Другие расширения