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
whitelabelpode 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 flagENABLE_CUSTOM_AFFILIATE_BOOKING(defaultfalse).ifriend_agent/config/agent_persona.py— lêagentName/agentCompanyName/agentGreeting/agentTone, sempre presente (sem feature flag, é injeção direta no prompt viaifriend_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)