Agent Selection
Алгоритм выбора агента для обработки пользовательского запроса. Реализация: bot/app/services/agent_selection.py
Входные параметры
async def select_agent(
db: "Database",
*,
intent: str = "", # Интент пользователя (web_search, code, sql...)
process_code: str = "", # Код процесса (default_chat, daily_digest...)
scope: str = "direct", # direct / group
user_text: str = "", # Текст запроса (для контекста)
complexity: str = "simple", # simple / complex / high
team_id: Optional[str] = None,
chat_mode: str = "direct",
) -> dict[str, Any]:intent
Классифицированный интент из классификатора. Примеры:
- • web_search / web / research
- • code / sql / db / data
- • planning / plan / decompose
- • summarize / summary / digest
- • review / evaluate
- • workflow / automate / schedule
process_code
Код процесса, если запрос пришёл через процесс:
- • default_chat
- • web_search / web_research_brief
- • daily_digest
- • kpi_coach / deadline_report
Алгоритм выбора
Поиск по политике
Запрос к agent_selection_policy с matching по intent, process_code, scope:
matched = await db.find_agent_by_policy(
intent=intent,
process_code=process_code,
scope=scope,
)
-- SQL запрос (пример):
SELECT * FROM agent_selection_policy
WHERE is_active = true
AND (match_intent @> ARRAY[intent] OR match_process_code @> ARRAY[process_code])
AND scope = ANY(match_scope)
ORDER BY priority DESC
LIMIT 1;Fallback на core_orchestrator
Если агент не найден, используется core_orchestrator:
FALLBACK_AGENT = "core_orchestrator"
if matched and matched.get("agent_code"):
agent_code = matched["agent_code"]
else:
agent_code = FALLBACK_AGENT
selection_reason_parts.append("fallback=core_orchestrator")Загрузка профиля и методов
Загружаются agent_profile, agent_methods, agent_sub_agents:
profile = await db.get_agent_profile(agent_code)
methods = await db.get_agent_methods(agent_code)
sub_agents = await db.get_agent_sub_agents(agent_code)Сборка prompt из фрагментов
Сначала ищутся явно привязанные фрагменты, если нет — авто-подбор:
mapped_fragments = await db.get_agent_prompt_fragments(agent_code)
if not mapped_fragments:
# Авто-подбор по agent_family и fragment_type
agent_family = _infer_agent_family(agent_code)
fragment_type = _infer_fragment_type(intent)
top_fragments = await db.get_top_prompt_fragments(
fragment_type=fragment_type,
agent_family=agent_family,
limit=3,
)Сборка system prompt
Функция _assemble_prompt:
def _assemble_prompt(profile, fragments):
parts = []
# 1. Goal из профиля
if profile.get("goal"):
parts.append(profile["goal"])
# 2. Tool constraints
if profile.get("allowed_tools"):
parts.append(f"Available tools: {', '.join(allowed)}")
if profile.get("forbidden_tools"):
parts.append(f"Forbidden tools: {', '.join(forbidden)}")
# 3. Фрагменты по assembly_mode
for frag in fragments:
mode = frag.get("assembly_mode", "append")
if mode == "prepend": prepend.append(content)
elif mode == "replace": return content # Полная замена!
else: append.append(content)
return "
".join(prepend + parts + append)Выходные данные
return {
"selected_agent_code": "web_researcher",
"selected_fragments": [...], # Список фрагментов
"assembled_system_prompt": "...", # Готовый system prompt
"system_prompt_hash": "a1b2c3d4", # SHA256 hash для кэширования
"fragment_ids": [...],
"allowed_tools": ["web_search", "rag_search"],
"forbidden_tools": ["db_query"],
"preferred_model": "gemma3:4b",
"default_temperature": 0.4,
"response_contract": {"format": "markdown", "max_tokens": 4096},
"execution_mode": "research", # simple / multi_step / research / code
"selection_reason": "intent_match=web_search",
"methods": [...], # Доступные методы
"sub_agents": [...], # Sub-agent отношения
"chat_mode": "both",
}Маппинг intent → agent_family / fragment_type
_infer_agent_family()
Файл: bot/app/services/agent_selection.py:171
mapping = {
"core_orchestrator": "orchestrator",
"web_researcher": "researcher",
"coder_executor": "coder",
"sql_analyst": "coder",
"task_decomposer": "planner",
"summarizer": "",
"critic_reviewer": "reviewer",
"memory_keeper": "",
"n8n_composer": "coder",
}_infer_fragment_type()
Файл: bot/app/services/agent_selection.py:186
mapping = {
"web_search": "research",
"web": "research",
"code": "coding",
"sql": "coding",
"db": "coding",
"data": "coding",
"planning": "planning",
"summarize": "summarization",
"review": "review",
"workflow": "tool_use",
}Режим исполнения (execution_mode)
Функция _infer_execution_mode() из bot/app/services/agent_selection.py:209
def _infer_execution_mode(intent: str, complexity: str) -> str:
if complexity in ("complex", "high"):
return "multi_step"
research_intents = {"web_search", "web", "research"}
if intent in research_intents:
return "research"
code_intents = {"code", "sql", "db", "data"}
if intent in code_intents:
return "code"
return "simple"simple
Один LLM вызов, прямой ответ
research
web_search + synthesis
code
code/SQL generation + review
multi_step
Декомпозиция + несколько шагов
Пример flow
Пользователь пишет: "найди информацию о React 19"
web_searchweb_researcher с match_intent=['web_search', 'web']API Endpoint
POST /internal/agent/select
Authorization: Bearer {INTERNAL_API_TOKEN}
Content-Type: application/json
{
"intent": "web_search",
"process_code": "",
"scope": "direct",
"user_text": "найди информацию о React 19",
"complexity": "simple"
}