Практические советы: ваш ИИ-агент не умный. Вот как создать такого, который
Пошаговое руководство по практическим советам: ваш искусственный интеллект-агент не является умным. Вот как создать агента, который будет выполнять определенные задачи: шаблоны контрактов, проверок и готовые блоки кода для команд, использующих эту архитектуру.
Используйте это как пособие для специалистов, основанное на идеях статьи «Ваш ИИ-агент не умный. Вот как создать того, кто действительно мыслит»: четкие этапы, упорядоченные блоки кода и записи для восстановления, сохраняющиеся при передаче задач.
Оглавление
Этап оглавления работает наилучшим образом, если рассматривать его как измеримую структуру. Соберите один идеальный пример работы, один случай сбоя и записи для возврата к предыдущему состоянию перед расширением объема работы. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. При сбое какого-либо шага причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций. Сохраняйте структуру графа простой и типизированной. Вложенные структуры скрывают информацию о том, какой узел заполнил тот или иной поле, и мешают возобновлению работы после прерываний.
Почему большинство ИИ-агентов — это всего лишь усовершенствованные цепочки промптов
Наиболее эффективно работает этот этап для большинства ИИ-агентов, если рассматривать его как измеримую структуру. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма задачи. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия создаваемым элементам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задачи. Установите лимит токенов на каждый ход и на всю сессию. Инструменты агентов активно расширяют контекст; жёсткие ограничения предотвращают появление неожиданных счетов.
Проблема подхода «Просто используйте ReAct»
Подход «Только стадия» работает наилучшим образом, когда его рассматривают как измеримую среду. Сначала зафиксируйте один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию, прежде чем расширять объём работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счёты при переходе от демо-среды к общедоступным средам. Сохраняйте структуру графа простой и типизированной; вложенные структуры скрывают информацию о том, какой узел заполнил тот или иной поле, и могут нарушить возобновление работы после прерываний.
Архитектура: четыре узла, один цикл
Модель четырех узлов в архитектуре работает наилучшим образом, когда рассматривается как измеримая структура. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь граф. Сохраняйте состояние графа простым и типизированным. Вложенные структуры данных скрывают информацию о том, какой узел записал тот или иной поле, и мешают возобновлению работы после прерываний.
START --> Planner --> Executor <--> Replanner --> Reporter --> END
Управление состоянием: основа всего
Этап управления состоянием The Backbone работает наилучшим образом, когда его рассматривают как измеримую сферу. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Документируйте как успешный, так и восстановительный пути выполнения. Повторные попытки, проверки со стороны человека и обработка неработоспособных сообщений являются частью продукта, а не последующими доработками. Сохраняйте структуру состояния простой и типизированной. Вложенные структуры данных скрывают информацию о том, какой узел заполнил тот или иной поле, и мешают возобновлению работы после прерываний. Этап управления состоянием The Backbone работает наилучшим образом, когда его рассматривают как измеримую сферу. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия элементам, определите критерии успеха и не допускайте безответственного частичного выполнения задач.
import operator
from typing import Annotated, TypedDict
from pydantic import BaseModel, Field
class StrategyState(TypedDict, total=False):
"""Global state that flows through the LangGraph nodes."""
query: str
plan: list[dict]
scratchpad: Annotated[list[dict], operator.add]
current_step: int
final_report: str
replan_count: int
Структурированные схемы вывода: PlanStep и Plan
Для этапа PlanStep схем структурированного вывода необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение затрат на раннем этапе предотвращает неожиданные счета при переходе с демо-среды в общедоступные среды. Необходимо ввести человеческое утверждение для операций, связанных с тратой денег или изменением производственных данных. Подключение компонентов во время компиляции не гарантирует полноты функционала для бизнеса.
AVAILABLE_TOOLS_TEXT = """
- get_metrics(ticker, metric?): Return stock metrics. 'metric' is optional
(P/E, EPS, Revenue, Market Cap, Sector).
- search_news(ticker): Return recent news headlines for a ticker.
- compare_metrics(tickers: list, metric): Compare one metric across
multiple tickers.
"""
class PlanStep(BaseModel):
"""A single executable step inside an analysis plan."""
step_id: int = Field(description="Sequential step number")
tool: str = Field(
description=f"Tool to use. Must be one of:\n{AVAILABLE_TOOLS_TEXT}"
)
args: dict = Field(description="Arguments for the tool call")
purpose: str = Field(description="Why this step is needed")
class Plan(BaseModel):
"""The full plan generated by the planner node."""
goal: str = Field(description="The overall analysis goal")
steps: list[PlanStep] = Field(
description="Ordered list of steps to execute"
)
class ReplanDecision(BaseModel):
"""Output of the replanner node."""
reasoning: str = Field(
description="Analysis of current progress and findings"
)
should_replan: bool = Field(
description="Whether the plan needs modification"
)
updated_steps: list[PlanStep] = Field(
default_factory=list,
description="Remaining steps if replan is needed. Empty if no changes.",
)
Система инструментов: три инструмента, один реестр
Для третьей стадии системы инструментов необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Конфигурацию следует хранить отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Аутентификация происходит на шлюзе, а повторная авторизация — на уровне обработки данных. Один только токен-носитель не является границей между тенантами.
The ToolRegistry
На этапе The ToolRegistry необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Необходимо задокументировать как успешный, так и восстановительный сценарии работы. Повторные попытки, проверки со стороны человека и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Аутентификация происходит на шлюзе, а повторная авторизация — на уровне обработки данных. Одного лишь токена-носителя недостаточно для обозначения границ аренды ресурсов. На этапе The ToolRegistry необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия элементам, определите критерии успеха и не допускайте безусловного частичного завершения задачи.
from langchain_core.tools import BaseTool
from typing import Iterable, Mapping, Any
class ToolRegistry:
"""Namespace-aware container for LangChain tools."""
def __init__(self) -> None:
self._tools_by_toolset: dict[str, dict[str, BaseTool]] = {}
def add_tools(self, toolset: str, tools: Iterable[BaseTool]) -> None:
bucket = self._tools_by_toolset.setdefault(toolset, {})
bucket.update({t.name: t for t in tools})
def get_tools(self, toolset: str) -> tuple[BaseTool, ...]:
return tuple(self._tools_by_toolset.get(toolset, {}).values())
def invoke(
self, toolset: str, tool_name: str, tool_args: Mapping[str, Any]
) -> Any:
t = self._tools_by_toolset.get(toolset, {}).get(tool_name)
if t is None:
raise ValueError(
f"Unknown tool '{tool_name}' in toolset '{toolset}'"
)
return t.invoke(dict(tool_args))
Узел планировщика: думайте перед действием
При работе с этапом планирования необходимо сначала записать условия работы: требуемые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает избегать ошибок при последующих изменениях кода. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды. Создавайте контрольные точки после дорогостоящих операций. Функция возобновления работы не должна снова взимать плату за один и тот же вызов большой языковой модели при повторной попытке выполнения последующего узла.
def planner_node(state: StrategyState) -> dict:
"""Create a step-by-step research plan using structured output."""
planner = model.with_structured_output(Plan)
prompt = PLAN_PROMPT.format(
available_tools=AVAILABLE_TOOLS_TEXT,
ticker_choices=ticker_choices_text(),
metric_choices=metric_choices_text(),
query=state["query"],
)
plan: Plan = planner.invoke(prompt)
steps = [s.model_dump() for s in plan.steps]
return {"plan": steps, "current_step": 0}
PLAN_PROMPT: где находятся ограничения
При работе над этапом PLANPROMPT Where the Guardrails сначала запишите условия контракта: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой чек-лист помогает сохранять честность при последующих изменениях кода. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Храните в кэше стабильные инструкции системы и схемы инструментов. Пересылка одинаковых заголовков — распространенная причина избыточных затрат ресурсов.
PLAN_PROMPT = """\
You are a financial research planner. Given a user's analysis request,
create a step-by-step research plan using the available tools.
Available tools:
{available_tools}
Rules:
- Use only the tools listed above.
- Every plan step must be executable with one of those tools.
- When a tool accepts 'ticker' or 'tickers', use only these exact values:
{ticker_choices}
- When a tool accepts 'metric', use one of these exact values:
{metric_choices}
- There are no other tools available. Final synthesis is handled separately.
Create an efficient plan. Group related lookups. Aim for 4-8 steps.
User request: {query}"""
Узел исполнителя: шаг за шагом
При работе над этапом The Executor Node One сначала запишите условия работы: необходимые входные данные, сигнал о успехе и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Документируйте как успешный, так и восстановительный пути выполнения. Повторные попытки, проверки со стороны человека и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Создавайте контрольные точки после затратных операций. Механизм возобновления работы не должен повторно оплачивать один и тот же вызов LLM при попытке оператора выполнить следующий узел. При работе над этапом The Executor Node One сначала запишите условия работы: необходимые входные данные, сигнал о успехе и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия создаваемым элементам, определите критерии успеха и не допускайте безусловного частичного завершения работы.
MAX_STEPS = 12 # Safety limit on total steps
def executor_node(state: StrategyState) -> dict:
"""Execute the next pending step from the plan."""
plan = state.get("plan", [])
current_step = state.get("current_step", 0)
if current_step >= len(plan):
return {}
if current_step >= MAX_STEPS:
return {"current_step": len(plan)}
step = plan[current_step]
tool_name = step["tool"]
tool_args = step["args"]
try:
result = str(
TOOL_REGISTRY.invoke(
AgentName.EXECUTOR.value, tool_name, tool_args
)
)
status = "Error" if result.startswith("Error:") else "Success"
except Exception as exc:
result = f"Error: {exc}"
status = "Error"
entry = {
"step": current_step + 1,
"tool": tool_name,
"args": tool_args,
"result": result,
"status": status,
}
return {
"scratchpad": [entry],
"current_step": current_step + 1,
}
The Replanner Node: Где происходит самокоррекция
Узел Replanner — идеальное место для работы с этапами, когда они рассматриваются как измеримые объекты. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отображение стоимости на ранних этапах предотвращает неожиданные счёты при переходе от демо-среды к общедоступным средам. Сохраняйте структуру графа простой и типизированной; вложенные структуры скрывают информацию о том, какой узел заполнил тот или иной поле, и могут нарушить возобновление работы после прерываний.
MAX_REPLANS = 2 # Prevent infinite replanning
def replanner_node(state: StrategyState) -> dict:
"""Review progress and optionally modify the remaining plan."""
plan = state.get("plan", [])
current_step = state.get("current_step", 0)
replan_count = state.get("replan_count", 0)
scratchpad = state.get("scratchpad", [])
remaining = plan[current_step:]
if len(remaining) = MAX_REPLANS:
return {}
scratchpad_text = "\n".join(
f"Step {e['step']}: {format_tool_call(e['tool'], e['args'])} "
f"-> [{e['status']}] {e['result'][:150]}..."
for e in scratchpad
)
remaining_text = "\n".join(
f"Step {s['step_id']}: {format_tool_call(s['tool'], s['args'])} "
f"- {s['purpose']}"
for s in remaining
)
replanner = model.with_structured_output(ReplanDecision)
prompt = REPLAN_PROMPT.format(
goal=state["query"],
scratchpad=scratchpad_text,
remaining_steps=remaining_text,
)
decision: ReplanDecision = replanner.invoke(prompt)
if decision.should_replan and decision.updated_steps:
new_steps = plan[:current_step] + [
s.model_dump() for s in decision.updated_steps
]
return {"plan": new_steps, "replan_count": replan_count + 1}
return {"replan_count": replan_count + 1}
REPLAN_PROMPT
Этап REPLANPROMPT работает наилучшим образом, если рассматривать его как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма задачи. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь граф структуры. Установите лимиты на количество токенов за ход и за сессию. Инструменты агентного типа активно расширяют контекст; строгие ограничения предотвращают появление неожиданных счетов при демонстрациях.
REPLAN_PROMPT = """\
You are a financial research planner reviewing progress on a research task.
Original goal: {goal}
Completed steps and findings so far:
{scratchpad}
Remaining steps in the plan:
{remaining_steps}
Based on the findings so far, should the remaining plan change?
If an expected tool failed or revealed something unexpected, add a step
to investigate.
If a step is now redundant, remove it.
Use only the available executable tools already shown in the plan.
Do not add recommendation, summary, or report-writing steps."""
Подключение графа: сборка LangGraph
Этап Wiring the Graph LangGraph работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Документируйте одновременно успешный и восстановительный пути выполнения. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не этапом последующей доработки. Сохраняйте состояние графа простым и типизированным. Вложенные структуры скрывают информацию о том, какой узел заполнил тот или иной поле, и мешают возобновлению работы после прерываний. Этап Wiring the Graph LangGraph работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия элементам, определите критерии успеха и не допускайте безответственного частичного выполнения задач.
from langgraph.graph import StateGraph, END
from enum import Enum
class AgentName(Enum):
PLANNER = "planner"
EXECUTOR = "executor"
REPLANNER = "replanner"
REPORT = "report"
def build_graph():
"""Build and compile the LangGraph planning-agent workflow."""
workflow = StateGraph(StrategyState)
workflow.add_node(AgentName.PLANNER.value, planner_node)
workflow.add_node(AgentName.EXECUTOR.value, executor_node)
workflow.add_node(AgentName.REPLANNER.value, replanner_node)
workflow.add_node(AgentName.REPORT.value, report_node)
workflow.set_entry_point(AgentName.PLANNER.value)
workflow.add_edge(AgentName.PLANNER.value, AgentName.EXECUTOR.value)
workflow.add_edge(AgentName.EXECUTOR.value, AgentName.REPLANNER.value)
workflow.add_conditional_edges(
AgentName.REPLANNER.value,
should_continue_execution,
{
AgentName.EXECUTOR.value: AgentName.EXECUTOR.value,
AgentName.REPORT.value: AgentName.REPORT.value,
},
)
workflow.add_edge(AgentName.REPORT.value, END)
return workflow.compile()
def should_continue_execution(state: StrategyState) -> str:
"""Return the next node name after re-planning."""
if state.get("current_step", 0) >= len(state.get("plan", [])):
return AgentName.REPORT.value
return AgentName.EXECUTOR.value
Пример реальной работы: выполнение одного запроса
Для примера реальной реализации на следующем этапе необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение затрат на раннем этапе предотвращает неожиданные счета при переходе с демо-среды в общедоступные среды. Необходимо ввести человеческое утверждение для операций, связанных с тратой денег или изменением производственных данных. Подключение компонентов во время компиляции не гарантирует полноты реализации в бизнес-среде.
{
"metric": "P/E",
"values": {
"NVDA": 58.3,
"AMD": 102.5
}
}
Что дальше
На этапе «Куда дальше?» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь кодовый граф. Вводите утверждение человека для операций, связанных с тратой денег или изменением производственных данных. Подключение на этапе компиляции не гарантирует полноты бизнес-логики.
Заключительные мысли
На этапе «Заключительные замечания» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Необходимо задокументировать как успешный, так и восстановительный сценарии работы. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не этапом последующей доработки. Внедрять утверждение человеком для операций, связанных с тратой денег или изменением производственных данных. Настройки во время компиляции не заменяют полноты бизнес-логики. На этапе «Заключительные замечания» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия элементам, определите критерии успеха и не допускайте молчаливого частичного завершения работы.
Давайте продолжать учиться вместе
При работе над этапом «Давайте продолжим учиться» сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает избегать ошибок при последующих изменениях кода. Рядом с результатами работы записывайте время выполнения и стоимость токенов или запросов. Отображение затрат с самого начала предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Выполняйте контрольные точки после дорогостоящих операций. Система возобновления работы не должна снова взимать плату за один и тот же вызов большой языковой модели, если оператор попытается выполнить последующий шаг.
Сообщение от нашего основателя
При работе над этапом «Сообщение A» сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист помогает сохранять честность последующих изменений в коде.
Чек-лист операционной работы
При работе над этапом чек-листа операционной работы сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист помогает сохранять честность последующих изменений в коде.
Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. При сбое какого-либо шага причина неудачи должна указывать на конкретную ответственность, а не на запутанную структуру обработки данных.
Пункт контроля после дорогостоящих операций. При возобновлении работы не следует снова взимать плату за один и тот же вызов LLM, если оператор пытается выполнить последующий узел заново.
Фиксируйте версии зависимостей и записывайте хэш изображения, с использованием которого выполнялась демонстрация. Воспроизводимость важнее устного опыта сотрудников.
Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Давайте названия файлам, определим критерии успеха и не допускайте молчаливого частичного выполнения задачи.
Пункт контроля после дорогостоящих операций. При возобновлении работы не следует снова взимать плату за один и тот же вызов LLM, если оператор пытается выполнить последующий узел заново.
Перед переходом на более продвинутую версию стека заморозьте версии, сохраните эталонный отчет для критически важных этапов и убедитесь, что известны шаги для возврата к предыдущей версии. В совместных средах необходимы ограничения на частоту запросов, проверки принадлежности ресурсов и четко определенный ответственный за обновление секретов. Лучше предпочесть надежность, чем красивые одноразовые демонстрации.
Примечание к пакету fea74fe7fb83: не включайте ключи поставщиков в репозиторий, установите лимит токенов на сессию и храните транскрипции рядом с фиксами для оценки, чтобы последующие замены моделей оставались сопоставимыми.