Галоўная / Артыкулы

Шматагентная даследчая сістэма на LangChain Deep Agents

Практычны погляд на патэрн Deep Agents: аркестратар, спецыялізаваныя агенты, бяспечныя інструменты і фронтэнд на Streamlit, правераныя на лакальных і хмарных LLM.

Выдавец: REACTAPP.TOP10 хв чытання2403 слоў

Крыніцы: 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 або local). Праект выкарыстоўвае LangGraph для стрымінгу, зберагання дадзеных і стварэння пунктав працы, а таксама для трэйсінгу, адмацявання і развертвання праз LangSmith. Спіс функцый включае пад-агентаў з ізольаваным кантэкстам, файловую сістэму на базе падключаемых бэкендоў, калектывнае кераванне кантэкстам, якае зберагае выходы інструментаў на дыск, доступ да шэлу ў сандбоксе, стойкую памяць, апраўленне вызываў інструментаў чалавекам, перадпісныя навыкі, а таксама можлівасць выкарыстоўвання спецыяльных інструментаў або будзь-каго сервера MCP.

Асновныя ідэі

  • Хіерархічная організацыя. Агент-керавальнік отрымае цэль высокага рангу і перадае падзеі спецыялістам, замест таго каб працаваць па кожным кроку ў аднам цыкле.
  • Спецыялізаваныя ролі. Кожны агент мае чыста вялічыну сістэмнага запіту, саве свае інструменты і адзінокае вікно кантэксту для задач такіх, як выкарыстанне інформацыі, вычыслення чыста стварэнне дакумента.
  • Ізольаваная памяць. Кожны агент зберагае саву рабочую памяць, факты чыста эпізоды, тады стан адной фазы не прасваюецца ў іншую.
  • Мысленне тыпу ReAct. Падагенты павтараюць цыклы «Мысль», «Дзеянне», «Абазораванне» па валідаваных інтэрфейсах інструментоў.

Адкалэнне

Дэмавы прыклад — гэта маленькая, навучальная версія, створаная з адпамогай IBM Bob, якая працуе локальна чыста проты модэляў у воблаку. Яна следуе шаблону Deep Agents у скромным масштабе.

Задача працюе па шортае дрэва:

User Task
  └─ OrchestratorAgent (Planning & Synthesis)
      ├─ ResearcherAgent (web_search)
      ├─ AnalystAgent (calculator, code_executor)
      └─ WriterAgent (file_reader)

Чатыры слоя з’еднаюць ўсё разам:

  1. Оркестрацыя (agents/orchestrator.py) прымае даныя ад карыстальніка, ацэнюе ўжоўвышанасць іх, стварае план, парадуе падзадачы па парадку і сінтазуе рэзультаты.
  2. Спецялісты (agents/specialists.py) візначаюць ResearcherAgent, AnalystAgent і WriterAgent.
  3. Інструменты (tools/) забезпечваюць безпечную арыметычную обработку праз AST, ізольаваную експансію Python, абстракцыю для пошуку ў інтэрнэце і працу з файламі в межах сандбокса.
  4. Перадача даных і UI (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, процэнт падышу з часоў дапраўднай працы і набор рэкамендацый па політыцы.

Што адбываецца далей:

  1. Організатор раздзеляе задачу на даследжэнне, анаіз і напісанне.
  2. Даследнік адрабатвае тры пошукі ў інтэрнэце, якія стосуюцца тэндэнцый CO2, адзінакоў на экасістэмы і рамок політыкі.
  3. Аналіцыст выкарыстоўвае інструмент для расчыткаў: (421 - 280) / 280 * 100, што дае прыблізна 50,36% падыш.
  4. Автар складае структураваны документ у фармаце Markdown з кінуцым падсумкам, рэзультатамі даследжэння, аналізам дадзеных і рэкамендацыямі.
  5. Організатор з’еднае усё ў фінальны аўтарапорт.

Рэзультат зберагаецца ў 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>

Дадзенне ўтрамбавання яшчэ аднаго спецыяліста

Новыя інструменты і агенты падходзяць без значных працоў. Чым дадаць спецыяліста:

  1. Створыце agents/my_specialist.py як падклас BaseAgent.
  2. Рэалізуйце атрыбут role_description.
  3. Зарэгіструйце агента ў OrchestratorAgent.__init__.
  4. Дадзіце логіку дэлегавання ў 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 sandbox, а таксама інтэрфейс, які перэдае апдэты без нарабатання палягань у правілах адмаўлень Streamlit. Складныя ланцюгі агентаў можу стабільна працаваць як з хмарнымі канцэнтрамі, так і з локальнымі рэалайзамі LLM, такімі як Ollama і llama.cpp.