Собери своего агента
В этом разделе погрузимся в техническую реализацию агента и разберём подробнее, как это можно адаптировать под свои нужды
Архитектура агентов
Информация о доступных агентах и их различиях находится в Основных концепциях.
Для детального понимания полной логики лучше ознакомиться с исходным кодом.
Интерфейс
Упрощённое представление основного цикла работы:
while agent.state not in FINISH_STATES:
reasoning = await agent._reasoning_phase()
action_tool = await agent._select_action_phase(reasoning)
await agent._action_phase(action_tool)
В BaseAgent предоставлен минимальный интерфейс для модификации поведения агента и работы с контекстом.
Основные атрибуты агента
class BaseAgent:
# Идентификация
id: str # Уникальный идентификатор агента
name: str # Имя класса агента
task: str # Задача для выполнения
# Конфигурация и клиенты
config: AgentConfig # Конфигурация агента
openai_client: AsyncOpenAI # Клиент для работы с LLM API
# Контекст и состояние
_context: AgentContext # Контекст выполнения агента
conversation: list[dict] # История диалога с LLM
# Инструменты и стриминг
toolkit: list[Type[BaseTool]] # Набор доступных инструментов
streaming_generator: OpenAIStreamingGenerator # Генератор стриминга
Методы для переопределения
При создании собственных решений стоит обратить внимание в первую очередь на эти методы:
async def _prepare_context(self) -> list[dict]:
"""Prepare a conversation context with system prompt, task data and any
other context. Override this method to change the context setup for the
agent.
Returns a list of dictionaries OpenAI like format, each containing a role and
content key by default.
"""
return [
{"role": "system", "content": PromptLoader.get_system_prompt(self.toolkit, self.config.prompts)},
{
"role": "user",
"content": PromptLoader.get_initial_user_request(self.task, self.config.prompts),
},
*self.conversation,
]
async def _prepare_tools(self) -> list[ChatCompletionFunctionToolParam]:
"""Prepare available tools for the current agent state and progress.
Override this method to change the tool setup or conditions for tool
usage.
Returns a list of ChatCompletionFunctionToolParam based
available tools.
"""
tools = set(self.toolkit)
if self._context.iteration >= self.config.execution.max_iterations:
raise RuntimeError("Max iterations reached")
return [pydantic_function_tool(tool, name=tool.tool_name) for tool in tools]
async def _reasoning_phase(self) -> ReasoningTool:
"""Call LLM to decide next action based on current context."""
raise NotImplementedError("_reasoning_phase must be implemented by subclass")
async def _select_action_phase(self, reasoning: ReasoningTool) -> BaseTool:
"""Select the most suitable tool for the action decided in the
reasoning phase.
Returns the tool suitable for the action.
"""
raise NotImplementedError("_select_action_phase must be implemented by subclass")
async def _action_phase(self, tool: BaseTool) -> str:
"""Call Tool for the action decided in the select_action phase.
Returns string or dumped JSON result of the tool execution.
"""
raise NotImplementedError("_action_phase must be implemented by subclass")
async def _execution_step(self):
"""Execute a single step of the agent workflow.
Note: Override this method to change the agent workflow for each step.
"""
raise NotImplementedError("_execution_step must be implemented by subclass")
Основные модули агента
AgentConfig
Хранит все настройки агента: параметры LLM, поиска, выполнения, промпты и MCP конфигурацию.
Подробнее о конфигурации
Полное описание системы конфигурации, иерархии настроек и примеры использования находятся в руководстве по конфигурации.
Расширение конфигурации
Имеющиеся схемы конфига позволяют расширять поля без изменения базового класса:
Пример 1: Добавление полей в YAML
agents:
custom_agent:
base_class: "SGRAgent"
execution:
max_iterations: 15
# Кастомные поля
custom_timeout: 300
retry_count: 5
enable_caching: true
tools:
- "WebSearchTool"
- "FinalAnswerTool"
Пример 2: Использование кастомных полей в AgentDefinition
from sgr_agent_core import AgentDefinition
from sgr_agent_core.agent_definition import ExecutionConfig
from sgr_agent_core.agents import SGRAgent
# Кастомные поля можно добавить напрямую в ExecutionConfig
agent_def = AgentDefinition(
name="custom_agent",
base_class=SGRAgent,
execution=ExecutionConfig(
max_iterations=15,
custom_timeout=600,
retry_count=5,
enable_caching=True
),
tools=["WebSearchTool", "FinalAnswerTool"]
)
Пример 3: Использование кастомных полей в агенте
class CustomAgent(BaseAgent):
async def _action_phase(self, tool: BaseTool) -> str:
# Прямой доступ к кастомным полям
timeout = self.config.execution.custom_timeout
retry_count = self.config.execution.retry_count
if self.config.execution.enable_caching:
# Логика с кешированием
pass
result = await tool(self._context, self.config)
return result
Важно: extra='allow'
Благодаря extra="allow" pydantic модели ExecutionConfig, все пользовательские атрибуты или дополнительные поля из YAML автоматически сохраняются и доступны через атрибуты объекта.
LLM адаптер
Клиент для взаимодействия с LLM API. Используется для всех запросов к языковой модели.
В существующих агентах используется openai-python клиент.
Streaming_generator — модуль стриминга
Идея модуля - регистрировать эвенты, происходящие в системе и выдавать результат по мере работы агента. Обеспечивает потоковую передачу ответов от агента в формате, совместимом с OpenAI API.
Стандартно поставляемая реализация содержит OpenAI-like streaming протокол как некоторое компромиссно-универсальное решение для совместимости. В зависимости от задач вашей системы этот модуль стоит переработать под более удобный/лаконичный формат
Для получения стрима используйте асинхронный итератор. События будут пополняться по мере их добавления в генератор:
_context — контекст выполнения
Хранит состояние агента, данные, счётчики и результаты выполнения.
# Основные поля:
state # Идентификатор текущего состояния агента
iteration # Номер текущей итерации
clarifications_used # Количество заданных запросов на уточнения
execution_result # Финальный результат выполнения агента
custom_context # Раздел для любых пользовательских данных
toolkit — набор инструментов
Список классов инструментов, доступных агенту для выполнения действий.
self.toolkit: list[Type[BaseTool]]
# Пример набора инструментов:
self.toolkit = [
WebSearchTool,
ExtractPageContentTool,
CreateReportTool,
FinalAnswerTool
]
# Использование в _prepare_tools():
tools = set(self.toolkit)
# Фильтрация инструментов на основе состояния
if self._context.searches_used >= self.config.search.max_searches:
tools -= {WebSearchTool}
conversation — история диалога
Список сообщений в формате OpenAI для поддержания контекста разговора с LLM.
conversation: list[dict]
# Формат сообщений:
conversation = [
{"role": "system", "content": "..."},
{"role": "user", "content": "..."},
{
"role": "assistant",
"content": "...",
"tool_calls": [{"type": "function", "id": "...", "function": {...}}]
},
{"role": "tool", "content": "...", "tool_call_id": "..."}
]
Примеры использования собственных агентов
Пример 1: Research агент с переопределением _prepare_tools
ResearchSGRAgent демонстрирует, как переопределить _prepare_tools() для динамического управления инструментами на основе состояния агента:
from typing import Type
from sgr_agent_core.agents import SGRAgent
from sgr_agent_core.tools import (
ClarificationTool,
CreateReportTool,
FinalAnswerTool,
WebSearchTool,
)
from sgr_agent_core.next_step_tool import NextStepToolsBuilder, NextStepToolStub
class ResearchSGRAgent(SGRAgent):
async def _prepare_tools(self) -> Type[NextStepToolStub]:
tools = set(self.toolkit)
if self._context.iteration >= self.config.execution.max_iterations: # (1)!
tools = {
CreateReportTool,
FinalAnswerTool,
}
if self._context.clarifications_used >= self.config.execution.max_clarifications: # (2)!
tools -= {ClarificationTool}
if self._context.searches_used >= self.config.search.max_searches: # (3)!
tools -= {WebSearchTool}
return NextStepToolsBuilder.build_NextStepTools(list(tools))
- Если достигнут лимит итераций, оставляем только финальные инструменты для завершения работы
- Если исчерпаны уточнения, убираем
ClarificationToolиз доступных инструментов - Если исчерпаны поиски, убираем
WebSearchToolиз доступных инструментов
Стейтмашина для управления инструментами... Или что-то большее
Для более сложной логики управления инструментами можно использовать более серьёзный движок состояний. Это позволит явно определить состояния агента и правила перехода между ними, что упростит управление доступными инструментами на каждом этапе работы.
Пример 2: Агент для анализа данных
from sgr_agent_core.base_agent import BaseAgent
from sgr_agent_core.models import AgentStatesEnum
from sgr_agent_core.tools import BaseTool, FinalAnswerTool
from sgr_agent_core.tools.reasoning_tool import ReasoningTool
class DataAnalysisTool(BaseTool):
"""Инструмент для анализа данных."""
tool_name: str = "data_analysis"
description: str = "Analyzes provided data"
data: str
async def __call__(self, context, config, **kwargs) -> str:
# Логика анализа данных
return f"Analysis result for: {self.data}"
class DataAnalysisAgent(BaseAgent):
"""Агент для анализа данных."""
name: str = "data_analysis_agent"
async def _select_action_phase(self, reasoning):
if "analyze" in reasoning.remaining_steps:
return DataAnalysisTool(data=self.task)
return FinalAnswerTool(answer="Analysis complete")
async def _action_phase(self, tool):
result = await tool(self._context, self.config)
if isinstance(tool, FinalAnswerTool):
self._context.execution_result = result
self._context.state = AgentStatesEnum.COMPLETED
return result
Общие рекомендации
Важные моменты
-
Наследуйтесь от готовых агентов: Используйте
SGRAgentилиToolCallingAgentкак базовые классы вместоBaseAgent, если вам не нужна полная кастомизация -
Регистрация агентов: Убедитесь, что ваш кастомный агент импортирован в проект или YAML конфигурацию до использования через
AgentFactory -
Асинхронность: Все методы работы с LLM и инструментами должны быть асинхронными
-
Память контекста:
conversationнакапливается во время выполнения, следите за размером и содержанием для избежания снижения качества LLM генерации