English translation is not available yet. Showing Russian content.
Собрать agentic mesh из 3 агентов
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Собрать agentic mesh из 3 агентов
1. Цель задачи
Научиться конструировать цепочку (mesh) из трёх агентов с передачей контекста через handoff-протокол. Агенты выполняют последовательную обработку запроса: Агент А парсит ввод, Агент Б обогащает контекст, Агент В формирует финальный ответ. Ключевая задача — обеспечить полную передачу промежуточных данных без потерь при каждом handoff.
Ключевой результат Рабочий скрипт (Python), который принимает входной текст, последовательно обрабатывает его тремя агентами и возвращает консолидированный результат. Контекст (все поля, сгенерированные Агентом А) должен быть доступен Агенту В без изменений.
2. Исходные данные
| Что нужно | Откуда взять |
|---|---|
| OpenTelemetry SDK (или аналог для трассировки) | pip install opentelemetry-api opentelemetry-sdk |
| Lightweight LLM (локальный или API) | Например, Ollama с моделью llama3.2:1b |
| Фреймворк для агентов | CrewAI (v0.105+) или LangGraph (опционально) |
| Тестовые входные данные | Сгенерировать самостоятельно: 3–5 запросов пользователя |
| Набор тестов на потерю контекста | Написать самому (сравнение входных и выходных полей) |
| Репозиторий Git | Локальный или GitHub |
Если нет реального инструмента — симулируем:
- Ollama — если нет возможности поставить, используем fastchat с эмулятором или заглушку: функция mock_llm(text) -> {"response": f"mock: {text}"}.
- OpenTelemetry — можно заменить простым JSON-логгером (записывать входящий/исходящий контекст в файл).
- CrewAI — если не ставится, пишем собственный минимальный фреймворк на dataclasses и asyncio (в этапе 1 дана заготовка).
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Язык | Python 3.12+ | Основной язык |
| Управление зависимостями | Poetry / pip + requirements.txt | Фиксация версий |
| LLM (локальный) | Ollama + llama3.2:1b | Генерация ответов агентов |
| Фреймворк агентов | CrewAI 0.105+ или самописный | Оркестрация агентов и handoff |
| Передача контекста | Pydantic models | Валидация и сериализация |
| Наблюдаемость | OpenTelemetry + Jaeger (опционально) | Трассировка handoff |
| Тестирование | pytest + custom asserts | Проверка контекста |
| CI (опционально) | GitHub Actions | Прогон тестов |
4. Этапы выполнения
Этап 1: Проектирование контрактов и моделей контекста (40 мин)
Действия
-
Определить структуру контекста — единый Pydantic model, который передаётся между агентами.
from pydantic import BaseModel, Field class AgentContext(BaseModel): user_query: str agent_a_output: str | None = None agent_b_enriched: str | None = None agent_c_final: str | None = None meta: dict = Field(default_factory=dict) -
Спроектировать HandoffContract — dataclass, который содержит контекст и управляющую информацию.
from dataclasses import dataclass @dataclass class HandoffSignal: source: str # имя агента-отправителя target: str # имя агента-получателя context: AgentContext -
Создать минимальную абстракцию агента:
from abc import ABC, abstractmethod class BaseAgent(ABC): name: str @abstractmethod async def process(self, signal: HandoffSignal) -> HandoffSignal: ... -
Подготовить заготовку для 3 агентов (заглушки с принтами). Агент А парсит запрос, Агент Б обогащает, Агент В формирует ответ.
Ожидаемый результат этапа Файл models.py с AgentContext, HandoffSignal, BaseAgent. Три класса-заглушки в agents/.
Этап 2: Реализация Агента А — ввод и парсинг (1 час)
Действия
-
Написать класс AgentA, который:
- Принимает HandoffSignal, где context.user_query заполнен.
- Вызывает LLM (Ollama) с промптом: «Извлеки из запроса ключевые сущности (сущность, действие, объект). Ответь JSON-строкой».
- Парсит ответ и записывает context.agent_a_output = ....
- Возвращает новый HandoffSignal с target="B".
-
Подключить реальный LLM через универсальный интерфейс:
class LLMClient: async def generate(self, prompt: str) -> str: # вызов Ollama или mockСоздать
llm_client.py. -
Написать unit-тест для AgentA: передать фиктивный сигнал с query, проверить, что
agent_a_outputне None и содержит поля "entity", "action", "object".
Ожидаемый результат этапа agents/agent_a.py с полной реализацией. Тест проходит.
Этап 3: Реализация Агента Б и Агента В (1.5 часа)
Действия
-
AgentB (обогащение):
-
AgentC (финальный ответ):
- Промпт: «У тебя есть пользовательский запрос: {user_query}, сущности: {agent_a_output}, обогащение: {agent_b_enriched}. Сформируй понятный ответ для пользователя (1-2 предложения).»
- Результат сохранить в context.agent_c_final.
-
Реализовать оркестратор — MeshOrchestrator:
class MeshOrchestrator: def __init__(self, agents: dict[str, BaseAgent]): self.agents = agents async def run(self, user_query: str) -> AgentContext: ctx = AgentContext(user_query=user_query) signal = HandoffSignal(source="user", target="A", context=ctx) for step in ["A", "B", "C"]: agent = self.agents[step] signal = await agent.process(signal) # проверка, что target изменился на следующий assert signal.target == step # или другой механизм return signal.context -
Написать интеграционный тест — полный прогон с тремя агентами и проверкой всех полей контекста.
Ожидаемый результат этапа Работающий пайплайн. При запуске orchestrator.run("Как установить Python?") получаем цепочку вызовов LLM и финальный ответ.
Этап 4: Мониторинг передачи контекста (40 мин)
Действия
-
Добавить трассировку с OpenTelemetry:
from opentelemetry import trace tracer = trace.get_tracer(__name__) # в каждом агенте: with tracer.start_as_current_span(f"agent_{self.name}") as span: # span.set_attribute("context.size", len(context.json())) # span.set_attribute("context.fields", str(context.dict().keys())) -
Создать скрипт проверки потери данных —
test_context_integrity.py:- Сохранить копию context после AgentA.
- После AgentB — проверить, что
agent_a_outputне изменился. - После AgentC — проверить, что все поля до
agent_c_finalостались неизменными.
-
Запустить 5 разных запросов и собрать статистику (число полей, размер context до / после).
-
Визуализировать в Jaeger (опционально) или вывести JSON-лог в файл.
Ожидаемый результат этапа Логи / трассы, подтверждающие полную передачу контекста. Тест integrity проходит.
Этап 5: Рефакторинг и документирование (30 мин)
Действия
- Вынести LLM-промпты в отдельные файлы
prompts/agent_a.txt,prompts/agent_b.txt,prompts/agent_c.txt. - Добавить обработку ошибок: retry при падении LLM, timeout.
- Написать README.md с инструкцией по запуску, примером вывода и архитектурой.
Ожидаемый результат этапа Код приведён к читаемому виду. README содержит пример запуска python main.py "Мой запрос".
5. Критерии приемки (Definition of Done)
- Реализованы 3 агента с handoff через HandoffSignal.
- Оркестратор последовательно вызывает агентов в порядке A → B → C.
- После прохождения всех агентов контекст содержит все три поля:
agent_a_output,agent_b_enriched,agent_c_final. - Набор из 5 тестовых запросов проходит интеграционный тест без изменения
agent_a_outputпосле AgentB и AgentC. - Код покрыт юнит-тестами (минимум 1 на агента) — pytest, тесты проходят.
- В трассировке (или логах) видны точки handoff и размер контекста.
- README содержит команду запуска и описание архитектуры.
- Репозиторий Git с историей коммитов.
6. Ожидаемый результат
Основной артефакт Папка проекта agentic_mesh/ с файлами:
| Файл | Содержание |
|---|---|
models.py | AgentContext, HandoffSignal, BaseAgent |
llm_client.py | Абстракция LLM (Ollama или mock) |
agents/agent_a.py | Реализация AgentA |
agents/agent_b.py | Реализация AgentB |
agents/agent_c.py | Реализация AgentC |
orchestrator.py | MeshOrchestrator |
main.py | Точка входа (CLI) |
tests/test_agents.py | Unit-тесты |
tests/test_integrity.py | Тест на потерю контекста |
prompts/ | Тексты промптов |
README.md | Документация |
Опционально Docker-compose с Ollama и Jaeger для локального запуска.
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| Ollama не отвечает или модель не найдена | Использовать mock-функцию; проверить ollama pull llama3.2:1b |
| Контекст теряется при handoff (изменение полей) | Добавить deepcopy(context) перед передачей; использовать Pydantic .model_copy(deep=True) |
| Промпты слишком длинные — превышение контекста LLM | Сократить вывод агента A до ключевых полей; добавить тримминг |
| Асинхронный вызов вызывает race condition | Использовать asyncio.Lock при записи в общий контекст (не требуется, т.к. последовательно) |
| Отсутствие OpenTelemetry | Заменить на простой logging.info() с сериализацией контекста |
8. Бюджет времени (оценка)
| Этап | Время |
|---|---|
| Этап 1: Проектирование контрактов | 40 мин |
| Этап 2: Агент A + LLM | 1 час |
| Этап 3: Агенты B, C + оркестратор | 1.5 часа |
| Этап 4: Мониторинг и тест integrity | 40 мин |
| Этап 5: Рефакторинг, README | 30 мин |
| Итого | ~4 часа 20 мин |
Примечание: Если используется самописный фреймворк вместо CrewAI, время увеличится на 1 час (первый этап более детальный). При использовании готового CrewAI — экономия ~30 мин.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 3 | Как обеспечить идемпотентность при handoff? |
| 17 | Паттерны отказоустойчивости в agentic-системах |
| 41 | Проектирование контрактов между агентами (Pydantic vs Protobuf) |
| 58 | Тестирование интеграций с LLM (mock vs sandbox) |
| 94 | Мониторинг потери контекста в цепочках агентов |
| 112 | Сравнение CrewAI и LangGraph для последовательных пайплайнов |
| 201 | Обработка ошибок в асинхронных agentic-мешах |
| 215 | Prompts engineering: как избежать потери контекста при handoff |
| 330 | OpenTelemetry в распределённых AI-системах |
| 411 | Стратегии версионирования контекстов агентов |
10. Чек-лист самопроверки
- Я спроектировал контракты (AgentContext, HandoffSignal) до написания кода.
- Каждый агент модифицирует только своё поле, не трогая чужие.
- Оркестратор не изменяет контекст вручную — только через handoff.
- Я запустил тест integrity на 5 разных запросах и убедился, что потерь нет.
- В README описаны шаги для запуска (установка зависимостей, запуск Ollama, выполнение
main.py). - Я добавил хотя бы один unit-тест на каждого агента (mock LLM).
- Код выложен в Git с минимум двумя коммитами (черновик и финальная версия).