Skip to main content

Skill Selector

O SkillSelector seleciona automaticamente a skill externa mais adequada para o contexto da conversa usando o LLM. Use quando o agente tem múltiplas skills externas e precisa decidir para qual encaminhar o usuário.

Inicialização​

from actions_sdk.core.types.action_context import ActionContext
from actions_sdk.features.conversational.skill_selector import SkillSelector

ctx: ActionContext

selector = SkillSelector(ctx)

# Com timezone para verificação de horário de atendimento
selector = SkillSelector(ctx, timezone="America/Sao_Paulo")

O SkillSelector extrai ctx.external_skills e ctx.messages automaticamente no construtor, e cria internamente um LLMClient.

Parâmetros do construtor:

ParâmetroTipoPadrãoDescrição
ctxActionContext—Contexto de execução
timezonestr"UTC"Fuso horário para verificação de horário de atendimento das skills

Métodos​

select_best_skill()​

Seleciona a skill mais adequada para o contexto da conversa usando LLM.

selector = SkillSelector(ctx)
skill_selecionada = selector.select_best_skill()

if skill_selecionada:
return ResponseToAgent(
status=ResponseStatus.SUCCESS,
instruction=f"Encaminhar o usuário para: {skill_selecionada.name}",
functions=[RedirectToSkill(
skill_name=skill_selecionada.name,
reason="Skill selecionada automaticamente.",
)],
)

Parâmetros:

ParâmetroTipoDescrição
external_skillslist[ExternalSkillConfig] | NoneSkills candidatas. Se None, usa as skills do construtor (ctx.external_skills)
messageslist[Message] | NoneHistórico da conversa. Se None, usa ctx.messages
context_hintstr | NoneDica adicional de contexto para auxiliar a seleção
fallback_skillExternalSkillConfig | NoneSkill de último recurso se nenhuma outra se encaixar

Retorna: ExternalSkillConfig | None

Se a seleção falhar (LLM indisponível, erro interno), retorna None silenciosamente — não lança exceção.


get_available_external_skills()​

Retorna todas as skills externas passadas na abertura do SkillSelector (ctx.external_skills), sem filtrar por horário de atendimento.

Use este método para obter a lista completa de skills configuradas. Para filtrar pelas que estão abertas agora, chame get_external_skills_availability() a seguir.

todas_as_skills = selector.get_available_external_skills()
print([s.name for s in todas_as_skills])

Retorna: list[ExternalSkillConfig]


get_external_skill_availability(external_skill)​

Verifica a disponibilidade de uma única skill e retorna o motivo do fechamento e a mensagem de exceção configurada, quando houver. Método preferido para checagem antes de transbordo.

from actions_sdk import SkillSelector

selector = SkillSelector(ctx, timezone="America/Sao_Paulo")
availability = selector.get_external_skill_availability(skill)

if not availability.available:
if availability.closed_reason == "exception" and availability.message:
# Exibe a mensagem que a operação configurou para o período de exceção.
# O Force bloqueia a transferência de qualquer forma — a checagem aqui
# existe para o usuário receber a mensagem em vez de um bloqueio sem explicação.
return ResponseToUser(
instruction="Skill fechada por exceção — mensagem configurada exibida ao usuário",
message=availability.message,
)

Parâmetros:

ParâmetroTipoDescrição
external_skillExternalSkillConfigA skill a verificar

Retorna: SkillAvailability


get_external_skills_availability(external_skills=None)​

Retorna a disponibilidade de cada skill externa, incluindo o motivo do fechamento e a mensagem de exceção configurada. Avalia a lista inteira numa passada.

from actions_sdk import SkillAvailability

status = selector.get_external_skills_availability()
for item in status:
if not item.available and item.closed_reason == "exception" and item.message:
print(f"{item.skill.name}: fechada por exceção — {item.message}")

Parâmetros:

ParâmetroTipoDescrição
external_skillslist[ExternalSkillConfig] | NoneSkills a avaliar. Se None, usa as skills do construtor (ctx.external_skills)

Retorna: list[SkillAvailability], uma entrada por skill, na mesma ordem de entrada.


is_external_skill_available(external_skill) (depreciado)​

Depreciado — permanece funcional com assinatura e retorno bool intactos (actions existentes não quebram). Use get_external_skill_availability() em actions novas.

disponivel = selector.is_external_skill_available(skill)  # bool

Retorna: bool


SkillAvailability​

Resultado da verificação de disponibilidade de uma skill:

CampoTipoDescrição
skillExternalSkillConfigA skill avaliada
availableboolSe a skill está disponível agora
closed_reason"opening_hours" | "exception" | NoneMotivo do fechamento; None quando disponível
messagestr | NoneMensagem de exceção configurada (terceiro elemento da janela opening_hours_exceptions), presente quando closed_reason == "exception" e a janela que casou carrega mensagem

ExternalSkillConfig​

Estrutura que representa uma skill externa:

CampoTipoDescrição
namestrNome da skill
descriptionstrDescrição usada pelo LLM para seleção
opening_hoursdict | NoneHorários de atendimento por dia da semana
opening_hours_exceptionslist[list[str]] | NoneExceções de horário. Cada janela é [início_iso, fim_iso] ou [início_iso, fim_iso, mensagem]. O terceiro elemento — mensagem opcional — é exibido ao usuário quando a skill está fechada por aquela exceção

Erros e Exceções​

O SkillSelector é fail-safe: todos os erros internos são capturados e retornam None em vez de lançar exceções. Verifique sempre se o retorno de select_best_skill() é None antes de usar.

skill = selector.select_best_skill()

if skill is None:
return ResponseToAgent(
status=ResponseStatus.ERROR,
instruction="Não foi possível selecionar uma skill automaticamente.",
)

Exemplos práticos​

Listar skills com disponibilidade atual​

from actions_sdk.features.conversational.skill_selector import SkillSelector

ctx: ActionContext

selector = SkillSelector(ctx, timezone="America/Sao_Paulo")

# Todas as skills configuradas (sem filtro de horário)
todas = selector.get_available_external_skills()

# Disponibilidade de cada uma
status = selector.get_external_skills_availability()

for item in status:
estado = "aberta" if item.available else f"fechada ({item.closed_reason})"
print(f"{item.skill.name}: {estado}")

Exibir mensagem de exceção antes de transferir​

Quando uma skill está fechada por um período de exceção configurado (ex.: feriado), a mensagem cadastrada pode ser exibida diretamente ao usuário.

selector = SkillSelector(ctx, timezone="America/Sao_Paulo")

# Supondo que a skill já foi identificada pelo nome ou por seleção manual
skill_alvo = next(
(s for s in selector.get_available_external_skills() if s.name == "Suporte Técnico"),
None,
)

if skill_alvo is None:
return ResponseToAgent(
status=ResponseStatus.ERROR,
instruction="Skill não encontrada.",
)

availability = selector.get_external_skill_availability(skill_alvo)

if not availability.available:
if availability.closed_reason == "exception" and availability.message:
# Exibe a mensagem de exceção que a operação configurou (ex.: "Fechado no feriado de Natal")
return ResponseToUser(
instruction="Skill fechada por exceção — mensagem configurada exibida ao usuário",
message=availability.message,
)
# Fechada por horário normal
return ResponseToUser(
instruction="Skill fora do horário de atendimento",
message="O suporte técnico está indisponível no momento. Tente novamente em horário comercial.",
)

# Skill disponível — prosseguir com a transferência
return ResponseToAgent(
status=ResponseStatus.SUCCESS,
instruction=f"Transferir para {skill_alvo.name}.",
functions=[RedirectToSkill(skill_name=skill_alvo.name, reason="Solicitado pelo usuário.")],
)

Filtrar skills disponíveis antes de selecionar a melhor​

O fluxo recomendado quando há múltiplas skills: checar disponibilidade primeiro e só então passar a lista filtrada para o LLM.

selector = SkillSelector(ctx, timezone="America/Sao_Paulo")

status = selector.get_external_skills_availability()

# Separar abertas das fechadas
abertas = [item.skill for item in status if item.available]
fechadas_por_excecao = [item for item in status if item.closed_reason == "exception" and item.message]

if not abertas:
# Nenhuma skill aberta — verificar se há mensagem de exceção para exibir
if fechadas_por_excecao:
mensagens = " | ".join(item.message for item in fechadas_por_excecao)
return ResponseToUser(
instruction="Skills fechadas por exceção",
message=mensagens,
)
return ResponseToUser(
instruction="Todas as skills estão fora do horário de atendimento",
message="Nosso atendimento especializado está indisponível no momento.",
)

# Selecionar a melhor entre as abertas
skill = selector.select_best_skill(external_skills=abertas)

if skill is None:
return ResponseToAgent(
status=ResponseStatus.ERROR,
instruction="Nenhuma skill adequada encontrada.",
)

return ResponseToAgent(
status=ResponseStatus.SUCCESS,
instruction=f"Encaminhar o usuário para: {skill.name}",
functions=[RedirectToSkill(skill_name=skill.name, reason="Skill selecionada automaticamente.")],
)

Exemplo completo​

Baseado na action skill_selector_selecionar do agente de homologação:

import traceback

from actions_sdk.core.types.action_context import ActionContext
from actions_sdk.core.types.responses import (
RedirectToSkill,
ResponseStatus,
ResponseToAgent,
)
from actions_sdk.features.conversational.skill_selector import SkillSelector
from actions_sdk.features.conversational.state import StateManager
from actions_sdk.features.tracing import ActionSpan

ctx: ActionContext

CHAVE_PROGRESSO = "action.skill_selector.progresso"


def selecionar_skill():
span = ActionSpan(ctx)
state = StateManager(ctx)

span.event("action.skill_selector.inicio", {})

try:
selector = SkillSelector(ctx, timezone="America/Sao_Paulo")
skill = selector.select_best_skill(
context_hint="O usuário parece ter uma dúvida técnica.",
)

if skill is None:
state.update(CHAVE_PROGRESSO, "SEM_SKILL")
return ResponseToAgent(
status=ResponseStatus.ERROR,
instruction="Nenhuma skill adequada encontrada para o contexto atual.",
state_updates=state.get_full_state(),
)

state.update(CHAVE_PROGRESSO, "SUCESSO ✓")
span.event("action.skill_selector.sucesso", {"skill": skill.name})

return ResponseToAgent(
status=ResponseStatus.SUCCESS,
instruction=f"Skill selecionada: {skill.name}. Encaminhar o usuário.",
functions=[
RedirectToSkill(
skill_name=skill.name,
reason=f"Skill '{skill.name}' selecionada automaticamente por LLM.",
)
],
state_updates=state.get_full_state(),
)

except Exception as erro:
state.update(CHAVE_PROGRESSO, "ERRO ✗")
span.event("action.skill_selector.erro", {"erro": str(erro)})
return ResponseToAgent(
status=ResponseStatus.ERROR,
instruction=f"Falha no seletor de skill: {traceback.format_exc()}",
state_updates=state.get_full_state(),
)