mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-09-07 10:13:46 +00:00
adjustments: route stickness usefull tokens only
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
# Release Notes — Generic Deterministic Intent Shift v15
|
||||||
|
|
||||||
|
## Problema corrigido
|
||||||
|
|
||||||
|
A Route Stickiness podia preservar a intent anterior quando a nova mensagem correspondia a uma intent configurada no `routing.yaml`, mas a frase do usuário omitia conectores curtos presentes na keyword configurada.
|
||||||
|
|
||||||
|
Exemplo real de configuração:
|
||||||
|
|
||||||
|
- keyword: `qual é o meu plano`
|
||||||
|
- mensagem: `qual o meu plano`
|
||||||
|
|
||||||
|
A classificação determinística não reconhecia a nova intent e a continuity acabava mantendo a intent anterior.
|
||||||
|
|
||||||
|
## Correção
|
||||||
|
|
||||||
|
O `EnterpriseRouter` continua usando, nesta ordem:
|
||||||
|
|
||||||
|
1. match exato;
|
||||||
|
2. sequência completa de tokens com palavras inseridas (`ordered_tokens`);
|
||||||
|
3. sequência de tokens informativos tolerando a omissão de conectores curtos presentes na keyword (`ordered_content_tokens`).
|
||||||
|
|
||||||
|
A terceira estratégia ignora, somente no lado da keyword, tokens de até dois caracteres e exige pelo menos dois tokens informativos. Não há nomes de intents, agentes, domínios ou verbos de negócio hardcoded.
|
||||||
|
|
||||||
|
Assim, a solução é dirigida integralmente pelas intents carregadas do `routing.yaml` da aplicação.
|
||||||
|
|
||||||
|
## Precedência sobre Route Stickiness
|
||||||
|
|
||||||
|
Quando o candidato determinístico encontrado é diferente da intent ativa, ele preempta a stickiness e retorna:
|
||||||
|
|
||||||
|
- `route_stickiness_preempted: true`
|
||||||
|
- `previous_agent`
|
||||||
|
- `previous_intent`
|
||||||
|
- `keyword_match_strategy`
|
||||||
|
|
||||||
|
A continuity LLM não é chamada nesse caminho.
|
||||||
|
|
||||||
|
## Casos cobertos
|
||||||
|
|
||||||
|
### Mesmo agente, nova intent
|
||||||
|
|
||||||
|
`retail_order_tracking` -> `quero cancelar meu pedido` -> `retail_order_cancel`
|
||||||
|
|
||||||
|
### Mesmo agente, tools diferentes
|
||||||
|
|
||||||
|
`contas_invoice_query` -> `qual o meu plano` -> `contas_plan_information`
|
||||||
|
|
||||||
|
Mesmo que ambas as intents usem `faturas_agent`, as tools mudam de `consultar_faturas` para `consultar_plano`.
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -181,6 +181,45 @@ class EnterpriseRouter:
|
|||||||
pos = found
|
pos = found
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _ordered_content_keyword_match(cls, keyword: str, text: str, *, max_gap: int = 4) -> bool:
|
||||||
|
"""Match determinístico tolerante à omissão de conectores curtos.
|
||||||
|
|
||||||
|
Alguns ``routing.yaml`` usam frases naturais como ``qual é o meu plano``.
|
||||||
|
A mesma intenção pode chegar como ``qual o meu plano``. O matcher legado
|
||||||
|
falhava porque exigia também o token ``e`` (resultado da normalização de
|
||||||
|
``é``). Aqui tokens de até dois caracteres são tratados como conectores
|
||||||
|
opcionais *apenas no lado da keyword*. Os tokens informativos continuam
|
||||||
|
obrigatórios, em ordem e próximos entre si.
|
||||||
|
|
||||||
|
A heurística é propositalmente linguística-neutra e não contém nomes de
|
||||||
|
intents, agentes, domínios ou listas de verbos de negócio. Assim funciona
|
||||||
|
com qualquer configuração carregada pelo ``routing.yaml`` sem LLM extra.
|
||||||
|
"""
|
||||||
|
wanted_all = cls._keyword_tokens(keyword)
|
||||||
|
actual = cls._keyword_tokens(text)
|
||||||
|
if len(wanted_all) < 2 or not actual:
|
||||||
|
return False
|
||||||
|
|
||||||
|
wanted = [token for token in wanted_all if len(token) > 2]
|
||||||
|
# Exigimos pelo menos dois tokens informativos para não transformar
|
||||||
|
# keywords curtas em matches amplos demais.
|
||||||
|
if len(wanted) < 2 or len(wanted) == len(wanted_all):
|
||||||
|
return False
|
||||||
|
|
||||||
|
pos = -1
|
||||||
|
for token in wanted:
|
||||||
|
found = None
|
||||||
|
upper = min(len(actual), pos + max_gap + 2)
|
||||||
|
for idx in range(pos + 1, upper):
|
||||||
|
if actual[idx] == token:
|
||||||
|
found = idx
|
||||||
|
break
|
||||||
|
if found is None:
|
||||||
|
return False
|
||||||
|
pos = found
|
||||||
|
return True
|
||||||
|
|
||||||
def _route_by_keyword(self, text: str) -> RouteDecision | None:
|
def _route_by_keyword(self, text: str) -> RouteDecision | None:
|
||||||
normalized = text.casefold()
|
normalized = text.casefold()
|
||||||
matches: list[tuple[int, int, int, IntentDefinition, str, str]] = []
|
matches: list[tuple[int, int, int, IntentDefinition, str, str]] = []
|
||||||
@@ -195,10 +234,17 @@ class EnterpriseRouter:
|
|||||||
strategy = "exact"
|
strategy = "exact"
|
||||||
elif self._ordered_keyword_match(kw, text):
|
elif self._ordered_keyword_match(kw, text):
|
||||||
strategy = "ordered_tokens"
|
strategy = "ordered_tokens"
|
||||||
|
elif self._ordered_content_keyword_match(kw, text):
|
||||||
|
strategy = "ordered_content_tokens"
|
||||||
|
|
||||||
if strategy:
|
if strategy:
|
||||||
# menor priority vence; exact vence fuzzy; keyword maior desempata
|
# menor priority vence; estratégias mais estritas vencem as relaxadas;
|
||||||
strategy_rank = 0 if strategy == "exact" else 1
|
# keyword maior desempata dentro da mesma prioridade/estratégia.
|
||||||
|
strategy_rank = {
|
||||||
|
"exact": 0,
|
||||||
|
"ordered_tokens": 1,
|
||||||
|
"ordered_content_tokens": 2,
|
||||||
|
}[strategy]
|
||||||
matches.append((intent.priority, strategy_rank, -len(kw), intent, kw, strategy))
|
matches.append((intent.priority, strategy_rank, -len(kw), intent, kw, strategy))
|
||||||
if not matches:
|
if not matches:
|
||||||
return None
|
return None
|
||||||
@@ -208,11 +254,19 @@ class EnterpriseRouter:
|
|||||||
route=intent.agent,
|
route=intent.agent,
|
||||||
agent=intent.agent,
|
agent=intent.agent,
|
||||||
intent=intent.name,
|
intent=intent.name,
|
||||||
confidence=0.85 if strategy == "exact" else 0.82,
|
confidence={
|
||||||
|
"exact": 0.85,
|
||||||
|
"ordered_tokens": 0.82,
|
||||||
|
"ordered_content_tokens": 0.80,
|
||||||
|
}[strategy],
|
||||||
reason=(
|
reason=(
|
||||||
f"Keyword '{kw}' correspondeu à intent '{intent.name}'."
|
f"Keyword '{kw}' correspondeu à intent '{intent.name}'."
|
||||||
if strategy == "exact"
|
if strategy == "exact"
|
||||||
else f"Sequência de tokens da keyword '{kw}' correspondeu à intent '{intent.name}'."
|
else (
|
||||||
|
f"Sequência de tokens da keyword '{kw}' correspondeu à intent '{intent.name}'."
|
||||||
|
if strategy == "ordered_tokens"
|
||||||
|
else f"Tokens informativos da keyword '{kw}' corresponderam à intent '{intent.name}'."
|
||||||
|
)
|
||||||
),
|
),
|
||||||
method="keyword",
|
method="keyword",
|
||||||
metadata={"matched_keyword": kw, "keyword_match_strategy": strategy},
|
metadata={"matched_keyword": kw, "keyword_match_strategy": strategy},
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,207 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# AI AGENT PLATFORM - CONFIGURAÇÃO ÚNICA
|
|
||||||
# Este arquivo é lido por Pydantic Settings no framework e no backend template.
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
APP_NAME=ai-agent-template
|
|
||||||
APP_ENV=local
|
|
||||||
LOG_LEVEL=INFO
|
|
||||||
API_HOST=0.0.0.0
|
|
||||||
API_PORT=8000
|
|
||||||
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# LLM - OCI Generative AI como provider principal
|
|
||||||
###############################################################################
|
|
||||||
# Opções: mock, oci_openai, oci_sdk, openai_compatible
|
|
||||||
LLM_PROVIDER=oci_sdk
|
|
||||||
LLM_TEMPERATURE=0.2
|
|
||||||
LLM_MAX_TOKENS=2048
|
|
||||||
LLM_TIMEOUT_SECONDS=120
|
|
||||||
|
|
||||||
# OCI OpenAI-compatible endpoint
|
|
||||||
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com
|
|
||||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
|
||||||
OCI_GENAI_API_KEY=sk-ph3FgX6iP3fxAQCXb9IpPIDTadkeeYAWntUWhzcWysIM6zsS
|
|
||||||
OCI_GENAI_PROJECT_OCID=
|
|
||||||
|
|
||||||
#OCI_GENAI_BASE_URL=https://pegruagntaiatenddev.pe.inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com
|
|
||||||
#OCI_GENAI_MODEL=openai.gpt-4.1
|
|
||||||
#OCI_GENAI_API_KEY=
|
|
||||||
#OCI_GENAI_PROJECT_OCID=
|
|
||||||
|
|
||||||
|
|
||||||
# OCI_AUTH_MODE=config_file|instance_principal|resource_principal
|
|
||||||
OCI_AUTH_MODE=config_file
|
|
||||||
# OCI SDK / signer / profiles
|
|
||||||
OCI_CONFIG_FILE=~/.oci/config
|
|
||||||
OCI_PROFILE=LATINOAMERICA-Chicago
|
|
||||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaexpiw4a7dio64mkfv2t273s2hgdl6mgfvvyv7tycalnjlvpvfl3q
|
|
||||||
OCI_REGION=us-chicago-1
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Persistência
|
|
||||||
###############################################################################
|
|
||||||
# Opções: memory, autonomous, mongodb
|
|
||||||
SESSION_REPOSITORY_PROVIDER=autonomous
|
|
||||||
MEMORY_REPOSITORY_PROVIDER=autonomous
|
|
||||||
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
|
|
||||||
|
|
||||||
# Autonomous Database
|
|
||||||
ADB_USER=admin
|
|
||||||
ADB_PASSWORD=Moniquinha19721972
|
|
||||||
ADB_DSN=oradb23ai_high
|
|
||||||
ADB_WALLET_LOCATION=/mnt/d/Dropbox/ORACLE/LatinoAmerica/Wallet_ORADB23ai
|
|
||||||
ADB_WALLET_PASSWORD=Moniquinha1972
|
|
||||||
ADB_TABLE_PREFIX=AGENTFW
|
|
||||||
|
|
||||||
# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente
|
|
||||||
MONGODB_URI=mongodb://mongo:mongopassword@localhost:27017
|
|
||||||
MONGODB_DATABASE=agent_platform
|
|
||||||
|
|
||||||
# Redis
|
|
||||||
REDIS_URL=redis://localhost:6379/0
|
|
||||||
ENABLE_REDIS_CACHE=false
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# RAG / Vector / Graph
|
|
||||||
###############################################################################
|
|
||||||
VECTOR_STORE_PROVIDER=autonomous
|
|
||||||
GRAPH_STORE_PROVIDER=autonomous
|
|
||||||
RAG_TOP_K=5
|
|
||||||
EMBEDDING_PROVIDER=oci
|
|
||||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
|
||||||
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Observabilidade
|
|
||||||
###############################################################################
|
|
||||||
ENABLE_LANGFUSE=true
|
|
||||||
# Opcional: verbose, compact
|
|
||||||
LANGFUSE_TRACE_MODE=compact
|
|
||||||
# Nome customizado do trace pai, ex.: backoffice.checklist.workflow ou backoffice.emulador.workflow
|
|
||||||
LANGFUSE_COMPACT_VISIBLE_EVENT_PREFIXES=AGA.,NOC., IC.
|
|
||||||
LANGFUSE_COMPACT_SUPPRESSED_PREFIXES=llm.chat_completion
|
|
||||||
LANGFUSE_IGNORE_HEALTHCHECKS=true
|
|
||||||
LANGFUSE_IGNORED_PATHS=/health,/ready,/metrics
|
|
||||||
LANGFUSE_PUBLIC_KEY=pk-lf-4a1e3921-5158-4fd3-a16d-7a77549fb312
|
|
||||||
LANGFUSE_SECRET_KEY=sk-lf-efc6fd59-c5ec-4858-b6ec-4aa129734915
|
|
||||||
LANGFUSE_HOST=http://localhost:3005
|
|
||||||
ENABLE_OTEL=false
|
|
||||||
OTEL_EXPORTER_OTLP_ENDPOINT=
|
|
||||||
OTEL_SERVICE_NAME=ai-agent-template
|
|
||||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
|
||||||
ENABLE_LANGFUSE_ANALYTICS_PUBLISHER=false
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Analytics / Observer corporativo
|
|
||||||
###############################################################################
|
|
||||||
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
|
|
||||||
ENABLE_ANALYTICS=false
|
|
||||||
# Providers aceitos: oci_streaming,pubsub,noop
|
|
||||||
ANALYTICS_PROVIDERS=oci_streaming
|
|
||||||
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
|
|
||||||
AGENT_PUBSUB_TOPIC=
|
|
||||||
GCP_PUBSUB_TOPIC_PATH=
|
|
||||||
GCP_PROJECT_ID=
|
|
||||||
GCP_PUBSUB_TOPIC=
|
|
||||||
GCP_PUBSUB_TIMEOUT_SECONDS=30
|
|
||||||
# Credencial GCP segue padrão Google:
|
|
||||||
# GOOGLE_APPLICATION_CREDENTIALS=/secrets/gcp-service-account.json
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# OCI Streaming
|
|
||||||
###############################################################################
|
|
||||||
ENABLE_OCI_STREAMING=false
|
|
||||||
OCI_STREAM_ENDPOINT=
|
|
||||||
OCI_STREAM_OCID=
|
|
||||||
OCI_STREAM_PARTITION_KEY=agent-events
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Guardrails, Judges, Supervisor
|
|
||||||
###############################################################################
|
|
||||||
ENABLE_INPUT_GUARDRAILS=true
|
|
||||||
ENABLE_OUTPUT_GUARDRAILS=true
|
|
||||||
ENABLE_JUDGES=true
|
|
||||||
ENABLE_SUPERVISOR=true
|
|
||||||
ENABLE_OUTPUT_SUPERVISOR=true
|
|
||||||
ENABLE_PARALLEL_GUARDRAILS=true
|
|
||||||
GUARDRAILS_FAIL_FAST=true
|
|
||||||
OUTPUT_SUPERVISOR_MAX_RETRIES=3
|
|
||||||
GUARDRAILS_CONFIG_PATH=./config/guardrails.yaml
|
|
||||||
JUDGES_CONFIG_PATH=./config/judges.yaml
|
|
||||||
PROMPT_POLICY_PATH=./config/prompt_policy.yaml
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Gateway de canais
|
|
||||||
###############################################################################
|
|
||||||
DEFAULT_CHANNEL=web
|
|
||||||
# embedded = backend may parse simple/native channel payloads.
|
|
||||||
# external = backend only accepts GatewayRequest normalized by an external Channel Gateway.
|
|
||||||
FRAMEWORK_CHANNEL_INPUT_MODE=embedded
|
|
||||||
ENABLE_VOICE_ADAPTER=true
|
|
||||||
ENABLE_WHATSAPP_ADAPTER=true
|
|
||||||
ENABLE_TEXT_ADAPTER=true
|
|
||||||
|
|
||||||
#################################################
|
|
||||||
# ENTERPRISE ROUTING
|
|
||||||
#################################################
|
|
||||||
# Arquivo YAML com intents, keywords, políticas de estado e fallback.
|
|
||||||
ROUTING_CONFIG_PATH=./config/routing.yaml
|
|
||||||
# true = usa LLM para classificar quando keywords/estado não resolverem.
|
|
||||||
# Em produção, costuma ser útil; em desenvolvimento, false evita custo e latência.
|
|
||||||
ENABLE_LLM_ROUTER=true
|
|
||||||
|
|
||||||
# Semantic route stickiness (optional).
|
|
||||||
# Uses a lightweight LLM profile to decide only CONTINUE vs ROUTE.
|
|
||||||
# There are no regexes or deterministic language rules.
|
|
||||||
ENABLE_ROUTE_STICKINESS=true
|
|
||||||
ROUTE_STICKINESS_LLM_PROFILE=route_continuity
|
|
||||||
ROUTE_STICKINESS_CONFIDENCE_THRESHOLD=0.90
|
|
||||||
ROUTE_STICKINESS_HISTORY_TURNS=2
|
|
||||||
ROUTE_STICKINESS_MAX_TOKENS=80
|
|
||||||
HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa.
|
|
||||||
END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# MCP / Tools
|
|
||||||
###############################################################################
|
|
||||||
ENABLE_MCP_TOOLS=true
|
|
||||||
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
|
|
||||||
TOOLS_CONFIG_PATH=./config/tools.yaml
|
|
||||||
MCP_TOOL_TIMEOUT_SECONDS=30
|
|
||||||
|
|
||||||
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes
|
|
||||||
ROUTING_MODE=router
|
|
||||||
|
|
||||||
# Usage/cost accounting
|
|
||||||
USAGE_REPOSITORY_PROVIDER=autonomous
|
|
||||||
IDENTITY_CONFIG_PATH=./config/identity.yaml
|
|
||||||
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
# ConversationSummaryMemory / compressão de contexto conversacional
|
|
||||||
# -----------------------------------------------------------------------------
|
|
||||||
ENABLE_CONVERSATION_SUMMARY_MEMORY=true
|
|
||||||
MEMORY_CONTEXT_STRATEGY=summary
|
|
||||||
MEMORY_HISTORY_LIMIT=80
|
|
||||||
MEMORY_RECENT_MESSAGES_LIMIT=8
|
|
||||||
MEMORY_SUMMARY_TRIGGER_MESSAGES=20
|
|
||||||
MEMORY_MAX_SUMMARY_CHARS=6000
|
|
||||||
MEMORY_SUMMARY_USE_LLM=true
|
|
||||||
MEMORY_INJECT_RECENT_MESSAGES=true
|
|
||||||
MEMORY_INJECT_SUMMARY=true
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# LONG-TERM MEMORY
|
|
||||||
###############################################################################
|
|
||||||
ENABLE_LONG_TERM_MEMORY=true
|
|
||||||
LONG_TERM_MEMORY_PROVIDER=sqlite
|
|
||||||
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
|
|
||||||
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
|
|
||||||
# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY
|
|
||||||
# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY
|
|
||||||
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
|
|
||||||
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
|
|
||||||
LONG_TERM_MEMORY_AUTO_EXTRACT=true
|
|
||||||
LONG_TERM_MEMORY_INJECT_CONTEXT=true
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -158,3 +158,83 @@ def test_ordered_keyword_match_is_conservative():
|
|||||||
assert EnterpriseRouter._ordered_keyword_match("cancelar pedido", "quero cancelar meu pedido") is True
|
assert EnterpriseRouter._ordered_keyword_match("cancelar pedido", "quero cancelar meu pedido") is True
|
||||||
assert EnterpriseRouter._ordered_keyword_match("cancelar pedido", "quero pedido e talvez cancelar depois") is False
|
assert EnterpriseRouter._ordered_keyword_match("cancelar pedido", "quero pedido e talvez cancelar depois") is False
|
||||||
assert EnterpriseRouter._ordered_keyword_match("pedido", "meu pedido") is False
|
assert EnterpriseRouter._ordered_keyword_match("pedido", "meu pedido") is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_ordered_content_keyword_match_tolerates_omitted_short_connectors():
|
||||||
|
assert EnterpriseRouter._ordered_content_keyword_match(
|
||||||
|
"qual é o meu plano", "qual o meu plano"
|
||||||
|
) is True
|
||||||
|
assert EnterpriseRouter._ordered_content_keyword_match(
|
||||||
|
"qual é o meu plano", "quero ver minha fatura"
|
||||||
|
) is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_loaded_domain_style_plan_shift_preempts_continuity_without_llm():
|
||||||
|
"""Regression: invoice -> plan must work from configured keywords, even same agent.
|
||||||
|
|
||||||
|
This mirrors applications such as Contas where two intents may be handled by
|
||||||
|
the same agent but require different MCP tools. The routing core must not
|
||||||
|
know application-specific intent or agent names.
|
||||||
|
"""
|
||||||
|
from agent_framework.routing.models import IntentDefinition
|
||||||
|
|
||||||
|
class _Continuity:
|
||||||
|
async def evaluate(self, state, *, intents):
|
||||||
|
raise AssertionError("continuity LLM must not run for explicit configured intent shift")
|
||||||
|
|
||||||
|
router = object.__new__(EnterpriseRouter)
|
||||||
|
router.state_policies = []
|
||||||
|
router.intents = [
|
||||||
|
IntentDefinition(
|
||||||
|
name="domain_plan_information",
|
||||||
|
domain="customer_domain",
|
||||||
|
agent="customer_agent",
|
||||||
|
description="informações de plano",
|
||||||
|
priority=142,
|
||||||
|
mcp_tools=["consultar_plano"],
|
||||||
|
keywords=["qual é o meu plano", "plano contratado"],
|
||||||
|
examples=[],
|
||||||
|
enabled=True,
|
||||||
|
),
|
||||||
|
IntentDefinition(
|
||||||
|
name="domain_invoice_query",
|
||||||
|
domain="customer_domain",
|
||||||
|
agent="customer_agent",
|
||||||
|
description="consulta de fatura",
|
||||||
|
priority=140,
|
||||||
|
mcp_tools=["consultar_faturas"],
|
||||||
|
keywords=["quero minha fatura", "fatura atual"],
|
||||||
|
examples=[],
|
||||||
|
enabled=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
router.continuity = _Continuity()
|
||||||
|
router.enable_llm_router = False
|
||||||
|
router.llm = None
|
||||||
|
router.telemetry = None
|
||||||
|
router.fallback_agent = "customer_agent"
|
||||||
|
|
||||||
|
message = "qual o meu plano"
|
||||||
|
decision = await router.route({
|
||||||
|
"user_text": message,
|
||||||
|
"sanitized_input": message,
|
||||||
|
"active_agent": "customer_agent",
|
||||||
|
"intent": "domain_invoice_query",
|
||||||
|
"route_decision": {
|
||||||
|
"route": "customer_agent",
|
||||||
|
"agent": "customer_agent",
|
||||||
|
"intent": "domain_invoice_query",
|
||||||
|
"domain": "customer_domain",
|
||||||
|
"mcp_tools": ["consultar_faturas"],
|
||||||
|
},
|
||||||
|
"context": {"session": {}},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert decision.intent == "domain_plan_information"
|
||||||
|
assert decision.agent == "customer_agent"
|
||||||
|
assert decision.mcp_tools == ["consultar_plano"]
|
||||||
|
assert decision.method == "keyword"
|
||||||
|
assert decision.metadata["route_stickiness_preempted"] is True
|
||||||
|
assert decision.metadata["previous_intent"] == "domain_invoice_query"
|
||||||
|
assert decision.metadata["keyword_match_strategy"] == "ordered_content_tokens"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user