Skip to content

Affiliate — Configuração

O que é um Affiliate?

Um affiliate na iFriend é uma agência de turismo afiliada que comercializa os serviços da iFriend.

Definição

  • Affiliate = Agência: Uma agência de turismo que vende experiências, tours e serviços da iFriend
  • Pode ter whitelabel: Nem todo affiliate possui whitelabel — é uma relação opcional
  • Pode ter acesso ao agente: Affiliate com whitelabel pode expor o agente iFriend sob seu próprio domínio

Relação entre conceitos

flowchart LR
    A["Affiliate (agência)"] -->|pode ter| WL["Whitelabel (domínio próprio)"]
    WL -->|pode expor| AG["Agente iFriend<br/>(via Embed ou API)"]
    A -->|pode ter| MK["Markups de preço<br/>(siteMarkupGuide, etc.)"]

Campos Reais do Affiliate

Baseado no código, um affiliate contém os seguintes campos:

Identificação

Campo Tipo Descrição
id int ID único do affiliate
name str Nome da agência
company str Nome da empresa (jurídico)

Contato

Campo Tipo Descrição
phone str Telefone de contato
emailComercial str Email comercial
email str Email principal

Endereço

Campo Tipo Descrição
address str Endereço linha 1
address2 str Endereço linha 2
city str Cidade
state str Estado (UF)
postcode str CEP
neighborhood str Bairro

Markups de Preço

Campo Tipo Descrição
siteMarkupGuide float Markup para guias (ex: 0.8 = 20% desconto)
siteMarkupExperience float Markup para experiências
siteMarkupTicket float Markup para ingressos
siteMarkupTransfer float Markup para transfers
siteMarkupPackage float Markup para pacotes

Branding (Emails)

Campo Tipo Descrição
logoUrl str URL do logo para emails

Operador (para API)

Campo Tipo Descrição
operatorMarkupGuide float Markup operacional guias
operatorMarkupExperience float Markup operacional experiências
operatorMarkupTicket float Markup operacional tickets
operatorMarkupTransfer float Markup operacional transfers
operatorMarkupPackage float Markup operacional pacotes

Como é identificado

Método 1: Via JWT do usuário

# GET /affiliates?platformUser.user.userEmail={email}
affiliates = await client.get("/affiliates", {
    "platformUser.user.userEmail": user_email,
    "fields": ["id", "name", "siteMarkupExperience"]
})
affiliate = affiliates[0] if affiliates else None

Método 2: Via partner_code no JWT

# JWT contém custom_claims.partner_code
partner_code = jwt_context.get("custom_claims", {}).get("partner_code")

affiliates = await client.get("/affiliates", {
    "partner_code": partner_code,
    "fields": ["id", "name", "logoUrl"]
})

Método 3: Via affiliate_id no message_metadata

# SDK JS envia no metadata
affiliate_id = message_metadata.get("affiliate_id")

# Carrega config do affiliate para root_agent
affiliate_config = await AgentBuilder.load_affiliate_config(affiliate_id)
# GET /affiliates/{id}/root_agent

Este método é o único que carrega a configuração de comportamento do agente (affiliate_config), diferente dos Métodos 1 e 2, que carregam apenas os dados de negócio do affiliate (markup, contato, branding). Veja a seção seguinte.

Configuração do Agente (affiliate_config / root_agent)

Além dos dados de negócio do affiliate (markups, contato, branding — descritos acima), existe um segundo mecanismo, específico para customizar o comportamento do agente de IA por affiliate.

Fluxo

flowchart TD
    A["1. SDK JS envia message_metadata.affiliate_id"]
    B["2. inject_affiliate_config (callback, antes do agente rodar)<br/>affiliate_config_callback.py:16<br/>já tem config no session.state? não busca de novo"]
    C["3. AgentBuilder.load_affiliate_config()<br/>agent_builder.py:144<br/>GET /affiliates/{id}/root_agent (API externa, api.theifriend.com)"]
    D["4. session.state['affiliate_config'] = dict<br/>(sem schema tipado)"]
    E["5. Tools leem tool_context.state.get('affiliate_config')<br/>get_affiliate_config / transform_product_url"]
    F["6. custom_affiliate_booking_agent gera URL de reserva<br/>(só ativo com ENABLE_CUSTOM_AFFILIATE_BOOKING)"]

    A --> B --> C --> D --> E --> F

Campos reais observados no código

affiliate_config é um dict retornado pela API externa, sem schema tipado neste repositório. Os campos abaixo são efetivamente lidos por algum código:

Campo Tipo Lido em
id — agent_builder.py:159 (checagem de que a resposta é válida)
enabled bool get_affiliate_config_tool.py (retornado ao LLM, não há branching no código)
bookingUrltemplate str transform_product_url_tool.py:45 — atenção ao casing: t minúsculo aqui, mas docstrings/evalsets usam bookingUrlTemplate (T maiúsculo). Resolver essa inconsistência antes de adicionar campos novos.
productUrlTemplate str transform_product_url_tool.py:50 — fallback se não houver bookingUrltemplate
affiliate str/int transform_product_url_tool.py:88 — ecoado de volta na resposta
agentName str ifriend_agent/config/agent_persona.py — nome que o agente usa ao se apresentar (ausente → "Trip")
agentCompanyName str ifriend_agent/config/agent_persona.py — nome da empresa do affiliate, usado na frase de apresentação (ausente → omitido)
agentGreeting str ifriend_agent/config/agent_persona.py — saudação customizada sugerida ao agente (ausente → saudação padrão)
agentTone str (enum: formal/descontraido/amigavel/profissional) ifriend_agent/config/agent_persona.py — ajusta o registro de linguagem; valor fora do enum cai no tom padrão

Os 4 campos acima são lidos por build_persona_state() e injetados como placeholders {{agent_name}}/{{agent_company_line}}/{{agent_greeting_hint}}/ {{agent_tone_hint}} no prompt do orquestrador (mesmo mecanismo de {{current_date}}) — sempre sanitizados (sem newline, truncados) e com fallback ao comportamento atual quando ausentes/inválidos, para não abrir brecha de prompt injection nem violar os guardrails de identidade fixos no template.

Campos documentados mas não implementados

O docstring de get_affiliate_config_tool.py cita também sub_agents e custom_greeting como exemplo de retorno, mas nenhum código lê esses campos hoje — são aspiracionais/mortos, distintos de agentGreeting (este sim implementado, ver acima). Não assuma que sub_agents/ custom_greeting já funcionam.

Onde é consumido

  • ifriend_agent/tools/get_affiliate_config_tool.py — expõe o dict bruto ao LLM.
  • ifriend_agent/tools/transform_product_url_tool.py — único tool que de fato usa os campos para montar a URL final de reserva.
  • ifriend_agent/agents/custom_affiliate_booking_agent.py — sub-agent dedicado, só ativo via feature flag ENABLE_CUSTOM_AFFILIATE_BOOKING (default false).
  • ifriend_agent/config/agent_persona.py — lê agentName/agentCompanyName/ agentGreeting/agentTone, sempre presente (sem feature flag, é injeção direta no prompt via ifriend_agent/callbacks/agent_callbacks.py).

bookingUrltemplate/productUrlTemplate/affiliate são acessados exclusivamente via tool_context.state dentro das tools, nunca injetados no prompt. A exceção é a persona (agentName/agentCompanyName/agentGreeting/ agentTone): esses 4 campos são injetados diretamente na instrução do orquestrador via placeholders {{...}} (mesmo mecanismo de {{current_date}} — ver instructions_utils.inject_session_state do ADK), sempre sanitizados e com fallback ao comportamento atual.

Para adicionar uma nova feature configurável por affiliate (ex.: FAQ customizado), veja Estendendo a Configuração do Agente.

Fluxo de Identificação

flowchart TD
    A["1. Usuário faz request com JWT"]
    B["2. Callback injeta affiliate no state<br/>(via platformUser.user.userEmail)"]
    C["3. Tools buscam dados do affiliate<br/>(markup, logo, contato)"]
    D["4. Resposta customizada:<br/>preço com markup, email com logo da agência,<br/>WhatsApp da agência"]

    A --> B --> C --> D

Uso nos Módulos

1. precificação (markup de preço)

# ifriend_agent/tools/context/enrich_products.py
markup = _get_context_product_markup(affiliate, product)

if markup:
    price = price_net / markup  # Aplica markup

2. Emails (branding)

# ifriend_agent/tools/enviar_email_sendgrid_tool.py
logo_url = affiliate.get("logoUrl")
email = affiliate.get("emailComercial")
phone = affiliate.get("phone")

# Template usa logo do affiliate se disponível

3. Suporte (WhatsApp)

# ifriend_agent/tools/gerar_formulario_suporte_tool.py
affiliate_id = whitelabel.get("affiliate", {}).get("id")
affiliate = whitelabel.get("affiliate", {})

whatsapp = _resolve_contact_whatsapp() or affiliate.get("phone")

Configuração de Whitelabel (Opcional)

Um affiliate pode ter um whitelabel associado:

{
  "id": 123,
  "domain": "agencia.example.com",
  "Affiliate": {
    "id": 456,
    "name": " Agência Exemplo",
    "phone": "+5511999999999"
  }
}

Neste caso: - O whitelabel usa o affiliate para contato - O WhatsApp de suporte vem do affiliate - O dominio é exposed sob a marca do affiliate

Variáveis de Ambiente

Variável Descrição
ENABLE_WHITELABEL Habilita detecção por domínio
WHITELABEL_DOMAINS Domínios permitidos (JSON)

API Reference

Listar Affiliates

GET /affiliates

Buscar por Email

GET /affiliates?platformUser.user.userEmail=email@agencia.com&fields[]=id&fields[]=name

Buscar por ID

GET /affiliates/{id}

Buscar Configuração Root Agent

GET /affiliates/{id}/root_agent

Diferença: Affiliate vs Whitelabel

Aspecto Affiliate Whitelabel
O que é Agência Domínio próprio
Relação Entidade principal Personalização do affiliate
Sempre existe Sim Opcional
Exibe agente Não Sim (se habilitado)
Domínio iFriend (theifriend.com) Próprio (agencia.com)

Resumo

  • Affiliate = agência com dados (nome, contato, markups, logo)
  • Whitelabel = customização visual (domínio, cores, logo) atrelada ao affiliate
  • Identificação = via JWT (user email ou partner_code) ou message_metadata (affiliate_id)