Створення багатоагентної дослідницької системи з використанням LangChain Deep Agents
Практичний огляд паттерну Deep Agents: оркестратор, спеціалізовані агенти, безпечні інструменти та фронтенд у форматі Streamlit, протестовані з локальними та хмарними моделями LLM.
Джерела: Deep Agents in Action: Building a Multi-Agent Research System
Одиночні цикли запитів поступаються місцем багатоагентним системам. Нова структура розподіляє обов’язки, зберігає стан між кроками та надає кожному агенту вузький набір інструментів. Тестова версія ще одного оркестратора, створеного на основі цієї ідеї — Deep Agents від LangChain, є гарним прикладом дослідження; ось що він робить та як було створено невеликий дослідницький додаток на його основі.
Що вирішує архітектура Deep Agents
LangChain нещодавно оголосив про керовану платформу для запуску топологій глибоких агентів. Цей підхід спрямований на усунення трьох слабкостей окремого агента, який виконує складне завдання: знижується обсяг контексту, агент не може вирішити, який інструмент обрати, а помилки міркувань наростають з кожним кроком.
Структура є ієрархічною:
- Оркестратор верхнього рівня розбиває складне завдання на частини та передає кожну з них спеціалістові.
- Кожен спеціаліст працює у власному ізольованому циклі ReAct з власними інструментами та пам’яттю.
- Спеціалісти надсилають структуровані результати, а оркестратор їх об’єднує.
- Ці елементи взаємопов’язані, тож оркестратор може сам слугувати підагентом для когось іншого.
У порівнянні з одним агентом, який робить усе сам, різниця виглядає так:
| Single Agent | Deep Agents |
| ---------------------------------- | ------------------------------------------------- |
| One context window gets overloaded | Each agent has an isolated, focused context |
| All reasoning in one prompt | Specialised reasoning per domain |
| Hard to scale | Add specialists without changing the orchestrator |
| Hard to debug | Full delegation trace for auditability |
Власна документація цієї платформи описує її як найшвидший шлях до агентів, керованих LLM, з вбудованими файловими системами для керування контекстом, створення підагентів та довгостроковою пам’яттю. Планування завдань та навички є додатковими функціями. У ній перелічені такі можливості: дія в середовищі за допомогою інструментів, файлів та виконання коду; отримання пам’яті, навичок та доменних знань у потрібний момент; узагальнення історії та виведення великих результатів за межі контексту; делегування завдань підагентам у окремих вікнах контексту; зупинка для схвалення людиною у критичних моментах; а також оновлення пам’яті, навичок та запитів на основі реального використання.
У репозиторії проекту зазначено, що це інструмент відкритого коду з чітко визначеною архітектурою, який готовий до використання зразу після завантаження, і його можна розширювати, перевизначати чи замінювати будь-яку його частину. За замовчуванням налаштовано роботу з довгими багатокроковими завданнями; приймаються будь-які моделі виклику інструментів (frontier, open-weight чи локальні). Інструмент базується на LangGraph для стрімінгу даних, їх зберігання та створення контрольних точок, а також для відстеження дій, оцінки та розгортання через LangSmith. Серед функцій — підагенти з ізольованим контекстом, файлова система на основі підключуваних бекендів, керування контекстом із збереженням результатів роботи інструментів на диску, доступ до шеллу в сендбоксі, постійна пам’ять, схвалення викликів інструментів людиною, повторно використовувані навички, а також можливість створення власних інструментів чи будь-якого сервера MCP.
Основні ідеї
- Ієрархічна організація. Агент-керівник отримує ціль високого рівня та передає підзавдання спеціалістам, замість того щоб проходити кожен крок у одному циклі.
- Спеціалізовані ролі. Кожен агент має чітко визначений системний запит, власні інструменти та окреме вікно контексту для завдань таких як пошук інформації, обчислення чи складання документа.
- Ізольована пам’ять. Кожен агент зберігає власну робочу пам’ять, факти та записи подій, тож дані з однієї фази не потрапляють у іншу.
- Міркування типу ReAct. Підагенти повторюють цикли „Мислення“, „Дія“, „Спостереження“ через перевірені інтерфейси інструментів.
Реалізація
Демонстраційний додаток — це невелика, навчальна версія, створена за допомогою IBM Bob, яка працює локально або з використанням хмарних моделей. Він дотримується патерну Deep Agents у помірних масштабах.
Завдання проходить через коротке дерево:
User Task
└─ OrchestratorAgent (Planning & Synthesis)
├─ ResearcherAgent (web_search)
├─ AnalystAgent (calculator, code_executor)
└─ WriterAgent (file_reader)
Чотири шари забезпечують її цілісність:
- Оркестрація (
agents/orchestrator.py) отримує вхідні дані від користувача, оцінює їх складність, створює план, послідовно доручає підзавдання та об’єднує результати. - Спеціалісти (
agents/specialists.py) визначаютьResearcherAgent,AnalystAgentтаWriterAgent. - Інструменти (
tools/) забезпечують безпечну арифметичну обробку через AST, ізольовану експекутацію Python, абстракцію веб-пошуку та роботу з файлами у безпечному середовищі. - Передавач та інтерфейс користувача (
app.pyтаmain.py) пропонують фронтенд Streamlit, який асинхронно синхронізує стан між потоками та в реальному часі відстежує етапи, а також інтерфейс командного рядка.
Це звичайний Python із Streamlit для інтерфейсу. Для локальних обчислень його тестували з Ollama, а llama.cpp також підтримується через налаштування.
Структура папок, визначена вимогами:
deepagents-usecase/
├── agents/
│ ├── __init__.py # Package exports
│ ├── base.py # Abstract BaseAgent + ReAct loop
│ ├── llm_client.py # LLM adapter (Ollama/llama.cpp/OpenAI/Anthropic)
│ ├── messages.py # Typed message protocol
│ ├── orchestrator.py # OrchestratorAgent (top-level)
│ ├── researcher.py # ResearcherAgent specialist
│ ├── analyst.py # AnalystAgent specialist
│ └── writer.py # WriterAgent specialist
├── tools/
│ ├── __init__.py
│ ├── base.py # BaseTool + ToolResult
│ ├── calculator.py # Safe AST-based arithmetic evaluator
│ ├── code_executor.py # Sandboxed Python execution (exec with allow-list)
│ ├── file_reader.py # Sandboxed file reading (input/ only)
│ └── web_search.py # Web search (stub + live Tavily)
├── memory/
│ ├── __init__.py
│ └── store.py # AgentMemoryStore (short/long-term/episodic)
├── config/
│ ├── __init__.py
│ └── settings.py # Centralised env-based configuration
├── tests/
│ ├── test_tools.py # Tool unit tests (71 tests)
│ ├── test_memory.py # Memory unit tests (24 tests)
│ ├── test_agents.py # Agent integration tests (92 tests)
│ └── test_code_executor.py # Code Executor tests (134 tests)
├── input/ # Input documents (content gitignored)
├── output/ # Generated reports (content gitignored)
├── memory/ # Persistent agent memory (JSON files)
├── scripts/
│ ├── start.sh # Launch Streamlit in detached mode
│ ├── stop.sh # Gracefully stop the application
│ ├── cleanup.sh # Remove .venv, __pycache__, etc.
│ └── check_code_executor.py # Standalone Code Executor sanity-check
├── Docs/
│ ├── Architecture.md # Mermaid architecture diagrams
│ ├── Quickstart.md # Step-by-step getting started guide
│ ├── API.md # Full public API reference
│ └── CodeExecutorVerification.md # Code Executor test & verification guide
├── app.py # Streamlit web UI
├── main.py # CLI entry point
├── requirements.txt
├── .env.example # Environment variable template
└── README.md
Конфігурація є гнучкою, тож код адаптується до поширених налаштувань. Бекенд може взаємодіяти з локальними середовищами виконання, такими як llama.cpp та Ollama, або з постачальниками хмарних послуг через API-ключі. Файл середовища з примітками описує всі налаштування:
# =============================================================================
# Deep Agents System — Environment Configuration
# =============================================================================
# Copy this file to .env and fill in your values.
# NEVER commit the .env file to version control.
#
# Usage:
# cp .env.example .env
# # edit .env with your actual keys
# =============================================================================
# ─── LLM Provider ──────────────────────────────────────────────────────────
# Select ONE provider. Comment out the others.
# Option A: Ollama (local, default — no API key needed)
LLM_PROVIDER=ollama
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=llama3.2
# Option B: llama.cpp (local)
# LLM_PROVIDER=llamacpp
# LLAMACPP_BASE_URL=http://localhost:9931/v1
# LLAMACPP_MODEL=local-model
# Option C: OpenAI
# LLM_PROVIDER=openai
# OPENAI_API_KEY=sk-...
# OPENAI_MODEL=gpt-4o
# Option D: Anthropic
# LLM_PROVIDER=anthropic
# ANTHROPIC_API_KEY=sk-ant-...
# ANTHROPIC_MODEL=claude-3-5-sonnet-20241022
# ─── Application Settings ──────────────────────────────────────────────────
APP_PORT=8501
APP_HOST=0.0.0.0
LOG_LEVEL=INFO
# ─── Agent Configuration ───────────────────────────────────────────────────
# Maximum reasoning steps per agent (lower = faster on slow local LLMs)
MAX_AGENT_STEPS=8
# Maximum tokens per LLM call (1024 is enough for ReAct; raise for longer reports)
MAX_TOKENS=1024
# Temperature for LLM responses (0.0 = deterministic, 1.0 = creative)
TEMPERATURE=0.1
# ─── Memory & Storage ──────────────────────────────────────────────────────
# Directory for persistent agent memory (relative to project root)
MEMORY_DIR=./memory
# Maximum number of memories to retain per agent
MAX_MEMORY_ENTRIES=100
# ─── Tool Configuration ────────────────────────────────────────────────────
# Enable or disable specific tools (true/false)
TOOL_WEB_SEARCH_ENABLED=true
TOOL_CALCULATOR_ENABLED=true
TOOL_FILE_READER_ENABLED=true
TOOL_CODE_EXECUTOR_ENABLED=true
# Web search stub — set to real Tavily/SerpAPI key for live search
# TAVILY_API_KEY=tvly-...
# ─── Output ────────────────────────────────────────────────────────────────
OUTPUT_DIR=./output
Код, який варто переглянути
Цей додаток призначений для демонстрації того, як завдання розділяються та передаються до окремих наборів інструментів. Він створений так, щоб його можна було розширювати, тож додаткові інструменти та фахівці легко вписуються в його структуру.
Безпечний оцінювач арифметичних виразів
Дозволити моделі виконувати довільний код для виконання математичних операцій створювало б проблеми з безпекою. Тому калькулятор аналізує вирази за допомогою модуля ast мови Python та приймає лише визначений список допустимих типів вузлів та функцій, ніколи не використовуючи пряме виконання eval():
# Whitelisted AST operators and math functions
_SAFE_OPERATORS = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
ast.USub: operator.neg,
}
_SAFE_FUNCTIONS = {
"abs": abs, "round": round, "sqrt": math.sqrt,
"sin": math.sin, "cos": math.cos, "log": math.log,
"pi": math.pi, "e": math.e,
}
def _safe_eval(node: ast.expr) -> float:
"""Recursively evaluate an AST expression node in a safe sandbox."""
if isinstance(node, ast.Constant):
if isinstance(node.value, (int, float)):
return float(node.value)
raise ValueError(f"Unsupported constant type: {type(node.value).__name__}")
if isinstance(node, ast.BinOp):
op_type = type(node.op)
if op_type not in _SAFE_OPERATORS:
raise ValueError(f"Unsupported binary operator: {op_type.__name__}")
left = _safe_eval(node.left)
right = _safe_eval(node.right)
return _SAFE_OPERATORS[op_type](left, right)
if isinstance(node, ast.Call):
func_name = node.func.id
if func_name not in _SAFE_FUNCTIONS:
raise ValueError(f"Function '{func_name}' is not whitelisted.")
args = [_safe_eval(a) for a in node.args]
return _SAFE_FUNCTIONS[func_name](*args)
raise ValueError(f"Unsupported AST node type: {type(node).__name__}")
Екзекутор коду в ізольованому середовищі
Для обробки великих обсягів даних екзекутор забезпечує ізольоване простір імен із обмеженими вбудованими функціями, кількома науковими модулями, які вже імпортовані, перенаправленням результатів та таймаутом на основі потоків:
def _build_exec_namespace() -> dict[str, Any]:
return {
"__builtins__": _SAFE_BUILTINS, # Explicit whitelist (no open, __import__, eval)
"math": math,
"statistics": statistics,
"pi": math.pi,
"e": math.e,
}
def _execute_code(code: str, timeout: int = 5) -> ToolResult:
exec_result = _ExecResult()
captured_io = io.StringIO()
def _worker():
namespace = _build_exec_namespace()
namespace["__builtins__"]["print"] = lambda *args, **kw: print(*args, **{**kw, "file": captured_io})
try:
exec(code, namespace)
exec_result.stdout = captured_io.getvalue()
except Exception as exc:
exec_result.error = f"{type(exc).__name__}: {exc}"
thread = threading.Thread(target=_worker, daemon=True)
thread.start()
thread.join(timeout=timeout)
if thread.is_alive():
return ToolResult(success=False, error=f"Execution timed out after {timeout}s.")
Безпечний для багатопотокової роботи релелятор для інтерфейсу Streamlit
Робочі потоки Streamlit не можуть безпосередньо записувати дані у st.session_state, оскільки в них відсутній ScriptRunContext. Для обходу цієї проблеми додаток використовує синглтонний релелятор, який кешується за допомогою @st.cache_resource та захищається рекурентним замком (threading.RLock), що дозволяє етапам виконання агента передаватися через межі потоків до сторінки:
class _PipelineRelay:
"""Thread-safe relay store for the running multi-agent pipeline."""
def __init__(self) -> None:
self.lock = threading.RLock()
self.status_messages: list[dict[str, str]] = []
self.agent_cards: dict[str, list[str]] = {}
self.running: bool = False
def append_status(self, msg_dict: dict[str, str]) -> None:
with self.lock:
self.status_messages.append(msg_dict)
agent_id = msg_dict.get("agent_id", "orchestrator")
self.agent_cards.setdefault(agent_id, []).append(msg_dict.get("detail", ""))
@st.cache_resource
def _get_relay() -> _PipelineRelay:
return _PipelineRelay()
Приклад виконання завдання: зміна клімату
Завдання полягало у аналізі того, як зростання концентрації CO2 впливає на екосистеми у всьому світі, включаючи поточний рівень CO2, відсоток зростання з доіндустріальних часів та низку рекомендацій щодо політики.
Що відбувається далі:
- Організатор розділяє завдання на дослідження, аналіз та написання тексту.
- Дослідник виконує три пошукові запити в Інтернеті щодо тенденцій CO2, впливу на екосистеми та політичних рамок.
- Аналітик використовує калькулятор:
(421 - 280) / 280 * 100, що дає приблизно 50,36% зростання. - Автор складає структурований документ у форматі Markdown із виконавчим резюме, результатами дослідження, аналізом даних та рекомендаціями.
- Організатор об’єднує все це у остаточний звіт.
Результат зберігається у output/deep_agents_report_<timestamp>.md, а зразок виглядає так:
Benefits of Renewable Energy and Calculation of 280 Times 42
Benefits of Renewable Energy
Renewable energy reduces greenhouse gas emissions, contributing to climate change mitigation (Source: [National Renewable Energy Laboratory](https://www.nrel.gov/renewables/energy-benefits.html)). This is a key finding supported by credible sources, including the International Renewable Energy Agency (2020) and the World Health Organization (2020).
Renewable energy creates jobs and stimulates local economies (Source: [International Renewable Energy Agency](https://www.irena.org/publications/2020/Jun/Global-Status-Report-2020)). This is a significant benefit highlighted by the International Energy Agency (2020) and the National Bureau of Economic Research (2020).
Renewable energy improves air quality and public health (Source: [World Health Organization](https://www.who.int/news-room/fact-sheets/detail/air-pollution)). This is a critical aspect of renewable energy, supported by the National Renewable Energy Laboratory (n.d.).
The global renewable energy market is projected to reach 30% of total energy production by 2025 (Source: [International Energy Agency](https://www.iea.org/news/pressrelease/2020/june/global-renewables-report-2020/)). This growth is expected to reduce energy costs by 10-30% compared to fossil fuels (Source: [National Bureau of Economic Research](https://www.nber.org/papers/w28822)).
Calculation of 280 Times 42
The calculation of 280 times 42 yields 11,840. This result is supported by the Data Analysis report, which provides a detailed calculation of the product (280 * 42 = 11,760).
Trend Analysis
The global renewable energy market is expected to grow significantly, with a projected 30% share of total energy production by 2025. This growth is expected to have a significant impact on the environment and the economy.
Key Insights
1. Renewable energy can significantly reduce greenhouse gas emissions and improve air quality.
2. Renewable energy can create jobs and stimulate local economies.
Limitations
The data provided is based on projections and may not reflect actual outcomes.
Sources
1. National Renewable Energy Laboratory. (n.d.). Energy Benefits of Renewable Energy. Retrieved from <https://www.nrel.gov/renewables/energy-benefits.html>
2. International Renewable Energy Agency. (2020). Global Status Report 2020. Retrieved from <https://www.irena.org/publications/2020/Jun/Global-Status-Report-2020>
3. World Health Organization. (2020). Air pollution. Retrieved from <https://www.who.int/news-room/fact-sheets/detail/air-pollution>
4. International Energy Agency. (2020). Global Renewables Report 2020. Retrieved from <https://www.iea.org/news/pressrelease/2020/june/global-renewables-report-2020/>
5. National Bureau of Economic Research. (2020). The Economics of Renewable Energy. Retrieved from <https://www.nber.org/papers/w28822>
Додавання ще одного спеціаліста
Нові інструменти та агенти легко інтегруються. Щоб додати спеціаліста:
- Створіть файл
agents/my_specialist.pyяк підклас класуBaseAgent. - Реалізуйте властивість
role_description. - Зареєструйте агента у функції
OrchestratorAgent.__init__. - Додайте логіку делегування у функцію
OrchestratorAgent.run_task.
Шаблон для початку роботи:
from agents.base import BaseAgent
class MySpecialistAgent(BaseAgent):
def __init__(self, llm_client=None, on_status=None):
super().__init__(
agent_id="my_specialist",
tools=[my_custom_tool],
llm_client=llm_client,
on_status=on_status,
)
@property
def role_description(self) -> str:
return "You are a specialist that does X…"
Висновки
Цей проект демонструє практичне застосування паттерну Deep Agents: делегування завдань у межах ієрархії, забезпечення безпеки інструментів за допомогою сандбоксу AST, а також інтерфейс користувача, який передає оновлення без порушення правил потоків у Streamlit. Складні конвеєри агентів можуть стабільно працювати як з хмарними кінцевими точками, так і з локальними середовищами виконання LLM, такими як Ollama та llama.cpp.